使用 Mercurial,在推送之前,如何将一系列变更集“压缩”为一个?

假设我有一个本地和一个远程 Mercurial 存储库。现在,我开始做一个专题。我在它上面工作,当我认为它已经完成时,我提交变更集。通过进一步测试,我发现可以通过调整代码中的某些内容来进一步改进这个特性。我做出改变并且承诺。20分钟后,我发现这个新特性中有一个 bug,所以我修复了它并提交了它。

我现在有3个变更集,我真的想把它们作为一个变更集推送到远程存储库,例如,它们带有消息“实现特性 X”。

我怎样才能不费吹灰之力地做到这一点呢?我相信我可以做到这一点与补丁,但似乎有很多工作。

26578 次浏览

I've never used Mercurial, but this sounds a lot like what Martin Fowler was talking about on his blog not too long ago:

http://martinfowler.com/bliki/MercurialSquashCommit.html

Yes, you can do it with patches: Let's assume your work is in changesets 100 through 110, inclusive

  1. Create a patch:

    % hg export -o mypatch 100:110 --git

  2. Update to 99:

    % hg update 99

  3. Apply the patch with --no-commit (otherwise you'll get all your changesets back):

    % hg import --no-commit mypatch

  4. Commit all changes at once:

    % hg commit

  5. You now have two heads (110 and 111) which should be equivalent in terms of files they produce in your working directory -- maybe diff them for sanity before stripping the old ones out:

    % hg strip 100

OK, now that I have spelled it all out, it does seem lengthy, but having done it a bunch of times myself, I don't find it to be too much of a chore...

The histedit extension is exactly what you are looking for.

hg histedit -o

or

hg histedit --outgoing

will bring up a list of the outgoing changesets. From the list you can

  • Fold 2 or more changesets creating one single changeset
  • Drop changesets removing them from the history
  • Reorder changesets however you like.

histedit will prompt you for the new commit message of the folded changesets which it defaults to the two messages with "\n***\n" separating them.

You can also get similar results using the mq extension, but it is a lot more difficult.

You can also use the collapse extension to just do folding, but it doesn't provide as nice a UI and doesn't provide a way to edit the resulting commit message. Editing the resulting commit message also allows for cleaning up the final message, which is something I always end up utilizing.

HistEdit will do what you want, but it's probably overkill. If the only thing you need is to fold some changesets together the Collapse Extension will do the job.

My preferred method of using mq for this folding is using TortoiseHg as described here. However, it can easily be done from the command line like so:

hg qimport -r <first>:<last>
-- where <first> and <last> are the first and last changesets
-- in the range of revisions you want to collapse


hg qpop <first>.diff
-- remove all except for the first patch from the queue
-- note: mq names patches <#>.diff when it imports them, so we're using that here


hg qfold <next>.diff
-- where <next> is <first>+1, then <first>+2, until you've reached <last>


hg qfinish -a
-- apply the folded changeset back into the repository

(There may be a better way to do the qfold step, but I'm not aware of it, as I usually use TortoiseHg for that operation.)

It seems a little complicated at first, but once you've started using mq, it's pretty straightforward and natural -- plus you can do all kinds of other things with mq that can be pretty handy!

If you are using TortoiseHg, use can just select two revisions (use CTRL to select non-subsequent ones), right click and select "Compress History".

After that you'll get a new change list in new head starting from the first change you selected before, it will contain all descendant change lists between the ones you selected.

You can simply strip out old change lists if you don't need them anymore: use MQ extensions for it. Again, in TortoiseHg: right click on the first change list that needs to be stripped with all it's descendants, "Modify History -> Strip".

hg collapse and hg histedit are the best ways. Or, rather, would be the best ways, if they worked reliably... I got histedit to crash with a stack dump within three minutes. Collapse is not that much better.

Thought I might share two other BKMs:

  1. hg rebase --collapse

    This extension is distributed with Mercurial. I haven't had problems with it yet. You may have to play some games to work around hg rebase limitations -- basically, it doesn't like rebasing to an ancestor on the same branch, named or default, although it does allow it if rebasing between (named) branches.

  2. Move the repository (foo/.hg) to the working directory (bar) and its files. Not the other way around.

Some people have talked about creating two clone trees, and copying files between them. Or patching between them. Instead, its easier to move the .hg directories.

hg clone project work
... lots of edits
... hg pull, merge, resolve
hg clone project, clean
mv work/.hg .hg.work
mv clean/.hg work/.hg
cd work
... if necessary, pull, nerge, reconcile - but that would only happen because of a race
hg push

This works as long as the true repositories, the .hg trees, are independent of the working directory and its files.

If they are not independent...

Why not just hg strip --keep command?

Then you can commit all changes as a one commit.

Suppose you have two unpublished THIS and THAT commits in Mercurial and like them to join into single commit at THIS point::

... --> THIS --> ... --> THAT --> ... --> LAST

Check that your commits are not published::

$ hg glog -r "draft() & ($THIS | $THAT)"

Update to LAST commit::

$ hg up

and import commits up to THIS into MQ::

$ hg qimport $THIS::.

Un-apply all patches and apply only first THIS::

$ hg qpop -a
$ hg qpush
$ hg qapplied
... THIS ...

Join with THAT::

$ hg qfold $THATNAME

NOTE To find name THATNAME use::

$ hg qseries

Apply all patches and move them to repository history::

$ hg qpush -a
$ hg qfinish -a

My blog post on subject is Joining two commits in Mercurial.

Yes, strip --keep works for Author's question. But it was slightly different from others, for example, if you have version from 1 to 30 but only want to collapse version 12-15. Other solutions work but not strip --keep.