关于 Git 重写提交历史的一些笔记

写在前面
今天和小伙伴们分享一些 Git 重写提交历史的笔记
提交代码遇到相关问题,这里整理笔记
博文为《Pro Git》读书笔记整理
感谢开源这本书的作者和把这本书翻译为中文的大佬们
理解不足小伙伴帮忙指正,书很不错,感兴趣小伙伴可以去拜读下
傍晚时分,你坐在屋檐下,看着天慢慢地黑下去,心里寂寞而凄凉,感到自己的生命被剥夺了。当时我是个年轻人,但我害怕这样生活下去,衰老下去。在我看来,这是比死亡更可怕的事。--------王小波

公司要求在代码提交的时候,需要提交信息中包含任务单号,我在一次需求代码提交中,一个分支只有最后的几次提交信息中包含了任务单号,在最初的提交中没有包含任务单号,所以一直 push 不上去,提示没有包含任务单信息。所以需要修改之前的提交信息。

在 Git 中 这样的操作叫做 重写历史(本质上是些变基操作)

许多时候,在使用Git时,可能想要修订提交历史。Git可以通过git stash来决定不与某些内容同时提交,也可以重写已经发生的提交就像它们以另一种方式发生的一样。比如 改变提交的顺序,改变提交中的信息或修改文件,将提交压缩或是拆分,或完全地移除提交,当然这些操作的前提是 在将你的工作成果与他人共享之前完成

修改最后一次提交
修改你最近一次提交可能是所有修改历史提交的操作中最常见的一个。对于你的最近一次提交,你往往想做两件事情:简单地修改提交信息,或者通过添加、移除或修改文件来更改提交实际的内容。

如果,你只是想修改最近一次提交的提交信息,那么很简单:
git commit --amend
上面这条命令会将最后一次的提交信息载入到编辑器(Vi/Vim)中供你修改。当保存并关闭编辑器后,编辑器会将更新后的提交信息写入新提交中,它会成为新的最后一次提交。

需要注意的是,这里编辑会使用 Vim  编辑器,修改内容作为一个变基脚本存在,所以它无法识别你带 # 的提交信息,他会当作 Vim 中的注释存在。

如果你想要修改最后一次提交的实际内容,那么流程很相似:
首先作出你想要补上的修改,暂存 stash 它们,然后用git commit--amend 以新的改进后的提交来替换掉旧有的最后一次提交,使用这个技巧的时候需要小心,因为修正会改变提交的SHA-1校验和。它类似于一个小的变基一一如果已经推送了最后一次提交就不要修正它。

另一方面,如果你的修补是琐碎的(如修改了一个笔误或添加了一个忘记暂存的文件),那么之前的提交信息不必修改,你只需作出更改,暂存它们,然后通过以下命令避免不必要的编辑器环节即可:

$ git commit --amend --no-edit
修改多个提交信息
为了修改在提交历史中较远的提交,必须使用更复杂的工具。Git没有一个改变历史工具,但是可以使用变基工具来变基一系列提交,基于它们原来的 HEAD 而不是将其移动到另一个新的上面。通过 交互式变基工具,可以在任何想要修改的提交后停止,然后修改信息、添加文件或做任何想做的事情。

可以通过给 git rebase 增加 -i 选项来交互式地运行变基。必须指定想要重写多久远的历史,这可以通过告诉命令将要变基到的提交来做 到。

例如,如果想要修改  最近三次提交信息,或者那组提交中的任意一个提交信息,将想要修改的最近一次提交的父提交 作为参数传递给 git rebase -i 命令,即 HEAD~2^ 或 HEAD~3。

$git rebase -i HEAD~3
pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
# Rebase 710f0f8..a5f4a0d onto 710f0f8
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase
--continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
如果你希望指定 head 指针位置,那么你可以使用下面的命令

$git rebase -i commId
你需要修改脚本来让它停留在你想修改的变更上。要达到这个目的,你只要将你想修改的每一次提交前面的 ‘pick’ 改为 ‘edit’。例如,只想修改第三次提交信息,可以像下面这样修改文件:

edit f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
当保存并退出编辑器时,Git 将你带回到列表中的最后一次提交,把你送回命令行并提示以下信息:

$ git rebase -i HEAD~3
Stopped at f7f3f6d... changed my name a bit
You can amend the commit now, with
  git commit --amend
Once youre satisfied with your changes, run
  git rebase --continue
这些指令准确地告诉你该做什么。输入






$ git commit --amend
修改提交信息,然后退出编辑器。然后,运行

$ git rebase --continue
这个命令将会自动地应用另外两个提交,然后就完成了。如果需要将不止一处的 pick 改为 edit,需要在每一个

修改为 edit 的提交上重复这些步骤。每一次,Git 将会停止,让你修正提交,然后继续直到完成。

重新排序提交或完全移除提交
也可以使用交互式变基来重新排序或完全移除提交。

pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
如果想要移除 added cat-file 提交然后修改另外两个提,交引入的顺序,可以将变基脚本改成这样:

pick 310154e updated README formatting and added blame
pick f7f3f6d changed my name a bit
当保存并退出编辑器时,Git 将你的分支带回这些提交的父提交,应用 310154e 然后应用 f7f3f6d,最后停止。修改了那些提交的顺序并完全地移除了 “added cat-file” 提交。

合并提交
通过交互式变基工具,也可以将一连串提交压缩成一个单独的提交。在变基信息中脚本给出了有用的指令:

# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase
--continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
#
如果,指定 “squash” 而不是 “pick” 或 “edit”,Git 将应用两者的修改并合并提交信息在一起。所以,如果想要这三次提交变为一个提交,可以这样修改脚本:

pick f7f3f6d changed my name a bit
squash 310154e updated README formatting and added blame
squash a5f4a0d added cat-file
当保存并退出编辑器时,Git 应用所有的三次修改然后将你放到编辑器中来合并三次提交信息:

# This is a combination of 3 commits.
# The first commit's message is:
changed my name a bit
# This is the 2nd commit message:
updated README formatting and added blame
# This is the 3rd commit message:
added cat-file
当你保存之后,你就拥有了一个包含前三次提交的全部变更的提交。

拆分提交
拆分一个提交会撤消这个提交,然后多次地部分地暂存与提交直到完成你所需次数的提交。

例如,假设想要拆分三次提交的中间那次提交。想要将它拆分为两次提交:第一个“updated README formatting”,第二个 “added blame” 来代替原来的“updated README formatting and added blame”。可以通过修改 rebase -i 的脚本来做到这点,将要拆分的提交的指令修改为“edit”:

pick f7f3f6d changed my name a bit
edit 310154e updated README formatting and added blame
pick a5f4a8d added cat-file
然后,当脚本带你进入到命令行时,重置那个提交,拿到被重置的修改,从中创建几次提交。当保存并退出编辑器时,Git带你到列表中第一个提交的父提交,应用第一个提交(f7f3f6d),应用第二个提交( 310154e),然后让你进入命令行。那里,可以通过 git reset HEAD 做一次针对那个提交的混合重置,实际上将会撤消那次提交并将修改的文件取消暂存。现在可以暂存并提交文件直到有几个提交,然后当完成时运行 git rebase--continue:

$ git reset HEAD^
$ git add README
$ git commit -m 'updated README formatting'
$ git add lib/simplegit.rb
$ git commit -m 'added blame'
$ git rebase --continue
Git 在脚本中应用最后一次提交(a5f4a0d),历史记录看起来像这样:

$ git log -4 --pretty=format:"%h %s"
1c002dd added cat-file
9b29157 added blame
35cfb2b updated README formatting
f3cc40e changed my name a bit

核武器级选项:filter-branch
从每一个提交中移除一个文件, filter-branch 是一个可能会用来擦洗整个提交历史的工具。为了从整个提交历史中移除一个叫做passwords.txt的文件,可以使用 --tree-filter 选项给  filter-branch

$ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD
Rewrite 6b9b3cf04e7c5686a9cb838c3f36a8cb6a0fc2bd (21/21)
Ref 'refs/heads/master' was rewritten
博文参考
《Pro Git》

作者:山河已无恙


欢迎关注微信公众号 :山河已无恙