Why am I merging "remote-tracking branch 'origin/develop' into develop"?

Jordan Feldstein picture Jordan Feldstein · Jun 20, 2011 · Viewed 76.2k times · Source

I'm the only one in my organization who's making commits with the following message:

Merge remote-tracking branch 'origin/develop' into develop

Not sure what I'm doing to cause them, but I'd like to stop.

What command am I issuing to create this commit, and what is the proper command I ought to be using to not produce it?

Answer

Richard Hansen picture Richard Hansen · Jun 20, 2011

git pull is probably creating the commit. If you make a local commit and then run git pull after someone else pushes a commit up to the repository, Git downloads the other developer's commit and then merges it into your local branch.

How to avoid these merge commits in the future

You could use git pull --rebase to prevent this from happening in the future, but rebasing has its perils, and I recommend avoiding pull altogether.

Instead, I encourage you to follow this usage pattern:

# download the latest commits
git remote update -p

# update the local branch
git merge --ff-only @{u}

# if the above fails with a complaint that the local branch has
# diverged:
git rebase -p @{u}

Explanation

  • git remote update -p downloads all of the commits in the remote repositories and updates the remote tracking branches (e.g., origin/master). It does NOT touch your working directory, index, or local branches.

    The -p argument prunes deleted upstream branches. Thus, if the foo branch is deleted in the origin repository, git remote update -p will automatically delete your origin/foo ref.

  • git merge --ff-only @{u} tells Git to merge the upstream branch (the @{u} argument) into your local branch but only if your local branch can be "fast forwarded" to the upstream branch (in other words, if it hasn't diverged).

  • git rebase -p @{u} effectively moves the commits you've made but haven't yet pushed on top of the upstream branch, which eliminates the need to create the silly merge commits you're trying to avoid. This improves the linearity of the development history, making it easier to review.

    The -p option tells Git to preserve merges. This prevents Git from linearizing the commits being rebased. This is important if, for example, you merged a feature branch into master. Without -p, every commit on the feature branch would be duplicated on master as part of the linearization done by git rebase. This would make the development history harder to review, not easier.

    Beware: git rebase might not do what you expect it to do, so review the results before pushing. For example:

    git log --graph --oneline --decorate --date-order --color --boundary @{u}..
    

I prefer this approach over git pull --rebase for the following reasons:

  • It allows you to see the incoming upstream commits before your modify your history to incorporate them.
  • It allows you to pass the -p (--preserve-merges) option to git rebase in case you need to rebase an intentional merge (e.g., merge of an already-pushed feature branch into master).

Shorthand: git up instead of git pull

To make it easy to do the above, I recommend creating an alias called up:

git config --global alias.up '!git remote update -p; git merge --ff-only @{u}'

Now all you need to do to bring your branch up to date is to run:

git up

instead of git pull. If you get an error because your local branch has diverged from the upstream branch, that's your cue to rebase.

Why not git pull --rebase?

Running git pull --rebase is equivalent to running git fetch followed by git rebase. This attempts to fast-forward to the new upstream commits, but if that's not possible then it will rebase your local commits onto the new upstream commits. This is usually OK, but be careful:

  • Rebase is an advanced topic, and you should understand the implications before rebasing.
  • git pull --rebase does not give you an opportunity to examine the commits before incorporating them. Depending on what changed upstream, it's quite possible that rebase is the wrong operation—a rebase --onto, merge, reset, or push -f might be more appropriate than a plain rebase.
  • It is not (currently) possible to pass --preserve-merges to the rebase operation, so any intentional merge of a feature branch will be linearized, replaying (and thus duplicating) all of the feature branch commits.

"Fixing" an existing merge commit created by git pull

If you haven't yet pushed a merge commit created by git pull, you can rebase out the merge commit. Assuming you haven't made any intentional merges (e.g., merging an already-pushed feature branch into your current branch), the following should do it:

git rebase @{u}

The above command tells Git to select all of the non-merge commits reachable from HEAD (the current commit), minus all the commits reachable from @{u} (which is shorthand for "the upstream branch", i.e., origin/master if HEAD is master), replay (cherry-pick) them on top of the upstream branch, and then move the current branch reference to point to the result of replaying the commits. This effectively moves the non-merge commits onto the most recent upstream commit, which eliminates the merge created by git pull.

If you have an intentional merge commit, you don't want to run git rebase @{u} because it will replay everything from the other branch. Dealing with this case is substantially more complicated, which is why it's good to use git up and avoid git pull altogether. You'll probably have to use reset to undo the merge created by pull and then do git rebase -p @{u}. The -p argument to git rebase hasn't worked reliably for me, so you might end up having to use reset to undo the intentional merge, update your local branch to @{u}, and then redo the intentional merge (which is a pain if there were a lot of hairy merge conflicts).