Why are people so against fast-forward commits? I don’t really get this, and seeing all of these merges with ancestors on the same branch seems confusing to me.
It makes it hard to back out a logical changeset if the history is just one long set of changes. You can, of course, look carefully to find where to delimit a changeset, but why not let the history indicate it for you?
This is why I personally decide on a case-by-case basis. If it’s a big new feature (still consisting of nicely cleaned up self-contained commits of course), I will merge it with no-ff, but if it’s general bug fixing or smaller new changes, then I prefer fast forward whenever possible
Yeah, this tends to be my way of it. If it’s a thing that I could conceivably have developed on master anyway, then it’s probably not worth cluttering the history with the merge commit.
Okay, so the oedipus merges are delimiters in history, right? Why not just use empty commits instead as delimiters? What are you gaining by non-linearising your history?
Because .. you still have to backtrack manually? Your graph shows the lines wrong; the branch’s history should be on the right, whereas the mainline can be stepped through easily by going up a single merge commit at a time (master^, master^^, master^^^). An empty commit stores no metadata about what was merged; a merge commit does. Seems straightforward?
(And it’s still effectively linear, there’s just extra metadata. If you want a purely linear view without jumps just step through mergecommit^2 every time you hit one.)
It destroys working product. Every merge has the potential to introduce errors. You start with a working X. Now you forward it and do a merge, introducing a bug. Later the bug is found and you want to go back to a working version. But you can’t. The pre merge version of X no longer exists. Now you’re stuck trying to find a merge bug hiding in the commit adding the feature. The forward ported version of X was not how it was originally developed, nor tested. It never existed in the wild.
One reason I love changeset evolution in Hg: while the main history can be linear, the full history of a given patch is also preserved (at least locally, and optionally server-side) for this use case.
Oh, awesome. I’ve been thinking about this recently. What I wanted is “shadow” branches that are usually invisible, but discoverable when necessary. These would contain the original development work, but when a merge is performed the diffs of the branch would be reapplied on top of the other head, creating the all important pristine linear history. This sounds like exactly that.
If you want to play with this, you’ll probably want to grab the evolve extension (which hasn’t yet been merged into Mercurial core), and will also likely want to enable histedit (which is built-in). That’s it; you don’t need anything else. (In fact, evolve is the only third-party extension I have; the rest are the core ones that amount to conveniences. You can see my hgrc if it’s helpful.) Note that evolve is even used by the core Mercurial team, so if you enable it and grab the main repo, you can actually see/play with obsmarkers to get a feel for how this all works.
The pre merge version of X no longer exists.
What? We’re talking about fast-forward merges vs oedipus merges. Neither of them changes anything in the working directory. The only difference is whether you want an extra commit that does a no-op merge, or not. Or if you want the oedipus merges as delimiters, why not use empty commits as delimiters instead, and keep your DAG linear?
The forward ported version of X
I surmise that you’re using the “official” git terminology for “rebase”. I’m no talking about rebasing here. I’m just talking about, once you have rebased (and tested and made sure your rebased version works and everything), what are you gaining by having an oedipus merge?
This obviously requires a car analogy.
On your branch, you add a red spoiler to your red Camaro. On the main branch, somebody repaints the car black with flames. You merge your work which applies cleanly because you changed different things, but which also results in a hideous mess, so you probably add another commit to repaint the spoiler with flames. So far, so good.
Your boss calls you into the office. “It’s a bit much, don’t you think? A spoiler and flames?” You explain how totally sweet it looked with just the red spoiler added. “Show me.” uh oh…
There is no command you can run to recreate a red car with a red spoiler. It does not exist at any point in the repository because all of your pre-merge work is now post-merge, after the repaint. The only way to get it back is to manually take your commits, cherry pick them and now backport them to some previous branch point, hoping nothing goes wrong or gets lost in the shuffle.
No, it requires a diagram.
These are the three possible situations I am talking about, after rebasing and testing. There was a base state in red, two green commits for some new feature. In all three cases, the contents of the working directory at the master branch (i.e. the commit being pointed to by “master”) are identical.
Now, a number of people seem to think that the no-op gold commits in the empty commit and oedipus merge situation are useful as delimiters for features. Okay, I can see that there is some utility in having delimiters for your features. What I don’t get is what utility are you getting by making those delimiters merges (and thus non-linearising your history) instead of just making them empty commits.
a) To “just” use empty commits, wouldn’t you have to know ahead of time whether a given branch was going to be a fast-forward merge or not?
b) If you want to delimit your features then all those delimiters should look the same. Why would I want to use one command to look at features which happened to have nothing committed to master while they were being developed, and another for features which happened to have another feature land while they were developed? (Also the concept of a “merge commit” is built into the tooling, making it much easier to use as a delimiter)
I always use fast-forward commits but I’ve heard that merge commits make it easier to roll back when you have automated deploys.
--no-ff means it’s always possible to revert a merge. That’s why I use it.
I’m not sure I understand the problem that this solves.
In a team of developers, with topic branches living for multiple days (I consider that short lived branches are better, but not everyone agrees with me…) then when problem X occurs on master it is useful information that Alice saw something which might have been problem X on her branch approx two days ago, if we can work out the state of her branch at that point.
So - I see a benefit to the “merge master into topic branch to keep it up to date” and “merge topic branch as-is (no rebase) back into master when done”.
I don’t see a cost - so for me the cost/benefit is clear, it’s better not to rebase prior to merge.
Can someone elaborate on the problem(s) this causes?
I more or less subscribe to this methodology, however it breaks down in long-lived feature branches (I know, a bad thing anyway) with several collaborators. If you wait to rebase until the branch is “complete” then you risk some really hairy conflicts and potentially flat-out breaking the feature you’ve been collaborating on in hard-to-identify ways. If you rebase frequently, then you screw up peoples' local repo when they get out-of-sync with the newly forced branch refs. Generally rewriting history in a collaborative environment requires great care.
One of the best things I picked up from this post was actually from the comments - I didn’t realize --force-with-lease was a thing, but I like it. It’s too bad it’s such a long option, but it may help this collaborating with rebases problem somewhat.
Ditto on --force-with-lease. A little surprised it hasn’t become the default behaviour for force!
When you do --force it means you don’t care about prompts or warnings, and you just want to do what you want to do. Making --force behave as --force-with-lease means it won’t force when you really want it to force. I’d prefer something like --overwrite or --rewrite to make it clear you’re removing history, but it’s also not going to get held back by the conventional semantics of CLI toos.
Very salient point, in particular with regards to bisect:
The less linear your history is, the less valuable it is.
That’s not quite true.. that’s actually why bisect is a built-in command and not just a common trick that people build out of other commands (such as git-flow), because bisect knows how to bisect non-linear history! Letting humans figure out bisection is error-prone.
Or at least that’s what bisect does in Mercurial. I assume git’s bisect is at least as smart as hg’s and knows how to follow the DAG and handle merges.
I find it rather ironic that one of the greatest benefits of distributed development through git is also it’s biggest flaws.