美文网首页
Git 的各种undo技巧

Git 的各种undo技巧

作者: 葡萄柚子茶 | 来源:发表于2019-08-14 14:27 被阅读0次

    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命令,请查看下面的相关文档。

    相关文章

      网友评论

          本文标题:Git 的各种undo技巧

          本文链接:https://www.haomeiwen.com/subject/qwegjctx.html