如何重写作为两个分支的父级的提交(消息)?

时间:2015-07-23 17:30:11

标签: git git-rewrite-history

假设我有一个像这样的回购

$ git log --oneline --graph 
*   3e0a28f Merge branch 'other_branch'
|\  
| * d4fd67a Add something else
| *   d0f16bf Merge branch 'master' into other_branch
| |\  
| |/  
|/|   
* | 3684fe5 Make a change in master
| * 45b3ecb Make a change
|/  
* b2a9034 Added some text, reword me
* 7b1ac57 Initial commit

d0f16bf涉及修复一些合并冲突。 (3684fe545b3ecb修改同一行)。 我想改写b2a9034

$ git rebase -i b2a9034^
[detached HEAD a9bd978] Added some text, REWORDED
 1 file changed, 2 insertions(+)
error: could not apply 3684fe5... Make a change in master

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
Could not apply 3684fe570517de37e1ce7661e3821372e1eee967... Make a change in master

有没有办法在不修复合并冲突的情况下重新编写b2a9034

2 个答案:

答案 0 :(得分:3)

当你说" reword"我假设你的意思是"更改提交消息,但不是提交的文件" (git rebase -i含义)。

Git的"Swiss-Army Chainsaw"命令git filter-branch,有一个选项。 (当然,filter-branch很难使用,因此"Swiss-Army chainsaw" appellation。)

具体来说,filter-branch有--msg-filter,允许在更改消息时复制提交:

  

这是用于重写提交消息的过滤器。争论              在shell中使用原始提交消息进行评估              标准输入;其标准输出用作新提交              消息。

这意味着您可以保留现有消息以用于不是重写目标的提交,并使用合适的--msg-filter编辑或替换作为目标的提交,例如:

git filter-branch ... \
    --msg-filter 'if [ $GIT_COMMIT == b2a9034... ]; then \
        cat $HOME/new_msg; else cat; fi' \
    ...

此过滤器显然按原样(cat)复制邮件,除非它是一个目标提交,在这种情况下它忽略了stdin消息并打印了预先准备的内容$HOME/new_msg个文件。您当然可以编写任何有效的shell脚本,只需确保保留其他提交的确切原始消息。

您还需要在此填写完整的提交ID(或使用前缀匹配,但它可能更明智地填写完整的ID)。要从部分ID中获取完整ID,最简单的方法是使用git rev-parse

$ git rev-parse b2a9034
b2a9034...                  <-- full 40 char SHA-1 comes out
$ 

您还需要填写... git filter-branch部分的其余部分,这是非常重要的。

由于filter-branch很慢,您可能希望将其限制为少量提交。您可以使用git rev-list参数执行此操作:filter-branch将它们传递给git rev-list,并仅复制这样列出的提交。因此,您可以先测试全部:

$ git rev-list ^b2a9034^ branch1 branch2

这里两个branch es是你想要重写的分支提示的分支名称(可能其中一个是master,基于你上面的文字)。第一个参数^b2a9034^应该导致git rev-list省略b2a9034的父提交和所有早期提交。 (第一个^字符是git rev-list的&#34; not&#34;运算符,第二个是gitrevisions的父跟踪运算符。这可能有点令人困惑,所以替代拼写是^b2a9034~1,它具有完全相同的含义,但不会以两种不同的方式使用^。我不确定它到底有多混乱,虽然。)

(如果您的存储库提交很少,则此限制并不那么重要。)

最后,请注意,正如filter-branch文档所说:

  

该命令只会重写中提到的引用        命令行(例如,如果您通过a..b,则只会重写b。 ...

这个短语对我来说并不明显,直到我正确理解git的内部结构,并从那里开始,filter-branch如何做它的作用。在内部,git只能添加内容,因此git filter-branch只需复制现有提交到新的提交。如果新提交与原始提交完全相同,因此副本与原始提交完全相同,则会获得副本的原始SHA-1,这意味着不会添加任何内容,也不会发生任何更改。但是,如果您更改了任何内容,则会获得一个新的,不同的SHA-1 ID。

这意味着当过滤器分支沿着复制提交运行时,它会复制&#34;在被修改之前的任何提交并再次获得原始SHA-1。然后它会击中您想要更改的第一个(也许只有),并获得一个新的SHA-1。原始提交仍保留在存储库中,但现在有一个带有新SHA-1的新副本。

一旦发生这种情况,所有后续提交过滤器分支副本至少进行了一次更改,即使您的过滤器都没有更改它们。特别是,它们具有(至少一个)其父ID,新ID。第一个新的子提交具有新的父ID,因此它也获得了新的ID;然后它的子承诺必须拿起那个新的ID;等等。

最终结果是提交的新副本为您提供了一个新的commit-ID链,以您过滤的(在这种情况下为两个)分支的新提交ID结尾。然后Git必须将这些ID保存为新的分支提示。当文档说&#34;只有b将被重写&#34;时,这意味着filter-branch将更新refs/heads/b - 包含分支{{1}的ID的文件1}} - 获得复制的branch-tip的最终SHA-1。

因此,通过列出,例如b,您提供了两个&#34;正面参考&#34;,即^b2a9034^ master developrefs/heads/master,这些是两个filter-branch将更新。 refs/heads/develop是&#34;否定参考&#34; (&#34;排除^b2a9034^及更早版本),因此filter-branch在将其传递给b2a9034^之后不做任何事情。

答案 1 :(得分:0)

您可以使用git rerere功能。

您必须使用git config --global rerere.enabled 1启用它,之后,您解决的每个冲突都会被存储以供以后使用,并且在相同的上下文中重新应用解析。但是,如果您现在尝试它将不会应用它,因为它尚未记录您的冲突合并。设置rerere&amp; amp;它将从那里开始。

您可以使用git rerere diff检查存储的分辨率。

有关git rerere的更多信息,请查看此tutorial

谨慎之言

正如J. C. Hamano在他的文章中提及的那样#34; Fun with rerere&#34;

  
      
  • Rerere记得您选择如何解决冲突地区;
  •   
  • Rerere还记得你是如何在冲突地区之外进行调整以适应语义变化的;
  •   
  • Rerere可以重复使用以前的分辨率,即使您合并了两个分支,其内容与您之前解析的分支不同
  •   
     

即使长期使用rerere的人也常常没有注意到最后一点。

因此,如果您在内容过于宽泛的情况下激活rerere,则由于最后一点,您最终可能会出现令人惊讶或令人困惑的合并解决方案。

忘记合并错误

如果您错误地进行了合并,则将其丢弃,然后执行&#34;相同的&#34;再次合并,它将再次不正确。但是,您可以忘记记录的分辨率。来自the documentation

  

git rerere forget <pathspec>

     

这会重新解决rerere为<pathspec>中当前冲突记录的冲突解决方案。

小心在特定路径上使用它;你不想在任何地方吹走所有录制的分辨率。 (forget没有参数deprecated可以帮助您避免这样做,除非您输入git rerere forget .来明确请求它。)

但是,如果你不想这样做,你很容易就会把错误的合并放到你的历史中。