是五月呀!

git撤销修改

基本的命令含义参考:代码回滚:Reset、Checkout、Revert的选择

初始化一个空的测试目录:

1
2
3
4
 ~/git/ git init
Initialized empty Git repository in /Users/git/.git/
 ~/git/ [master] ls
 ~/git/ [master]

1.撤销新增文件

1.1 还没有add

如果是新增的文件,还没有add提交到缓冲区,只是工作区做了修改,此时该文件是不受git管理的,所以直接修改或者删除该文件就好.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 ~/git/ [master] touch A.java
 ~/git/ [master] ls
A.java
 ~/git/ [master] git status
On branch master
Initial commit
Untracked files:
(use "git add <file>..." to include in what will be committed)
A.java
nothing added to commit but untracked files present (use "git add" to track)

假如我们写了一个新的代码类A.java,然后还没有add提交,现在你后悔了,发现这个类没什么卵用,打算删除掉,那么,直接干掉就好,git也不会在意,反正它根本就不知道A.java的存在(Untracked)。

1.2 add了,但是还没有commit

假如你觉得A.java写的还有点意义,把它加入到缓存区,准备交由git管理:

1
2
3
4
5
6
7
8
9
10
 ~/git/ [master] git add A.java
 ~/git/ [master] git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: A.java

刚add完,发现有个错误,或者又反悔了,不想add了,想从缓存区中拿出来,返回到没有add时的状态,那么,可以根据提示,使用下面的命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
 ~/git/ [master] git rm --cached A.java
rm 'A.java'
 ~/git/ [master] git status
On branch master
Initial commit
Untracked files:
(use "git add <file>..." to include in what will be committed)
A.java
nothing added to commit but untracked files present (use "git add" to track)

看,回到了没有add时的状态了。
如果你觉得A.java根本毫无存在价值,从缓存区拿出来之后就直接扔掉了,不需要恢复到add前的状态,而是直接删除,那么可以使用下面的命令:

1
2
3
4
5
6
7
8
9
 ~/git/ [master] git rm -f A.java
rm 'A.java'
 ~/git/ [master] ls
 ~/git/ [master] git status
On branch master
Initial commit
nothing to commit (create/copy files and use "git add" to track)

看,A.java已经从这个世界消失了。
有人不服,说,我直接删除A.java不就好了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 ~/git/ [master] rm A.java
 ~/git/ [master] git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: A.java
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
deleted: A.java
 ~/git/ [master] ls

可以看到,A.java确实也不见了。
但是仍然会看到有一个修改没有提交,在缓存区里发呆。
不仅如此,工作区多了一个为提交的修改,是delete: A.java。

由此看出,直接删除文件,相当于是一种新增的修改操作,而不是对之前git操作的撤销。

注意看提示,使用git add/rm <file>命令将修改更新缓存区或者使用git checkout -- <file>将修改撤销。
我们先试一下第一种:

1
2
3
4
5
6
7
8
 ~/git/ [master] git add A.java
 ~/git/ [master] git status
On branch master
Initial commit
nothing to commit (create/copy files and use "git add" to track)
 ~/git/ [master] ls

提交修改之后,缓冲区也清净了。
再试试第二种:

1
2
3
4
5
6
7
8
9
10
11
12
13
 ~/git/ [master] git checkout -- A.java
 ~/git/ [master] git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: A.java
 ~/git/ [master] ls
A.java

看到,缓存区保持原样,A.java又回到了现实世界,撤销了上一次的文件删除修改。

1.3 commit了

如果自信满满的commit了,然后后悔了,想干掉。分两种情况:
第一种情况是,像例子里面这样,第一次提交,之前没有commit过,那如果想撤销本次提交,我还没想到方法。
只能去删除A.java,然后再来一次提交,第一次提交的黑历史无法抹去。
或者使用revert命令,通过一个新的提交,撤销某一次提交:

1
2
3
4
5
 ~/git/ [master] git revert HEAD
[master 32a2e57] Revert "A"
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 A.java
 ~/git/ [master] ls

revert命令是撤销某一次提交,HEAD指的是最新的一次提交。
期间,会让你确认一下提交备注:

1
2
3
4
5
6
7
8
9
10
Revert "A"
This reverts commit 969bb74c6125dc9fe4d1ef2327d4fda41a81fa6f.
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Changes to be committed:
# deleted: A.java
#

我们看到,上一次提交添加的A.java已经消失了,只是在git提交日志里面,能看到两条记录:

1
2
* 32a2e57 (HEAD -> master) Revert "A"
* 5b6ce5c A

第二种情况是,非第一次提交,那么可以使用reset命令撤销提交,在下文中给出。

2.撤销修改

我们重新把A.java请回来,初始内容为hello,然后做一次提交:

1
2
3
4
5
6
7
8
9
10
 ~/git/ [master] vim A.java
 ~/git/ [master] cat A.java
hello
 ~/git/ [master] git add .
 ~/git/ [master+] git cm 'A init'
[master 5b9ab26] A init
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 A.java
 ~/git/ [master] ls
A.java

2.1 还没有add

老大说A.java有bug,需要修改,于是我们开始做每天最常见的游戏fix,将内容修改为world

1
2
3
4
5
6
7
8
9
10
11
12
 ~/git/ [master] vim A.java
 ~/git/ [master*] cat A.java
world
 ~/git/ [master*] git status
On branch master
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: A.java
no changes added to commit (use "git add" and/or "git commit -a")

修改完,可以看到提示,如果你觉得没问题,就add,这很正常~
但是,有一天脑袋不清醒,写了一大堆看不懂的代码,而且把原来的逻辑改的乱七八糟,现在唯一想做的就是把这些该死的代码删掉,恢复到上一次add或者commit的状态。
当然,你可以选择手动的删除,或者,使用提示中的命令:

1
2
3
 ~/git/ [master*] git checkout -- A.java
 ~/git/ [master] cat A.java
hello

意思就是,把A.java在工作区的修改全部撤销,这里有两种情况:
一种是A.java自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态(上一次commit);
一种是A.java已经add到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态(上一次add)。

2.2 已经add了,但是没有commit

信心满满的add了:

1
2
3
4
5
6
7
 ~/git/ [master*] git add A.java
 ~/git/ [master+] git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: A.java

如果发现有问题,想把A.java从缓存区拿出来,可以使用提示命令:

1
2
3
4
5
6
7
8
9
10
11
12
 ~/git/ [master+] git reset HEAD A.java
Unstaged changes after reset:
M A.java
 ~/git/ [master*] git status
On branch master
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: A.java
no changes added to commit (use "git add" and/or "git commit -a")

现在,A.java回到了add前的状态。

注意,这里是对指定文件的撤回,参数--hard是没用的(后面讲),因为该操作不会影响工作区,肯定会回到add前的状态,保留工作区修改。
如果想撤销工作区的修改,可以进一步使用上面的git checkout --<file>命令。

3.3 已经commit了

如果已经commit了:

1
2
3
4
5
6
 ~/git/ [master+] git commit -m 'fix A.java'
[master 17acd80] fix A.java
1 file changed, 1 insertion(+), 1 deletion(-)
 ~/git/ [master] git status
On branch master
nothing to commit, working directory clean

现在,如果想撤回这次提交,干掉黑历史,回到之前的某个提交版本,可以使用下面的方法:

1
2
3
4
5
6
7
8
9
10
11
12
 ~/git/ [master] git reset HEAD^
Unstaged changes after reset:
M A.java
 ~/git/ [master*] git status
On branch master
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: A.java
no changes added to commit (use "git add" and/or "git commit -a")

看,代码回到了上一次提交,add前的状态。并且git log中,提交黑历史也不见了。

git reset命令常用的参数包括三种:

  • –soft – 缓存区和工作目录都不会被改变
  • –mixed – 默认选项。缓存区和你指定的提交同步,但工作目录不受影响
  • –hard – 缓存区和工作目录都同步到你指定的提交

把这些标记想成定义git reset操作的作用域就容易理解多了。
git reset

这些标记往往和HEAD作为参数一起使用。比如,git reset --mixed HEAD 将你当前的改动从缓存区中移除,但是这些改动还留在工作目录中。另一方面,如果你想完全舍弃你没有提交的改动,你可以使用git reset --hard HEAD。这是git reset最常用的两种用法。

关于回退版本:
查看提交历史,以便确定要回退到哪个版本:

1
git log

查看命令历史,以便确定要回到未来的哪个版本:

1
git reflog

版本更换

1
git reset [--hard] commit_id

由于HEAD指向的版本就是当前版本,所以上一个版本就是HEAD^,上上个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100。