117

I have a pair of commits that should really be just one. If I was using git, I would use:

git rebase -i <some-commit-before>

and then squash them.

Can I do that in mercurial? If so, how?

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510

8 Answers8

94

Yes, you can do this using mercurial without any extensions by Concatenating Changesets.

Alternately if you want to use an extension you could use:

sage444
  • 5,661
  • 4
  • 33
  • 60
Ry4an Brase
  • 78,112
  • 7
  • 148
  • 169
  • Yeah, I fished that answer out of the dupe questions I liked to in my comment on the general question. I think it's your answer on that one. – Ry4an Brase Nov 12 '09 at 21:53
  • Collapse extension was easy to install and very easy to use, exactly what I was looking for! – MattGWagner Dec 30 '10 at 03:02
  • 4
    A little side note: The Histedit extension is distributed with Mercurial 2.3 and later. You just have to enable it. – Paidhi Apr 16 '15 at 14:00
  • 1
    In the Concatenating Changesets doc uses abstract concepts of "repos", how to I reference the those? For example: hg -R oldrepo export ... produces "abort: repository oldrepo not found! – Aleksandr Levchuk Apr 28 '15 at 19:21
  • 21
    Just trying to squash 2 commits. Do I really need a wiki page with 10+ commands or alternative extensions? – Aleksandr Levchuk Sep 13 '15 at 22:43
  • 3
    See the comments. Histedit is now built-in, you just need to enable it (because no default commands will modify history) – Ry4an Brase Sep 28 '15 at 15:12
49

My favourite is hg strip <commit_hash> --keep command. And then I commit all changes in one commit.

It is the fastest and most comfortable way for me, because I like to do many small commits during my daily work ;)


Note 1: strip needs a built-in extension mq to be enabled.
Note 2: My favourite Git/ Mercurial client (SmartGit/Hg) appends by default --keep parameter during strip. And what is even more convenient: it provides option called join commits :]

Snackoverflow
  • 5,332
  • 7
  • 39
  • 69
G. Demecki
  • 10,145
  • 3
  • 58
  • 58
35

The Rebase extension worked like a charm. To squash 2 commits:

$ hg rebase --dest .~2 --base . --collapse

Dot is a shortcut for current revision.

It's even easier when you have a few commits on a branch and want to collapse them all into one:

$ hg rebase --dest {destination branch (e.g. master)} --base . --collapse

How this works:

enter image description here

(from http://mercurial-scm.org/wiki/RebaseExtension#Collapsing)

Pulkit Goyal
  • 780
  • 4
  • 20
Martin Konicek
  • 39,126
  • 20
  • 90
  • 98
15

If you are reading this answer, you can forget every other option mentioned in this answer and use the fold command from the evolve extension.

evolve is an extension of mercurial which helps us in having safe mutable history, it's still experimental though. You can use it by cloning it from its repo and adding it in your .hgrc like this.

[extensions]
evolve = ~/evolve/hgext/evolve.py

Assuming that you cloned evolve repo in your home directory. Now you are good to go. You can also look for help by hg help fold.

Fold Command

You tell fold to squash/fold a linear chain of commits which is not broken. What fold does is, it creates a new changeset which contains changes from all the changesets and mark all those commits as obsolete. You can have a more deep view into this at docs.

Now suppose you have the following history.

a -> b -> c -> d -> e -> f -> g

You want to squash e, f and g. You can do

hg up g
hg fold -r e

The result will be

a -> b -> c -> d -> h

where h is the changeset which contains changes from all the three commits e, f and g.

You can also fold changesets from the middle of the history, i.e. not necessarily you have to pick a chain which includes the tip. Suppose you want to fold b, c and d. You can do

hg up d
hg fold -r b
hg evolve --all

This will result in

a -> i -> j

where i is the folded changeset of b, c, d and j is the same changeset as h. Evolve user guide is a must read.

Pulkit Goyal
  • 780
  • 4
  • 20
  • Seems like rebase covers most (maybe all?) use cases of that extension, and certainly the one being asked in this question. The killer feature of that extension is to hide (rather than delete) the revisions you replace, but the `--keep` option of rebase covers this (followed by marking revisions as secret, or using strip on them once you've checked the result). Even moving revisions in between other revisions is possible with a sequence of two rebase commands. – Arthur Tacca Jul 17 '17 at 10:03
  • ... plus, if you're doing something really complicated you can always just clone the local repo first to use as a backup. Considering how rare (hopefully!) that is, it's less effort than learning how to use a totally new extension. – Arthur Tacca Jul 17 '17 at 10:03
  • "NameError: name 'execfile' is not defined"—which means evolve is written in Python 2, which is basically the stone age. – Neil G Apr 27 '18 at 15:21
  • 1
    @NeilG mercurial does not support Python 3 yet. – Pulkit Goyal May 04 '18 at 14:01
  • I see. In that case, mercurial is in the stone age. – Neil G May 04 '18 at 15:34
  • 2
    @NeilG yep, mercurial community is working hard to get py3 support asap. – Pulkit Goyal May 21 '18 at 18:56
1

With Mercurial 4.8 (Nov. 2018, 9 years later), you could consider the new command hg absorb (it was an experimental feature before).

See "Absorbing Commit Changes in Mercurial 4.8"

The absorb extension will take each change in your working directory, figure out which commits in your series modified that line, and automatically amend the change to that commit.
If there is any ambiguity (i.e multiple commits modified the same line), then absorb will simply ignore that change and leave it in your working directory to be resolved manually.

At a technical level, hg absorb finds all uncommitted changes and attempts to map each changed line to an unambiguous prior commit.
For every change that can be mapped cleanly, the uncommitted changes are absorbed into the appropriate prior commit. Commits impacted by the operation are rebased automatically.
If a change cannot be mapped to an unambiguous prior commit, it is left uncommitted and users can fall back to an existing workflow (e.g. using hg histedit).

The automatic rewriting logic of hg absorb is implemented by following the history of lines: This is fundamentally different from the approach taken by hg histedit or git rebase, which tend to rely on merge strategies based on the 3-way merge to derive a new version of a file given multiple input versions.

This approach combined with the fact that hg absorb skips over changes with an ambiguous application commit means that hg absorb will never encounter merge conflicts!

Now, you may be thinking if you ignore lines with ambiguous application targets, the patch would always apply cleanly using a classical 3-way merge. This statement logically sounds correct. But it isn't: hg absorb can avoid merge conflicts when the merging performed by hg histedit or git rebase -i would fail.

Robert Longson
  • 118,664
  • 26
  • 252
  • 242
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
0

I think chistedit (built in since Mercurial 2.3) is the closest to rebase -i that is pure Mercurial (chistedit is the interactive version of histedit). Once in histedit the fold command maps to rebase's squash and roll command maps to rebase's fixup. See histedit docs for more info.

Here is a simple example. Assume you have the following and want to move all 1e21c4b1's changes into the previous revision and just keeping the previous revision's message.

@  1e21c4b1 drees tip
|  A commit you want to squash
o  b4a738a4 drees
|  A commit
o  788aa028 drees
|  Older stuff

You can run hg chistedit -r b4a738a4 to edit history back to b4a738a4. In chistedit you then cursor down to 1e21c4b1 and hit r to indicate you want to roll that revision. Do note that the order in histedit (oldest to newest) is reversed from hg log (newest to oldest).

#0  pick   160:b4a738a49916   A commit
#1  ^roll  161:1e21c4b1500c

After choosing your changes, you then choose c to commit them. The result is the following:

@ bfa4a3be drees tip | A commit o 788aa028 drees | Older stuff

If you are relatively new to them, then histedit can be a better choice than chistedit because it provides the command descriptions in histedit file for reference. It just takes a bit more editing to set the commands using normal text editing (just like normal rebase).

Note, to use either histedit or chistedit you need to add histedit to your extensions in your ~/.hgrc:

[extensions]
histedit =

I suggested chistedit since it is closest to rebase -i and works anywhere in the history. If you really just want subsume/tweak the current revision into the previous one then @G. Demecki's strip suggestion can be good since what is happening is clear. It is built in since Mercuria 2.8. To get the equivalent results as above you can do the following:

hg strip .
hg add
hg commit --amend

Note strip, like histedit, needs to be enabled in your ~/.hgrc:

[extensions]
strip =
studgeek
  • 14,272
  • 6
  • 84
  • 96
0

Let's assume you want to squash (unite) 2 most recent commits.

  1. Find a revision number

    hg log -G -l 3
    

    possible output:

    @  changeset:   156:a922d923cf6f
    |  branch:      default
    |  tag:         tip
    |  user:        naXa!
    |  date:        Thu Dec 13 15:45:58 2018 +0300
    |  summary:     commit message 3
    |
    o  changeset:   155:5feb73422486
    |  branch:      default
    |  user:        naXa!
    |  date:        Thu Dec 13 15:22:15 2018 +0300
    |  summary:     commit message 2
    |
    o  changeset:   154:2e490482bd75
    |  branch:      default
    ~  user:        naXa!
       date:        Thu Dec 13 03:28:27 2018 +0300
       summary:     commit message 1
    
  2. Soft reset branch

    hg strip --keep -r 155
    
  3. Commit changes again

    hg commit -m "new commit message"
    

Notes

strip requires a built-in extension to be enabled. Create/edit ~/.hgrc config file with the following content:

[extensions]
strip = 
naXa stands with Ukraine
  • 35,493
  • 19
  • 190
  • 259
-2

I use:

hg phase --draft --force -r 267
...
hg rebase --dest 282 --source 267 --collapse
Unheilig
  • 16,196
  • 193
  • 68
  • 98
sasha-x
  • 11
  • 3