Fork me on GitHub

Git 撤销与回滚

git resetgit checkoutgit revert,都可以撤销代码仓库中的某些更改,前两条命令既可以用于commit级别,也可以用于file级别,也就是说可以指定撤销的文件,而revert不能指定文件。

在了解这三个命令前,我们先需要了解git的工作区Working Directory、暂存区Stage(快照:add的缓存库)和历史记录区History(commit历史)的关系。

Git 撤销与回滚

对应source tree来看:
Git 撤销与回滚


Checkout

checkout最常用的用法莫过于切换分支,

1
git checkout <branch>

其原理就是将HEAD指针指向另一个分支,并对当前工作区的内容进行覆盖。所以git会强制你提交或者缓存工作目录中的所有更改,不然在checkout的时候这些更改会丢失。

除了切换分之,它也可以达到用暂存区或某一次历史提交还原工作区的效果:
未添加到暂存区的撤销,还没有git add

1
use 'git checkout -- <file>...' to discard changes in working directory

1
2
3
4
5
git checkout HEAD // 用最新的一次提交覆盖工作区和暂存区
git checkout HEAD~2 // 用上上次提交覆盖工作区和暂存区
git checkout commit_id // 用某次提交覆盖工作区和暂存区
git checkout -- <file> // 撤销工作区中对文件的修改,实际上是用暂存区中的文件内容覆盖工作区中的文件内容
git checkout -- <folder> // 一次性撤销多个文件

Git 撤销与回滚

Reset

git reset命令通过移动HEAD到某个提交commit,可以用来撤暂存区和工作区的提交。

1
use "git reset HEAD <file>..." to unstage

commit级别上,一次性将所有暂存区的修改撤销:

1
2
3
4
git reset HEAD  // HEAD 移动到上一次提交
git reset HEAD~2 // HEAD 移动两步
git reset commit_id // HEAD 移动到某个提交
git reset [commit_id] -- <file> // 用某个文件某次历史提交的内容覆盖暂存区中的该文件的修改

reset这个指令虽然可以用来撤销commit,但它的实质行为并不是撤销,而是移动HEAD,重置HEAD以及它所指向的branch的位置的。
例如reset HEAD~2回退两步,则HEAD会指向第三个commit,而最近的两个commit会处于HEAD之后,这意味着在下一次提交时,最近的两个提交会被删掉。
Git 撤销与回滚

Revert

git revert命令是撤销某次操作,此次操作之前和之后的commithistory都会保留,并且把这次撤销作为一次最新的提交。
revert撤销一个提交的同时会创建一个新的提交。相比reset,它不会改变现有的提交历史,可以用revert撤销已经提交的更改,用reset撤销没有提交的更改。

1
2
git revert HEAD 删除最后一次提交
git revert [commit_id]

Git 撤销与回滚

下面我们来实操一下看看。

reset 撤销暂存区、checkout 撤销工作区:

新增index.js,并git add添加到暂存区,还没有git commit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git status
On branch dev
Your branch is up to date with 'origin/dev'.

Changes to be committed:
(use 'git reset HEAD <file>...' to unstage)

modified: index.js

Changes not staged for commit:
(use 'git add <file>...' to update what will be committed)
(use 'git checkout -- <file>...' to discard changes in working directory)

modified: index.js

reset撤销清空暂存区:

1
2
3
$ git reset HEAD index.js
Unstaged changes after reset:
M index.js

再查看:

1
2
3
4
5
6
7
8
9
$ git status
On branch dev
Your branch is up to date with 'origin/dev'.

Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)

modified: index.js

再用git checkout清空工作区,之后再查看:

1
2
3
4
5
6
7
$ git checkout -- index.js

$ git status
On branch dev
Your branch is up to date with 'origin/dev'.

nothing to commit, working tree clean

Git 撤销与回滚


revert 撤销历史区:

commit提交后想撤销,先提交修改然后再用git log查看提交记录。

Git 撤销与回滚

1
2
3
4
5
6
$ git log
commit 707ffbee6b5b2f7eb7ff42e0a12992aaab43140c (HEAD -> dev, origin/dev)
Author: lzx <[email protected]>
Date: Tue Oct 27 09:19:54 2020 +0800

third commit on dev

然后使用git revert后面跟上git提交的commit_id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
git revert 707ffbee6b5b2f7eb7ff42e0a12992aaab43140c

Revert "third commit on dev"

This reverts commit 707ffbee6b5b2f7eb7ff42e0a12992aaab43140c.

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch dev
# Your branch is up to date with 'origin/dev'.
#
# Changes to be committed:
# modified: index.js

修改后输入esc退出编辑模式,输入:wq保存编辑并退出。

1
2
3
$ git revert 707ffbee6b5b2f7eb7ff42e0a12992aaab43140c
[dev a137703] Revert "third commit on dev"
1 file changed, 1 insertion(+), 3 deletions(-)

Git 撤销与回滚

之后再推送到远端更新远程仓库代码,修改的文件就撤销了。

source tree操作:

回滚提交:

Git 撤销与回滚

回滚生成新的提交:

Git 撤销与回滚

提交到远程仓库:

Git 撤销与回滚


Git Reset 三种模式:

Git 撤销与回滚

Git 撤销与回滚

Git 撤销与回滚

reset –soft commit_id:
保留本地working directory工作区,并把重置HEAD所带来的新的差异放进本地stage暂存区

Git 撤销与回滚

Git 撤销与回滚

reset –mixed commit_id:
保留本地working directory工作区,并清空本地stage暂存区。
也就是说,工作目录的修改、暂存区的内容以及由reset所导致的新的文件差异,都会被放进工作目录。简而言之,就是把所有差异都混合(mixed)放在工作目录中。

Git 撤销与回滚

Git 撤销与回滚

reset –hard commit_id:
强制将本地stage暂存区和本地working directory工作区都同步到你指定的提交。
会在重置HEADbranch的同时,重置stage区和working directory里的内容。你的暂存区和工作目录里的内容会被完全重置为和HEAD的新位置相同的内容。没有commit的修改会被全部擦掉。
可以回退到某次提交 A,A 之后的提交都会回滚,覆盖是不可逆的,谨慎使用。

重置前:

Git 撤销与回滚

Git 撤销与回滚

重置后:

Git 撤销与回滚

Git 撤销与回滚

之后强行推送更新远程仓库:

1
2
3
4
$ git push origin dev -f
Total 0 (delta 0), reused 0 (delta 0)
To github.com:luanzhuxian/git-examples.git
+ def670f...707ffbe dev -> dev (forced update)

Git 撤销与回滚