MikeyB MikeyB - 1 month ago 10
Git Question

Git creates extra branch when merging

I have two git branches R1609 and R1609_dpl in my gitlab.

Every time I push something to R1609 I want to merge those changes into R1609_dpl using a trigger.
For some reason git creates an extra branch just for a merge commit. The Commit History looks like this after a merge:

Network

The commit "Merge branch X of URL into X" seems completely unnecessary.
My code for the merge:

git checkout R1609
git pull origin R1609
git checkout R1609_dpl
git pull origin R1609_dpl
git merge --no-ff R1609
git commit --amend -m "Push to R1609 triggered: Merge R1609 into R1609_dpl"
git push origin R1609_dpl


What am I missing? I want to get rid of the extra branch and simply merge right into the R1609_dpl branch without having two merge commits.

Thanks in advance!

EDIT:
Got rid of the

git commit --amend -m "Push to R1609 triggered: Merge R1609 into R1609_dpl"


and added a rebase but still no luck

git checkout R1609
git pull origin R1609
git checkout R1609_dpl
git pull origin R1609_dpl
git merge --no-ff R1609
#Added rebase
git pull --rebase origin R1609_dpl
git push origin R1609_dpl

Answer

Remember that git pull means git merge.

Well, it means "First run git fetch, then run git merge unless I tell you to run git rebase as your second step instead."

You therefore have four items that potentially—we'll get back to the "potentially" part in a minute—create merges:

  1. git checkout R1609; git pull origin R1609: this may create a new merge commit on your current (i.e., R1609) branch, using whatever it finds and fetches from your upstream, i.e., the other (origin's) Git's R1609.

  2. git checkout R1609_dpl; git pull origin R1609_dpl. As with step 1, this potentially creates a new merge commit.

  3. git merge --no-ff R1609: this definitely creates a merge commit, unless it does nothing at all. (It had best not do nothing because of item 4 here.)

  4. git commit --amend -m "Push to R1609 triggered: Merge R1609 into R1609_dpl": this "amend" copies the current commit to a new commit with a new, different message. It shoves the current commit aside. Assuming we made a merge in step 3, this shoves the merge aside and replaces it with a new merge. The shoved-aside merge will vanish from git log output, since the current branch (now R1609_dpl) points to the new copy and only its reflog points to the original, once the amend finishes.

In some ways, then, it's a bit surprising that you don't get three commits. The one you are getting is coming from step 1, since the log shows that its message is "Merge branch 'R1609' of <url> into R1609". So now it's time to describe the "potentially" thing.

There are two possibilities for git fetch, and the second one splits into two cases:

  • The upstream, which your Git calls origin/R1609 but their (origin's) Git just calls R1609, is in sync with your own R1609, or is behind: you have some commit(s) they lack (until you push them). In this case, git fetch does nothing, and there is nothing to merge, and git merge does nothing. (Yay, but also boo, why did we even bother? :-) Well, to check, of course.)

  • The upstream is not in sync with your R1609. Git fetches some commit(s) from the upstream. This could be strictly ahead of your own commit(s) on your R1609, or it might be the case that their R1609 has a commit that you lack, while your R1609 has a commit that they lack. The fetch step does not need to care; it just brings in whichever commits they have, that you don't. It then updates your origin/R1609 to remember their new commit(s).1

When fetch does bring something in, though, you may now be strictly behind—you just need to catch up to their R1609—or you may be simultaneously ahead and behind. In the former case, git merge defaults to doing a "fast forward" operation.2 Git changes your branch label, R1609, so that it points to the same commit as their R1609 (your origin/R1609). Of course, if you specify --no-ff, Git makes a real merge instead. In the latter case, though, Git must make a real merge, to combine your commit(s) and their commit(s). So you get a merge here, and its message is Merge branch 'R1609' of <url> into R1609.

Clearly, then, something upstream is causing new commits to appear on their R1609, which your Git finds it must merge in step 1 above. Nothing happens to the upstream R1609_dpl so your Git is able to fast-forward, or do nothing at all, as appropriate, in step 2. Step 3 creates a new merge and step 4 shoves it aside while creating a third merge, leaving you with the two visible merges.

(I see that by the time I finished writing all this, you already found the culprit that's introducing a commit on R1609 on origin.)


1Well, as long as your Git is version 1.8.4 or newer. Older Git versions sometimes fail to update the remote-tracking branch. (Specifically, they deliberately do not update it on the fetch run from the pull script. This turned out to be a mistake, so it was changed in 1.8.4.)

2Although the git merge documentation describes this as a fast-forward merge, it's really not a merge at all. Fast-forward is a property of a label move. Given a label that points to commit X, and a request to change it to point to label Y, we test: "Is X an ancestor of Y?" If so, this change is a fast-forward. If not, it's not.

Both git fetch and git pull have built-in code to detect fast-forward moves, vs non-fast-forward moves. This is what the --force flag is all about, when pushing—and for that matter, when fetching, although for fetch it's invariably done via the + syntax in a refspec instead. Without --force or the + flag, Git refuses to change a branch label from commit X to commit Y unless commit X is an ancestor of Y.

The merge command, of course, also has this same code, and will fast-forward the label instead of merging, provided (a) this is possible and (b) you have not forbidden it. Since this operation is being done on the current branch, using the current index and work-tree, git merge also updates the index and work-tree to match. The fetch and push code update neither the index nor the work-tree.