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).