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

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


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

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

Perform an interactive rebase including the target commit (e.g. git rebase -i <commit-to-split>^ branch) and mark it to be edited. When the rebase reaches that commit, use git reset HEAD^ to reset to before the commit, but keep your work tree intact. Incrementally add changes and commit them, making as many commits as desired. add -p can be useful to add only some of the changes in a given file. Use commit -c ORIG_HEAD if you want to re-use the original commit message for a certain commit. If you want to test what you're committing (good idea!) use git stash to hide away the part you haven't committed (or stash --keep-index before you even commit it), test, then git stash pop to return the rest to the work tree. Keep making commits until you get all modifications committed, i.e. have a clean work tree. Run git rebase --continue to proceed applying the commits after the now-split commit.

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

Edit the commit before <commit> git rebase -i <commit>^^ NB: perhaps it will be also needed to edit <commit> as well. Cherry pick <commit> into the index git cherry-pick -n <commit> Interactively reset unneeded changes from the index and reset the working tree git reset -p && git checkout-index -f -a As alternative, just stash unneeded changes interactively: git stash push -p -m "tmp other changes" Make other changes (if any) and create the new commit git commit -m "upd something" . Optionally, repeat the items 2-4 to add more intermediate commits. Continue rebasing git rebase --continue

下面是如何使用Magit。

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

然后按r打开rebase弹出框

m用来修改提交点。

注意@现在在你想要拆分的提交上——这意味着HEAD现在在提交上:

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

现在,点击q进入常规的Magit状态,然后使用常规的unstage u命令取消第一次提交中没有进入的阶段,像往常一样提交c,然后stage并提交第二次提交的内容,完成后:点击r打开rebase弹出框

还有一个r继续,就完成了!Ll现在显示:

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

Use your editor and delete the lines you want to extract from the_file. Close the_file. That's the only edition you need, all the rest is just git commands. Stage that deletion in the index: git add the_file Restore the lines you just deleted back into the file without affecting the index! git show HEAD:./the_file > the_file "SHA1" is the commit you want to extract the lines from: git commit -m 'fixup! SHA1' Create the second, brand new commit with the content to extract restored by step 3: git commit -m 'second and new commit' the_file Don't edit, don't stop/continue - just accept everything: git rebase --autosquash -i SHA1~1

当然,如果要提取的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)中做到这一点:

Create a tag on the current state so that it isn't lost. Now move your actual local branch pointer to the messy commit. Reset (mixed) to the previous one, so that the files from the commit in (2) are kept. You can now split the commit in two or more by staging files that are needed and commiting with the correct message, until there's no unstaged files left. Cherry pick the next commit in line (from the history you've tagged). You do this by right clicking the desired commit and choosing "cherry pick". Goto (4), do until no more unaccounted commits left. Don't worry if as a result you have some commits that would be best squashed into one. You can squash them with an optional interactive rebase in GUI. It's as simple right-clicking the commit before the mess & clicking "Interactive rebase" then dragging commits onto each other to squash (fix the commit message to keep it simple), or move them up or down as desired. Remove the tag created in (1).