Rebase and fast-forward-merge workflows

234 views
Skip to first unread message

Jimmy Bogard

unread,
Feb 7, 2011, 4:01:56 PM2/7/11
to merc...@selenic.com
Hi all,

In my typical daily workflow, the first action is to create a branch on the topic or feature I'm working on.  Most of the time, this local branch is pushed to up to the central repo, and later merged to default.

Sometimes, the amount of work is so small, that I'd rather just fold that work into the default branch, as I might in Git to do a rebase and fast-forward merge (and most likely a squash/collapse in the rebase process as well):

hg branch MyTopicBranch
..work work work..
hg ci -Am "Some intermediate point"
..work work work..
hg ci -Am "Another intermediate point"
..work work work..
hg ci -Am "Looks like I finished quickly, time to integrate"
hg rebase -b MyTopicBranch -d default --collapse <-- comes back "nothing to rebase"

My action at this point is to:

hg co default
hg ci -m "Pointless commit so that default's head is no longer in the direct ancestry of MyTopicBranch"

I can then proceed with the rebase.  I'm trying to get out of this extra commit.  I don't want to merge the branch, it's a local branch that I'm fine with it just going away.  If I wanted to keep the branch as a named branch, I would have pushed it.

I thought about using Hg's bookmarks, but doing FF-merges is still just as painful, and the rebase command still has whatever fail-safe in place to prevent a rebase-squash in this scenario, so no dice there.

Any pointers?

Thanks,

Jimmy

Becker, Mischa J

unread,
Feb 7, 2011, 5:21:12 PM2/7/11
to Jimmy Bogard, merc...@selenic.com

From: mercuria...@selenic.com [mailto:mercuria...@selenic.com] On Behalf Of Jimmy Bogard
Sent: Monday, February 07, 2011 1:02 PM
To: merc...@selenic.com
Subject: Rebase and fast-forward-merge workflows

Jimmy

 

It isn't as fast as rebase but as long as default does NOT have any extra commits, it is safe to use revert to pull your last changeset over.

 

i.e. 

hg up default                    

hg revert -a -r feature-branch

hg commit

 

After you have verified that everything is fine, you can strip out the feature-branch.

 

Mischa



This e-mail message, including any attachments, is for the sole use of the intended recipient(s) and may contain information that is confidential and protected by law from unauthorized disclosure. Any unauthorized review, use, disclosure or distribution is prohibited. If you are not the intended recipient, please contact the sender by reply e-mail and destroy all copies of the original message.

Kevin Bullock

unread,
Feb 7, 2011, 9:08:17 PM2/7/11
to Jimmy Bogard, merc...@selenic.com
On Feb 7, 2011, at 3:01 PM, Jimmy Bogard wrote:

> Hi all,
>
> In my typical daily workflow, the first action is to create a branch on the topic or feature I'm working on. Most of the time, this local branch is pushed to up to the central repo, and later merged to default.
>
> Sometimes, the amount of work is so small, that I'd rather just fold that work into the default branch, as I might in Git to do a rebase and fast-forward merge (and most likely a squash/collapse in the rebase process as well):
>
> hg branch MyTopicBranch
> ..work work work..
> hg ci -Am "Some intermediate point"
> ..work work work..
> hg ci -Am "Another intermediate point"
> ..work work work..
> hg ci -Am "Looks like I finished quickly, time to integrate"
> hg rebase -b MyTopicBranch -d default --collapse <-- comes back "nothing to rebase"
>
> My action at this point is to:
>
> hg co default
> hg ci -m "Pointless commit so that default's head is no longer in the direct ancestry of MyTopicBranch"
>
> I can then proceed with the rebase. I'm trying to get out of this extra commit. I don't want to merge the branch, it's a local branch that I'm fine with it just going away. If I wanted to keep the branch as a named branch, I would have pushed it.

Named branches aren't good for topic branches. Unlike Git's concept of branch-by-ref, branch names in Mercurial stick around forever. They're best used for long-running permanent branches like 'stable', 'release-1.x', etc.

> I thought about using Hg's bookmarks, but doing FF-merges is still just as painful, and the rebase command still has whatever fail-safe in place to prevent a rebase-squash in this scenario, so no dice there.

Not sure what you mean about 'fast-forward merges' being painful.

What Git calls a 'fast-forward merge' is just an `hg update`. I've never been able to discern why Git introduced this terminology. When you run `hg update`, provided you're not crossing topological branches, a 3-way merge is performed between your working directory and the target revision, with the working directory parent as common ancestor. This is only a non-trivial 'merge' if your working directory has uncommitted changes.

If the update would cross branches, Mercurial aborts (and suggests using --check or --clean to force the update). This is the same situation that in Git disqualifies a fast-forward merge (without rebasing).

If your history is already linear, then as I understand it `hg rebase` will do nothing even with --collapse, because no actual rebasing is required. If you insist on changing history to collapse changesets, here's one way you can do it using no extensions. Assume that you're not using named branches (i.e. all work is on 'default'), 'master' points at the head of the main development line, and 'topic-branch' is a bookmark pointing at the head of your local work:

$ hg up master
$ hg diff -r .:topic-branch | hg import --no-commit -
$ hg ci -m 'topic branch to add feature X'
$ hg push -r.

Now you have your actual local history dangling alongside your shared, collapsed changeset. You can strip it off using any of the well-known methods (clone -r, strip, `hg rebase -s topic-branch`).

Alternatively you can take a look at the histedit extension <http://mercurial.selenic.com/wiki/HisteditExtension> or MQ to fold commits.

But keep in mind: "Rebasing doesn't result in cleaner history. It just results in _incorrect_ history that looks simpler." —Linus Torvalds, <http://lkml.org/lkml/2010/9/28/362>

pacem in terris / mir / shanti / salaam / heiwa
Kevin R. Bullock

_______________________________________________
Mercurial mailing list
Merc...@selenic.com
http://selenic.com/mailman/listinfo/mercurial

Jimmy Bogard

unread,
Feb 7, 2011, 11:26:24 PM2/7/11
to Kevin Bullock, merc...@selenic.com
I'm still not gathering how "hg update" is like a fast-forward merge.  Git's FF merge moves the branch pointer up, where hg up, either with branches or bookmarks does nothing of the sort.  If I merge a bookmark into its child, in git the branch pointer is moved up.  In Hg, nothing happens with rebase/merge.  I wind up doing a "hg bookmark -f -r Topic1 master", and that's after I've had to check exactly what the ancestry looks like.

I tried this workflow with bookmarks for a while, as that gave me the most flexibility from the get-go.  I really don't want to choose when I start how my commits are going to get folded back in to the mainline branch.  It could be a small topic that I want to rebase, or a larger topic/feature that I actually push separately.

I get what Linus is implying there - but I'm not developing on the Linux kernel; I just want to choose how my work shows up in the mainline branch.  I don't want intermediate commits that are really checkpoints that don't even compile sometimes - just finished products.  With long-lived named branches (i.e., more than 1 day), this is easy as it's a merge.  I'm just struggling to see how to make the seamless transition that I do with git, and have a deferred decision that waits until the last responsible moment.

Right now the workflow is:

1) Always work on a named branch
2) Only push/pull named branches (i.e., not the whole entire repo goes up/down to the central repo)
3) Merge between named branches
4) Rebase inside of a single named branch

This forces a linear history for each single named branch, with merges between.  It gets away from the "true" history, but is a lot easier to follow when I have to look back in history for things.  My only hangup now is the topic branch and how to wrestle this with a named branch.

John D. Mitchell

unread,
Feb 7, 2011, 11:35:52 PM2/7/11
to Jimmy Bogard, merc...@selenic.com

On Feb 7, 2011, at 20:26 , Jimmy Bogard wrote:
[...]

> This forces a linear history for each single named branch, with
> merges between. It gets away from the "true" history, but is a lot
> easier to follow when I have to look back in history for things. My
> only hangup now is the topic branch and how to wrestle this with a
> named branch.

IMHO&E, don't use a named branch for topic/feature "branches" -- use a
clone instead. Hg's branches exist forever but you can control your
private clones.

It's easy to collapse history on a per-clone basis, if that's what you
want. I've done this to create repos of code for talks that I've given
(so that people can follow along as the code evolves).

Have fun,
John

Matt Mackall

unread,
Feb 7, 2011, 11:43:36 PM2/7/11
to Jimmy Bogard, merc...@selenic.com
On Mon, 2011-02-07 at 22:26 -0600, Jimmy Bogard wrote:
> I'm still not gathering how "hg update" is like a fast-forward merge.

> Git's FF merge moves the branch pointer up, where hg up, either with
> branches or bookmarks does nothing of the sort. If I merge a bookmark
> into its child, in git the branch pointer is moved up. In Hg, nothing
> happens with rebase/merge. I wind up doing a "hg bookmark -f -r
> Topic1 master", and that's after I've had to check exactly what the
> ancestry looks like.

We don't speak Git here but it sounds like you want to turn on
"[bookmarks] track.current".

--
Mathematics is the supreme nostalgia of our time.

Kevin Bullock

unread,
Feb 7, 2011, 11:57:53 PM2/7/11
to Jimmy Bogard, merc...@selenic.com
On 7 Feb 2011, at 10:26 PM, Jimmy Bogard wrote:

I'm still not gathering how "hg update" is like a fast-forward merge.  Git's FF merge moves the branch pointer up,

and that is precisely the -only- difference between `hg update` and Git's 'fast-forward merge'.

where hg up, either with branches or bookmarks does nothing of the sort.

because branch names in Mercurial are nothing like branch refs in Git. A branch name is a permanent stamp on a commit, included in the changeset hash.

If I merge a bookmark into its child,

Apart from shifting refs around (which are just pointers and have very little to do with actual history), "merging" a changeset into its child is a nonsensical operation.

in git the branch pointer is moved up.  In Hg, nothing happens with rebase/merge.  I wind up doing a "hg bookmark -f -r Topic1 master", and that's after I've had to check exactly what the ancestry looks like.

There are some rough edges being worked out as bookmarks get merged into core. One of the things being worked on is a sensible semantic for updating bookmarks. And if there's ways that rebase can work better with bookmarks, you can always submit wishlist items to the bug tracker: <http://mercurial.selenic.com/bts/>. But for now, there's nothing wrong with using `hg bookmark -f` to update your 'master' bookmark once you've finished your topical line.

More generally I'd recommend learning to think of branches in terms of actual branches (the DAG), not the names given to them (via refs in Git, or bookmarks or named branches in Mercurial). To that end you might find this useful: <http://stevelosh.com/blog/2009/08/a-guide-to-branching-in-mercurial/>.

Jouni Airaksinen

unread,
Feb 8, 2011, 5:10:48 AM2/8/11
to Matt Mackall, mercuria...@selenic.com, merc...@selenic.com, Jimmy Bogard
Just wondering why it cannot be set default "true"? Of course there's the backwards compability, but this silly configuration option which "everybody" has to specifically configure is easy to forget if you don't know about it. Ie. with TortoiseHg you have to manually configure it instead of just enabling the extension. Are there people who consider the non-tracked bookmarks useful? I would guess that is the minority or the people who don't know about this configuration which makes bookmarks THE bookmarks.

Maybe for Mercurial 1.8 as it's major revision, so this kind of small change for convience would be allowed instead of making backwards compatibility to extreme. I would consider non tracking bookmarks more or less bug instead of a feature to switch.. ;)

Martin Geisler

unread,
Feb 8, 2011, 6:49:49 AM2/8/11
to Jouni Airaksinen, merc...@selenic.com, Jimmy Bogard
Jouni Airaksinen <Jouni.Ai...@descom.fi> writes:

> Just wondering why it cannot be set default "true"? Of course there's
> the backwards compability, but this silly configuration option which
> "everybody" has to specifically configure is easy to forget if you
> don't know about it. Ie. with TortoiseHg you have to manually
> configure it instead of just enabling the extension. Are there people
> who consider the non-tracked bookmarks useful? I would guess that is
> the minority or the people who don't know about this configuration
> which makes bookmarks THE bookmarks.
>
> Maybe for Mercurial 1.8 as it's major revision, so this kind of small
> change for convience would be allowed instead of making backwards
> compatibility to extreme. I would consider non tracking bookmarks more
> or less bug instead of a feature to switch.. ;)

You're in luck, it has already been decided that the track.current
option will be dropped for Mercurial 1.8:

http://mercurial.markmail.org/message/5ehnjubugygofzm2

--
Martin Geisler

Mercurial links: http://mercurial.ch/

Jimmy Bogard

unread,
Feb 8, 2011, 8:28:08 AM2/8/11
to Jouni Airaksinen, merc...@selenic.com
And as an aside, [track.current] doesn't have this behavior of ff-merge.  Tracked bookmarks only move on commits, not on this operation of "nothing to rebase/merge, we'll just move your bookmark for you".

Thanks,

Jimmy

Jason Harris

unread,
Feb 8, 2011, 1:53:52 AM2/8/11
to Kevin Bullock, mercurial@selenic.com Mailing-list, Jimmy Bogard
I think Jimmy has a very valid point:

On Feb 8, 2011, at 10:01 AM, Jimmy Bogard wrote:

My action at this point is to:

hg co default
hg ci -m "Pointless commit so that default's head is no longer in the direct ancestry of MyTopicBranch"

I can then proceed with the rebase.  I'm trying to get out of this extra commit.  I don't want to merge the branch, it's a local branch that I'm fine with it just going away.  If I wanted to keep the branch as a named branch, I would have pushed it.

I do these exact same steps myself every so often and if I have a linear tree, I have to update to the previous revision, do a pointless commit followed by the rebase, followed by histedit to drop the pointless commit. (This is independent of any branch names, or bookmarks, etc... its just basically a rebase problem)

Rebase should just "work" in this case. Its a complaint that various users have had and there is nothing intrinsically more or less "bad" about rebasing onto an ancestor then rebasing elsewhere. Its just currently Mercurial doesn't like doing this and thus its an implementation problem. Current behavior shouldn't be mixed up with what is the correct design here.

Just to be clear what we are talking about is going from:


to this:


Cheers,
  Jas
Reply all
Reply to author
Forward
0 new messages