Mercurial: 如何修改最后一次提交?

225
我在Mercurial中寻找类似于“git commit --amend”的命令,即修改与我的工作副本相关联的提交记录的方法。我只对最后一次提交感兴趣,而不是任意较早的提交。
该修订程序的要求如下:
- 如可能,不需要任何扩展程序。它不能需要非默认扩展程序,即不随正式Mercurial安装提供的扩展程序。 - 如果要修改的提交记录是当前分支的一个头,则不应创建新的头。如果提交记录不是头,则可以创建新的头。 - 这个过程应该是安全的,如果由于任何原因修订失败,我希望恢复到修订前的相同工作副本和存储库状态。换句话说,如果修订本身可能会失败,那么应该有一个故障转移程序来恢复工作副本和存储库状态。我指的是在修订过程中可能出现的“失败”(例如冲突),而不是文件系统相关的问题(例如访问限制,无法锁定文件以进行写入等)。
更新(1):
- 该过程必须是可自动化的,因此可以由GUI客户端执行,无需任何用户交互。
更新(2):
- 工作目录中的文件不能被触及(某些修改后的文件可能存在文件系统锁定)。这特别意味着,可能的方法在任何时候都不能要求干净的工作副本。

通过一个图形用户界面(GUI)客户端?你肯定是指命令行界面(CLI)客户端吧? - undefined
8个回答

302
自从Mercurial 2.2发布以来,您现在可以在hg commit命令中使用--amend选项来更新最后一次提交与当前工作目录的内容。
命令行参考中可以看到:

如果有需要,--amend标志可以用于将工作目录的父提交与包含父提交更改以及当前由hg status报告的更改的新提交进行修正。旧的提交将存储在.hg/strip-backup中的备份捆绑包中(请参阅hg help bundle和hg help unbundle以了解如何恢复它)。

除非另有指定,否则消息、用户和日期将从修订的提交中获取。当命令行上没有指定消息时,编辑器将打开并显示修订提交的消息。

这个机制的好处在于它是“安全的”,因为它依赖于“阶段”功能来防止更新,以避免改变已经在本地仓库之外发布的历史记录。

2
好的回答!实验性的evolve扩展允许您安全地修改非头部提交。旧的提交将被标记为过时并隐藏。在非发布服务器上,即使在推送更改集之后,您也可以安全地执行此操作。 - Martin Geisler
10
更新最新提交的消息:hg commit --amend -m "this is my new message" - Jay Sheth

57
您有三种选项可以编辑Mercurial中的提交记录:
  1. 使用hg strip --keep --rev -1 撤消最近的(1)个提交,这样您就可以再做一次 (了解更多信息请参见此回答)。
  2. 使用与Mercurial捆绑在一起的MQ扩展
  3. 即使没有捆绑在Mercurial中,也值得提到Histedit扩展。

您还可以查看Mercurial维基上的编辑历史记录页面。

简而言之,编辑历史记录非常困难且不鼓励这样做。如果您已经推送了更改,除非您完全控制所有其他克隆,否则几乎无能为力。

我不太熟悉git commit --amend命令,但据我所知,Histedit似乎是最接近的方法,但遗憾的是它没有与Mercurial捆绑在一起。MQ很难使用,但您几乎可以做任何事情。


1
我不确定为什么我错过了回滚,但它似乎做到了(几乎)我想要的。唯一的问题是,当一个文件在我的原始提交中被删除并在我的修改提交中被恢复时:在回滚之前,它将是未版本化的,在回滚之后,它将被计划删除(但文件仍然存在于工作目录中)。 - mstrap
1
对于自动化部分我不清楚,但是你可以使用hg revert myfile来撤销删除操作。也许在rollback之后使用hg add重新添加文件也可以起作用。 - krtek
不幸的是,“hg rollback”和MQ扩展都无法处理以下情况:
  • 已提交修订版2,其中对file1和file2进行了更改。
  • 工作副本包含对file2和file3的更改。
  • 只有file3的更改,而不是file2,应该被提交。
“hg rollback”显然无法做到这一点。无论我尝试使用补丁队列,本地修改都需要被隐藏,即我的工作目录需要被触及。“git commit --amend”可以处理这种情况。
- mstrap
@Marc 就像我说的那样,Mercurial 不鼓励历史编辑。这是与 Git 的主要区别之一。您可以通过将更改存储到存储库中(查看 Shelve 扩展)或将差异保存到补丁文件中,然后在使用 MQ 之前使用 patch 命令重新应用它们来实现您想要的操作,但这不符合 Mercurial 的理念:历史是神圣的! - krtek
3
我同意避免编辑已发布更改的历史记录,但编辑我本地历史记录是分布式版本控制系统(DVCS)的亮点之一。根据我的理解,使用qimport的MQ就是纯粹的历史记录编辑。 - mstrap
显示剩余3条评论

42

hg commit --amend 的 GUI 等效方式:

在 TortoiseHG 的 GUI 中也可以使用此功能(我正在使用 v2.5 版本):

切换到“提交”视图或者在工作台视图中选择“工作目录”条目。 “提交”按钮有一个名为“修正当前版本”的选项(点击按钮的下拉箭头查找它)。

enter image description here

          ||
          ||
          \/

enter image description here

请注意:

只有当mercurial版本至少为2.2.0且当前修订版本不是公开的、不是补丁且没有子级时,才会启用此额外选项。[...]

单击该按钮将调用“commit --amend”来“修改”修订版本。

有关此问题的更多信息,请参见THG dev频道


非常有帮助,谢谢。THG 足够聪明,将提交(修订)消息默认为上一个提交的消息 - 这正是我想要的。 - StayOnTarget

8
我正在研究krtek所写的内容,具体来说是解决方案1:
假设: - 您已经提交了一个更改集(!),但尚未推送它。 - 您想修改此更改集(例如添加、删除或更改文件和/或提交消息)。
解决方案: - 使用hg rollback撤消上次提交。 - 然后再次提交,将新更改放置到位。
回滚实际上撤消了最后一次操作。它的工作方式非常简单:HG中的正常操作只会追加到文件中;这包括提交。Mercurial跟踪上一个事务的文件长度,因此可以通过将文件截断回到其旧长度来完全撤消一步操作。

1
感谢您使用解决方案(1)。但是回滚仍然存在一个小问题,请查看我在krtek解决方案下的评论。 - mstrap
8
在回滚时要特别强调一点,因为这很容易让人们困惑:被回滚的是仓库上的最后一次事务,而不是最后一次提交。所以,如果其他原因导致了对仓库的写入,回滚就无法帮助解决问题。记住这个微妙但非常重要的点非常关键。当回滚窗口已关闭时,MQ和histedit可以提供帮助,但仍然只能到达一定程度。 - Paul S

7
假设您还没有传播更改,那么您可以执行以下操作。
  • Add to your .hgrc:

    [extensions]
    mq =
    
  • In your repository:

    hg qimport -r0:tip
    hg qpop -a
    

    Of course you need not start with revision zero or pop all patches, for the last just one pop (hg qpop) suffices (see below).

  • remove the last entry in the .hg/patches/series file, or the patches you do not like. Reordering is possible too.

  • hg qpush -a; hg qfinish -a
  • remove the .diff files (unapplied patches) still in .hg/patches (should be one in your case).

如果你不想撤回所有的补丁,你可以使用hg qimport -r0:tip(或类似命令)来编辑它,然后编辑内容并使用hg qrefresh将更改合并到堆栈顶部的最高补丁中。阅读hg help qrefresh

通过编辑.hg/patches/series,你甚至可以删除多个补丁或重新排序一些补丁。如果你的最后一条修订是99,你可以使用hg qimport -r98:tip; hg qpop; [edit series file]; hg qpush -a; hg qfinish -a

当然,这个过程是极为不鼓励和有风险的。在进行此操作之前,请备份所有内容!

另外,我曾在私有仓库上多次执行此操作。


我也考虑过使用mq扩展,但是它需要执行相当多的操作,其中一些可能会失败(例如,如果涉及二进制文件)。此外,必须编辑.hg/patch/series是不可接受的,因为这个过程应该在GUI客户端内使用(我已经更新了上面的要求)。 - mstrap
抱歉,这对你来说行不通,在私有存储库中,这真的很棒(带有备份 - 我已经用它意外删除了一个存储库 ^^)。顺便说一句,使用 hg qfold 将补丁合并成一个再推送本地更改非常酷。 - hochl
使用MQ是个好主意,但我认为你有点过了。他只是在询问如何修改最后一次提交。而且那个导入语句一旦合并就会崩溃。'qimport -r tip; <edit stuff>; qrefresh -e; qfin -a' 可以完成任务(-e 用于编辑提交信息)。 - Paul S
true,合并是一个问题,我通常只弹出一个补丁并使用hg import -r<prev>:tip。遗憾的是,在Subversion中没有上一个版本的快捷方式。 - hochl

3

最近版本的Mercurial已经包含了evolve扩展,该扩展提供了hg amend命令。这允许在版本控制中修改提交(commit)而不会丢失修改之前的历史记录。

hg amend [OPTION]... [FILE]...

aliases: refresh

combine a changeset with updates and replace it with a new one

Commits a new changeset incorporating both the changes to the given files
and all the changes from the current parent changeset into the repository.

See 'hg commit' for details about committing changes.

If you don't specify -m, the parent's message will be reused.

Behind the scenes, Mercurial first commits the update as a regular child
of the current parent. Then it creates a new commit on the parent's
parents with the updated contents. Then it changes the working copy parent
to this new combined changeset. Finally, the old changeset and its update
are hidden from 'hg log' (unless you use --hidden with log).

请参考https://www.mercurial-scm.org/doc/evolution/user-guide.html#example-3-amend-a-changeset-with-evolve,了解有关evolve扩展的完整说明。


重复使用相同的提交信息是一个不错的功能! - mpen

2

可能不能解决原问题的所有问题,但由于这似乎是关于如何使用mercurial修改先前提交的事实帖子,我将添加我的两分信息。

如果您和我一样,只希望修改以前的提交消息(修复拼写错误等),而不添加任何文件,则可以使用此方法。

hg commit -X 'glob:**' --amend

如果没有任何包含或排除模式,hg commit默认将包括工作目录中的所有文件。使用模式-X 'glob:**'将排除所有可能的文件,只允许修改提交消息。

在索引/暂存区中没有文件时,它的功能与git commit --amend相同。


-1

另一种解决方案可能是使用uncommit命令从当前提交中排除特定文件。

hg uncommit [file/directory]

当您想保留当前提交并取消选择某些文件以进行提交时,这非常有帮助(尤其适用于已删除的文件/目录)。


uncommit 是一个实验性的扩展。 - darw

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接