0

Here's an example of a commit history, where some files were added (a, b, and c):

* 51c3dd7 (HEAD -> master) add c
| * ded49bb (my_branch) add b
|/
* f3cea38 add a

Suppose I start a merge from my_branch into the current branch (HEAD) using git merge --no-commit --no-ff my_branch, as described e.g. here.

Now, while in the MERGING state, I unstage everything, followed by git commit to complete the merge.

As a result I still get a merge commit, even though there is actually no change to HEAD:

*   5990dce (HEAD -> master) Merge branch 'my_branch'
|\
| * ded49bb (my_branch) add b
* | 51c3dd7 add c
|/
* f3cea38 a

I find this quite confusing. Shouldn't the merge automatically abort?

Of course this is a trivial example, but a similar confusion may arise after a "selective" merge, as described e.g. here.

Should a merge commit be interpreted as: "Something may, or may not, have been added from this branch?"

djvg
  • 11,722
  • 5
  • 72
  • 103
  • You merged the history of `my_branch` that `master` doesn't have. – pishpish Apr 19 '18 at 16:42
  • You shouldn't be merging those at all, imho. Git tracks changes, it doesn't care if you change something back the way it was, the history should remain. – pishpish Apr 19 '18 at 16:45
  • I personally prefer flat histories (no branching whatsoever), but your case makes no sense to me: why would you unstage everything a branch staged? – pishpish Apr 19 '18 at 16:59
  • If you want to keep the (soon to be undone) change in history, your way is ok. If not, you should rewrite the branch (for example `git rebase -i`) to not contain it at all and then merge. – pishpish Apr 19 '18 at 17:11

1 Answers1

2

I find this quite confusing.

Many people say that very often about Git. :-)

Before I get any further, let me make this terminology note. The word merge here can be both adjective—a merge commit—and verb: merge another commit. The adjective form can become a noun, when a merge commit is shortened to just a merge. It's important to distinguish between the verb to merge and the adjective (or noun) a merge commit. A merge commit is simply any commit with two or more parent commits. We typically use the git merge command to start the merge-as-a-verb process, which ends by making one of these merge-as-a-noun things.

(The git merge command can be told to do merge-as-a-verb without making a merge-as-a-noun. There are more commands in Git that can also do merge-as-a-verb without making a merge-as-a-noun. We don't need to worry about them here, though.)

Shouldn't the merge automatically abort?

No: Git assumes you meant what you said.

You said: Start the process of merging, but don't finish it, even if it seems to work. Let me fuss with the index, then complete the merge myself later, by running git commit.1 To make sense of this, you need to understand the roles of the index, also known as the staging area and sometimes the cache.

To put it as simply as I think it can be put, the index is where you build the next commit you will make. This is true whether or not the next commit is going to be a merge commit. For ordinary non-merge commits, the index simply holds copies of every file: It starts out with a copy of every file from the current commit, then you overwrite some of these using git add to copy the work-tree version into the index, replacing the old current-commit version. If you git add a totally new file, the action taken is the same, it's just that the file is new rather than replaced. If you git rm a file—with or without --cached—you are removing the file from the index, so that the file won't be in the next commit.

During the merge-as-a-verb process, the index expands its role rather a lot, at least in the case of merge conflicts. Here, the index winds up holding (up to) three copies of each file. These are numbered with "higher stage" numbers. Stage slot zero is for normal files; slots 1-3 are for merge-conflict-state files. While there are any entries in these higher stage numbers, the index cannot be used to make a commit. Git forces you to resolve the conflicts first.

Now git add has a new function: it tells Git that it should rip out the three higher-stage versions of the file, as well as write into the index the normal stage-zero entry. You are not doing any of this here, but it is worth mentioning.

Note that git rm simply removes the files, regardless of stage-slot numbers. Meanwhile, git reset, like git add, acquires a new function: git reset copies a file from the current commit to stage-slot zero as usual, but like git add, also rip out the higher stage-slot entries if they exist. If the file does not exist in the current commit, git reset removes the file from the index.

(In fact, git add and git reset always do this extra work. It just does nothing when there are no higher stage numbers lying around. If file foo.txt exists only at stage zero as :0:foo.txt, removing :1:foo.txt—the stage-slot-1 entry for the same file—has no effect.)

In any case, at the end of this merge process, the index has all the files that will go into the new merge commit, just as it has all the files that will go into a normal non-merge commit. The final git commit step notices the information that git merge left for it—specifically, the extra parent to use to make the new commit be a merge commit (in .git/MERGE_HEAD). So, git commit makes a new commit, using the index contents just like any commit, but setting up at least one extra parent as recorded in this MERGE_HEAD file. All that matters is that the index be in a commit-able state. Whatever its contents are, those files become the files in the commit. Every (Git) command you ran between git merge --no-commit and git commit had, as its true end purpose, some modification to the files in the index.


1In relatively recent versions of Git you can run git merge --continue to have git merge run git commit for you. If you intend to stop the merge, use git merge --abort, which is a synonym for the positively ancient git reset --merge. (Everyone should have a Git new enough to use git merge --abort; once git merge --continue is common enough, I'd recommend using that here too.)

torek
  • 448,244
  • 59
  • 642
  • 775