Git 的各种undo技巧

How to undo (almost) anything with Git
任何版本控制系统中最有用的功能之一就是能够“撤销(undo)”你之前的错误。在Git中“undo”功能可能因为场景的不同而有些许的差异。

当你进行一个新的提交时,Git会保存你在这个特定时间点的快照到本地的仓库中,之后,你可以通过Git来回到你早期的某个版本。

我们来先看看一些需要你“撤销”的常见场景,你可以尝试使用Git来用最佳的方式来解决它。

一.撤销已经推送到远程的变更

场景:

你已经执行git push,把你的修改推送到远程的仓库,现在你意识到之前推送的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 ‘Fixes bug #42’ ,但是在git push之前你意识到这个消息应该是“Fix bug #43”。

方案:

git commit --amend

git commit --amend -m'Fixes bug #43'

原理:

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过的)东西。

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

一些注意事项:

  • 它涉及的只是HEAD的改变。在你切换分支、用git commit进行提交、以及用git reset撤销commit时,HEAD会发生改变,但当你使用git checkout -- <bad filename>撤销时,HEAD并不会发生改变。就像我们在上面说的,这些修改从来没有被提交过,因此reflog也无法帮助我们恢复它们。
  • git reflog不会永远保持。Git会定期清理那些“用不到的”对象。不要指望几个月前的提交还一直躺着那里。
  • 你的reflog就是你的,只是你的,你不能用git reflog来恢复另外一个开发者没有push过的commit。

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

  • 如果你希望准确的恢复项目的历史到某个时间点,用git reset --hard <SHA>
  • 如果你希望重建工作目录里的一个或多个文件,让它们恢复到某个时间点的状态,用git checkout <SHA> -- <filename>
  • 如果你希望把这些commit里的某个重新提交到你的代码库里,用git cherry-pick <SHA>

六.利用分支的另一种做法

场景:

你进行了一些提交,然后意识到你开始checkout的是master分支。希望这些提交进入到另外一个特性(feature)分支。

方案:

git branch feature
git reset --hard origin/master
git checkout feature

原理:

你可能习惯用 git checkout -b <name>创建一个新的分支(这是创建新分支并马上checkout的流行捷径),但是你不希望马上切换分支。这里,git branch feature创建了一个叫做feature的新分支,并指向你最近的commit,但是还是让你checkout在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会做下面的这些事情:

  • 首先它会找到你当前checkout的分支和master分支的共同祖先。
  • 然后它reset当前checkout的分支到那个共同祖先 ,在一个临时保存区存放所有之前的提交。
  • 然后它把当前checkout的分支提到master的末尾部分,并从临时保存区重新把存放的commit提交到master分支的最后 一个commit之后。

八.大量的撤销/恢复

场景:

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

方案:

git rebase -i <carlier SHA>

原理:

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

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

九.修复更早期的commit

场景:

你在一个更早期的commit里忘记了加入一个文件,如果更早的commit能包含这个忘记的文件就太棒了。你还没有push,但这个commit不是最近的,所以你还没法用commit –amend

方案:

git commit --squash <SHA of the earlier commit>
git rebase --autosquash -i <even earlier SHA>

原理:

git commit --squash会创建一个新的commit,它带有一个commit消息,类似于squash! Earlier commit。(你也可以手工创建一个带有类似commit消息的commit,但是 commit –squash 可以帮你省下输入的工作)

如果你不想被提示为新合并的commit输入一条新的commit消息,你也可以利用 git commit --fixup。在这个情况下,你很有可能会用commit --fixup,因为你只是希望在rebase的适合,使用早期commit的commit消息。

rebase --autosquash -i 会激活一个交互式的rebase编辑器,但是编辑器打开的适合,在commit清单里任何squash!和fixup!的commit都已经配对到目标commit上了。
在使用 --squash 和 --fixup的时候,你可能不记得想要修正的commit的SHA了(只是记得它是前面的第1个或者第5个commit)。你会发现Git的^和操作符特别好用。HEAD是HEAD的前一个commit。HEAD~4是HEAD往前第4个(或者一起算,倒数第5个commit)。

十.停止追踪一个文件

场景:
你偶然把application.log加到代码库里了,现在每次你运行应用,Git都会报告在application.log里有未提交的修改。你把 *.log放到了.gitignore文件里,可文件还是在代码库里,你怎样才能让Git“撤销”对这个文件的追踪呢?
方法:

git rm --cached application.log

原理:
虽然.gitignore会阻止Git追踪文件的修改,甚至不关注文件是否存在,但这只是针对那些以前从来没有追踪过的文件。一旦有个文件被加入并提交了,Git就会持续关注改文件的改变。类似地,如果你利用git add -f来强制或覆盖了.gitignore,Git还会持续追踪改变的情况。之后你就不必用-f来添加这个文件了。

如果你希望从Git的追踪对象中删除那个本应忽略的文件,git rm --cached会从追踪对象中删除它,但让文件在磁盘上保持原封不动。因为现在它已经被忽略了,你在git status里就不会再看见这个文件,也不回再偶然提交该文件的修改了。

这就是如何在Git里撤销任何操作的方法。要了解更多关于本文中用到的Git命令,请查看下面的相关文档。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容

  • 安装Git Git的下载地址:Git官网下载地址 Git本地仓库和命令 配置用户 下载完Git后,右键会有一个Gi...
    TokyoZ阅读 4,438评论 1 7
  • git 使用笔记 git原理: 文件(blob)对象,树(tree)对象,提交(commit)对象 tree对象 ...
    神刀阅读 3,714评论 0 10
  • 声明:这篇文章来源于廖雪峰老师的官方网站,我仅仅是作为学习之用 Git简介 Git是什么? Git是目前世界上最先...
    横渡阅读 3,920评论 3 27
  • PS:小白的入门笔记,希望能有用,主要参考廖君Git教程 操作环境说明: github仓库 https://git...
    呆呆的张先生阅读 558评论 3 2
  • Git和Github学习 1.创建版本库 安装git,至于这个话题就不细说了,按照你电脑的操作系统下载一个合适的g...
    郭子web阅读 352评论 0 0