211

In Git I can do this:

1. Start working on new feature:
$ git co -b newfeature-123  # (a local feature development branch)
do a few commits (M, N, O)

master A---B---C
                \
newfeature-123   M---N---O

2. Pull new changes from upstream master:
$ git pull
(master updated with ff-commits)

master A---B---C---D---E---F
                \
newfeature-123   M---N---O

3. Rebase off master so that my new feature 
can be developed against the latest upstream changes:
(from newfeature-123)
$ git rebase master

master A---B---C---D---E---F
                            \
newfeature-123               M---N---O


I want to know how to do the same thing in Mercurial, and I've scoured the web for an answer, but the best I could find was: git rebase - can hg do that

That link provides 2 examples:
1. I'll admit that this: (replacing the revisions from the example with those from my own example)

hg up -C F  
hg branch -f newfeature-123  
hg transplant -a -b newfeature-123 

is not too bad, except that it leaves behind the pre-rebase M-N-O as an unmerged head and creates 3 new commits M',N',O' that represent them branching off the updated mainline.

Basically the problem is that I end up with this:

master A---B---C---D---E---F
                \           \
newfeature-123   \           M'---N'---O'
                  \
newfeature-123     M---N---O

this is not good because it leaves behind local, unwanted commits that should be dropped.

  1. The other option from the same link is
hg qimport -r M:O
hg qpop -a
hg up F
hg branch newfeature-123
hg qpush -a
hg qdel -r qbase:qtip

and this does result in the desired graph:

master A---B---C---D---E---F
                            \
newfeature-123               M---N---O

but these commands (all 6 of them!) seem so much more complicated than

$ git rebase master

I want to know if this is the only equivalent in Hg or if there is some other way available that is simple like Git.

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
jpswain
  • 14,642
  • 8
  • 58
  • 63
  • 8
    "this is not good because it leaves behind local, unwanted commits that should be dropped." -- actually, git does the same thing. It doesn't change or remove the commits in the original branch, it just makes new ones that apply the same set of changes on top of master. You can still access the old ones using `git reflog` and they're not totally gone until they get garbage collected. If you want to keep them around in a named branch so that you don't have to use the reflog, just do `git branch feature-123_original` before rebasing. – Tyler Dec 24 '10 at 01:55
  • 5
    Random question: did you ascii-draw the changesets/branches yourself or is there a tool that does that? – Amir Rachum Dec 23 '12 at 18:05
  • 2
    Just did them myself with TextWrangler set to "overwrite." – jpswain Dec 25 '12 at 05:27
  • Working with both hg and git lately myself I noticed too, that they behave differently. For the folks arriving here, like me, searching for the problem: As other answers below point out, use `--keepbranches` these days. If you use TortoiseHg, there is a switch in the rebase dialog for that. – Rainer Schwarze Nov 08 '20 at 10:42

5 Answers5

240

VonC has the answer you're looking for, the Rebase Extension. It is, however, worth spending a second or two thinking about why neither mq nor rebase are enabled by default in mercurial: because mercurial is all about indelible changesets. When I work in the manner you're describing, which is nearly daily, here's the pattern I take:

1. Start working on a new feature:
$ hg clone mainline-repo newfeature-123
do a few commits (M, N, O)

master A---B---C
                \
newfeature-123   M---N---O

2. Pull new changes from upstream mainline:
$ hg pull

master A---B---C---D---E---F
                \
newfeature-123   M---N---O

3. merge master into my clone so that my new feature 
can be developed against the latest upstream changes:
(from newfeature-123)
$ hg merge F

master A---B---C---D---E---F
                \           \
newfeature-123   M---N---O---P

and that's really all that's necessary. I end up with a newfeature-123 clone I can easily push back to the mainline when I'm happy with it. Most importantly, however, I never changed history. Someone can look at my csets and see what they were originally coded against and how I reacted to changes in the mainline throughout my work. Not everyone thinks that has value, but I'm a firm believer that it's the job of source control to show us not what we wished had happened, but what actually happened -- every deadend and every refactor should leave an indelible trace, and rebasing and other history editing techniques hide that.

Now go pick VonC's answer while I put my soapbox away. :)

Community
  • 1
  • 1
Ry4an Brase
  • 78,112
  • 7
  • 148
  • 169
  • 18
    Note: off course, Git does not *exactly* allow you to rewrite history, only to create new one easily (http://utcc.utoronto.ca/~cks/space/blog/tech/GitNewHistory). By adding the RebaseExtension, Mercurial provides the same exact convenient way to replace an old history by a new one. Why? Because a merge is not always the right answer, especially when your changeset should be viewed as *evolutions* on top of F, and not the reverse (P merged on top of O) – VonC Apr 20 '10 at 06:21
  • 19
    VonC, I agree, a merge isn't always the right choice, but I think the difference in is what one wants one's VCS history to be able to tell them. I think the history should _always_ be able to answer questions like "What was that way I tried to integrate it at first that didn't work out and I thought was useless at the time". Scientists keep logbooks in pen with numbered pages, and AFAIC software engineers should save every byte they've ever typed. Rewriting history, even cset parentage, but certainly CollapseExtension, HistEdit, etc. violate that. It's totally a matter of personal choice. – Ry4an Brase Apr 20 '10 at 13:46
  • 15
    +1 for personal choice. With Git I consequently use rebase for trivial diverges and merge for non-trivial. This allows me to preserve merge history where I feel is important, but keep the log clean and linear most of the time. – Kos Aug 07 '12 at 17:43
  • 18
    Also I do tons of interactive rebases because I tend to firstly do lots of small commits, then join, label and clean them up, then merge them back with (or rebase on top of) the main branch. I like coding and managing changes to be separate steps. – Kos Aug 07 '12 at 17:45
  • 1
    If merge commits ever cloud my vision I just use the `--no-merges` option available on most cset listing commands, but as we're all saying it's entirely a matter of personal preference in both git and Mercurial. – Ry4an Brase Oct 18 '13 at 19:33
  • 7
    The "save every byte along the path of development" philosophy isn't really appropriate for large open-source projects, where contributors propose a set of patches, then rework them based on feedback from the maintainers. Then eventually the main project repository has just the proper version of all the changes, with all related changes in a single commit. git interactive rebase is really good for cleaning up working changes into a sequence of commits that don't leave the tree broken, and with commit messages reflecting what you decide to say after you're done working on the whole thing. – Peter Cordes Dec 15 '14 at 01:45
  • 1
    Knowing that you made a mistake at one point helps nobody in two weeks. Indelible changesets are not helpful. – Tim Harper May 06 '16 at 03:36
106

You might be looking for Rebase Extension. (implemented as part of the SummerOfCode 2008)

In those cases it can be useful to "detach" the local changes, synchronize the repository with the mainstream and then append the private changes on top of the new remote changes. This operation is called rebase.

Getting from:

alt text

to:

alt text


As commented below by steprobe:

In the case where you aren't pulling the changes in, and you have the two branches in your repo, you can do (using keepbranches):

hg up newfeature-123 
hg rebase -d master --keepbranches

(--keepbranches: Inherit the original branch name.)

Mojca mentions:

I like using hg rebase --source {L1's-sha} --dest {R2's-sha}, but I didn't know I could add --keepbranches at the end.

As illustrated below by Jonathan Blackburn:

 hg rebase -d default --keepbranches
Catskul
  • 17,916
  • 15
  • 84
  • 113
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • 1
    I have looked at the Rebase Extension, but it still isn't clear to me. Could you please explain the steps to do what I have described above? – jpswain Apr 20 '10 at 04:02
  • 6
    In the case where you aren't pulling the changes in, and you have the two branches in your repo, you can do: `hg up newfeature-123` followed by `hg rebase -d master --keepbranches` – steprobe May 26 '11 at 09:13
  • 2
    I believe nothing is wrong with rebase, it's just a matter of choice. The problem is that rebase gets abused and I'm with @Ry4an on this don't rewrite history so you can know what happen and when. – Jorge Vargas Aug 01 '11 at 04:46
  • @steprobe: thank you for the hint for using --keepbranches. I like using `hg rebase --source {L1's-sha} --dest {R2's-sha}`, but I didn't know I could add `--keepbranches` at the end. This is mentioned in other lower ranking answers, but it would be nice to explicitly write it to this answer as well. – Mojca Feb 22 '19 at 09:47
  • @Mojca No problem. I have edited the answer accordingly. – VonC Feb 22 '19 at 12:25
43

Assuming you have a modern Hg installation, you can simply add:

[extensions]
rebase = 

to ~/.hgrc.

Then you can use the commands hg rebase, hg pull --rebase, or hg help rebase.

sblom
  • 26,911
  • 4
  • 71
  • 95
21

I don't think the answers above achieve the OP's goal, which was to maintain his task branch, just rebased against a later point on the parent branch.

Let's say I start with this graph (generated using the graphlog extension. Serious geek love for graphlog).

@  9a4c0eb66429 Feature 3 commit 2 tip feature3
|
| o  af630ccb4a80 default againagainagain  
| |
o |  98bdde5d2185 Feature 3 branch commit 1  feature3
|/
o  e9f850ac41da foo   

If I'm on the feature3 branch and want to rebase it off of the againagainagain commit, I understand that I would run hg rebase -d default. This has the following result:

@  89dada24591e Feature 3 commit 2 tip 
|
o  77dcce88786d Feature 3 branch commit 1  
|
o  af630ccb4a80 default againagainagain  
|
o  e9f850ac41da foo  

Mission accomplished? I don't think so. The problem is that when the commits on the feature3 branch were rebased on againagainagain, the feature3 branch was deleted. My commits have been moved to the default branch, which was what I was trying to avoid in the first place.

In Git, the result would look like this:

@  9a4c0eb66429 Feature 3 commit 2 tip
|
o  98bdde5d2185 Feature 3 branch commit 1 **feature3**
|
o  af630ccb4a80 default againagainagain
|
o  e9f850ac41da foo

Notice that the feature3 branch still exists, the two commits are still on the feature3 branch, and not visible on default. Without preserving the task branch, I don't see how this is functionally different from a merge.

UPDATE: I discovered the --keepbranches flag supported by hg rebase, and I'm happy to report everything is okey-dokey. Using hg rebase -d default --keepbranches, I exactly replicate the Git behavior I craved. A couple of aliases later and I'm rebasing like nobody's business.

Richard Dingwall
  • 2,692
  • 1
  • 31
  • 32
  • 7
    Just discovered the --keepbranches flag on rebase. Problem solved. Were this my code I'd make that the default, but that's just me. – Jonathan Blackburn Apr 19 '13 at 16:48
  • 1
    I think it'd be useful if you added that bit of information to your response above - took me a while to find it. – HansMari Apr 21 '13 at 15:14
3

Since some people have chimed in saying they think it's good to keep every iteration of everything, I'll point out that for larger open-source projects, accepting changes full of merges and development iteration would make for a messy mainline revision history, and make the revision history less useful for seeing how the current version got there.

This works well when submitted changes are reviewed by people that didn't write them, before they're accepted, so changes that do go into the mainline are generally debugged and working. Then when you backtrack to the origin of a line, you see all the changes that go with it, not some point in the middle of development of the change it's part of.

The x265 contributors page explains how to re-commit a set of changes you're working on, to get them ready for submission to the x265 project. (Including use of TortoiseHG to commit some but not all changes in an individual file, like git gui's stage/unstage diff hunk for commit).

The process is to get hg updated to the upstream tip, and then get all your changes uncommitted in the working directory. Shelve any that aren't part of what you want to submit, then break the rest into as many separate commits are appropriate, with nice commit messages.

I guess you'd copy/paste and then edit commit messages from previous iterations of a patchset that you're revising. Or maybe you could graft your old commits (cherry-pick in git language), and then amend them one by one, to get your old commit messages as a start point for editting.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847