[m-dev.] Merge commits

Matt Giuca matt.giuca at gmail.com
Sat Jan 12 13:14:27 AEDT 2013

[I don't do development on Mercury, so take this advice with a grain of

Is this advice just for people who are doing local commits to master and
then pushing them, or also for people working with branches? I can see the
appeal of trying to rebase remote changes onto a few short local commits,
and keeping the history clean. But this will end up pretty chaotic for
anyone who is either a) working on a feature branch long-term, or b) has
made quite a lot of local commits to master without pushing them for awhile.

My main concern is that you end up with this Subversion-like state of
having local changes, doing a pull, getting a conflict, and then having to
fix your local changes to match the master -- not just the current
revision, but all historical revisions going back to the branch point as
well. One of the key advantages of distributed revision control is that you
don't have to do that -- you can let the branch history accurately reflect
the changes you actually made, instead of changing history to show what
changes you *would* have made, had you made them against the current HEAD.

In certain cases, this can result in main-line commits being broken, so the
project history is not stable. To take an example, let's say the master has
the following commits on the server (in reverse chronological order):

Then I start working locally, and, I do two commits called C1 and C2. My
local history looks like this:

Now in the mean time, let's say that the other developers continue working
on master. By the time I am ready to submit C2, master looks like this:

Now D actually changed an API that I am relying on in my local changes, so
when I pull from the server, I get a semantic conflict -- git won't detect
it as a merge conflict, but my code will no longer compile until I change
my branch to call the new introduced in D.

Say that I use git pull. My branch ends up like this (with git log --graph):

* F
| \
|  * C2
|  |
|  * C1
|  |
*  | E
|  |
*  | D
|  /
* C
* B
* A

I think this is what you meant by "complicating the history". Well, I don't
find this complicated -- I find it *truthful*. C1 was not developed before
or after D, it was developed simultaneously to D on a different branch.

Now I have a compiler error which I need to resolve. I'll commit that as G
(or potentially as an --amend to F itself, to avoid having a broken history
in my branch). Then I'll git push the result up to the server. Now the
server's history will include this little detour, but at least the *main
line* of development (which you can see with git log --first-parent) will
just be F, E, D, C, B, A -- all of which are good builds. In fact, there
are no broken builds here.

Now let's go back to the same scenario, but this time use git pull
--rebase. My local history ends up like this:


It's all flattened into a single line of history, which is a lie. It lies
about the fact that C1-rebased and C2-rebased were developed strictly after
D and E. Now I'm going to fix the compile error which I may commit as F or
possibly as an --amend to C2. But note that C1-rebased also does not
compile, because it was written before D, but has been re-based so that it
is after D. In fact, the entire history of my local commits will be
potentially broken by the rebase.

A good developer will carefully go back and check that each commit compiles
and passes the tests before pushing. But that is a waste of time. And
certainly someone will not be bothered to do this. So when I do a git push,
while the most recent commit will still compile, I will be pushing changes
into the history that are broken. They won't even show up as a branch in
the git logs. They are main-line commits, because they were rebased into
the main line of development.

The other problem, of course, is that you have an *actual* merge conflict,
because both changes are making changes to the same text. With the rebase
approach, you don't just resolve them at the merge point, but you have to
resolve the conflicts for each of your local commits. Not to mention that
if the point of revision control is to reflect the history of development,
we are failing at it, because we've forever lost (git reflog aside) the
original commits C1 and C2, in favour of C1-rebased and C2-rebased, which
were never authored, compiled, tested or even seen by a human being.

It seems to me like it's better to have "complicated", but accurate,
history, than it is to have history that is a) a lie, and b) potentially
untested and broken.

Certainly if you were actually developing a feature branch over some time,
you would not want to rebase the changes from master into the branch. So I
assume this advice is only for people making local commits to master with
the intention of immediately pushing them up.

(Note: I am not saying that you should never rebase. The advice that Paul
gave is sound and useful when used sparingly and appropriately. I just
don't think that blindly rebasing as a solution to forked development is a
good idea.)

On Fri, Jan 11, 2013 at 2:33 PM, Peter Wang <novalazy at gmail.com> wrote:

> Hi,
> If you try to push some changes and git tells you that you're not up to
> date, please use "git pull --rebase" (you can make it the default).
> By default, "git pull" will merge, complicating the history (see gitk)
> and making it harder to work with.
> Peter
> PS. git log -p --simplify-merges seems to DWIM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.mercurylang.org/archives/developers/attachments/20130112/a01d87b5/attachment.html>

More information about the developers mailing list