如何在过去的任意两个提交之间插入一个提交?

242

假设我在我的本地分支上有以下提交历史:

A -- B -- C

我该如何在AB之间插入一个新的提交?


我有一个非常类似的问题,关于如何在过去插入一个新版本而不是提交。链接为:https://dev59.com/2KXja4cB1Zd3GeqPXdq3 - Pavel P
要创建一个新的提交节点,只需在AB之间的可视图形上双击-哦,等等,不对,现在是2023年,我们还没有那个功能。 - Nils Lindemann
7个回答

333

这比OP的回答还要简单。

  1. git rebase -i <任意早期提交>。这将在您配置的文本编辑器中显示提交列表。
  2. 找到您想要在其后插入的提交(假设是a1b2c3d)。在您的编辑器中,对于该行,请将pick更改为edit
  3. 通过关闭文本编辑器(保存更改)来开始变基。这将使您保持在带有您之前选择的提交(a1b2c3d)的命令提示符下,就好像刚刚提交了该提交。
  4. 进行更改并git commit(与大多数edit不同,不修改原始提交)。这会创建一个新提交,在您选择的提交之后。
  5. git rebase --continue。这将重新应用连续的提交,将您的新提交插入正确的位置。

请注意,这将重写历史记录,并且会破坏尝试拉取提交的其他人。


3
这个操作在我进行变基的目标提交之后(也是最后一个提交)的下一个提交之后添加了新的提交,而不是直接添加到我要变基的目标提交之后。结果就像我只是在最后做了一个新的提交一样。我的提交历史变成了 A -- B -- C -- D,而不是期望的 A -- D -- B -- C - XedinUnknown
3
你没有正确使用Rebase。 - SLaks
4
要使用此答案,首先您需要确定新提交。在某些情况下,这可能只是在顶部;我们称之为 D,因此我们有 A - B - C - D,目标是 A - D - B - C。运行命令 git rebase -i HEAD~4,并在编辑器中重新排序提交以达到 ADBC 的顺序,保存并退出。 - Kaz
11
现在,D 可能是任何一个提交。假设我们有 A - B - C 这样的一个分支,并且有一个提交 D,它甚至不在这个分支中。我们知道它的 SHA 值。我们可以运行 git rebase -i HEAD~3 命令。在 ABpick 行之间插入一行新的 pick 命令,命令内容为 pick SHA,其中 SHA 是所需的 D 提交的哈希值。它不必是完整的哈希值,只需要是缩短后的哈希值即可。git rebase -i 只会挑选在缓存区中由 pick 命令列出的提交记录;它们不必是原来列表中所列出的提交记录。 - Kaz
15
更简单的方法是,在两个提交(commit)之间在编辑器中使用break关键字(或者在第一行使用它,以在指定的提交(commit)之前插入一个提交)。 - SimonT
显示剩余10条评论

57
原来很简单,答案在这里找到这里。假设你在一个分支branch上。按照以下步骤进行操作:
  • 从您想要插入新提交之后的提交(在本例中为提交A)创建一个临时分支:

     git checkout -b temp A
    
  • 进行更改并提交它们,创建一个提交,我们称之为N

     git commit -a -m "Message"
    
(或者先执行git add,然后执行git commit
将你想要在新提交之后的提交(在这种情况下是提交B和C)重新基于新提交: git rebase temp branch

(如果有的话,可能需要使用-p来保留合并,感谢一个已经不存在的评论 by ciekawy

删除临时分支: ``` git branch -d temp ```
此后,历史如下所示:
A -- N -- B -- C

当进行变基操作时,可能会出现一些冲突,这是很正常的。
如果你的分支不仅仅是本地的,这将引入重写历史的操作,可能会导致严重的问题。

5
SLaks的答案我不太懂,但这个方法对我有用。获取到我需要的提交历史后,我必须使用 git push --force 命令来更改远程代码库。 - escapecharacter
1
使用rebase命令时,加上-Xtheirs选项可以自动正确解决冲突,因此命令为git rebase temp branch -Xtheirs。这对于在脚本中注入非常有帮助! - David C
1
对于像我这样的新手,我想补充一点,在执行 git rebase temp branch 之后,但在执行 git branch -d temp 之前,你只需要解决和暂存合并冲突,并发出 git rebase --continue 命令即可,无需提交任何内容等。 - Pugsley
对于较新版本的Git,你可能需要使用git rebase --rebase-merges temp branch,而不是git rebase -p temp branch - undefined

44

更简单的解决方案:

  1. 在结尾创建你的新提交,D。现在你有:

    A -- B -- C -- D
    
  2. 然后运行:

    $ git rebase -i hash-of-A
    
  3. Git会打开你的编辑器,界面看起来是这样的:

  4. pick 8668d21 B
    pick 650f1fc C
    pick 74096b9 D
    
  5. 将 D 移动到顶部,然后保存并退出

  6. pick 74096b9 D
    pick 8668d21 B
    pick 650f1fc C
    
  7. 现在你将拥有:

  8. A -- D -- B -- C
    

13
好的想法,但是如果你打算对A进行这些更改,那么引入D到C可能会很困难。 - BartoszKP
2
我有一个情况,我想要将3个提交合并在一起,并且中间有一个不相关的提交。这非常好,可以轻松地将该提交向前或向后移动到提交行中。 - unflores
@BartoszKP,我不太明白。你能详细解释一下这个情况吗?我想你的意思是如果D和C都编辑fileX,并且D对fileX的编辑实质上依赖于C对fileX的编辑,那么将很难将D向上移动,但感觉这种情况会排除最初的问题...这里的另一个答案是否可以避免你所提到的问题? - ruffin
1
@ruffin 不,我的意思是在状态 A 下更容易进行 D 的编辑。我并不是说这会使答案无效或排除任何东西 - 我说“可能很难”,所以只涉及(可能很少的)一部分情况 :) - BartoszKP

44

这里有一种策略可以避免在其他答案中所看到的rebase时进行“编辑hack”。

通过使用git rebase -i,您可以获得自上次提交以来的提交列表。只需在文件顶部添加一个“break”,就会导致rebase在该点中断。

break
pick <B's hash> <B's commit message>
pick <C's hash> <C's commit message>

一旦启动,git rebase 将停在 "break" 点。 现在您可以正常编辑文件并创建提交。 然后,您可以使用 git rebase --continue 继续重塑。这可能会导致必须解决的冲突。 如果您迷失了方向,请不要忘记您始终可以使用 git rebase --abort 中止它。

此策略可概括为在任何位置插入提交,只需将 "break" 放在要插入提交的位置即可。

重写历史后,请不要忘记使用 git push -f。其他人获取您的分支时通常会出现警告。


1
没错。我的回答也没有使用rebase的“编辑”功能。然而,这仍然是另一种有效的方法-谢谢! :-) - BartoszKP
6
这绝对是最佳解决方案! - Theodore R. Smith
1
感谢使用 rebase -i。这使得许多挑选操作更容易跟踪。 - rookie1024
1
这应该放在顶部...不需要对此进行编辑。 - polynomial_donut

15

已经有很多好的答案了。我只想添加一个“不要变基”的解决方案,仅需4个简单步骤。


概要

git checkout A
# <<< modify your files at this point
git commit -am "Message for commit D"
git cherry-pick A..C
git branch -f master HEAD

解释

(注意:这种解决方案的一个优点是,直到最后一步时你非常确定结果没问题之前都不需要修改你的分支,因此你有一个非常方便的"预确认"步骤,可以进行A/B测试。)


初始状态(我假设你的分支名称为master

A -- B -- C <<< master <<< HEAD

1) 首先将 HEAD 指向正确的位置

git checkout A

     B -- C <<< master
    /
   A  <<< detached HEAD

(可选)在这里,我们可以通过使用git checkout -b temp A创建一个临时分支,而不是分离HEAD,在过程结束时需要删除该分支。两种变体都有效,请按照您的喜好操作,因为其他所有内容保持不变)


2) 创建要插入的新提交(D)

# at this point, make the changes you wanted to insert between A and B, then

git commit -am "Message for commit D"

     B -- C <<< master
    /
   A -- D <<< detached HEAD (or <<< temp <<< HEAD)

3) 然后带上最后缺失的提交B和C的副本(即使在这两个提交之间有更多的提交,命令也是相同的,因为它是选择一系列提交)

git cherry-pick A..C

# (if any, resolve potential conflicts between D and these last commits)

     B -- C <<< master
    /
   A -- D -- B' -- C' <<< detached HEAD (or <<< temp <<< HEAD)

(如果需要,可以在这里进行舒适的AB测试)

现在是检查您的代码并测试任何需要测试的东西的时刻,您还可以通过交替检查CC'来比较/差异/检查您所拥有的您将要获得的操作后的内容。


4)根据CC'之间的测试结果,它可能是正常的(OK),也可能是不正常的(KO)。

(两者选一)4-OK)最后,将master的引用移动

git branch -f master HEAD

     B -- C <<< (B and C are candidates for garbage collection)
    /
   A -- D -- B' -- C' <<< master

(OR) 4-KO) 只需保持 master 不变

如果您创建了一个临时分支,请使用git branch -d <name>命令将其删除,但是如果您选择了分离的 HEAD 路线,则此时无需执行任何操作,新提交将在您使用 git checkout master 重新附加 HEAD 后立即符合垃圾回收的条件。

在这两种情况下(OK 或 KO),此时只需再次检出 master 以重新附加 HEAD


很好的回答。如果存在 A -- B -- C -- E -- F,并且 BE 是合并提交,会发生什么? - Umair Khan
1
我的答案(和大多数人一样)是针对非合并提交的。如果涉及到合并,情况就更复杂了。根据上下文,最好选择挑选这些合并提交所带来的(非合并)提交,而不是挑选合并提交,这可能会有些棘手。如果你真的考虑挑选合并提交,请查看-m选项。 - Romain Valeri

14

假设提交历史是preA -- A -- B -- C,如果你想在AB之间插入一个提交,步骤如下:

  1. git rebase -i hash-of-preA

  2. Git将打开你的编辑器。内容可能如下所示:

  3. pick 8668d21 A
    pick 650f1fc B
    pick 74096b9 C
    

    将第一个 pick 改为 edit

    edit 8668d21 A
    pick 650f1fc B
    pick 74096b9 C
    

    保存并退出。

  4. 修改你的代码,然后 git add . && git commit -m "I"

  5. git rebase --continue

现在你的Git提交历史是preA -- A -- I -- B -- C


如果遇到冲突,Git 会停在这个提交处。你可以使用 git diff 找到冲突标记并解决它们。解决所有冲突后,你需要使用 git add <filename> 告诉 Git 冲突已经解决,然后重新运行 git rebase --continue

如果想撤销变基,则使用 git rebase --abort


1

假设您要插入的提交由D标识:

# Temporarily append the commit you want to insert to the end 
git cherry-pick D
# Results in D -- A -- B -- C

# Start interactive rebase
git rebase -i B^
# Let's imagine that this is what the rebase prompt looks like:
# pick B Third commit
# pick A Second commit
# pick D First commit
# Then reorder the commits:
# pick B Third commit
# pick D First commit
# pick A Second commit
# Save and exit
# After completing the rebase you will find
# A -- D -- B -- C

你的回答与Matthew的回答非常相似,尽管解释较少。我猜这就是为什么有人给你点了踩的原因? - Romain Valeri

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