如何拆分一个隐藏在历史记录中的Git提交?

我把我的历史搞砸了,想对它做些修改。问题是,我有一个包含两个不相关更改的提交,并且这个提交被本地(非推送的)历史记录中的一些其他更改包围。

我想在推出这个提交之前拆分它,但我看到的大多数指南都与拆分最近的提交或未提交的本地更改有关。对一个在历史中隐藏了一点的提交执行此操作,而不必从那时起“重新执行”我的提交,这可行吗?

51147 次浏览

如果你还没有推送,只需使用git rebase。更好的方法是使用git rebase -i来交互式地移动提交。您可以将有问题的提交移到前面,然后根据需要将其拆分并将补丁移到后面(如果需要的话)。

这里有一个关于拆分提交在rebase manpage中的指南。简要总结如下:

  • 执行包括目标提交(例如git rebase -i <commit-to-split>^ branch)在内的交互式rebase,并将其标记为可编辑。

  • 当rebase到达提交时,使用git reset HEAD^重置到提交之前,但保持你的工作树完整。

  • 增量地添加更改并提交它们,根据需要进行尽可能多的提交。add -p可以用于仅在给定文件中添加部分更改。使用commit -c ORIG_HEAD如果你想在某个提交中重用原始的提交消息。

  • 如果你想测试你提交的内容(好主意!),使用git stash来隐藏你还没有提交的部分(或者在你提交之前使用stash --keep-index), test,然后git stash pop将其余部分返回到工作树。继续提交,直到你提交了所有的修改,即有一个干净的工作树。

  • 运行git rebase --continue继续在现在拆分的提交之后应用提交。

要分割一个提交<commit>并添加在此之前重新提交,并保存<commit>的作者日期,-步骤如下:

  1. 编辑提交之前 <commit>

    git rebase -i <commit>^^
    

    注:也许还需要编辑<commit>

  2. 樱桃选择<commit>到索引

    git cherry-pick -n <commit>
    
  3. Interactively reset unneeded changes from the index and reset the working tree

    git reset -p && git checkout-index -f -a
    

    作为替代,只需交互式地保存不需要的更改:git stash push -p -m "tmp other changes"

  4. 进行其他更改(如果有的话)并创建新的提交

    git commit -m "upd something" .
    

    可选地,重复第2-4项以添加更多的中间提交

  5. < p >继续重新定价

    git rebase --continue
    

下面是如何使用Magit

比如提交ed417ae是你想要修改的;它包含两个不相关的更改,并隐藏在一个或多个提交之下。点击ll来显示日志,并导航到ed417ae:

initial log

然后点击r打开rebase弹出框

rebase popup

m来修改提交点。

注意在你想要拆分的提交中@现在是怎样的——这意味着HEAD现在在提交中:

modifying a commit

我们想要将HEAD移动到父节点,因此导航到父节点(47e18b3)并点击x (magit-reset-quickly,如果你使用evil-magit,则绑定到o),然后输入“是的,我的意思是在点提交”。您的日志现在应该如下所示:

log after resetting

现在,点击q进入常规的Magit状态,然后使用常规的unstage u命令取消第一次提交中没有出现的内容,照常提交c其余部分,然后stage和commit第二次提交中出现的内容,完成后:点击r打开rebase弹出窗口

rebase popup

和另一个r继续,你就完成了!ll现在显示:

all done log

如果你只想从一个文件中提取内容,还有一个更快的版本。它更快,因为交互式rebase实际上不再是交互式的(当然,如果您想从上次提交中提取,那么根本不需要rebase,那么它甚至更快)

  1. 使用编辑器删除你想从the_file中提取的行。the_file关闭。这是你唯一需要的版本,剩下的都是git命令。
  2. 删除索引:

    git  add  the_file
    
  3. Restore the lines you just deleted back into the file without affecting the index!

    git show HEAD:./the_file > the_file
    
  4. "SHA1" is the commit you want to extract the lines from:

    git commit -m 'fixup! SHA1'
    
  5. Create the second, brand new commit with the content to extract restored by step 3:

    git commit -m 'second and new commit' the_file
    
  6. Don't edit, don't stop/continue - just accept everything:

    git rebase --autosquash -i SHA1~1
    

Of course even faster when the commit to extract from is the last commit:

4. git commit -C HEAD --amend
5. git commit -m 'second and new commit' thefile
6. no rebase, nothing

如果你使用magit,那么步骤4、5和6是一个单独的动作:提交,立即修复

在某些情况下,手动纠正历史也可以。

我更喜欢使用我的git GUI(而不是命令行),我有问题的提交只提交了3次,我还没有推送任何文件,下面的文件也不完全整洁,所以我选择通过精选完全重建所有文件,这比通过命令行使用交互式rebase编辑更快,但方法相似。

下面是我如何在我最喜欢的git GUI(我个人使用SourceTree)中做到这一点:

  1. 在当前状态上创建标签,这样它就不会丢失。
  2. 现在移动你实际的本地分支的指针到凌乱的提交。
  3. 重置(混合)到前一个,以便(2)中提交的文件被保存。
  4. 你现在可以通过暂存需要的文件并提交正确的消息来将提交一分为二或更多,直到没有未暂存的文件。
  5. 做出最佳选择一行中的下一个提交(来自你标记的历史记录)。你可以通过右击想要提交的文件并选择“cherry pick”来实现。Goto(4),直到没有更多未解释的提交。
  6. 如果结果是将一些提交压缩成一个最好的提交,也不要担心。你可以在GUI中使用可选的交互式变基来压缩它们。这很简单,在混乱之前右键单击commit &点击“Interactive rebase”;然后将提交拖放到彼此之间以压缩(修复提交消息以使其简单),或者根据需要上下移动它们。
  7. 移除标签在(1)中创建。