如何在 Git 里撤销(几乎)任何操作

任何版本控制系统的一个最有的用特性就是“撤销 (undo)”你的错误操作的能力。在 Git 里,“撤销” 蕴含了不少略有差别的功能。

当你进行一次新的提交的时候,Git 会保存你代码库在那个特定时间点的快照;之后,你可以利用 Git 返回到你的项目的一个早期版本。

在本篇博文里,我会讲解某些你需要“撤销”已做出的修改的常见场景,以及利用 Git 进行这些操作的最佳方法。

撤销一个“已公开”的改变

场景:你已经执行了git push, 把你的修改发送到了 GitHub,现在你意识到这些 commit 的其中一个是有问题的,你需要撤销那一个 commit.

方法:git revert <SHA>

原理:git revert会产生一个新的 commit,它和指定 SHA 对应的 commit 是相反的(或者说是反转的)。如果原先的 commit 是“物质”,新的 commit 就是“反物质” — 任何从原先的 commit 里删除的内容会在新的 commit 里被加回去,任何在原先的 commit 里加入的内容会在新的 commit 里被删除。

这是 Git 最安全、最基本的撤销场景,因为它并不会改变历史 — 所以你现在可以 git push新的“反转” commit 来抵消你错误提交的 commit。

修正最后一个 commit 消息

场景:你在最后一条 commit 消息里有个笔误,已经执行了git commit -m “Fxies bug #42″,但在git push之前你意识到消息应该是 “Fixes bug #42″。

方法:git commit –amend或git commit –amend -m “Fixes bug #42”

原理:git commit –amend会用一个新的 commit 更新并替换最近的 commit ,这个新的 commit 会把任何修改内容和上一个 commit 的内容结合起来。如果当前没有提出任何修改,这个操作就只会把上次的 commit 消息重写一遍。

撤销“本地的”修改

场景:一只猫从键盘上走过,无意中保存了修改,然后破坏了编辑器。不过,你还没有 commit 这些修改。你想要恢复被修改文件里的所有内容 — 就像上次 commit 的时候一模一样。

方法:git checkout — <bad filename>

原理:git checkout会把工作目录里的文件修改到 Git 之前记录的某个状态。你可以提供一个你想返回的分支名或特定 SHA ,或者在缺省情况下,Git 会认为你希望 checkout 的是HEAD,当前 checkout 分支的最后一次 commit。

记住:你用这种方法“撤销”的任何修改真的会完全消失。因为它们从来没有被提交过,所以之后 Git 也无法帮助我们恢复它们。你要确保自己了解你在这个操作里扔掉的东西是什么!(也许可以先利用git diff确认一下)

重置“本地的”修改

场景:你在本地提交了一些东西(还没有 push),但是所有这些东西都很糟糕,你希望撤销前面的三次提交 — 就像它们从来没有发生过一样。

方法:git reset <last good SHA>或git reset –hard <last good SHA>

原理:git reset会把你的代码库历史返回到指定的 SHA 状态。 这样就像是这些提交从来没有发生过。缺省情况下,git reset会保留工作目录。这样,提交是没有了,但是修改内容还在磁盘上。这是一种安全的选择,但通常我们会希望一步就“撤销”提交以及修改内容 — 这就是–hard选项的功能。

在撤销“本地修改”之后再恢复

场景:你提交了几个 commit,然后用git reset –hard撤销了这些修改(见上一段),接着你又意识到:你希望还原这些修改!

方法:git reflog和git reset或git checkout

原理:git reflog对于恢复项目历史是一个超棒的资源。你可以恢复几乎任何东西 — 任何你 commit 过的东西 — 只要通过 reflog。

你可能已经熟悉了git log命令,它会显示 commit 的列表。git reflog也是类似的,不过它显示的是一个HEAD发生改变的时间列表.

一些注意事项:

那么…你怎么利用 reflog 来“恢复”之前“撤销”的 commit 呢?它取决于你想做到的到底是什么:

利用分支的另一种做法

场景:你进行了一些提交,然后意识到你开始 check out 的是master 分支。你希望这些提交进到另一个特性(feature)分支里。

方法:git branch feature,git reset –hard origin/master, andgit checkout feature

原理:你可能习惯了用git checkout -b <name> 创建新的分支— 这是创建新分支并马上 check out 的流行捷径 — 但是你不希望马上切换分支。这里,git branch feature创建一个叫做feature的新分支并指向你最近的 commit,但还是让你 check out 在master 分支上。

下一步,在提交任何新的 commit 之前,用git reset –hard把master分支倒回origin/master 。不过别担心,那些 commit 还在feature 分支里。

最后,用git checkout切换到新的feature分支,并且让你最近所有的工作成果都完好无损。

及时分支,省去繁琐

场景:你在master分支的基础上创建了feature分支,但master分支已经滞后于origin/master 很多。现在master分支已经和origin/master 同步,你希望在feature上的提交是从现在开始,而不是也从滞后很多的地方开始。

方法:git checkout feature和git rebase master

原理:要达到这个效果,你本来可以通过git reset(不加–hard, 这样可以在磁盘上保留修改) 和git checkout -b <new branch name>然后再重新提交修改,不过这样做的话,你就会失去提交历史。我们有更好的办法。

git rebase master会做如下的事情:

大量的撤销/恢复

场景:你向某个方向开始实现一个特性,但是半路你意识到另一个方案更好。你已经进行了十几次提交,但你现在只需要其中的一部分。你希望其他不需要的提交统统消失。

方法:git rebase -i <earlier SHA>

原理:-i参数让rebase进入“交互模式”。它开始类似于前面讨论的 rebase,但在重新进行任何提交之前,它会暂停下来并允许你详细地修改每个提交。

rebase -i会打开你的缺省文本编辑器,里面列出候选的提交。如下所示:

前面两列是键:第一个是选定的命令,对应第二列里的 SHA 确定的 commit。缺省情况下,rebase -i 假定每个 commit 都要通过 pick命令被运用。

要丢弃一个 commit,只要在编辑器里删除那一行就行了。如果你不再需要项目里的那几个错误的提交,你可以删除上例中的1、3、4行。

环境不会改变,解决之道在于改变自己。

如何在 Git 里撤销(几乎)任何操作

相关文章:

你感兴趣的文章:

标签云: