Reputation: 32127
This is how gitk currently looks in one of our projects:
https://dl.dropbox.com/u/2582508/gitk.png
This apparently, from all we can tell, happened after a single "git merge" was done with a remote branch - we're not sure why or what is going on. Any idea what happened here?
More importantly, what's the best way to fix it? Those merge commits are empty, but when doing "git rebase -i" merge commits generally don't seem to appear.
Most importantly, I would prefer to not make the history incompatible with other clones, i.e. they should still able to pull/push/merge with it. Is that even possible?
Upvotes: 4
Views: 1066
Reputation: 214506
This is the result of someone updating their project using git pull
(or, equivalently, git fetch
and git merge
). Imagine you're repository looks like this:
o---o---o [origin/master] \ A---B---C [master]
That is, you've done commits A
, B
and C
on top of what was in the original repo.
In the mean time, some else makes changes and pushes them up to the shared repository. If you then run git fetch
your repository looks like this:
o---o---o---D---E---F [origin/master] \ A---B---C [master]
Now, if you run git merge
(remember: git pull
is just git fetch
followed by git merge
). You'll have this:
o---o---o---D---E---F [origin/master] \ \ A---B---C---G [master]
Assuming all goes well, G
is probably just a "stupid" merge commit; technically, the state of G
is different from F
and C
and so it must be considered differently.
Now, if you push this change, you'll have this:
o---o---o---D---E---F \ \ A---B---C---G [origin/master]
And if you continue development you'll get this:
o---o---o---D---E---F \ \ A---B---C---G [origin/master] \ H---I---J [master]
Now, if you keep doing this (and if many people keep doing this) you'll end up with a tree like the one in your picture. This isn't "wrong" but many people don't like it because it makes the development history very hard to follow.
The solution to this problem is to teach people to rebase. Rebasing will (as you noted) remote the useless merge commits and git you a much cleaner history. In the case above, you'll end up with a linear development history. That's much easier to follow. However, you need to know that after you rebase you need to rebuild and retest your code... just because the code merged easily doesn't mean the result is correct.
If you're using a central repository to share an official development line, you can implement a pre-receive hook that detects these automatic ("stupid"/"useless") merge commits and rejects the push -- forcing the user to either rebase. Actually, you want the hook to look for the default merge commit message... so, if you really do want to keep the merge commit (sometimes that makes sense) you must at least write an intelligent commit message.
Here's an example hook. I stripped a bunch of extra stuff out so I didn't get to test it.
#!/bin/bash # This script is based on Gnome's pre-receive-check-policy hook. # This script *only* checks for extraneous merge commits. # Used in some of the messages server=git.wherever.com GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) in_import() { test -e "$GIT_DIR/pending" } forced() { test -n "$GNOME_GIT_FORCE" } check_commit() { commit=$1 subject="$(git log $commit -1 --pretty=format:%s)" if expr "$subject" : ".*Merge branch.*of.*\(git\|ssh\):" > /dev/null 2>&1; then if ! in_import && ! forced ; then cat &2 --- The commit: EOF git log $commit -1 >&2 cat &2 Looks like it was produced by typing 'git pull' without the --rebase option when you had local changes. Running 'git pull --rebase' now will fix the problem. Then please try, 'git push' again. Please see: http://live.gnome.org/Git/Help/ExtraMergeCommits --- EOF exit 1 fi fi } check_ref_update() { oldrev=$1 newrev=$2 refname=$3 change_type=update if expr $oldrev : "^0\+$" > /dev/null 2>&1; then change_type=create fi if expr $newrev : "^0\+$" > /dev/null 2>&1; then if [ x$change_type = xcreate ] ; then # Deleting an invalid ref, allow return 0 fi change_type=delete fi case $refname in refs/heads/*) # Branch update branchname=${refname#refs/heads/} range= # For new commits introduced with this branch update, we want to # run some checks to catch common mistakes. # # Expression here is same as in post-receive-notify-cia; we take # all the branches in the repo, as "^/ref/heads/branchname", other # than the branch we are actualy committing to, and exclude commits # already on those branches from the list of commits between # $oldrev and $newrev. if [ -n "$range" ] ; then for merged in $(git rev-parse --symbolic-full-name --not --branches | \ egrep -v "^\^$refname$" | \ git rev-list --reverse --stdin "$range"); do check_commit $merged done fi ;; esac return 0 } if [ $# = 3 ] ; then check_ref_update $@ else while read oldrev newrev refname; do check_ref_update $oldrev $newrev $refname done fi exit 0
Upvotes: 3