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

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

A -- B -- C

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

72191 次浏览

其实很简单,答案是在这里。假设你在一个分支branch上。执行以下步骤:

  • 在你想要插入新的提交(在本例中是commit A)之后,从提交创建一个临时分支:

    git checkout -b temp A
    
  • perform the changes and commit them, creating a the commit, let's call it N:

    git commit -a -m "Message"
    

    (或git add后面跟着git commit)

  • 在新提交(在这种情况下,提交BC)后,将你想要的提交重设为新提交:

    git rebase temp branch
    

(possibly you need to use -p to preserve merges, if there were any - thanks to a no longer existing comment by ciekawy)

  • delete the temporary branch:

    git branch -d temp
    

After this, the history looks as follows:

A -- N -- B -- C

当然,在改基过程中可能会出现一些冲突。

如果您的分支不是本地分支,这将引入重写历史,因此可能导致严重的问题。

这比OP的回答还要简单。

  1. git rebase -i <any earlier commit>。这将在配置的文本编辑器中显示提交列表。
  2. 找到你想在后面插入的提交(让我们假设它是a1b2c3d)。在编辑器中,对于这一行,将pick更改为edit
  3. 通过关闭文本编辑器(保存更改)开始重基。这将使您在命令提示符前选择提交(a1b2c3d) 就像刚刚犯下罪行一样
  4. 进行您的更改和git commit (修改,不像大多数__abc1)。这将创建一个新的提交,就是你选择的那个。
  5. git rebase --continue。这将重播连续的提交,将新提交插入到正确的位置。

当心,这将改写历史,并破坏任何试图拉扯的人。

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

  1. git rebase -i hash-of-preA

  2. Git会打开你的编辑器。内容可能是这样的:

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

    将第一个pick更改为edit:

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

    .保存并退出
  3. 修改你的代码,然后git add . && git commit -m "I"

  4. git rebase --continue

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


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

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

更简单的解决方案:

  1. 在最后创建你的新提交,d,现在你有:

    A -- B -- C -- D
    
  2. Then run:

    $ git rebase -i hash-of-A
    
  3. Git will open your editor and it will look like this:

    pick 8668d21 B
    pick 650f1fc C
    pick 74096b9 D
    
  4. Just move D to the top like this, then save and quit

    pick 74096b9 D
    pick 8668d21 B
    pick 650f1fc C
    
  5. Now you will have:

    A -- D -- B -- C
    

这里有一个策略,可以避免在我读过的其他答案中看到的重基过程中进行“编辑黑客”。

通过使用git rebase -i,您可以获得自该提交以来的提交列表。 只需在文件顶部添加一个“break”,这将导致rebase在此处中断
break
pick <B's hash> <B's commit message>
pick <C's hash> <C's commit message>
一旦启动,git rebase现在将在“中断”点停止。 现在可以正常编辑文件并创建提交了。 然后,你可以继续使用git rebase --continue进行改基。这可能会导致你必须解决的冲突。如果你迷路了,不要忘记你总是可以使用git rebase --abort中止

这个策略可以推广到任何地方插入一个提交,只要把“break”放在你想要插入一个提交的地方。

在重写历史之后,不要忘记git push -f。关于其他人获取你的分支的通常警告适用。

这里已经有很多好答案了。我只是想加一句“不退让”;解决方法,只需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

解释

(注意:这个解决方案的一个优点是,直到最后一步,当你100%确定你对最终结果满意时,你才会接触你的分支,所以你有一个非常方便的“pre-confirmation"步骤允许AB测试。)


初始状态(我已经假设你的分支名称为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的ref

git branch -f master HEAD


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

(或)只要保持master不变

如果你创建了一个临时分支,只需使用git branch -d <name>删除它,但如果你选择分离HEAD路径,此时根本不需要任何操作,在你用git checkout master重新连接HEAD后,新的提交将符合垃圾收集的条件

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

假设要插入的提交由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