How to commit to merging branch during merge












0















I often do a git merge --no-ff --no-commit <feature_branch> to my master to check that everything works as expected before actually committing the merge.



While it's fine to fix merge conflicts in there, I sometimes find more severe things to fix. If I just fix them during merge, those changes will be hidden inside the merge commit. Others might not notice them and miss them if they also merge from the feature branch.



So I instead cancel the merge (git reset --hard) loosing all conflict resolution I already did, switch back to the feature branch (git checkout <feature_branch>), implement the fix (which I have to remember until here and can't test in the context of the merge), git commit (+ git push), then switch back to master (git checkout master) and re-do the merge.



That process is cumbersome and error prone.



Is there a way to commit changes to the feature branch from within the merge resolution or, if that's not possible, commit to the feature branch in another terminal and then update the merge to incorporate the new change-set without loosing existing progress?



Or is there another workflow that would solve that problem?










share|improve this question


















  • 1





    Why not rebase instead of merging? That would allow editing the conflicts and keep track of things, but I still wouldn’t make any other changes while in the middle of the rebase. You still can do the changes after and they’re nicely on your feature branch. Also keeps the master much cleaner when there aren’t a lot of merges

    – Sami Kuhmonen
    Nov 22 '18 at 8:07











  • @SamiKuhmonen If you're closing feature_branch after merge, rebase works fine but if you intend to implement additional feature on feature_branch you shouldn't rebase because it will require push --force option.

    – ik1ne
    Nov 22 '18 at 9:44











  • Feature branches are pushed to a central repo and may be used by other developers. Rebasing is not an option (at least for the feature branch).

    – Chaos_99
    Nov 22 '18 at 9:49
















0















I often do a git merge --no-ff --no-commit <feature_branch> to my master to check that everything works as expected before actually committing the merge.



While it's fine to fix merge conflicts in there, I sometimes find more severe things to fix. If I just fix them during merge, those changes will be hidden inside the merge commit. Others might not notice them and miss them if they also merge from the feature branch.



So I instead cancel the merge (git reset --hard) loosing all conflict resolution I already did, switch back to the feature branch (git checkout <feature_branch>), implement the fix (which I have to remember until here and can't test in the context of the merge), git commit (+ git push), then switch back to master (git checkout master) and re-do the merge.



That process is cumbersome and error prone.



Is there a way to commit changes to the feature branch from within the merge resolution or, if that's not possible, commit to the feature branch in another terminal and then update the merge to incorporate the new change-set without loosing existing progress?



Or is there another workflow that would solve that problem?










share|improve this question


















  • 1





    Why not rebase instead of merging? That would allow editing the conflicts and keep track of things, but I still wouldn’t make any other changes while in the middle of the rebase. You still can do the changes after and they’re nicely on your feature branch. Also keeps the master much cleaner when there aren’t a lot of merges

    – Sami Kuhmonen
    Nov 22 '18 at 8:07











  • @SamiKuhmonen If you're closing feature_branch after merge, rebase works fine but if you intend to implement additional feature on feature_branch you shouldn't rebase because it will require push --force option.

    – ik1ne
    Nov 22 '18 at 9:44











  • Feature branches are pushed to a central repo and may be used by other developers. Rebasing is not an option (at least for the feature branch).

    – Chaos_99
    Nov 22 '18 at 9:49














0












0








0








I often do a git merge --no-ff --no-commit <feature_branch> to my master to check that everything works as expected before actually committing the merge.



While it's fine to fix merge conflicts in there, I sometimes find more severe things to fix. If I just fix them during merge, those changes will be hidden inside the merge commit. Others might not notice them and miss them if they also merge from the feature branch.



So I instead cancel the merge (git reset --hard) loosing all conflict resolution I already did, switch back to the feature branch (git checkout <feature_branch>), implement the fix (which I have to remember until here and can't test in the context of the merge), git commit (+ git push), then switch back to master (git checkout master) and re-do the merge.



That process is cumbersome and error prone.



Is there a way to commit changes to the feature branch from within the merge resolution or, if that's not possible, commit to the feature branch in another terminal and then update the merge to incorporate the new change-set without loosing existing progress?



Or is there another workflow that would solve that problem?










share|improve this question














I often do a git merge --no-ff --no-commit <feature_branch> to my master to check that everything works as expected before actually committing the merge.



While it's fine to fix merge conflicts in there, I sometimes find more severe things to fix. If I just fix them during merge, those changes will be hidden inside the merge commit. Others might not notice them and miss them if they also merge from the feature branch.



So I instead cancel the merge (git reset --hard) loosing all conflict resolution I already did, switch back to the feature branch (git checkout <feature_branch>), implement the fix (which I have to remember until here and can't test in the context of the merge), git commit (+ git push), then switch back to master (git checkout master) and re-do the merge.



That process is cumbersome and error prone.



Is there a way to commit changes to the feature branch from within the merge resolution or, if that's not possible, commit to the feature branch in another terminal and then update the merge to incorporate the new change-set without loosing existing progress?



Or is there another workflow that would solve that problem?







git merge






share|improve this question













share|improve this question











share|improve this question




share|improve this question










asked Nov 22 '18 at 7:59









Chaos_99Chaos_99

1,18211423




1,18211423








  • 1





    Why not rebase instead of merging? That would allow editing the conflicts and keep track of things, but I still wouldn’t make any other changes while in the middle of the rebase. You still can do the changes after and they’re nicely on your feature branch. Also keeps the master much cleaner when there aren’t a lot of merges

    – Sami Kuhmonen
    Nov 22 '18 at 8:07











  • @SamiKuhmonen If you're closing feature_branch after merge, rebase works fine but if you intend to implement additional feature on feature_branch you shouldn't rebase because it will require push --force option.

    – ik1ne
    Nov 22 '18 at 9:44











  • Feature branches are pushed to a central repo and may be used by other developers. Rebasing is not an option (at least for the feature branch).

    – Chaos_99
    Nov 22 '18 at 9:49














  • 1





    Why not rebase instead of merging? That would allow editing the conflicts and keep track of things, but I still wouldn’t make any other changes while in the middle of the rebase. You still can do the changes after and they’re nicely on your feature branch. Also keeps the master much cleaner when there aren’t a lot of merges

    – Sami Kuhmonen
    Nov 22 '18 at 8:07











  • @SamiKuhmonen If you're closing feature_branch after merge, rebase works fine but if you intend to implement additional feature on feature_branch you shouldn't rebase because it will require push --force option.

    – ik1ne
    Nov 22 '18 at 9:44











  • Feature branches are pushed to a central repo and may be used by other developers. Rebasing is not an option (at least for the feature branch).

    – Chaos_99
    Nov 22 '18 at 9:49








1




1





Why not rebase instead of merging? That would allow editing the conflicts and keep track of things, but I still wouldn’t make any other changes while in the middle of the rebase. You still can do the changes after and they’re nicely on your feature branch. Also keeps the master much cleaner when there aren’t a lot of merges

– Sami Kuhmonen
Nov 22 '18 at 8:07





Why not rebase instead of merging? That would allow editing the conflicts and keep track of things, but I still wouldn’t make any other changes while in the middle of the rebase. You still can do the changes after and they’re nicely on your feature branch. Also keeps the master much cleaner when there aren’t a lot of merges

– Sami Kuhmonen
Nov 22 '18 at 8:07













@SamiKuhmonen If you're closing feature_branch after merge, rebase works fine but if you intend to implement additional feature on feature_branch you shouldn't rebase because it will require push --force option.

– ik1ne
Nov 22 '18 at 9:44





@SamiKuhmonen If you're closing feature_branch after merge, rebase works fine but if you intend to implement additional feature on feature_branch you shouldn't rebase because it will require push --force option.

– ik1ne
Nov 22 '18 at 9:44













Feature branches are pushed to a central repo and may be used by other developers. Rebasing is not an option (at least for the feature branch).

– Chaos_99
Nov 22 '18 at 9:49





Feature branches are pushed to a central repo and may be used by other developers. Rebasing is not an option (at least for the feature branch).

– Chaos_99
Nov 22 '18 at 9:49












1 Answer
1






active

oldest

votes


















1















Is there a way to commit changes to the feature branch from within the merge resolution




No: merge conflicts use the index to store all three versions (base, --ours, and --theirs, in index slots 1, 2, and 3 respectively) of each conflicted file. Git builds new commits from whatever is in the index, and requires that every file in the index be in its normal "resolved" slot (slot zero) rather than as the three nonzero slots for merge-conflict-resolution.



(The work-tree copy of the file holds Git's best effort at merging these three inputs, but Git only uses it again after you've fixed it up and run git add file. This copies the contents of file back into the index, writing to slot zero and emptying out slots 1-3. Now the file is resolved and ready to commit.)



Since there is only one index for any given work-tree,1 and that index is "busy" with the merge, you have no index (and no work-tree, for that matter) in which to make a change to the feature branch. This phrasing—specifically, in any given work-tree—is a big hint, though:




or, if that's not possible, commit to the feature branch in another terminal ...




Yes, this is possible. It's considerably easier since Git version 2.5, which learned a new feature: git worktree add.





1It's possible to set up a temporary index, and in versions of Git predating 2.5, there were some hacky scripts to do the equivalent of git worktree add using symlinks and temporary index files and a lot of other magic.





Using git worktree add



Each added work-tree has its own index (and, not coincidentally, its own HEAD as well). If you are in the middle of merging feature to master, so that the repository's main work-tree and index are dealing with branch master, you can run, from the top level of the work-tree:



git worktree add ../feature


or:



git worktree add /path/to/where/I/want/feature


or:



git worktree add /path/to/dir feature


(but not just git worktree add /path/to/dir, as that would try to check out a branch named dir).



This will:




  • create a new directory ../feature or /path/to/where/I/want/feature or /path/to/dir; and

  • run, in essence, git checkout feature in that path.


You now have an extra work-tree associated with the current repository. This added work-tree is on branch feature. (Your main work-tree is still on master, with the ongoing merge still going on.) In this other work-tree, you can modify files, git add them to its index, and git commit to add new commits to branch feature.



There is still a problem, though. It's more obvious if you use a separate clone; let's cover that a bit now.



Using a separate clone



If you have a Git before 2.5, or are worried about the various bugs in git worktree add (there are several, including some fairly significant ones only fixed recently in 2.18 or 2.19 or so), you can simply re-clone your repository. You can either re-clone the original repository to a new clone, or re-clone your clone to a new clone. Either way, you get a new repository, with its own branches and index and work-tree, and in that clone you can do anything you want.



Obviously, anything you do in this new clone does not affect your existing clone at all (at least, not until you fetch or push to transfer commits from clone to clone). Likewise, things you do in the original clone do not affect the new clone (until you transfer the commits).



Transferring the commits gets the commits into the original clone, which is fine; but obviously the merge you're doing uses the commits you had when you started the merge, not any new ones you made in the other clone. But that's true even with git worktree add, as we'll see in a moment.



Added work-trees vs separate clones



When you use git worktree add, the two work-trees share the underlying repository. This means commits you make from either work-tree are immediately available to yourself working in the other work-tree. However, they also share branch names, which leads to a restriction that git worktree add includes that a separate clone does not.



In particular, each added work-tree has the side effect of "locking out" access to that branch name. That is, once you've added a feature-branch work-tree, no other work-tree can use branch feature. If the main work-tree is on master, no added work-tree can use the branch master. Each branch name is exclusive to each added work-tree. (Note: you can use git checkout in the added work-tree to change branches, provided you maintain the exclusivity property.)



You can, of course, just remove an added work-tree. Since that work-tree is now gone, the branch it had exclusive rights to, is now available for any other work-tree. See the documentation for details.



The bugs that were fixed the most recently have to do with older added work-trees. If you add a work-tree, it's advisable to finish up your work in that added work-tree within a few weeks, then ditch it. Use it for relatively quick projects, in other words. (The scariest bug, in my opinion, is that git gc will sometimes think that an object isn't in use, because it fails to check added work-trees' HEAD and index files for object IDs. The default prune time of 2 weeks means that you're safe from this bug for at least two weeks from the time you start working in an added work-tree. You get more time, resetting the clock, whenever you commit, provided the added work-tree is not on a detached HEAD.)



The hitch



I mentioned that no matter what you do here, there is still a problem. That has to do with how Git works "under the hood".2




and then update the merge to incorporate the new change-set without losing existing progress?




The merge you are in the middle of, merging feature to master, is—at least in a sense—not merging the branch. It's merging the commit.



Remember that in Git, a branch name is just a human-readable identifier containing a hash ID. Git is really all about commits, and the true name of a commit is a big, ugly, apparently-random, unfriendly-to-humans hash ID like 8858448bb49332d353febc078ce4a3abcc962efe (this is the hash ID of a commit in the Git repository for Git).



Each commit stores, along with all its other data, a list of parent hash IDs, usually just one hash ID. We can, and Git does, use these to link commits up into backward chains. If we have a repository with only three commits in it, we might draw it like this, using single uppercase letters to stand in for the actual commit hash IDs:



A <-B <-C


Since commit A is the very first commit, its parent-list is empty: it has no parent. B, however, lists A as its (sole) parent, and C lists B as its parent. Git needs only to find commit C somehow, and then C finds B which finds A. (After finding A, there are no parents left, and we can rest.)



The main thing that a branch name does in Git is allow Git to find that last commit on the branch:



A <-B <-C   <--master


The name master finds commit C. Git defines this so that whatever ID is inside master, that's the tip commit of master. Hence, to add a new commit to master, we have Git write out our new commit's contents, setting the new commit's parent to C. The new commit gets some new big ugly hash ID, but we'll just call it D. Then we have Git write D's hash ID into the name master:



A <-B <-C <-D   <--master


The name is still master, but the hash ID that master represents has changed. In effect, the name moved, from commit C to commit D when we made the new commit.



These inside-commit arrows, which always point backwards,3 are as unchangeable as any other part of any commit. So we don't need to draw them: we know they attach to the second commit of a linked pair. The arrows coming out of branch names, however, move about, so we should draw those. This gives us what we see when we are about to run git merge:



...--F--G--H   <-- master (HEAD)

I--J--K--L <-- feature


At this point, with HEAD attached to master, we run git merge feature. Git:




  • locates our current commit H using HEAD;

  • locates the other commit L using the name feature;

  • uses the graph itself to find the best commit that's on both branches, which is commit F;


  • runs, in effect, two git diffs:



    git diff --find-renames <hash-of-F> <hash-of-H>   # what we changed on master
    git diff --find-renames <hash-of-F> <hash-of-L> # what they changed on feature


  • tries to combine the two diffs and apply the resulting changes to the snapshot from F;


  • if that succeeds, makes a new commit, but if not, stops with a merge conflict.


The new commit will have two parents—two backwards-looking links, pointing to H and L. Once Git makes it—either automatically because the merge succeeds, or because we resolve conflicts and use git merge --continue or git commit to finish the merge ourselves—we will have:



...--F--G--H------M   <-- master (HEAD)
/
I--J--K--L <-- feature


But suppose the merge stops with conflicts, so that we still have:



...--F--G--H   <-- master (HEAD)

I--J--K--L <-- feature


with our index and work-tree containing the partial merge result? If we now use git worktree add to create a second work-tree whose HEAD attaches to feature. The added work-tree's files are those from commit L; its index also holds a copy of the files from L. If we then add a new commit using that work-tree—let's call this one N since we've reserved M for our eventual merge—we get:



...--F--G--H   <-- master (HEAD of main work-tree)

I--J--K--L--N <-- feature (HEAD of feature work-tree)


The ongoing merge, however, is still merging commits H and L. When we eventually finish the merge, we get:



...--F--G--H------M   <-- master (HEAD of main work-tree)
/
I--J--K--L--N <-- feature (HEAD of feature work-tree)


This isn't what you wanted!





2This link goes to a Quora answer about the phrase "under the hood". (The first link that Google came up with for me went to Urban Dictionary, which has a rather ... different definition, ahem. This Quora article makes a claim that beginning Python programmers don't need to be aware of Python's somewhat peculiar approach to variables, where all objects are always boxed and variables are merely bound to the boxes, and I disagree with the claim—or at least, would say only for very-beginning—but the description of "under the hood" is still good.)



3For whatever reason, I like to use the British English distinction between "backward" and "backwards": backward is an adjective while backwards, with the -s, is the adverb. Then again, I also like to spell "grey" with an E. But I omit the U in "color".





Tackling the re-merge



What you wanted was:




and then update the merge to incorporate the new change-set without losing existing progress?




This effectively requires that we re-merge. That is, you wanted:



...--F--G--H---------M   <-- master (HEAD)
/
I--J--K--L--N <-- feature


What you have—let's assume you remove the added work-tree at this point, to simplify the drawing—is:



...--F--G--H------M   <-- master (HEAD)
/
I--J--K--L--N <-- feature


Commit M will point back to H and L, no matter what. But you can, now, do more things.



For instance, you can just allow M to continue to exist, and run git merge feature again. Git will now do the same thing it did last time: resolve HEAD to a commit (M), resolve feature to a commit (N), find their merge base—the best shared commit—which will be commit L, and have Git combine two sets of diffs. The effect will be to pick up the fixes you just made in N, and this will even usually work without conflicts, though the details depend on the precise fixes you made in L-vs-N. Git will make a new merge commit M2:



...--F--G--H------M--M2   <-- master (HEAD of main work-tree)
/ /
I--J--K--L--N <-- feature (HEAD of feature work-tree)


Note that M2 depends on M for M2's existence. You can't just ditch M entirely, at least not yet. But what if you want to ditch M in favor of M2?



Well, M2 has the correctly merged result as a snapshot. So let's save the hash ID of M2 somewhere, using another branch or tag name:



$ git tag save


Now let's use git reset --hard HEAD~2 to forcibly strip both M and M2 from the graph. HEAD~2 means "walk back two first-parent links from the current commit". The first parent of M2 is M, and the first parent of M is H, so this tells Git to make the name master point to H again:



             .............<-- master (HEAD)
.
...--F--G--H------M--M2 <-- tag: save
/ /
I--J--K--L--N <-- feature


If we did not have the tag keeping M2 and M visible, it would look as though those commits were completely gone. Meanwhile, the --hard part of the git reset told Git to overwrite our index and work-tree with the contents of commit H as well.



Now we can run:



git merge feature


which tells Git to use the HEAD commit to find commit H, to use the name feature to find commit N, to find their merge base (F), and to begin the process of merging. This will hit all the same conflicts as before, and you would have to resolve them all over again—but you have the resolutions available to you in commit M2. So now you just need to tell Git: Take the resolved files from M2. Put them in my index and work-tree. To do that, run:



$ git checkout save -- .


(from the top level of the work-tree). The name save points to commit M2, so that's the commit from which git checkout will extract files. The -- . tells git checkout: Don't directly check out that commit; instead, leave HEAD alone, but get the files from that commit that go with the name .. Copy those files into the index, at slot zero, wiping out any merge conflict information in slots 1-3. Copy the files from the index to the work-tree.



Since . means all files in this directory, and you're in the top level directory, this replaces all of your index and work-tree files with whatever is in M2.



Caveat: if you have a file in your index and work-tree right now that are missing in M2, this won't remove that file. That is, suppose one of the fixes you made in N was to remove a file named bogus. The file bogus exists in M but is gone in M2. If bogus is in H as well, it's in your index and work-tree right now, as you started with the files from F—which either had bogus or didn't—and took all the changes from H, which either kept bogus or added bogus. To work around that, use git rm -r . before git checkout save -- .. The remove step removes every file from the index and work-tree, which is OK because we just want whatever is in M2, and the git checkout save -- . step is going to get all of those.



(There is a shorter way to do all of this, but it's not very "tutorial", so I'm using the longer method here. Actually, there are two such ways, one using git read-tree in the middle of the merge, and the other using git commit-tree with two -p arguments to sidestep the need to run git merge at all. Both require a high level of comfort and familiarity with the inner workings of Git.)



Once you have all the files extracted from save (commit M2), you are ready to finish the merge as usual, using git merge --continue or git commit. This will make a new merge M3:



             -------------M3   <-- master (HEAD)
/ /
...--F--G--H------M--M2 / <-- tag: save
/ /__/
I--J--K--L--N <-- feature


You can now delete the tag save, which makes commits M and M2 become invisible (it takes a while for them to go away for real as their hash IDs are also stored in the HEAD reflog). Once we stop drawing them, we can start calling M3 just M instead:



...--F--G--H---------M   <-- master (HEAD)
/
I--J--K--L--N <-- feature


and that's what you wanted.



Using git rerere



There's another method to doing all of this that avoids the tag and saving the merge. Git has a feature called "rerere", which stands for re-use recorded resolution. I don't actually use this feature myself but it is designed for this kind of thing.



To use it, you run git config rerere.enabled true (or git config rerere.enabled 1, both mean the same thing) before you start the merge that may or may not have conflicts and may or may not need to be re-done. So you do have to plan in advance for this.



Having enabled rerere, you then just do the merge as usual, resolve any conflicts as usual—perhaps also adding a work-tree and more commits to the feature branch, even though they won't be in this merge—and then finish your merge as usual. Then you do:



git reset --hard HEAD^    # or HEAD~, use whichever spelling you prefer


This strips the merge off, losing your resolutions—but the earlier step of finishing the merge saved the resolutions. Specifically, Git saved the conflicts at the time the merge stopped, and then saved just their resolutions (not the entire files!) at the time you told Git this merge is finished.



Now you can run git merge feature again. You can wait until you have added even more commits to feature; the rerere-saved resolutions will stick around for a few months. (As the documentation notes:





By default, unresolved conflicts older than 15 days and resolved
conflicts older than 60 days are [garbage collected]





whenever Git runs git rerere gc, which git gc will run for you. Git itself will run git gc for you automatically, though usually not every day, so these might stick around more than 15 and 60 days on their own.)



This time, after you run git merge, instead of just recording the conflicts, Git will notice that it already has recorded resolutions and will use those to fix the conflicts. You can then inspect the results, and if all looks good, git add the files and finish the merge. (You can even have Git auto-add files that are completely resolved, using another rerere configuration item; see the git config documentation for details.)



(This is probably the friendliest development mode, if you have to re-do merges a lot. I don't like it much myself because it is hard to see what resolutions are recorded and when they will expire, and the automatic re-use can happen even if you don't want it to. You can use git rerere forget to clear recorded resolutions, but that's kind of a pain too. So I prefer keeping full commits, which have clearer lifetimes. But I also do not have to keep re-merging a lot.)






share|improve this answer























    Your Answer






    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "1"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














    draft saved

    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53426259%2fhow-to-commit-to-merging-branch-during-merge%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    1 Answer
    1






    active

    oldest

    votes








    1 Answer
    1






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    1















    Is there a way to commit changes to the feature branch from within the merge resolution




    No: merge conflicts use the index to store all three versions (base, --ours, and --theirs, in index slots 1, 2, and 3 respectively) of each conflicted file. Git builds new commits from whatever is in the index, and requires that every file in the index be in its normal "resolved" slot (slot zero) rather than as the three nonzero slots for merge-conflict-resolution.



    (The work-tree copy of the file holds Git's best effort at merging these three inputs, but Git only uses it again after you've fixed it up and run git add file. This copies the contents of file back into the index, writing to slot zero and emptying out slots 1-3. Now the file is resolved and ready to commit.)



    Since there is only one index for any given work-tree,1 and that index is "busy" with the merge, you have no index (and no work-tree, for that matter) in which to make a change to the feature branch. This phrasing—specifically, in any given work-tree—is a big hint, though:




    or, if that's not possible, commit to the feature branch in another terminal ...




    Yes, this is possible. It's considerably easier since Git version 2.5, which learned a new feature: git worktree add.





    1It's possible to set up a temporary index, and in versions of Git predating 2.5, there were some hacky scripts to do the equivalent of git worktree add using symlinks and temporary index files and a lot of other magic.





    Using git worktree add



    Each added work-tree has its own index (and, not coincidentally, its own HEAD as well). If you are in the middle of merging feature to master, so that the repository's main work-tree and index are dealing with branch master, you can run, from the top level of the work-tree:



    git worktree add ../feature


    or:



    git worktree add /path/to/where/I/want/feature


    or:



    git worktree add /path/to/dir feature


    (but not just git worktree add /path/to/dir, as that would try to check out a branch named dir).



    This will:




    • create a new directory ../feature or /path/to/where/I/want/feature or /path/to/dir; and

    • run, in essence, git checkout feature in that path.


    You now have an extra work-tree associated with the current repository. This added work-tree is on branch feature. (Your main work-tree is still on master, with the ongoing merge still going on.) In this other work-tree, you can modify files, git add them to its index, and git commit to add new commits to branch feature.



    There is still a problem, though. It's more obvious if you use a separate clone; let's cover that a bit now.



    Using a separate clone



    If you have a Git before 2.5, or are worried about the various bugs in git worktree add (there are several, including some fairly significant ones only fixed recently in 2.18 or 2.19 or so), you can simply re-clone your repository. You can either re-clone the original repository to a new clone, or re-clone your clone to a new clone. Either way, you get a new repository, with its own branches and index and work-tree, and in that clone you can do anything you want.



    Obviously, anything you do in this new clone does not affect your existing clone at all (at least, not until you fetch or push to transfer commits from clone to clone). Likewise, things you do in the original clone do not affect the new clone (until you transfer the commits).



    Transferring the commits gets the commits into the original clone, which is fine; but obviously the merge you're doing uses the commits you had when you started the merge, not any new ones you made in the other clone. But that's true even with git worktree add, as we'll see in a moment.



    Added work-trees vs separate clones



    When you use git worktree add, the two work-trees share the underlying repository. This means commits you make from either work-tree are immediately available to yourself working in the other work-tree. However, they also share branch names, which leads to a restriction that git worktree add includes that a separate clone does not.



    In particular, each added work-tree has the side effect of "locking out" access to that branch name. That is, once you've added a feature-branch work-tree, no other work-tree can use branch feature. If the main work-tree is on master, no added work-tree can use the branch master. Each branch name is exclusive to each added work-tree. (Note: you can use git checkout in the added work-tree to change branches, provided you maintain the exclusivity property.)



    You can, of course, just remove an added work-tree. Since that work-tree is now gone, the branch it had exclusive rights to, is now available for any other work-tree. See the documentation for details.



    The bugs that were fixed the most recently have to do with older added work-trees. If you add a work-tree, it's advisable to finish up your work in that added work-tree within a few weeks, then ditch it. Use it for relatively quick projects, in other words. (The scariest bug, in my opinion, is that git gc will sometimes think that an object isn't in use, because it fails to check added work-trees' HEAD and index files for object IDs. The default prune time of 2 weeks means that you're safe from this bug for at least two weeks from the time you start working in an added work-tree. You get more time, resetting the clock, whenever you commit, provided the added work-tree is not on a detached HEAD.)



    The hitch



    I mentioned that no matter what you do here, there is still a problem. That has to do with how Git works "under the hood".2




    and then update the merge to incorporate the new change-set without losing existing progress?




    The merge you are in the middle of, merging feature to master, is—at least in a sense—not merging the branch. It's merging the commit.



    Remember that in Git, a branch name is just a human-readable identifier containing a hash ID. Git is really all about commits, and the true name of a commit is a big, ugly, apparently-random, unfriendly-to-humans hash ID like 8858448bb49332d353febc078ce4a3abcc962efe (this is the hash ID of a commit in the Git repository for Git).



    Each commit stores, along with all its other data, a list of parent hash IDs, usually just one hash ID. We can, and Git does, use these to link commits up into backward chains. If we have a repository with only three commits in it, we might draw it like this, using single uppercase letters to stand in for the actual commit hash IDs:



    A <-B <-C


    Since commit A is the very first commit, its parent-list is empty: it has no parent. B, however, lists A as its (sole) parent, and C lists B as its parent. Git needs only to find commit C somehow, and then C finds B which finds A. (After finding A, there are no parents left, and we can rest.)



    The main thing that a branch name does in Git is allow Git to find that last commit on the branch:



    A <-B <-C   <--master


    The name master finds commit C. Git defines this so that whatever ID is inside master, that's the tip commit of master. Hence, to add a new commit to master, we have Git write out our new commit's contents, setting the new commit's parent to C. The new commit gets some new big ugly hash ID, but we'll just call it D. Then we have Git write D's hash ID into the name master:



    A <-B <-C <-D   <--master


    The name is still master, but the hash ID that master represents has changed. In effect, the name moved, from commit C to commit D when we made the new commit.



    These inside-commit arrows, which always point backwards,3 are as unchangeable as any other part of any commit. So we don't need to draw them: we know they attach to the second commit of a linked pair. The arrows coming out of branch names, however, move about, so we should draw those. This gives us what we see when we are about to run git merge:



    ...--F--G--H   <-- master (HEAD)

    I--J--K--L <-- feature


    At this point, with HEAD attached to master, we run git merge feature. Git:




    • locates our current commit H using HEAD;

    • locates the other commit L using the name feature;

    • uses the graph itself to find the best commit that's on both branches, which is commit F;


    • runs, in effect, two git diffs:



      git diff --find-renames <hash-of-F> <hash-of-H>   # what we changed on master
      git diff --find-renames <hash-of-F> <hash-of-L> # what they changed on feature


    • tries to combine the two diffs and apply the resulting changes to the snapshot from F;


    • if that succeeds, makes a new commit, but if not, stops with a merge conflict.


    The new commit will have two parents—two backwards-looking links, pointing to H and L. Once Git makes it—either automatically because the merge succeeds, or because we resolve conflicts and use git merge --continue or git commit to finish the merge ourselves—we will have:



    ...--F--G--H------M   <-- master (HEAD)
    /
    I--J--K--L <-- feature


    But suppose the merge stops with conflicts, so that we still have:



    ...--F--G--H   <-- master (HEAD)

    I--J--K--L <-- feature


    with our index and work-tree containing the partial merge result? If we now use git worktree add to create a second work-tree whose HEAD attaches to feature. The added work-tree's files are those from commit L; its index also holds a copy of the files from L. If we then add a new commit using that work-tree—let's call this one N since we've reserved M for our eventual merge—we get:



    ...--F--G--H   <-- master (HEAD of main work-tree)

    I--J--K--L--N <-- feature (HEAD of feature work-tree)


    The ongoing merge, however, is still merging commits H and L. When we eventually finish the merge, we get:



    ...--F--G--H------M   <-- master (HEAD of main work-tree)
    /
    I--J--K--L--N <-- feature (HEAD of feature work-tree)


    This isn't what you wanted!





    2This link goes to a Quora answer about the phrase "under the hood". (The first link that Google came up with for me went to Urban Dictionary, which has a rather ... different definition, ahem. This Quora article makes a claim that beginning Python programmers don't need to be aware of Python's somewhat peculiar approach to variables, where all objects are always boxed and variables are merely bound to the boxes, and I disagree with the claim—or at least, would say only for very-beginning—but the description of "under the hood" is still good.)



    3For whatever reason, I like to use the British English distinction between "backward" and "backwards": backward is an adjective while backwards, with the -s, is the adverb. Then again, I also like to spell "grey" with an E. But I omit the U in "color".





    Tackling the re-merge



    What you wanted was:




    and then update the merge to incorporate the new change-set without losing existing progress?




    This effectively requires that we re-merge. That is, you wanted:



    ...--F--G--H---------M   <-- master (HEAD)
    /
    I--J--K--L--N <-- feature


    What you have—let's assume you remove the added work-tree at this point, to simplify the drawing—is:



    ...--F--G--H------M   <-- master (HEAD)
    /
    I--J--K--L--N <-- feature


    Commit M will point back to H and L, no matter what. But you can, now, do more things.



    For instance, you can just allow M to continue to exist, and run git merge feature again. Git will now do the same thing it did last time: resolve HEAD to a commit (M), resolve feature to a commit (N), find their merge base—the best shared commit—which will be commit L, and have Git combine two sets of diffs. The effect will be to pick up the fixes you just made in N, and this will even usually work without conflicts, though the details depend on the precise fixes you made in L-vs-N. Git will make a new merge commit M2:



    ...--F--G--H------M--M2   <-- master (HEAD of main work-tree)
    / /
    I--J--K--L--N <-- feature (HEAD of feature work-tree)


    Note that M2 depends on M for M2's existence. You can't just ditch M entirely, at least not yet. But what if you want to ditch M in favor of M2?



    Well, M2 has the correctly merged result as a snapshot. So let's save the hash ID of M2 somewhere, using another branch or tag name:



    $ git tag save


    Now let's use git reset --hard HEAD~2 to forcibly strip both M and M2 from the graph. HEAD~2 means "walk back two first-parent links from the current commit". The first parent of M2 is M, and the first parent of M is H, so this tells Git to make the name master point to H again:



                 .............<-- master (HEAD)
    .
    ...--F--G--H------M--M2 <-- tag: save
    / /
    I--J--K--L--N <-- feature


    If we did not have the tag keeping M2 and M visible, it would look as though those commits were completely gone. Meanwhile, the --hard part of the git reset told Git to overwrite our index and work-tree with the contents of commit H as well.



    Now we can run:



    git merge feature


    which tells Git to use the HEAD commit to find commit H, to use the name feature to find commit N, to find their merge base (F), and to begin the process of merging. This will hit all the same conflicts as before, and you would have to resolve them all over again—but you have the resolutions available to you in commit M2. So now you just need to tell Git: Take the resolved files from M2. Put them in my index and work-tree. To do that, run:



    $ git checkout save -- .


    (from the top level of the work-tree). The name save points to commit M2, so that's the commit from which git checkout will extract files. The -- . tells git checkout: Don't directly check out that commit; instead, leave HEAD alone, but get the files from that commit that go with the name .. Copy those files into the index, at slot zero, wiping out any merge conflict information in slots 1-3. Copy the files from the index to the work-tree.



    Since . means all files in this directory, and you're in the top level directory, this replaces all of your index and work-tree files with whatever is in M2.



    Caveat: if you have a file in your index and work-tree right now that are missing in M2, this won't remove that file. That is, suppose one of the fixes you made in N was to remove a file named bogus. The file bogus exists in M but is gone in M2. If bogus is in H as well, it's in your index and work-tree right now, as you started with the files from F—which either had bogus or didn't—and took all the changes from H, which either kept bogus or added bogus. To work around that, use git rm -r . before git checkout save -- .. The remove step removes every file from the index and work-tree, which is OK because we just want whatever is in M2, and the git checkout save -- . step is going to get all of those.



    (There is a shorter way to do all of this, but it's not very "tutorial", so I'm using the longer method here. Actually, there are two such ways, one using git read-tree in the middle of the merge, and the other using git commit-tree with two -p arguments to sidestep the need to run git merge at all. Both require a high level of comfort and familiarity with the inner workings of Git.)



    Once you have all the files extracted from save (commit M2), you are ready to finish the merge as usual, using git merge --continue or git commit. This will make a new merge M3:



                 -------------M3   <-- master (HEAD)
    / /
    ...--F--G--H------M--M2 / <-- tag: save
    / /__/
    I--J--K--L--N <-- feature


    You can now delete the tag save, which makes commits M and M2 become invisible (it takes a while for them to go away for real as their hash IDs are also stored in the HEAD reflog). Once we stop drawing them, we can start calling M3 just M instead:



    ...--F--G--H---------M   <-- master (HEAD)
    /
    I--J--K--L--N <-- feature


    and that's what you wanted.



    Using git rerere



    There's another method to doing all of this that avoids the tag and saving the merge. Git has a feature called "rerere", which stands for re-use recorded resolution. I don't actually use this feature myself but it is designed for this kind of thing.



    To use it, you run git config rerere.enabled true (or git config rerere.enabled 1, both mean the same thing) before you start the merge that may or may not have conflicts and may or may not need to be re-done. So you do have to plan in advance for this.



    Having enabled rerere, you then just do the merge as usual, resolve any conflicts as usual—perhaps also adding a work-tree and more commits to the feature branch, even though they won't be in this merge—and then finish your merge as usual. Then you do:



    git reset --hard HEAD^    # or HEAD~, use whichever spelling you prefer


    This strips the merge off, losing your resolutions—but the earlier step of finishing the merge saved the resolutions. Specifically, Git saved the conflicts at the time the merge stopped, and then saved just their resolutions (not the entire files!) at the time you told Git this merge is finished.



    Now you can run git merge feature again. You can wait until you have added even more commits to feature; the rerere-saved resolutions will stick around for a few months. (As the documentation notes:





    By default, unresolved conflicts older than 15 days and resolved
    conflicts older than 60 days are [garbage collected]





    whenever Git runs git rerere gc, which git gc will run for you. Git itself will run git gc for you automatically, though usually not every day, so these might stick around more than 15 and 60 days on their own.)



    This time, after you run git merge, instead of just recording the conflicts, Git will notice that it already has recorded resolutions and will use those to fix the conflicts. You can then inspect the results, and if all looks good, git add the files and finish the merge. (You can even have Git auto-add files that are completely resolved, using another rerere configuration item; see the git config documentation for details.)



    (This is probably the friendliest development mode, if you have to re-do merges a lot. I don't like it much myself because it is hard to see what resolutions are recorded and when they will expire, and the automatic re-use can happen even if you don't want it to. You can use git rerere forget to clear recorded resolutions, but that's kind of a pain too. So I prefer keeping full commits, which have clearer lifetimes. But I also do not have to keep re-merging a lot.)






    share|improve this answer




























      1















      Is there a way to commit changes to the feature branch from within the merge resolution




      No: merge conflicts use the index to store all three versions (base, --ours, and --theirs, in index slots 1, 2, and 3 respectively) of each conflicted file. Git builds new commits from whatever is in the index, and requires that every file in the index be in its normal "resolved" slot (slot zero) rather than as the three nonzero slots for merge-conflict-resolution.



      (The work-tree copy of the file holds Git's best effort at merging these three inputs, but Git only uses it again after you've fixed it up and run git add file. This copies the contents of file back into the index, writing to slot zero and emptying out slots 1-3. Now the file is resolved and ready to commit.)



      Since there is only one index for any given work-tree,1 and that index is "busy" with the merge, you have no index (and no work-tree, for that matter) in which to make a change to the feature branch. This phrasing—specifically, in any given work-tree—is a big hint, though:




      or, if that's not possible, commit to the feature branch in another terminal ...




      Yes, this is possible. It's considerably easier since Git version 2.5, which learned a new feature: git worktree add.





      1It's possible to set up a temporary index, and in versions of Git predating 2.5, there were some hacky scripts to do the equivalent of git worktree add using symlinks and temporary index files and a lot of other magic.





      Using git worktree add



      Each added work-tree has its own index (and, not coincidentally, its own HEAD as well). If you are in the middle of merging feature to master, so that the repository's main work-tree and index are dealing with branch master, you can run, from the top level of the work-tree:



      git worktree add ../feature


      or:



      git worktree add /path/to/where/I/want/feature


      or:



      git worktree add /path/to/dir feature


      (but not just git worktree add /path/to/dir, as that would try to check out a branch named dir).



      This will:




      • create a new directory ../feature or /path/to/where/I/want/feature or /path/to/dir; and

      • run, in essence, git checkout feature in that path.


      You now have an extra work-tree associated with the current repository. This added work-tree is on branch feature. (Your main work-tree is still on master, with the ongoing merge still going on.) In this other work-tree, you can modify files, git add them to its index, and git commit to add new commits to branch feature.



      There is still a problem, though. It's more obvious if you use a separate clone; let's cover that a bit now.



      Using a separate clone



      If you have a Git before 2.5, or are worried about the various bugs in git worktree add (there are several, including some fairly significant ones only fixed recently in 2.18 or 2.19 or so), you can simply re-clone your repository. You can either re-clone the original repository to a new clone, or re-clone your clone to a new clone. Either way, you get a new repository, with its own branches and index and work-tree, and in that clone you can do anything you want.



      Obviously, anything you do in this new clone does not affect your existing clone at all (at least, not until you fetch or push to transfer commits from clone to clone). Likewise, things you do in the original clone do not affect the new clone (until you transfer the commits).



      Transferring the commits gets the commits into the original clone, which is fine; but obviously the merge you're doing uses the commits you had when you started the merge, not any new ones you made in the other clone. But that's true even with git worktree add, as we'll see in a moment.



      Added work-trees vs separate clones



      When you use git worktree add, the two work-trees share the underlying repository. This means commits you make from either work-tree are immediately available to yourself working in the other work-tree. However, they also share branch names, which leads to a restriction that git worktree add includes that a separate clone does not.



      In particular, each added work-tree has the side effect of "locking out" access to that branch name. That is, once you've added a feature-branch work-tree, no other work-tree can use branch feature. If the main work-tree is on master, no added work-tree can use the branch master. Each branch name is exclusive to each added work-tree. (Note: you can use git checkout in the added work-tree to change branches, provided you maintain the exclusivity property.)



      You can, of course, just remove an added work-tree. Since that work-tree is now gone, the branch it had exclusive rights to, is now available for any other work-tree. See the documentation for details.



      The bugs that were fixed the most recently have to do with older added work-trees. If you add a work-tree, it's advisable to finish up your work in that added work-tree within a few weeks, then ditch it. Use it for relatively quick projects, in other words. (The scariest bug, in my opinion, is that git gc will sometimes think that an object isn't in use, because it fails to check added work-trees' HEAD and index files for object IDs. The default prune time of 2 weeks means that you're safe from this bug for at least two weeks from the time you start working in an added work-tree. You get more time, resetting the clock, whenever you commit, provided the added work-tree is not on a detached HEAD.)



      The hitch



      I mentioned that no matter what you do here, there is still a problem. That has to do with how Git works "under the hood".2




      and then update the merge to incorporate the new change-set without losing existing progress?




      The merge you are in the middle of, merging feature to master, is—at least in a sense—not merging the branch. It's merging the commit.



      Remember that in Git, a branch name is just a human-readable identifier containing a hash ID. Git is really all about commits, and the true name of a commit is a big, ugly, apparently-random, unfriendly-to-humans hash ID like 8858448bb49332d353febc078ce4a3abcc962efe (this is the hash ID of a commit in the Git repository for Git).



      Each commit stores, along with all its other data, a list of parent hash IDs, usually just one hash ID. We can, and Git does, use these to link commits up into backward chains. If we have a repository with only three commits in it, we might draw it like this, using single uppercase letters to stand in for the actual commit hash IDs:



      A <-B <-C


      Since commit A is the very first commit, its parent-list is empty: it has no parent. B, however, lists A as its (sole) parent, and C lists B as its parent. Git needs only to find commit C somehow, and then C finds B which finds A. (After finding A, there are no parents left, and we can rest.)



      The main thing that a branch name does in Git is allow Git to find that last commit on the branch:



      A <-B <-C   <--master


      The name master finds commit C. Git defines this so that whatever ID is inside master, that's the tip commit of master. Hence, to add a new commit to master, we have Git write out our new commit's contents, setting the new commit's parent to C. The new commit gets some new big ugly hash ID, but we'll just call it D. Then we have Git write D's hash ID into the name master:



      A <-B <-C <-D   <--master


      The name is still master, but the hash ID that master represents has changed. In effect, the name moved, from commit C to commit D when we made the new commit.



      These inside-commit arrows, which always point backwards,3 are as unchangeable as any other part of any commit. So we don't need to draw them: we know they attach to the second commit of a linked pair. The arrows coming out of branch names, however, move about, so we should draw those. This gives us what we see when we are about to run git merge:



      ...--F--G--H   <-- master (HEAD)

      I--J--K--L <-- feature


      At this point, with HEAD attached to master, we run git merge feature. Git:




      • locates our current commit H using HEAD;

      • locates the other commit L using the name feature;

      • uses the graph itself to find the best commit that's on both branches, which is commit F;


      • runs, in effect, two git diffs:



        git diff --find-renames <hash-of-F> <hash-of-H>   # what we changed on master
        git diff --find-renames <hash-of-F> <hash-of-L> # what they changed on feature


      • tries to combine the two diffs and apply the resulting changes to the snapshot from F;


      • if that succeeds, makes a new commit, but if not, stops with a merge conflict.


      The new commit will have two parents—two backwards-looking links, pointing to H and L. Once Git makes it—either automatically because the merge succeeds, or because we resolve conflicts and use git merge --continue or git commit to finish the merge ourselves—we will have:



      ...--F--G--H------M   <-- master (HEAD)
      /
      I--J--K--L <-- feature


      But suppose the merge stops with conflicts, so that we still have:



      ...--F--G--H   <-- master (HEAD)

      I--J--K--L <-- feature


      with our index and work-tree containing the partial merge result? If we now use git worktree add to create a second work-tree whose HEAD attaches to feature. The added work-tree's files are those from commit L; its index also holds a copy of the files from L. If we then add a new commit using that work-tree—let's call this one N since we've reserved M for our eventual merge—we get:



      ...--F--G--H   <-- master (HEAD of main work-tree)

      I--J--K--L--N <-- feature (HEAD of feature work-tree)


      The ongoing merge, however, is still merging commits H and L. When we eventually finish the merge, we get:



      ...--F--G--H------M   <-- master (HEAD of main work-tree)
      /
      I--J--K--L--N <-- feature (HEAD of feature work-tree)


      This isn't what you wanted!





      2This link goes to a Quora answer about the phrase "under the hood". (The first link that Google came up with for me went to Urban Dictionary, which has a rather ... different definition, ahem. This Quora article makes a claim that beginning Python programmers don't need to be aware of Python's somewhat peculiar approach to variables, where all objects are always boxed and variables are merely bound to the boxes, and I disagree with the claim—or at least, would say only for very-beginning—but the description of "under the hood" is still good.)



      3For whatever reason, I like to use the British English distinction between "backward" and "backwards": backward is an adjective while backwards, with the -s, is the adverb. Then again, I also like to spell "grey" with an E. But I omit the U in "color".





      Tackling the re-merge



      What you wanted was:




      and then update the merge to incorporate the new change-set without losing existing progress?




      This effectively requires that we re-merge. That is, you wanted:



      ...--F--G--H---------M   <-- master (HEAD)
      /
      I--J--K--L--N <-- feature


      What you have—let's assume you remove the added work-tree at this point, to simplify the drawing—is:



      ...--F--G--H------M   <-- master (HEAD)
      /
      I--J--K--L--N <-- feature


      Commit M will point back to H and L, no matter what. But you can, now, do more things.



      For instance, you can just allow M to continue to exist, and run git merge feature again. Git will now do the same thing it did last time: resolve HEAD to a commit (M), resolve feature to a commit (N), find their merge base—the best shared commit—which will be commit L, and have Git combine two sets of diffs. The effect will be to pick up the fixes you just made in N, and this will even usually work without conflicts, though the details depend on the precise fixes you made in L-vs-N. Git will make a new merge commit M2:



      ...--F--G--H------M--M2   <-- master (HEAD of main work-tree)
      / /
      I--J--K--L--N <-- feature (HEAD of feature work-tree)


      Note that M2 depends on M for M2's existence. You can't just ditch M entirely, at least not yet. But what if you want to ditch M in favor of M2?



      Well, M2 has the correctly merged result as a snapshot. So let's save the hash ID of M2 somewhere, using another branch or tag name:



      $ git tag save


      Now let's use git reset --hard HEAD~2 to forcibly strip both M and M2 from the graph. HEAD~2 means "walk back two first-parent links from the current commit". The first parent of M2 is M, and the first parent of M is H, so this tells Git to make the name master point to H again:



                   .............<-- master (HEAD)
      .
      ...--F--G--H------M--M2 <-- tag: save
      / /
      I--J--K--L--N <-- feature


      If we did not have the tag keeping M2 and M visible, it would look as though those commits were completely gone. Meanwhile, the --hard part of the git reset told Git to overwrite our index and work-tree with the contents of commit H as well.



      Now we can run:



      git merge feature


      which tells Git to use the HEAD commit to find commit H, to use the name feature to find commit N, to find their merge base (F), and to begin the process of merging. This will hit all the same conflicts as before, and you would have to resolve them all over again—but you have the resolutions available to you in commit M2. So now you just need to tell Git: Take the resolved files from M2. Put them in my index and work-tree. To do that, run:



      $ git checkout save -- .


      (from the top level of the work-tree). The name save points to commit M2, so that's the commit from which git checkout will extract files. The -- . tells git checkout: Don't directly check out that commit; instead, leave HEAD alone, but get the files from that commit that go with the name .. Copy those files into the index, at slot zero, wiping out any merge conflict information in slots 1-3. Copy the files from the index to the work-tree.



      Since . means all files in this directory, and you're in the top level directory, this replaces all of your index and work-tree files with whatever is in M2.



      Caveat: if you have a file in your index and work-tree right now that are missing in M2, this won't remove that file. That is, suppose one of the fixes you made in N was to remove a file named bogus. The file bogus exists in M but is gone in M2. If bogus is in H as well, it's in your index and work-tree right now, as you started with the files from F—which either had bogus or didn't—and took all the changes from H, which either kept bogus or added bogus. To work around that, use git rm -r . before git checkout save -- .. The remove step removes every file from the index and work-tree, which is OK because we just want whatever is in M2, and the git checkout save -- . step is going to get all of those.



      (There is a shorter way to do all of this, but it's not very "tutorial", so I'm using the longer method here. Actually, there are two such ways, one using git read-tree in the middle of the merge, and the other using git commit-tree with two -p arguments to sidestep the need to run git merge at all. Both require a high level of comfort and familiarity with the inner workings of Git.)



      Once you have all the files extracted from save (commit M2), you are ready to finish the merge as usual, using git merge --continue or git commit. This will make a new merge M3:



                   -------------M3   <-- master (HEAD)
      / /
      ...--F--G--H------M--M2 / <-- tag: save
      / /__/
      I--J--K--L--N <-- feature


      You can now delete the tag save, which makes commits M and M2 become invisible (it takes a while for them to go away for real as their hash IDs are also stored in the HEAD reflog). Once we stop drawing them, we can start calling M3 just M instead:



      ...--F--G--H---------M   <-- master (HEAD)
      /
      I--J--K--L--N <-- feature


      and that's what you wanted.



      Using git rerere



      There's another method to doing all of this that avoids the tag and saving the merge. Git has a feature called "rerere", which stands for re-use recorded resolution. I don't actually use this feature myself but it is designed for this kind of thing.



      To use it, you run git config rerere.enabled true (or git config rerere.enabled 1, both mean the same thing) before you start the merge that may or may not have conflicts and may or may not need to be re-done. So you do have to plan in advance for this.



      Having enabled rerere, you then just do the merge as usual, resolve any conflicts as usual—perhaps also adding a work-tree and more commits to the feature branch, even though they won't be in this merge—and then finish your merge as usual. Then you do:



      git reset --hard HEAD^    # or HEAD~, use whichever spelling you prefer


      This strips the merge off, losing your resolutions—but the earlier step of finishing the merge saved the resolutions. Specifically, Git saved the conflicts at the time the merge stopped, and then saved just their resolutions (not the entire files!) at the time you told Git this merge is finished.



      Now you can run git merge feature again. You can wait until you have added even more commits to feature; the rerere-saved resolutions will stick around for a few months. (As the documentation notes:





      By default, unresolved conflicts older than 15 days and resolved
      conflicts older than 60 days are [garbage collected]





      whenever Git runs git rerere gc, which git gc will run for you. Git itself will run git gc for you automatically, though usually not every day, so these might stick around more than 15 and 60 days on their own.)



      This time, after you run git merge, instead of just recording the conflicts, Git will notice that it already has recorded resolutions and will use those to fix the conflicts. You can then inspect the results, and if all looks good, git add the files and finish the merge. (You can even have Git auto-add files that are completely resolved, using another rerere configuration item; see the git config documentation for details.)



      (This is probably the friendliest development mode, if you have to re-do merges a lot. I don't like it much myself because it is hard to see what resolutions are recorded and when they will expire, and the automatic re-use can happen even if you don't want it to. You can use git rerere forget to clear recorded resolutions, but that's kind of a pain too. So I prefer keeping full commits, which have clearer lifetimes. But I also do not have to keep re-merging a lot.)






      share|improve this answer


























        1












        1








        1








        Is there a way to commit changes to the feature branch from within the merge resolution




        No: merge conflicts use the index to store all three versions (base, --ours, and --theirs, in index slots 1, 2, and 3 respectively) of each conflicted file. Git builds new commits from whatever is in the index, and requires that every file in the index be in its normal "resolved" slot (slot zero) rather than as the three nonzero slots for merge-conflict-resolution.



        (The work-tree copy of the file holds Git's best effort at merging these three inputs, but Git only uses it again after you've fixed it up and run git add file. This copies the contents of file back into the index, writing to slot zero and emptying out slots 1-3. Now the file is resolved and ready to commit.)



        Since there is only one index for any given work-tree,1 and that index is "busy" with the merge, you have no index (and no work-tree, for that matter) in which to make a change to the feature branch. This phrasing—specifically, in any given work-tree—is a big hint, though:




        or, if that's not possible, commit to the feature branch in another terminal ...




        Yes, this is possible. It's considerably easier since Git version 2.5, which learned a new feature: git worktree add.





        1It's possible to set up a temporary index, and in versions of Git predating 2.5, there were some hacky scripts to do the equivalent of git worktree add using symlinks and temporary index files and a lot of other magic.





        Using git worktree add



        Each added work-tree has its own index (and, not coincidentally, its own HEAD as well). If you are in the middle of merging feature to master, so that the repository's main work-tree and index are dealing with branch master, you can run, from the top level of the work-tree:



        git worktree add ../feature


        or:



        git worktree add /path/to/where/I/want/feature


        or:



        git worktree add /path/to/dir feature


        (but not just git worktree add /path/to/dir, as that would try to check out a branch named dir).



        This will:




        • create a new directory ../feature or /path/to/where/I/want/feature or /path/to/dir; and

        • run, in essence, git checkout feature in that path.


        You now have an extra work-tree associated with the current repository. This added work-tree is on branch feature. (Your main work-tree is still on master, with the ongoing merge still going on.) In this other work-tree, you can modify files, git add them to its index, and git commit to add new commits to branch feature.



        There is still a problem, though. It's more obvious if you use a separate clone; let's cover that a bit now.



        Using a separate clone



        If you have a Git before 2.5, or are worried about the various bugs in git worktree add (there are several, including some fairly significant ones only fixed recently in 2.18 or 2.19 or so), you can simply re-clone your repository. You can either re-clone the original repository to a new clone, or re-clone your clone to a new clone. Either way, you get a new repository, with its own branches and index and work-tree, and in that clone you can do anything you want.



        Obviously, anything you do in this new clone does not affect your existing clone at all (at least, not until you fetch or push to transfer commits from clone to clone). Likewise, things you do in the original clone do not affect the new clone (until you transfer the commits).



        Transferring the commits gets the commits into the original clone, which is fine; but obviously the merge you're doing uses the commits you had when you started the merge, not any new ones you made in the other clone. But that's true even with git worktree add, as we'll see in a moment.



        Added work-trees vs separate clones



        When you use git worktree add, the two work-trees share the underlying repository. This means commits you make from either work-tree are immediately available to yourself working in the other work-tree. However, they also share branch names, which leads to a restriction that git worktree add includes that a separate clone does not.



        In particular, each added work-tree has the side effect of "locking out" access to that branch name. That is, once you've added a feature-branch work-tree, no other work-tree can use branch feature. If the main work-tree is on master, no added work-tree can use the branch master. Each branch name is exclusive to each added work-tree. (Note: you can use git checkout in the added work-tree to change branches, provided you maintain the exclusivity property.)



        You can, of course, just remove an added work-tree. Since that work-tree is now gone, the branch it had exclusive rights to, is now available for any other work-tree. See the documentation for details.



        The bugs that were fixed the most recently have to do with older added work-trees. If you add a work-tree, it's advisable to finish up your work in that added work-tree within a few weeks, then ditch it. Use it for relatively quick projects, in other words. (The scariest bug, in my opinion, is that git gc will sometimes think that an object isn't in use, because it fails to check added work-trees' HEAD and index files for object IDs. The default prune time of 2 weeks means that you're safe from this bug for at least two weeks from the time you start working in an added work-tree. You get more time, resetting the clock, whenever you commit, provided the added work-tree is not on a detached HEAD.)



        The hitch



        I mentioned that no matter what you do here, there is still a problem. That has to do with how Git works "under the hood".2




        and then update the merge to incorporate the new change-set without losing existing progress?




        The merge you are in the middle of, merging feature to master, is—at least in a sense—not merging the branch. It's merging the commit.



        Remember that in Git, a branch name is just a human-readable identifier containing a hash ID. Git is really all about commits, and the true name of a commit is a big, ugly, apparently-random, unfriendly-to-humans hash ID like 8858448bb49332d353febc078ce4a3abcc962efe (this is the hash ID of a commit in the Git repository for Git).



        Each commit stores, along with all its other data, a list of parent hash IDs, usually just one hash ID. We can, and Git does, use these to link commits up into backward chains. If we have a repository with only three commits in it, we might draw it like this, using single uppercase letters to stand in for the actual commit hash IDs:



        A <-B <-C


        Since commit A is the very first commit, its parent-list is empty: it has no parent. B, however, lists A as its (sole) parent, and C lists B as its parent. Git needs only to find commit C somehow, and then C finds B which finds A. (After finding A, there are no parents left, and we can rest.)



        The main thing that a branch name does in Git is allow Git to find that last commit on the branch:



        A <-B <-C   <--master


        The name master finds commit C. Git defines this so that whatever ID is inside master, that's the tip commit of master. Hence, to add a new commit to master, we have Git write out our new commit's contents, setting the new commit's parent to C. The new commit gets some new big ugly hash ID, but we'll just call it D. Then we have Git write D's hash ID into the name master:



        A <-B <-C <-D   <--master


        The name is still master, but the hash ID that master represents has changed. In effect, the name moved, from commit C to commit D when we made the new commit.



        These inside-commit arrows, which always point backwards,3 are as unchangeable as any other part of any commit. So we don't need to draw them: we know they attach to the second commit of a linked pair. The arrows coming out of branch names, however, move about, so we should draw those. This gives us what we see when we are about to run git merge:



        ...--F--G--H   <-- master (HEAD)

        I--J--K--L <-- feature


        At this point, with HEAD attached to master, we run git merge feature. Git:




        • locates our current commit H using HEAD;

        • locates the other commit L using the name feature;

        • uses the graph itself to find the best commit that's on both branches, which is commit F;


        • runs, in effect, two git diffs:



          git diff --find-renames <hash-of-F> <hash-of-H>   # what we changed on master
          git diff --find-renames <hash-of-F> <hash-of-L> # what they changed on feature


        • tries to combine the two diffs and apply the resulting changes to the snapshot from F;


        • if that succeeds, makes a new commit, but if not, stops with a merge conflict.


        The new commit will have two parents—two backwards-looking links, pointing to H and L. Once Git makes it—either automatically because the merge succeeds, or because we resolve conflicts and use git merge --continue or git commit to finish the merge ourselves—we will have:



        ...--F--G--H------M   <-- master (HEAD)
        /
        I--J--K--L <-- feature


        But suppose the merge stops with conflicts, so that we still have:



        ...--F--G--H   <-- master (HEAD)

        I--J--K--L <-- feature


        with our index and work-tree containing the partial merge result? If we now use git worktree add to create a second work-tree whose HEAD attaches to feature. The added work-tree's files are those from commit L; its index also holds a copy of the files from L. If we then add a new commit using that work-tree—let's call this one N since we've reserved M for our eventual merge—we get:



        ...--F--G--H   <-- master (HEAD of main work-tree)

        I--J--K--L--N <-- feature (HEAD of feature work-tree)


        The ongoing merge, however, is still merging commits H and L. When we eventually finish the merge, we get:



        ...--F--G--H------M   <-- master (HEAD of main work-tree)
        /
        I--J--K--L--N <-- feature (HEAD of feature work-tree)


        This isn't what you wanted!





        2This link goes to a Quora answer about the phrase "under the hood". (The first link that Google came up with for me went to Urban Dictionary, which has a rather ... different definition, ahem. This Quora article makes a claim that beginning Python programmers don't need to be aware of Python's somewhat peculiar approach to variables, where all objects are always boxed and variables are merely bound to the boxes, and I disagree with the claim—or at least, would say only for very-beginning—but the description of "under the hood" is still good.)



        3For whatever reason, I like to use the British English distinction between "backward" and "backwards": backward is an adjective while backwards, with the -s, is the adverb. Then again, I also like to spell "grey" with an E. But I omit the U in "color".





        Tackling the re-merge



        What you wanted was:




        and then update the merge to incorporate the new change-set without losing existing progress?




        This effectively requires that we re-merge. That is, you wanted:



        ...--F--G--H---------M   <-- master (HEAD)
        /
        I--J--K--L--N <-- feature


        What you have—let's assume you remove the added work-tree at this point, to simplify the drawing—is:



        ...--F--G--H------M   <-- master (HEAD)
        /
        I--J--K--L--N <-- feature


        Commit M will point back to H and L, no matter what. But you can, now, do more things.



        For instance, you can just allow M to continue to exist, and run git merge feature again. Git will now do the same thing it did last time: resolve HEAD to a commit (M), resolve feature to a commit (N), find their merge base—the best shared commit—which will be commit L, and have Git combine two sets of diffs. The effect will be to pick up the fixes you just made in N, and this will even usually work without conflicts, though the details depend on the precise fixes you made in L-vs-N. Git will make a new merge commit M2:



        ...--F--G--H------M--M2   <-- master (HEAD of main work-tree)
        / /
        I--J--K--L--N <-- feature (HEAD of feature work-tree)


        Note that M2 depends on M for M2's existence. You can't just ditch M entirely, at least not yet. But what if you want to ditch M in favor of M2?



        Well, M2 has the correctly merged result as a snapshot. So let's save the hash ID of M2 somewhere, using another branch or tag name:



        $ git tag save


        Now let's use git reset --hard HEAD~2 to forcibly strip both M and M2 from the graph. HEAD~2 means "walk back two first-parent links from the current commit". The first parent of M2 is M, and the first parent of M is H, so this tells Git to make the name master point to H again:



                     .............<-- master (HEAD)
        .
        ...--F--G--H------M--M2 <-- tag: save
        / /
        I--J--K--L--N <-- feature


        If we did not have the tag keeping M2 and M visible, it would look as though those commits were completely gone. Meanwhile, the --hard part of the git reset told Git to overwrite our index and work-tree with the contents of commit H as well.



        Now we can run:



        git merge feature


        which tells Git to use the HEAD commit to find commit H, to use the name feature to find commit N, to find their merge base (F), and to begin the process of merging. This will hit all the same conflicts as before, and you would have to resolve them all over again—but you have the resolutions available to you in commit M2. So now you just need to tell Git: Take the resolved files from M2. Put them in my index and work-tree. To do that, run:



        $ git checkout save -- .


        (from the top level of the work-tree). The name save points to commit M2, so that's the commit from which git checkout will extract files. The -- . tells git checkout: Don't directly check out that commit; instead, leave HEAD alone, but get the files from that commit that go with the name .. Copy those files into the index, at slot zero, wiping out any merge conflict information in slots 1-3. Copy the files from the index to the work-tree.



        Since . means all files in this directory, and you're in the top level directory, this replaces all of your index and work-tree files with whatever is in M2.



        Caveat: if you have a file in your index and work-tree right now that are missing in M2, this won't remove that file. That is, suppose one of the fixes you made in N was to remove a file named bogus. The file bogus exists in M but is gone in M2. If bogus is in H as well, it's in your index and work-tree right now, as you started with the files from F—which either had bogus or didn't—and took all the changes from H, which either kept bogus or added bogus. To work around that, use git rm -r . before git checkout save -- .. The remove step removes every file from the index and work-tree, which is OK because we just want whatever is in M2, and the git checkout save -- . step is going to get all of those.



        (There is a shorter way to do all of this, but it's not very "tutorial", so I'm using the longer method here. Actually, there are two such ways, one using git read-tree in the middle of the merge, and the other using git commit-tree with two -p arguments to sidestep the need to run git merge at all. Both require a high level of comfort and familiarity with the inner workings of Git.)



        Once you have all the files extracted from save (commit M2), you are ready to finish the merge as usual, using git merge --continue or git commit. This will make a new merge M3:



                     -------------M3   <-- master (HEAD)
        / /
        ...--F--G--H------M--M2 / <-- tag: save
        / /__/
        I--J--K--L--N <-- feature


        You can now delete the tag save, which makes commits M and M2 become invisible (it takes a while for them to go away for real as their hash IDs are also stored in the HEAD reflog). Once we stop drawing them, we can start calling M3 just M instead:



        ...--F--G--H---------M   <-- master (HEAD)
        /
        I--J--K--L--N <-- feature


        and that's what you wanted.



        Using git rerere



        There's another method to doing all of this that avoids the tag and saving the merge. Git has a feature called "rerere", which stands for re-use recorded resolution. I don't actually use this feature myself but it is designed for this kind of thing.



        To use it, you run git config rerere.enabled true (or git config rerere.enabled 1, both mean the same thing) before you start the merge that may or may not have conflicts and may or may not need to be re-done. So you do have to plan in advance for this.



        Having enabled rerere, you then just do the merge as usual, resolve any conflicts as usual—perhaps also adding a work-tree and more commits to the feature branch, even though they won't be in this merge—and then finish your merge as usual. Then you do:



        git reset --hard HEAD^    # or HEAD~, use whichever spelling you prefer


        This strips the merge off, losing your resolutions—but the earlier step of finishing the merge saved the resolutions. Specifically, Git saved the conflicts at the time the merge stopped, and then saved just their resolutions (not the entire files!) at the time you told Git this merge is finished.



        Now you can run git merge feature again. You can wait until you have added even more commits to feature; the rerere-saved resolutions will stick around for a few months. (As the documentation notes:





        By default, unresolved conflicts older than 15 days and resolved
        conflicts older than 60 days are [garbage collected]





        whenever Git runs git rerere gc, which git gc will run for you. Git itself will run git gc for you automatically, though usually not every day, so these might stick around more than 15 and 60 days on their own.)



        This time, after you run git merge, instead of just recording the conflicts, Git will notice that it already has recorded resolutions and will use those to fix the conflicts. You can then inspect the results, and if all looks good, git add the files and finish the merge. (You can even have Git auto-add files that are completely resolved, using another rerere configuration item; see the git config documentation for details.)



        (This is probably the friendliest development mode, if you have to re-do merges a lot. I don't like it much myself because it is hard to see what resolutions are recorded and when they will expire, and the automatic re-use can happen even if you don't want it to. You can use git rerere forget to clear recorded resolutions, but that's kind of a pain too. So I prefer keeping full commits, which have clearer lifetimes. But I also do not have to keep re-merging a lot.)






        share|improve this answer














        Is there a way to commit changes to the feature branch from within the merge resolution




        No: merge conflicts use the index to store all three versions (base, --ours, and --theirs, in index slots 1, 2, and 3 respectively) of each conflicted file. Git builds new commits from whatever is in the index, and requires that every file in the index be in its normal "resolved" slot (slot zero) rather than as the three nonzero slots for merge-conflict-resolution.



        (The work-tree copy of the file holds Git's best effort at merging these three inputs, but Git only uses it again after you've fixed it up and run git add file. This copies the contents of file back into the index, writing to slot zero and emptying out slots 1-3. Now the file is resolved and ready to commit.)



        Since there is only one index for any given work-tree,1 and that index is "busy" with the merge, you have no index (and no work-tree, for that matter) in which to make a change to the feature branch. This phrasing—specifically, in any given work-tree—is a big hint, though:




        or, if that's not possible, commit to the feature branch in another terminal ...




        Yes, this is possible. It's considerably easier since Git version 2.5, which learned a new feature: git worktree add.





        1It's possible to set up a temporary index, and in versions of Git predating 2.5, there were some hacky scripts to do the equivalent of git worktree add using symlinks and temporary index files and a lot of other magic.





        Using git worktree add



        Each added work-tree has its own index (and, not coincidentally, its own HEAD as well). If you are in the middle of merging feature to master, so that the repository's main work-tree and index are dealing with branch master, you can run, from the top level of the work-tree:



        git worktree add ../feature


        or:



        git worktree add /path/to/where/I/want/feature


        or:



        git worktree add /path/to/dir feature


        (but not just git worktree add /path/to/dir, as that would try to check out a branch named dir).



        This will:




        • create a new directory ../feature or /path/to/where/I/want/feature or /path/to/dir; and

        • run, in essence, git checkout feature in that path.


        You now have an extra work-tree associated with the current repository. This added work-tree is on branch feature. (Your main work-tree is still on master, with the ongoing merge still going on.) In this other work-tree, you can modify files, git add them to its index, and git commit to add new commits to branch feature.



        There is still a problem, though. It's more obvious if you use a separate clone; let's cover that a bit now.



        Using a separate clone



        If you have a Git before 2.5, or are worried about the various bugs in git worktree add (there are several, including some fairly significant ones only fixed recently in 2.18 or 2.19 or so), you can simply re-clone your repository. You can either re-clone the original repository to a new clone, or re-clone your clone to a new clone. Either way, you get a new repository, with its own branches and index and work-tree, and in that clone you can do anything you want.



        Obviously, anything you do in this new clone does not affect your existing clone at all (at least, not until you fetch or push to transfer commits from clone to clone). Likewise, things you do in the original clone do not affect the new clone (until you transfer the commits).



        Transferring the commits gets the commits into the original clone, which is fine; but obviously the merge you're doing uses the commits you had when you started the merge, not any new ones you made in the other clone. But that's true even with git worktree add, as we'll see in a moment.



        Added work-trees vs separate clones



        When you use git worktree add, the two work-trees share the underlying repository. This means commits you make from either work-tree are immediately available to yourself working in the other work-tree. However, they also share branch names, which leads to a restriction that git worktree add includes that a separate clone does not.



        In particular, each added work-tree has the side effect of "locking out" access to that branch name. That is, once you've added a feature-branch work-tree, no other work-tree can use branch feature. If the main work-tree is on master, no added work-tree can use the branch master. Each branch name is exclusive to each added work-tree. (Note: you can use git checkout in the added work-tree to change branches, provided you maintain the exclusivity property.)



        You can, of course, just remove an added work-tree. Since that work-tree is now gone, the branch it had exclusive rights to, is now available for any other work-tree. See the documentation for details.



        The bugs that were fixed the most recently have to do with older added work-trees. If you add a work-tree, it's advisable to finish up your work in that added work-tree within a few weeks, then ditch it. Use it for relatively quick projects, in other words. (The scariest bug, in my opinion, is that git gc will sometimes think that an object isn't in use, because it fails to check added work-trees' HEAD and index files for object IDs. The default prune time of 2 weeks means that you're safe from this bug for at least two weeks from the time you start working in an added work-tree. You get more time, resetting the clock, whenever you commit, provided the added work-tree is not on a detached HEAD.)



        The hitch



        I mentioned that no matter what you do here, there is still a problem. That has to do with how Git works "under the hood".2




        and then update the merge to incorporate the new change-set without losing existing progress?




        The merge you are in the middle of, merging feature to master, is—at least in a sense—not merging the branch. It's merging the commit.



        Remember that in Git, a branch name is just a human-readable identifier containing a hash ID. Git is really all about commits, and the true name of a commit is a big, ugly, apparently-random, unfriendly-to-humans hash ID like 8858448bb49332d353febc078ce4a3abcc962efe (this is the hash ID of a commit in the Git repository for Git).



        Each commit stores, along with all its other data, a list of parent hash IDs, usually just one hash ID. We can, and Git does, use these to link commits up into backward chains. If we have a repository with only three commits in it, we might draw it like this, using single uppercase letters to stand in for the actual commit hash IDs:



        A <-B <-C


        Since commit A is the very first commit, its parent-list is empty: it has no parent. B, however, lists A as its (sole) parent, and C lists B as its parent. Git needs only to find commit C somehow, and then C finds B which finds A. (After finding A, there are no parents left, and we can rest.)



        The main thing that a branch name does in Git is allow Git to find that last commit on the branch:



        A <-B <-C   <--master


        The name master finds commit C. Git defines this so that whatever ID is inside master, that's the tip commit of master. Hence, to add a new commit to master, we have Git write out our new commit's contents, setting the new commit's parent to C. The new commit gets some new big ugly hash ID, but we'll just call it D. Then we have Git write D's hash ID into the name master:



        A <-B <-C <-D   <--master


        The name is still master, but the hash ID that master represents has changed. In effect, the name moved, from commit C to commit D when we made the new commit.



        These inside-commit arrows, which always point backwards,3 are as unchangeable as any other part of any commit. So we don't need to draw them: we know they attach to the second commit of a linked pair. The arrows coming out of branch names, however, move about, so we should draw those. This gives us what we see when we are about to run git merge:



        ...--F--G--H   <-- master (HEAD)

        I--J--K--L <-- feature


        At this point, with HEAD attached to master, we run git merge feature. Git:




        • locates our current commit H using HEAD;

        • locates the other commit L using the name feature;

        • uses the graph itself to find the best commit that's on both branches, which is commit F;


        • runs, in effect, two git diffs:



          git diff --find-renames <hash-of-F> <hash-of-H>   # what we changed on master
          git diff --find-renames <hash-of-F> <hash-of-L> # what they changed on feature


        • tries to combine the two diffs and apply the resulting changes to the snapshot from F;


        • if that succeeds, makes a new commit, but if not, stops with a merge conflict.


        The new commit will have two parents—two backwards-looking links, pointing to H and L. Once Git makes it—either automatically because the merge succeeds, or because we resolve conflicts and use git merge --continue or git commit to finish the merge ourselves—we will have:



        ...--F--G--H------M   <-- master (HEAD)
        /
        I--J--K--L <-- feature


        But suppose the merge stops with conflicts, so that we still have:



        ...--F--G--H   <-- master (HEAD)

        I--J--K--L <-- feature


        with our index and work-tree containing the partial merge result? If we now use git worktree add to create a second work-tree whose HEAD attaches to feature. The added work-tree's files are those from commit L; its index also holds a copy of the files from L. If we then add a new commit using that work-tree—let's call this one N since we've reserved M for our eventual merge—we get:



        ...--F--G--H   <-- master (HEAD of main work-tree)

        I--J--K--L--N <-- feature (HEAD of feature work-tree)


        The ongoing merge, however, is still merging commits H and L. When we eventually finish the merge, we get:



        ...--F--G--H------M   <-- master (HEAD of main work-tree)
        /
        I--J--K--L--N <-- feature (HEAD of feature work-tree)


        This isn't what you wanted!





        2This link goes to a Quora answer about the phrase "under the hood". (The first link that Google came up with for me went to Urban Dictionary, which has a rather ... different definition, ahem. This Quora article makes a claim that beginning Python programmers don't need to be aware of Python's somewhat peculiar approach to variables, where all objects are always boxed and variables are merely bound to the boxes, and I disagree with the claim—or at least, would say only for very-beginning—but the description of "under the hood" is still good.)



        3For whatever reason, I like to use the British English distinction between "backward" and "backwards": backward is an adjective while backwards, with the -s, is the adverb. Then again, I also like to spell "grey" with an E. But I omit the U in "color".





        Tackling the re-merge



        What you wanted was:




        and then update the merge to incorporate the new change-set without losing existing progress?




        This effectively requires that we re-merge. That is, you wanted:



        ...--F--G--H---------M   <-- master (HEAD)
        /
        I--J--K--L--N <-- feature


        What you have—let's assume you remove the added work-tree at this point, to simplify the drawing—is:



        ...--F--G--H------M   <-- master (HEAD)
        /
        I--J--K--L--N <-- feature


        Commit M will point back to H and L, no matter what. But you can, now, do more things.



        For instance, you can just allow M to continue to exist, and run git merge feature again. Git will now do the same thing it did last time: resolve HEAD to a commit (M), resolve feature to a commit (N), find their merge base—the best shared commit—which will be commit L, and have Git combine two sets of diffs. The effect will be to pick up the fixes you just made in N, and this will even usually work without conflicts, though the details depend on the precise fixes you made in L-vs-N. Git will make a new merge commit M2:



        ...--F--G--H------M--M2   <-- master (HEAD of main work-tree)
        / /
        I--J--K--L--N <-- feature (HEAD of feature work-tree)


        Note that M2 depends on M for M2's existence. You can't just ditch M entirely, at least not yet. But what if you want to ditch M in favor of M2?



        Well, M2 has the correctly merged result as a snapshot. So let's save the hash ID of M2 somewhere, using another branch or tag name:



        $ git tag save


        Now let's use git reset --hard HEAD~2 to forcibly strip both M and M2 from the graph. HEAD~2 means "walk back two first-parent links from the current commit". The first parent of M2 is M, and the first parent of M is H, so this tells Git to make the name master point to H again:



                     .............<-- master (HEAD)
        .
        ...--F--G--H------M--M2 <-- tag: save
        / /
        I--J--K--L--N <-- feature


        If we did not have the tag keeping M2 and M visible, it would look as though those commits were completely gone. Meanwhile, the --hard part of the git reset told Git to overwrite our index and work-tree with the contents of commit H as well.



        Now we can run:



        git merge feature


        which tells Git to use the HEAD commit to find commit H, to use the name feature to find commit N, to find their merge base (F), and to begin the process of merging. This will hit all the same conflicts as before, and you would have to resolve them all over again—but you have the resolutions available to you in commit M2. So now you just need to tell Git: Take the resolved files from M2. Put them in my index and work-tree. To do that, run:



        $ git checkout save -- .


        (from the top level of the work-tree). The name save points to commit M2, so that's the commit from which git checkout will extract files. The -- . tells git checkout: Don't directly check out that commit; instead, leave HEAD alone, but get the files from that commit that go with the name .. Copy those files into the index, at slot zero, wiping out any merge conflict information in slots 1-3. Copy the files from the index to the work-tree.



        Since . means all files in this directory, and you're in the top level directory, this replaces all of your index and work-tree files with whatever is in M2.



        Caveat: if you have a file in your index and work-tree right now that are missing in M2, this won't remove that file. That is, suppose one of the fixes you made in N was to remove a file named bogus. The file bogus exists in M but is gone in M2. If bogus is in H as well, it's in your index and work-tree right now, as you started with the files from F—which either had bogus or didn't—and took all the changes from H, which either kept bogus or added bogus. To work around that, use git rm -r . before git checkout save -- .. The remove step removes every file from the index and work-tree, which is OK because we just want whatever is in M2, and the git checkout save -- . step is going to get all of those.



        (There is a shorter way to do all of this, but it's not very "tutorial", so I'm using the longer method here. Actually, there are two such ways, one using git read-tree in the middle of the merge, and the other using git commit-tree with two -p arguments to sidestep the need to run git merge at all. Both require a high level of comfort and familiarity with the inner workings of Git.)



        Once you have all the files extracted from save (commit M2), you are ready to finish the merge as usual, using git merge --continue or git commit. This will make a new merge M3:



                     -------------M3   <-- master (HEAD)
        / /
        ...--F--G--H------M--M2 / <-- tag: save
        / /__/
        I--J--K--L--N <-- feature


        You can now delete the tag save, which makes commits M and M2 become invisible (it takes a while for them to go away for real as their hash IDs are also stored in the HEAD reflog). Once we stop drawing them, we can start calling M3 just M instead:



        ...--F--G--H---------M   <-- master (HEAD)
        /
        I--J--K--L--N <-- feature


        and that's what you wanted.



        Using git rerere



        There's another method to doing all of this that avoids the tag and saving the merge. Git has a feature called "rerere", which stands for re-use recorded resolution. I don't actually use this feature myself but it is designed for this kind of thing.



        To use it, you run git config rerere.enabled true (or git config rerere.enabled 1, both mean the same thing) before you start the merge that may or may not have conflicts and may or may not need to be re-done. So you do have to plan in advance for this.



        Having enabled rerere, you then just do the merge as usual, resolve any conflicts as usual—perhaps also adding a work-tree and more commits to the feature branch, even though they won't be in this merge—and then finish your merge as usual. Then you do:



        git reset --hard HEAD^    # or HEAD~, use whichever spelling you prefer


        This strips the merge off, losing your resolutions—but the earlier step of finishing the merge saved the resolutions. Specifically, Git saved the conflicts at the time the merge stopped, and then saved just their resolutions (not the entire files!) at the time you told Git this merge is finished.



        Now you can run git merge feature again. You can wait until you have added even more commits to feature; the rerere-saved resolutions will stick around for a few months. (As the documentation notes:





        By default, unresolved conflicts older than 15 days and resolved
        conflicts older than 60 days are [garbage collected]





        whenever Git runs git rerere gc, which git gc will run for you. Git itself will run git gc for you automatically, though usually not every day, so these might stick around more than 15 and 60 days on their own.)



        This time, after you run git merge, instead of just recording the conflicts, Git will notice that it already has recorded resolutions and will use those to fix the conflicts. You can then inspect the results, and if all looks good, git add the files and finish the merge. (You can even have Git auto-add files that are completely resolved, using another rerere configuration item; see the git config documentation for details.)



        (This is probably the friendliest development mode, if you have to re-do merges a lot. I don't like it much myself because it is hard to see what resolutions are recorded and when they will expire, and the automatic re-use can happen even if you don't want it to. You can use git rerere forget to clear recorded resolutions, but that's kind of a pain too. So I prefer keeping full commits, which have clearer lifetimes. But I also do not have to keep re-merging a lot.)







        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered Nov 22 '18 at 20:06









        torektorek

        193k18239321




        193k18239321
































            draft saved

            draft discarded




















































            Thanks for contributing an answer to Stack Overflow!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53426259%2fhow-to-commit-to-merging-branch-during-merge%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            Can a sorcerer learn a 5th-level spell early by creating spell slots using the Font of Magic feature?

            Does disintegrating a polymorphed enemy still kill it after the 2018 errata?

            A Topological Invariant for $pi_3(U(n))$