版本管理最有用的地方就是可以撤销发生的问题。在GIT
中,撤销可以有很多细微的不同。当创建新的提交的时候,GIT
会存储当时仓库的快照,这样就可以使用GIT
回顾工程之前的版本。在这片文章里,我将会重现一些需要做撤销操作的场景,并且展示如何使用GIT
进行处理。
撤销一个公共的改变
场景:你刚刚运行了git push
,提交已经改变的数据。但是在众多提交中你发现了一个问题,希望撤销那个提交。
命令:git revert <sha>
解释:git revert
会创建一个新的提交,但是它会是提供的SHA对应的提交的一个逆操作。如果旧的提交叫做matter
的话,新的提交就会命名为anti-matter
,所有删除的操作都会添加到新的分支里,所有添加操作都会在新分支里删除。
这是GIT
最安全、最基础的撤销操作,因为这个操作不改变历史提交。接下来可以使用git push
提交刚刚的改变了。
修改最后一次提交的消息
场景:你刚刚运行了命令git commit -m "Fxies bug #42"
,提交了最后一次消息,但是在git push
之前你发现应该是"Fixes bug #42"。
命令:git commit --amend
或者 git commit --amend -m "Fixes bug #42"
解释:git commit --amend
将会用新创建的包含前一次提交改变所有内容的提交来更新替换最近一次的提交。如果没有任何改变,这样将仅仅会重写上一次的提交信息。
撤销本地改变
场景:猫走过你的键盘,莫名其妙的保存了修改,之后编辑器崩溃了,但是你还没有提交修改。你想恢复文件,仅仅是恢复到上一次提交的版本。
命令:git checkout -- <bad filename>
解释:git checkout
会把文件修改到前一次已知提交。你应该提供一个分支名称或者指定SHA用来恢复,或者默认情况下,GIT
会假设你想要检出HEAD
,当前检出分支的最新提交。
记住:这种方式撤销的改变是不能恢复的。因为改变就没有提交过,所以GIT
不能帮助恢复这些文件。确定你知道你放弃的文件内容,或者比较来确认。
重置本地改变
场景:你在本地进行了几次提交,还没有推送到远程,但是看上去都不是很好,你想要撤销最新的三个提交,就像从来没提交过一样。
命令:git reset <last good SHA>
或者 git reset --hard <last good SHA>
解释:git reset
会恢复历史到指定的SHA对应的提交。就好像那些提交根本没发生过。默认情况下,git reset
保存工作目录的改变。提交删除后,内容却还在磁盘上。这是最安全的方式,但是通常,你想要撤销提交并且删除改变,这时候就可以使用--hard
。
在撤销本地之后重做
场景:你进行了几次提交,使用git reset --hard
撤销了改变,然后想要那些改变再恢复。
命令:git reflog
和 git reset
或者 git checkout
解释:git reflog
是一个很强大的历史恢复工具。基本上可以恢复任何提交。
你可能很熟悉git log
命令,这个命令会列出提交列表。git reflog
很相似,但是会显示HEAD
改变的时刻。
附加说明:
HEAD
仅改变。HEAD
会在你更换分支时、提交代码时(git commit
)和重置目录时(git reset
)改变,但是不会在你git checkout -- <bad filename>
时改变。(就像之前的场景,那些改变并没有提交,所以不能使用reflog恢复。)git reflog
不会永远保存。GIT
会周期性地清理无法获取的对象。不要寄希望于找到上个月的提交内容。- 你的reflog仅仅是你自己的。你不能使用reflog还原其他开发者的未推送提交。
所以...如何使用reflog重做一个或者多个之前撤销的提交呢?它基于你想要达成的目的:
- 如果想要恢复工程提交的历史,就像之前那样。使用
git reset --hard <SHA>
; - 如果你想恢复一个或者多个工程文件,单不改变历史提交。使用
git checkout <SHA> -- <filename>
; - 如果你想准确的恢复仓库众多提交中的一个。使用
git cherry-pick <SHA>
;
重用分支
场景:你做了一些提交,然后发现自己在master分支检出的。你希望在其他分支做这些提交。
命令:git branch feature
,git reset --hard origin/master
和 git checkout feature
解释:你可能会使用git checkout -b <name>
创建新分支,这是一种创建分支并检出的简单写法,但是你并不想马上更换分支。这里git branch feature
创建了一个称作feature的新分支,指向最新的提交,但是并没有检出到master分支。
下一步,git reset --hard
在所有提交之前将master分支重置到origin/master分支。不要担心,它们仍然在feature上可用。
最后,git checkout
转分支到新的拥有所有最近改变的feature分支。
及时保存分支
场景:你基于master分支创建feature分支,但是master分支远落后于origin/master分支。现在master已经和origin/master分支同步了,你希望提交feature的起点是现在的master分支,代替之前的。
命令:git checkout feature
和 git rebase master
解释:你可以使用git reset
(没有--hard)命令,然后git checkout -b <new branch name>
创建新的分支重新提交,但是那样会丢失提交的历史。有更好的办法。
git rebase master
做了如下几件事情:
- 第一,它定位了当前分支和master分支共同的祖先。
- 然后,它重置了当前分支到祖先,缓存了所有祖先之后的提交。
- 最后,将当前分支提前拼接到master分支结尾,并且恢复缓存提交到master最后的一次提交之后。
混合撤销或者重做
场景:你开起了一个特性开发,开发过程中你意识到,有更好的解决方式。你已经提交了很多次了,但是你只是需要一部分提交,希望另外一部分消失。
命令:git rebase -i <earlier SHA>
解释:-i
代表交互模式。它就像之前所说的rebase命令一样,但是在恢复提交之前,它会暂停允许你定义恢复的每一条提交。
rebase -i
将会在你的默认编辑器中打开一个命令列表,就像这样:
第一列代表提交选择的命令;第二列代表提交的对应SHA值;默认情况下,rebase -i
假设每条提交都会应用,使用pick
命令。
丢弃提交的方式就是删除提交所在行。如果你不希望再看到有问题的提交,你可以直接删除上面图片中的第一行和三四行。
如果你想保留提交的内容到那时修改提交消息的话,需要使用reword
命令。仅仅是替换pick
单词即可。这样就能够马上重写提交消息,但是这样并不起作用,rebase -i
忽略了SHA列之后的内容。之后的内容其实只是帮助我们理解前面的内容。当完成了rebase -i
之后,你需要重新写提交消息。
如果你想合并两条提交,你需要使用squash
和fixup
,像这样:
squash
和fixup
将会合并之前的提交。在上图中,0835fe2
和 6943e85
将会合并成一条;38f5e4e
和 af67f82
将会合并为另外一条。
当选则squash
,GIT将会给合并后的提交添加一个新的提交消息;fixup
将会把第一条提交的消息添加到新的提交里。在这里,af67f82
是“ooops”,仅仅使用38f5e4e
的提交消息;但是0835fe2
和 6943e85
合并后,你会写一条新的提交消息;
当你保存并且推出编辑器,GIT将会按顺序从上到下应用你的提交。你可以改变提交顺序。如果你想,你可以通过调整顺序合并 af67f82
和 0835fe2
,就像这样:
修复一个早期的提交
场景:你在之前的提交中忘记提交了一些文件,你想给它添加这个文件。你还没有推送,但是那并不是最近的提交,所以不能使用commit --amend
命令:git commit --squash <SHA of the earlier commit>
和 git rebase --autosquash -i <even earlier SHA>
解释:git commit --squash
将会创建一个新的分支,携带消息"squash! Earlier commit"。(你可以手动创建提交,并写上上边的信息,但是commit --squash
保存了一些输入。)
如果你不想提前写一些消息的话,你也可以使用git commit --fixup
。在这个场景中,最好使用commit --fixup
,因为在rebase过程中,你仅仅想要使用早期提交的消息。
rebase --autosquash -i
会执行交互式的rebase编辑,但是编辑器会使用任何squash
和 fixup
已经适配的提交展现列表,就像这样:
当使用--squash
和 --fixup
时,你可能不记得你想要修复的提交的SHA。你可能觉得使用GIT的^
和~
操作很难。HEAD^
代表HEAD
之前的一条提交。HEAD~4
代表HEAD
之前的4条提交或者组合在一起,五条纪录之前。
停止记录一个已经记录的文件
前提:你添加了log文件到仓库中,每次运行程序都保存log的修改,你已经把log放到了忽略文件中,但是文件始终在那里怎么样撤销跟踪文件呢?
命令:git rm --cached *.log
解释:忽略文件本身是阻止跟踪文件改变的,但是如果一个文件已经添加并且提交之后,GIT将会一致跟踪文件的改变。