[转]Git - 重写历史

转自http://git-scm.com/book/zh/Git-%E5%B7%A5%E5%85%B7-%E9%87%8D%E5%86%99%E5%8E%86%E5%8F%B2git

   重写历史服务器

不少时候,在 Git 上工做的时候,你也许会因为某种缘由想要修订你的提交历史。Git 的一个卓越之处就是它容许你在最后可能的时刻再做决定。你能够在你即将提交暂存区时决定什么文件纳入哪一次提交,你可使用 stash 命令来决定你暂时搁置的工做,你能够重写已经发生的提交以使它们看起来是另一种样子。这个包括改变提交的次序、改变说明或者修改提交中包含的文件,将提交归并、拆分或者彻底删除——这一切在你还没有开始将你的工做和别人共享前都是能够的。编辑器

在这一节中,你会学到如何完成这些颇有用的任务以使你的提交历史在你将其共享给别人以前变成你想要的样子。工具

改变最近一次提交学习

改变最近一次提交也许是最多见的重写历史的行为。对于你的最近一次提交,你常常想作两件基本事情:改变提交说明,或者改变你刚刚经过增长,改变,删除而记录的快照。测试

若是你只想修改最近一次提交说明,这很是简单:spa

$ git commit --amend

这会把你带入文本编辑器,里面包含了你最近一次提交说明,供你修改。当你保存并退出编辑器,这个编辑器会写入一个新的提交,里面包含了那个说明,而且让它成为你的新的最近一次提交。命令行

若是你完成提交后又想修改被提交的快照,增长或者修改其中的文件,可能由于你最初提交时,忘了添加一个新建的文件,这个过程基本上同样。你经过修改文件而后对其运行git add或对一个已被记录的文件运行git rm,随后的git commit --amend会获取你当前的暂存区并将它做为新提交对应的快照。指针

使用这项技术的时候你必须当心,由于修正会改变提交的SHA-1值。这个很像是一次很是小的rebase——不要在你最近一次提交被推送后还去修正它。code

修改多个提交说明

要修改历史中更早的提交,你必须采用更复杂的工具。Git没有一个修改历史的工具,可是你可使用rebase工具来衍合一系列的提交到它们原来所在的HEAD上而不是移到新的上。依靠这个交互式的rebase工具,你就能够停留在每一次提交后,若是你想修改或改变说明、增长文件或任何其余事情。你能够经过给git rebase增长-i选项来以交互方式地运行rebase。你必须经过告诉命令衍合到哪次提交,来指明你须要重写的提交的回溯深度。

例如,你想修改最近三次的提交说明,或者其中任意一次,你必须给git rebase -i提供一个参数,指明你想要修改的提交的父提交,例如HEAD~2或者HEAD~3。可能记住~3更加容易,由于你想修改最近三次提交;可是请记住你事实上所指的是四次提交以前,即你想修改的提交的父提交。

$ git rebase -i HEAD~3

再次提醒这是一个衍合命令——HEAD~3..HEAD范围内的每一次提交都会被重写,不管你是否修改说明。不要涵盖你已经推送到中心服务器的提交——这么作会使其余开发者产生混乱,由于你提供了一样变动的不一样版本。

运行这个命令会为你的文本编辑器提供一个提交列表,看起来像下面这样

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 = use commit
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

很重要的一点是你得注意这些提交的顺序与你一般经过log命令看到的是相反的。若是你运行log,你会看到下面这样的结果:

$ git log --pretty=format:"%h %s" HEAD~3..HEAD
a5f4a0d added cat-file
310154e updated README formatting and added blame
f7f3f6d changed my name a bit

请注意这里的倒序。交互式的rebase给了你一个即将运行的脚本。它会从你在命令行上指明的提交开始(HEAD~3)而后自上至下重播每次提交里引入的变动。它将最先的列在顶上而不是最近的,由于这是第一个须要重播的。

你须要修改这个脚原本让它停留在你想修改的变动上。要作到这一点,你只要将你想修改的每一次提交前面的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 7482e0d... updated the gemspec to hopefully work better
You can amend the commit now, with

       git commit --amend

Once you’re satisfied with your changes, run

       git rebase --continue

这些指示很明确地告诉了你该干什么。输入

$ git commit --amend

修改提交说明,退出编辑器。而后,运行

$ git rebase --continue

这个命令会自动应用其余两次提交,你就完成任务了。若是你将更多行的 pick 改成 edit ,你就能对你想修改的提交重复这些步骤。Git每次都会停下,让你修正提交,完成后继续运行。

重排提交

你也可使用交互式的衍合来完全重排或删除提交。若是你想删除"added cat-file"这个提交而且修改其余两次提交引入的顺序,你将rebase脚本从这个

pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file

改成这个:

pick 310154e updated README formatting and added blame
pick f7f3f6d changed my name a bit

当你保存并退出编辑器,Git 将分支倒回至这些提交的父提交,应用310154e,而后f7f3f6d,接着中止。你有效地修改了这些提交的顺序而且完全删除了"added cat-file"此次提交。

压制(Squashing)提交

交互式的衍合工具还能够将一系列提交压制为单一提交。脚本在 rebase 的信息里放了一些有用的指示:

#
# Commands:
#  p, pick = use commit
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

若是不用"pick"或者"edit",而是指定"squash",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 and added blame"拆分红两次提交:第一次为"updated README formatting",第二次为"added blame"。你能够在rebase -i脚本中修改你想拆分的提交前的指令为"edit":

pick f7f3f6d changed my name a bit
edit 310154e updated README formatting and added blame
pick a5f4a0d 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

再次提醒,这会修改你列表中的提交的 SHA 值,因此请确保这个列表里不包含你已经推送到共享仓库的提交。

核弹级选项: filter-branch

若是你想用脚本的方式修改大量的提交,还有一个重写历史的选项能够用——例如,全局性地修改电子邮件地址或者将一个文件从全部提交中删除。这个命令是filter-branch,这个会大面积地修改你的历史,因此你颇有可能不应去用它,除非你的项目还没有公开,没有其余人在你准备修改的提交的基础上工做。尽管如此,这个能够很是有用。你会学习一些常见用法,借此对它的能力有所认识。

从全部提交中删除一个文件

这个常常发生。有些人不经思考使用git add .,意外地提交了一个巨大的二进制文件,你想将它从全部地方删除。也许你不当心提交了一个包含密码的文件,而你想让你的项目开源。filter-branch大概会是你用来清理整个历史的工具。要从整个历史中删除一个名叫password.txt的文件,你能够在filter-branch上使用--tree-filter选项:

$ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD
Rewrite 6b9b3cf04e7c5686a9cb838c3f36a8cb6a0fc2bd (21/21)
Ref 'refs/heads/master' was rewritten

--tree-filter选项会在每次检出项目时先执行指定的命令而后从新提交结果。在这个例子中,你会在全部快照中删除一个名叫 password.txt 的文件,不管它是否存在。若是你想删除全部不当心提交上去的编辑器备份文件,你能够运行相似git filter-branch --tree-filter 'rm -f *~' HEAD的命令。

你能够观察到 Git 重写目录树而且提交,而后将分支指针移到末尾。一个比较好的办法是在一个测试分支上作这些而后在你肯定产物真的是你所要的以后,再 hard-reset 你的主分支。要在你全部的分支上运行filter-branch的话,你能够传递一个--all给命令。

将一个子目录设置为新的根目录

假设你完成了从另一个代码控制系统的导入工做,获得了一些没有意义的子目录(trunk, tags等等)。若是你想让trunk子目录成为每一次提交的新的项目根目录,filter-branch也能够帮你作到:

$ git filter-branch --subdirectory-filter trunk HEAD
Rewrite 856f0bf61e41a27326cdae8f09fe708d679f596f (12/12)
Ref 'refs/heads/master' was rewritten

如今你的项目根目录就是trunk子目录了。Git 会自动地删除不对这个子目录产生影响的提交。

全局性地更换电子邮件地址

另外一个常见的案例是你在开始时忘了运行git config来设置你的姓名和电子邮件地址,也许你想开源一个项目,把你全部的工做电子邮件地址修改成我的地址。不管哪一种状况你均可以用filter-branch来更换屡次提交里的电子邮件地址。你必须当心一些,只改变属于你的电子邮件地址,因此你使用--commit-filter

$ git filter-branch --commit-filter '
        if [ "$GIT_AUTHOR_EMAIL" = "schacon@localhost" ];
        then
                GIT_AUTHOR_NAME="Scott Chacon";
                GIT_AUTHOR_EMAIL="schacon@example.com";
                git commit-tree "$@";
        else
                git commit-tree "$@";
        fi' HEAD

这个会遍历并重写全部提交使之拥有你的新地址。由于提交里包含了它们的父提交的SHA-1值,这个命令会修改你的历史中的全部提交,而不只仅是包含了匹配的电子邮件地址的那些。

相关文章
相关标签/搜索