Can I squash commits in Mercurial?
Solution 1
Yes, you can do this using mercurial without any extensions by Concatenating Changesets.
Alternately if you want to use an extension you could use:
- The Collapse Extension
- The Rebase Extension or
- The Histedit Extension
Solution 2
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
:]
Solution 3
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:
(from http://mercurial-scm.org/wiki/RebaseExtension#Collapsing)
Solution 4
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
.
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.
Solution 5
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. usinghg histedit
).The automatic rewriting logic of
hg absorb
is implemented by following the history of lines: This is fundamentally different from the approach taken byhg histedit
orgit 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 byhg histedit
orgit rebase -i
would fail.
Related videos on Youtube
![R. Martinho Fernandes](https://i.stack.imgur.com/wjb79.jpg?s=256&g=1)
R. Martinho Fernandes
I'm rmf on keybase. If you are really desperate to contact me, you can reach me by sending e-mail to [email protected]. I'll post comments asking you for CC0 licensing of the code in your answers because: there's no better official way to directly contact the author; SO will force attribution on everyone despite significant negative feedback; I always ask for community-wide licensing, not personal; I believe CC0 is the most suitable license for such a scenario. All code posted in my answers on this site is licensed under CC0, even though from now on I will explicitly mention so in every single answer. Code in my questions may or may not be licensed under CC0; if you really need to know about a specific question, you can contact me on Stack Overflow chat, or via the e-mail address listed above.
Updated on July 08, 2022Comments
-
R. Martinho Fernandes almost 2 years
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?
-
Ry4an Brase over 14 yearsYeah, 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.
-
MattGWagner over 13 yearsCollapse extension was easy to install and very easy to use, exactly what I was looking for!
-
Nicolas Forney almost 10 yearsThe full command for hg strip is :
hg strip --keep --rev [rev]
Whererev
is the revision number of the first commit you want to squash with the last one -
G. Demecki almost 10 years@NicolasForney Not exactly,
--rev
is optional, full command ishg strip --keep [rev]
-
Paidhi about 9 yearsA little side note: The Histedit extension is distributed with Mercurial 2.3 and later. You just have to enable it.
-
Aleksandr Levchuk about 9 yearsIn 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!
-
Roman Starkov about 9 yearsRevision is mandatory for me in 3.3.3:
hg help strip
giveshg strip [-k] [-f] [-n] [-B bookmark] [-r] REV...
, and omitting revision gives meabort: empty revision set
. -
Aleksandr Levchuk almost 9 yearsJust trying to squash 2 commits. Do I really need a wiki page with 10+ commands or alternative extensions?
-
Ry4an Brase almost 9 yearsSee the comments. Histedit is now built-in, you just need to enable it (because no default commands will modify history)
-
Martin Thomson over 8 yearsUsing
hg strip
isn't the best idea. It's not exactly safe. Tryhg histedit
, maybe even try using the evolve extension. -
Mi-La almost 8 yearsWhere did you find the "~2" for two commits?
-
markand almost 8 yearsIt's explained in the revsets topic, see hg help revsets.
-
Arthur Tacca almost 7 yearsSeems 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 almost 7 years... 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.
-
foo over 6 yearsSeems the most natural way for git people ;)
-
Jake Hm over 6 years
hg strip --keep -r .
to strip the the current rev. -
Neil G about 6 years"NameError: name 'execfile' is not defined"—which means evolve is written in Python 2, which is basically the stone age.
-
Pulkit Goyal about 6 years@NeilG mercurial does not support Python 3 yet.
-
Neil G about 6 yearsI see. In that case, mercurial is in the stone age.
-
Pulkit Goyal about 6 years@NeilG yep, mercurial community is working hard to get py3 support asap.
-
Baptiste Mille-Mathias over 3 yearsdownvoting because you just give the command without explaining