Jason Clark
Jason Clark

Reputation: 1391

Should git merge change affect the source branch? (Why did I lose some work?)

I seem to have lost a few files worth of edits, so I'm trying to figure out what I did wrong. It certainly seems like a git merge affected both the source and target branches. I'm not worried about finding the lost edits, just fixing my understanding of git.

I have a develop branch. I created a feature-x branch, did some work, and made a single commit affecting 8 files (call that commit "WIP"). Then I switched back to develop, did some other work on other features (since merged), and today I needed to get a some pieces from feature-x into develop:

git checkout develop
git merge feature-x --no-commit --no-ff

So now develop has my feature-x changes on top, uncommitted/unstaged. I staged and committed two of the files (call that commit "Two Files"), but decided I wasn't ready to commit the rest to develop. I wanted to leave them in feature-x, but figured it was worth rebasing feature-x onto the current develop branch. At this point, I thought feature-x should still be unchanged - the merge should have only changed develop. So I cleaned up the uncommitted changes from the merge:

git reset --hard HEAD; git clean -f -d

And tried to rebase feature-x onto develop:

git checkout feature-x
git rebase develop

I expected my history now to show all my older develop commits, then the new "Two Files" commit, and then the "WIP" commit from feature-x rebased on top. But all I see is "Two Files"... the changes from the "WIP" commit seem to be gone.

So where did I go wrong? I forgot to check the commit log after checking out feature-x right before the rebase, but I have to assume the "WIP" commit was already gone since it was gone after the rebase. Did the merge --no-commit --no-ff onto develop also change feature-x?

Upvotes: 0

Views: 1130

Answers (2)

torek
torek

Reputation: 489678

Let me start by saying that I recommend working through the concepts at Think Like (a) Git. They are crucial to understanding how Git works.

As to what happened, well, I made a small repository and repeated some of your commands as best I could given what you've told us here:

git checkout develop
git merge feature-x --no-commit --no-ff

So now develop has my feature-x changes on top, uncommitted/unstaged.

No: You are now in the middle of an unfinished merge. The --no-commit option suppresses the commit, leaving you in that merge.

$ git merge feature-x --no-commit --no-ff
Automatic merge went well; stopped before committing as requested

Moreover, Git automatically copies all the successfully-merged files into the staging area. The staging area—also called the index, and I tend to use that name more—holds all the files that get committed. Each commit is a complete snapshot of every file, so that it can be extracted fully intact later.

$ git status
On branch develop
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:

        modified:   README
        new file:   new.1
        new file:   new.2

If you use git reset (in one of its many various modes—the command does too many different things, in my opinion, so it can be tricky to talk about it), you can copy files from one of the commits back into the staging area, so that the staging area version of some file matches the develop version of the file, or no copy of the file at all (if the file was new), rather than containing the result of some merging action.

I staged and committed two of the files (call that commit "Two Files") ...

No, at this point you simply finished the merge. Git now knows that the correct combination of the series of commits on the two branches is the result of this particular commit.

Note that git commit --only cannot be used at this point:

$ git commit --only README
fatal: cannot do a partial commit during a merge.

On the other hand, git commit --include can, but that's equivalent to running git add followed by git commit, so that it commits the merge.

$ git commit

At this point, one's editor open on a text file reading:

Merge branch 'feature-x' into develop
# 
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
#       .git/MERGE_HEAD
# and try again.


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch develop
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
#       modified:   README
#       new file:   new.1
#       new file:   new.2
#

Writing this file out and exiting produces:

[develop 2313673] Merge branch 'feature-x' into develop

I conclude that either you ran a command you're not showing—which could be crucial—and/or that you committed the merge, which would explain the behavior you're seeing. In particular:

Did the merge --no-commit --no-ff onto develop also change feature-x?

No: but committing a merge changes the merge base of future operations, and changes which sets of commits are reachable from various branch names. This affects git rebase. In particular, the WIP commit you made on feature-x is now reachable from—and hence contained in—develop, so making feature-x extend from the tip of develop no longer requires that feature-x contain a copy of the WIP commit, as it's already in the develop branch. Rebase will therefore not bother to copy the commit: it's going to be in both branches from now on.

Upvotes: 1

gazdagergo
gazdagergo

Reputation: 6721

I would do this the following way:

I realised that I need some part of the last (WIP) commit of feature-x. I checkout to feature-x.

git checkout feature-x

I reset my last commit:

git reset --soft HEAD~1

I unstage all and I stage only the desired changes of the two files (it is possible at least in my VSCode to stage only selected part of file). After this I commit:

git commit -m "Two files" // git will generate a commit with hash `a1b2c3d4`

And I stage the rest and commit:

git add .
git commit -m "WIP rest"

Now I can cherry-pick my desired changes on develop:

git checkout develop
git cherry-pick a1b2c3d4

The advantage of using cherry-pick is that it will not cause problem later when I rebase or merge my feature-x branch with develop, cause git will recognise that this is exactly the same change in both branches.

Upvotes: 1

Related Questions