我知道如何以git rebase
交互方式(在 Vim 中使用git rebase -i HEAD~2
, ddjp:x
)交换最后两个提交,但我想使用包装脚本以编程方式完成它,因为这是我相对经常做的事情。
更具体地说,我想重写历史
A---B---C---D HEAD
至
A---B---D---C HEAD
以完全脚本化的方式。理想情况下,如果交换失败,它应该允许我以交互方式修复它,或者只是放弃并告诉我手动进行。
我知道如何以git rebase
交互方式(在 Vim 中使用git rebase -i HEAD~2
, ddjp:x
)交换最后两个提交,但我想使用包装脚本以编程方式完成它,因为这是我相对经常做的事情。
更具体地说,我想重写历史
A---B---C---D HEAD
至
A---B---D---C HEAD
以完全脚本化的方式。理想情况下,如果交换失败,它应该允许我以交互方式修复它,或者只是放弃并告诉我手动进行。
这应该这样做:
git tag old
git reset --hard HEAD~2
git cherry-pick old
git cherry-pick old~1
git tag -d old
首先,您将您所在的位置标记为old
,然后返回两次提交,git cherry-pick
以另一个顺序提交,并删除该old
标记。
由于您想经常这样做,我假设您希望减少到单步过程。不过,我会让这有点教育意义,并将其分解。
不要对已经与其他开发人员共享或推送到远程的任何提交执行此类操作。重写共享历史是灾难的根源。
除了那个公共服务公告...
A---B---C---D (master, HEAD, ORIG_HEAD)
git rebase --quiet --onto HEAD~2 HEAD~1 HEAD
这会获取 HEAD 中不在 HEAD~1 中的任何内容,并将其应用于 HEAD~2。运行此 rebase 后,您将拥有此历史记录。请记住,这git-rebase --onto
会使您陷入无头状态。(我已经--quiet
进去了,所以最后的命令不会在你的屏幕上吐出一堵墙)。
A---B---C---D (master, ORIG_HEAD)
\
D' (HEAD)
现在我们需要得到C
应用后D'
,为此我们可以使用git-cherry-pick
. 当 agit-rebase --onto
像上面那样执行时,将保存 rebase 之前的原始提交历史记录,ORIG_HEAD
因为 git 在您执行其他一些活动之前不会更改它。如果你搞砸了一个变基,这很有用,但我们将在这里使用它来挑选。
git cherry-pick ORIG_HEAD~1
A---B---C---D (master, ORIG_HEAD)
\
D'---C' (HEAD)
HEAD 的状态现在完全符合您的要求,只需两个命令。我假设这通常发生在一个分支内,并且您会希望使用新的提交顺序更新该分支。如果我错了,那么就是这样,你就完成了。如果您确实想要更新您所在的分支,有几种方法可以做到这一点。
最简单的手动操作是执行以下操作...
git log -1 ###copy the the SHA1
git checkout master
git reset --hard <SHA1>
然而,重点是自动化,并且有一些方法不太明显,但需要的命令更少。
git update-ref refs/heads/master $(git rev-parse HEAD)
通过使用git-rev-parse
,我只得到 HEAD 的提交,没有别的。将其应用于`git-update-ref,我可以“重置”主分支,而无需先检查它。现在 master 已正确设置,我可以继续检查 master(更新引用的目的是减少最终别名/bash 脚本所涉及的步骤数)。
git checkout --quiet master
同样,我通过--quiet
以减少每个命令后在屏幕上转储的文本量。
如果您想将其作为 bash 脚本执行,您可以进一步自动化整个过程,并使其在您想要的任何分支上动态工作,而不仅仅是 master。
#!/bin/bash
branch=$(git name-rev --name-only HEAD)
git rebase --onto HEAD~2 HEAD~1 HEAD
git cherry-pick ORIG_HEAD~1
git update-ref refs/heads/$branch $(git rev-parse HEAD)
git checkout --quiet $branch
也就是说,这很容易转储到 git 别名中,而无需实际为 bash 脚本创建文件。创建文件和设置别名无论如何都不难,但这是另一回事,很多人不理解。这是一个简单的 git 别名,运行此命令一次..
git config --global alias.flip-last "!branch=$(git name-rev --name-only HEAD); git rebase --quiet --onto HEAD~2 HEAD~1 HEAD; git cherry-pick ORIG_HEAD~1; git update-ref refs/heads/$branch $(git rev-parse HEAD); git checkout --quiet $branch"
现在,只要您想翻转最后两个提交,只需使用...
git flip-last
这是一个有趣的小脚本,我把它扔到了github gist上。随意分叉,进行更改,加注星标,等等。
作为参考,这是我最终使用的脚本:
. "$(git --exec-path)/git-sh-setup"
require_clean_work_tree swap2
if [ -e $GIT_DIR/sequencer ]; then
die "Cherry-pick already in progress; swap aborted"
fi
HEAD=`git rev-list --max-count=1 HEAD`
git reset --hard HEAD~2
if git cherry-pick $HEAD $HEAD^; then
echo "Successfully swapped top two commits."
else
git cherry-pick --abort
git reset --hard $HEAD
die "Failed to swap top two commits."
fi