·  阅读 Git: 当你写了骚代码或做了骚操作后,怎么消灭证据?

虽然git记录着我们的一举一动,但是只要我们没有push到远程,我们总会有办法撤销操作,即便到最后,不还有终极大法嘛——删除分支或本地git(当然并不是推荐这样做)。

but,若是把骚代码推送到远程仓库了,那么终究会留下痕迹,只能说

当然,其实代码回撤是再正常不过的事儿了,只是我们尽量避免不必要的操作记录,及时发现,及时更改。

1、撤销本地的更改

丢弃本地文件更改的操作是最普遍的场景。例如现在我们的工作区,更改了一些文件

假如我们要丢弃 README.md 的更改,我们可以使用以下两种

git checkout README.md

git restore README.md (由于git checkout还具有其他功能,更推荐使用纯粹的git restore)

如果要把工作区的更改全部丢弃那么可以两种方式

git checkout .

git restore .

这两个操作都只会丢弃【文件更改内容】,和恢复【被删除的文件】。并不会删除【新增的文件】。因此新增的 note.txt 还会保留。

我们可以使用 git restore . && git clean -df (-d表示移除空目录,-f强制清除) ,这样就可以把增删改的文件全部放弃。

我们换成在Sourcetree图形工具中进行操作,我们可以选中删改的文件,右键点击重置。再将新增的文件移除即可。

2、撤销缓存区的更改

假如我们已经把工作区的更改都加到缓存区了 git add .

如果我们要把 README.md 文件撤销回工作区,可以使用下面几种方式

方式一: git restore README.md --staged (推荐使用)

方式二: git reset -- README.md 或者 git reset HEAD README.md

同理,如果撤销所有更改回工作区,那么可以

方式一: git restore . --staged

方式二: git reset 或者 git reset HEAD

对应的,在Sourcetree中,操作就比较简单了

如果不撤回到工作区,想一次性重置暂存区与工作区,那么可以使用

git reset --hard

对应的,Sourcetree中,我们可以右键重置

3、撤销文件的部分内容

假设我们更改了《出师表》的index.html,现在想除了第34行的修改错别字,其他都丢弃掉

我们可以使用 git restore -p index.html ,然后根据各个hunk(块)来决定是否丢弃

可以看到上面的动图操作,会出现两个hunk让我们选择,第一个hunk我们选择键入 y 丢弃。紧接着第二个hunk,我们想再细分一下,键入 s ,分成两个小hunk。第一个小hunk键入 n ,不丢弃更改。第二个小hunk键入 y 丢弃更改。最后只保留我们想要的更改

-      <p>今当远离,临表涕淋,不知所言。</p>
+      <p>今当远离,临表涕零,不知所言。</p>

至于对hunk的操作,有很多选项[y,n,q,a,d,j,J,g,/,e,?]?,具体可以查看帮助

y - discard this hunk from worktree
n - do not discard this hunk from worktree
q - quit; do not discard this hunk or any of the remaining ones
a - discard this hunk and all later hunks in the file
d - do not discard this hunk or any of the later hunks in the file
g - select a hunk to go to
/ - search for a hunk matching the given regex
j - leave this hunk undecided, see next undecided hunk
J - leave this hunk undecided, see next hunk
e - manually edit the current hunk
? - print help

同理,如果我们想要从暂存区取消暂存到工作区,只需要后面加--staged即可。

git restore -p index.html --staged

在Sourcetree图形工具中演示撤销暂存区域部分更改,那就更直观操作了。我们可以选中行丢弃即可,或者直接丢弃hunk(块)

反过来,如果我们需要提交部分修改到暂存区,可以使用

git add -p index.html

4、修改上一次提交信息

假如我们现在的提交是这样,最新一次的提交信息有误add inde.htm。我们想更改一下

可以使用git commit --amend -m "add index.html"

在Sourceetree中,我们可以在提交选项-更正上一次提交,重新编辑提交信息即可

当然尽量避免出现这种操作,每次提交前确保信息无误是种好习惯。 另外,如果我们已经push到远程了,那么这条记录就抹不掉了,强行更改提交信息的话,只会展示一条新的历史记录

如果我们要修改更早的提交记录信息,那么得使用git rebase

5、追加更改到最近一次的提交

假如我们已经提交了一次记录,push到远程之前,发现还有更改忘记一起提交。这时我们可以加个新的提交,但是如果是同一个功能修改,可以放在同一个提交里,保持历史记录的整洁直观。

假如现在工作区的更改都是要追加到上一次的提交里

  • 可以使用git add .放到暂存区。
  • 再使用git commit --amend --no-edit,即可合并到上一次的提交里。
  • 在Sourcetree中,我们先把修改放到暂存区,再提交选项-更改上一次提交,点击提交确认即可(如有需要,还可顺便修改提交信息)

    另外,我们还可以有另一种办法,就是使用git reset HEAD~1,丢弃上一次的提交,同时会把上一次的更改放回工作区。这时把这次的更改和上一次的更改重新一并提交即可。

    6、重置前几次的提交记录

    有时候,我们已经提交了好几次记录,突然发现之前的思路是错的或者这几个提交的需求被砍掉了。如果还没有push到远程,我们得及时把这些提交丢弃掉。

    那么可以使用git reset,其实上面已经多次使用到了该命令,这个命令还可以搭配参数使用,如--mixed、--soft、--hard。--mixed是默认值。

    现在,比如我们有多个提交。想回到feat:草图这个提交记录

    那么我们可以先执行git log --oneline拿到对应的提交[SHA-1],再执行git reset <commit>,就可以回到我们之前的提交

    另外,根据历史记录,可以看到feat:草图这个提交是HEAD的前两次提交,那么其实我们不用提交[SHA-1],直接使用git reset HEAD~2也可。

    刚才说到--mixed是默认值,那和另外两个参数有什么区别呢?这里汇总下区别,它们对现有工作区、暂存区的更改,还有重置提交的更改处理方式有所不同。

    --mixed--soft---hard
    工作区的更改保持不变保持不变删除
    暂存区的更改放回工作区保持不变删除
    被重置的提交的更改放到工作区放到暂存区删除

    例如这次模拟场景还是和刚才一样,我们想重置到feat: 草图这条提交记录。

    暂存区有配红色.txt更改,工作区有配黄色.txt更改

    这次我们用Sourceetree图形工具来操作

    mixed模式

    可以看到暂存区的更改和重置提交的更改都被放到工作区中,同时工作区的内容保持不变。其他模式就不展示了。

    同样是强调,重置提交记录只适合在push到远程之前,如果强行操作,只会让历史记录更加混乱。 如果我们就是需要回到feat:草图这条记录。那么我们可以基于这条记录创建一个新分支出来,再重新开发。

    使用git checkout -b <new_branch>] [<start_point>]

    或者使用git branch <branchname> [<start-point>]

    在Sourcetree中,找到提交记录右键点击分支,即可创建分支。

    7、丢弃第n次的提交记录

    上面的例子,第6节中我们是重置回到feat: 草图这条记录,同时把feat: 线稿feat: 底色两条最新记录丢弃。

    4c41094 (HEAD -> feature/dog) feat: 底色
    5126cce feat: 线稿
    f627c92 feat: 草图
    06bd421 feat: 起草
    

    这一次,假设我们想把feat: 草图这条记录丢弃,但保留它之后的提交记录,也即保留feat: 线稿feat: 底色。下面我们使用git rebase实际操作下

    首先我们以feat:起草的[SHA - 1]作为起点,即git rebase -i 06bd421

    可以看到会出现vim编辑器,我们编辑选择丢弃(drop)feat:草图这条记录(注意编辑器上列出来的记录顺序是由旧到新),保存退出即可。

    对应的,在Sourcetree中,我们右键选择交互式变基,选择要删除的那条记录。

    我们可以留意到Sourceetree的弹窗中,对提交记录还有更多的操作,例如编辑提交信息、排序、合并提交等等。这都是git rebase可以做到的功能,具体这里就不再详细展开了。

    注意:各个提交之间可能存在关联性,当更改了某个提交记录,对后续的提交记录可能会出现冲突,所以解决是我们使用git rebase需要注意的事情

    对于已经push到远程的情况,如果我们强行使用git rebase同样会造成历史记录的混乱。这时我们常用回滚命令git revert

    比如上面的例子,我们要把feat: 草图这条记录丢弃,首先拿到它的[SHA-1]为f627c92,那么使用

    git revert f627c92 --no-edit(加上--no-edit,表示不编辑提交信息)

    再使用git push推送到远程即可

    产生的历史记录将会是

    e6d014e Revert "feat: 草图"
    4c41094 feat: 底色
    5126cce feat: 线稿
    f627c92 feat: 草图
    06bd421 feat: 起草
    

    在Sourceetree中的操作,选中对应的提交记录-右键点击提交回滚,再推送远程即可(当然,若有冲突还需额外解决)

    8、撤回某次提交记录的文件更改

    有没有过这样的场景,有个文件更改提交后,后续也多次提交后,发现要撤回这个文件的更改。那么我们可以把这个文件更改对应的提交记录给重置,另外的方式就是单独撤回这个文件更改。

    比如例子还是上面的例子,使用git log --oneline 草图.txt,查看下草图.txt这个文件在feat: 底色feat: 草图两个提交中更改过,现在想回到feat: 草图这个提交记录的文件更改

    $ git log --oneline 草图.txt
    1d76327 (HEAD -> feature/cat) feat: 底色
    a4664ed feat: 草图
    

    那么可以使用git checkout a4664ed 草图.txt,文件更改会被放回暂存区

    我们重新提交git commit -m "update 草图.txt"即可。

    在Sourcetree中,我们搜索到该文件—右键点击“查看选中的修改日志”—重置文件到对应提交-再撤回的更改commit即可。

    9、提交到错误分支上

    你有没有遇到这样的情况?通常,我们会在自己的开发分支写代码提交,需要部署到测试服时,会将开发分支合并到test分支,触发ci/cd部署。可有时我们合并后,本地还是在test分支上,忘记切回自己的开发分支,下次写代码时直接提交到了test分支(当然test分支并无大碍)。我们需要尽量避免出现这种错误,但是人有失手的时候,万一出现了,我们需要把这次提交放回到自己的分支,保证开发流程的严谨。

    例如现在,有个feat: 补色的提交直接在test 分支上提交了,本应该先在feature/dog开发分支上提交。幸好还没有push到远程

    回顾之前的办法。

    我们可以使用git reset HEAD~1 --softtest分支上的提交撤回来,放到暂存区;

    再使用git switch feature/dog切换到开发分支,将暂存区的更改提交即可。

    那如果已经推送到远程,那么这里介绍另一种方式——遴选git cherry-pick

    首先我们找到那条提交记录的[SHA-1]

    git log --oneline
    200051d (HEAD -> test, origin/test) feat: 补色
    1d76327 (origin/feature/dog, feature/dog) feat: 底色
    5d37d9e feat: 线稿
    a4664ed feat: 草图
    4cc289a feat: 起草
    

    再使用git switch feature/dog切换到开发分支,然后执行git cherry-pick 200051d,即可自动将这条记录加到feature/dog分支上。(如果不想自动提交,可以加-n参数,把修改放到暂存区)

    至于test分支上的feat: 补色这条记录,需要处理的话可以提交回滚。

    在Sourcetree中,我们可以切换到feature/dog分支,找到提交记录右键点击遴选,确认即可。

    10、找回删除的分支/提交记录

    如果我们不小心删除了分支,或者重置了提交记录又后悔了,放心,我们总能找出线索。使用git reflog可以查看所有的操作记录。

    git reflog是查看所有操作记录,git log是查看提交记录

    找回删除的分支

    我们先留意下当前分支feature/dog的提交记录

    $ git log --oneline
    1d76327 (HEAD -> feature/dog) feat: 底色
    5d37d9e feat: 线稿
    a4664ed feat: 草图
    4cc289a feat: 起草
    

    现在使用git branch -D feature/dog强制删除分支

    当我们想要找回feature/dog这个分支时,我们必须找回某个记录点,使用git reflog

    git reflog
    c424e43 (HEAD -> feature/cat) HEAD@{0}: commit: docs: 猫粮
    2c1adfd (feature/lion) HEAD@{1}: checkout: moving from feature/dog to feature/cat
    1d76327 (feature/dog) HEAD@{2}: checkout: moving from test to feature/dog
    200051d (origin/test, test) HEAD@{3}: checkout: moving from feature/dog to test
    

    可以看到,我们倒数第三个步骤的操作是从test切换到feature/dog,这里的[SHA-1]为1d76327,和刚才大家留意的feature/dog分支的最后一次提交记录是一样。因此我们这个[SHA-1]创建一个新的feature/dog分支即可。

    git branch feature/dog 1d76327

    因此,如果我们要找回删除的分支,可以看操作记录中,最新什么时候从其他分支切换到过被删除分支的那个操作步骤,或者直接找被删除的分支最后一次提交的操作步骤,即可恢复分支。

    找回重置提交

    比如在feature/dog分支重置了上一次提交git reset HEAD~1,现在又后悔了,我们想拿回被重置的那个提交。同样使用git reflog

    $ git reflog
    5d37d9e (HEAD -> feature/dog) HEAD@{0}: reset: moving to HEAD~1
    1d76327 HEAD@{1}: checkout: moving from feature/cat to feature/dog
    c424e43 (feature/cat) HEAD@{2}: commit: docs: 猫粮
    

    找到最新切换到feature/dog分支的那个操作步骤的[SHA-1]为1d76327。

    使用git reset 1d76327 --hard即可。

    以上列举了日常开发使用git需要撤销操作的场景,当然实际开发可能会更复杂,比如需要解决各种冲突问题。但是掌握了基础知识,那就是万变不离其宗了。