git 各种撤销
因为git有三个区:工作区,索引区和版本区。所以git的撤销有很多种,比如:
- 撤销工作区: 刚写了几行代码,不想要了,想撤销。
- 撤销版本区: 刚提交了一次代码,但由于疏忽,漏掉了几个文件或者备注信息写错了,想撤销后重新提交。
撤销工作区
一个已经提交的文件中新加了一行,现在想撤销,怎么办?
新加一行因为新加了一行,左侧出现** > **符号,表示有修改。这个修改只发生在“工作区”,尚未进入“索引区”。
$ 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: src/main/java/io/downgoon/hello/boot/HelloWorld.java
如何撤销?
- 命令行方式
$ git checkout -- src/main/java/io/downgoon/hello/boot/HelloWorld.java
符号 ** -- ** 可以理解为“索引区”。
- GUI 方式
注意: 菜单在 Team 的下面。Eclipse里面说的 Replace With 对应的就是 git checkout
命令,解答了许多人问 “为什么Eclipse Git 没有checkout菜单”。
撤销版本区(重新编辑最后一次提交)
刚提交了一次代码,但由于疏忽,漏掉了几个文件或者备注信息写错了,想撤销后重新提交。怎么办?
git的设计者也考虑到人容易犯错误,特地为这种场景设计了一个“改过自新(amend)”的机会。这种情况都不需用更具一般性的 git reset
命令,然后再git commit
,而是直接git commit --amend
。
如下代码创建了c.txt和d.txt两个文件,本打算两个文件一起提交的,但一时笔误,只把c.txt提交了,d.txt没有提交。
➜ GitTutorial git:(master) echo "ccc" > c.txt
➜ GitTutorial git:(master) ✗ echo "ddd" > d.txt
➜ GitTutorial git:(master) ✗ git add *
➜ GitTutorial git:(master) ✗ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: c.txt
new file: d.txt
➜ GitTutorial git:(master) ✗ git commit c.txt -m 'add c.txt and d.txt'
[master 2ef4359] add c.txt and d.txt
1 file changed, 1 insertion(+)
create mode 100644 c.txt
➜ GitTutorial git:(master) ✗
如何把d.txt也提交? 直接 git commit --amend c.txt d.txt
进入vi编辑区,重新编辑提交注释信息(注意:新增加的d.txt在命令行上已经携带d.txt文件了),保存退出(:wq
)。
add c.txt and d.txt
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Wed Nov 30 20:36:05 2016 +0800
#
# On branch master
# Changes to be committed:
# new file: c.txt
# new file: d.txt
#
不妨假设新的注释内容修改为:
add c.txt and d.txt (don't forget d.txt)
按 :wq
保存退出后,显示
2 files changed, 2 insertions(+)
➜ GitTutorial git:(master) git commit --amend c.txt d.txt
[master 0935027] add c.txt and d.txt (don't forget d.txt)
Date: Wed Nov 30 20:36:05 2016 +0800
2 files changed, 2 insertions(+)
create mode 100644 c.txt
create mode 100644 d.txt
➜ GitTutorial git:(master) git status
On branch master
nothing to commit, working directory clean
➜ GitTutorial git:(master)
如果你愿意,你还可以继续后悔最后一次提交,比如我们还需要追加e.txt文件,这次我们用GUI图形界面演示(演示前,先自行增加e.txt,并加入索引区):
进入commit会话框 选择Amend图标上图所示的步骤:
- 右击Project -> Team -> Commit ...
- 选择右上角的 **Amend (Edit Previous Commit) 图标
- 勾选需要追加的 e.txt
- 修改注释内容,并提交。
事后可以查看日志,并对比 git log
和 git reflog
有什么不同 ? 自己做实验观察。
amend失灵的时候
但是如果我们不是追加,而是想删除一些呢? 比如从之前的提交了c.txt,d.txt和e.txt,要amend成只提交d.txt呢? 尝试的结果居然不可以(命令
git commit --amend d.txt -m 'only d.txt, remove c.txt and e.txt'
)。
需要真的撤销,再提交。
撤销版本区(真的撤销再提交)
撤销实操
所谓的
回滚
其实就是将分支游标
master指向之前的提交,重置命令git reset
上场:git reset --hard commit-ID
即可。
$ git reset --hard <tag/branch/commit id>
接着需要强制推送到远程:
$ git push <reponame> --force
但是强制
推送远程,这个操作很危险,通常权限会被禁止:
remote: GitLab: You are not allowed to force push code to a
protected branch
on this project.
To http://gitlab.com/blueocean/boxstore.git
! [remote rejected] master -> master (pre-receive hook declined)
在gitlab
中,项目的master
与owner
都很可能没有这个权限,只有gitlab
的管理员才有这个权限。
撤销图解
章节开头提到:
所谓的
回滚
其实就是将分支游标
master指向之前的提交,重置命令git reset
上场:git reset --hard commit-ID
即可。
- 一个分支提交序列
- 回退到
HEAD
当执行git reset HEAD
时,不会做任何事情。因为当前分支本身就是指向HEAD
的。
- 回退1步骤
$ git reset HEAD~1
其中符号HEAD~1
表示HEAD
回退1步(即:HEAD的父亲节点)。
HEAD~1
is shorthand case for “the commit right before HEAD”, or put differently “HEAD’s parent”。
回退完后,HEAD
指针:
- 回退2步
$ git reset HEAD~2
指针指向:
回退2步复杂的参数
git reset
概念很简单,就是回退到某个提交点。但是它的参数蛮复杂,如下三条命令的区别是什么?
git reset --hard <commit-id>
git reset --soft <commit-id>
git reset --mixed <commit-id> (默认情况)
我们知道git
有:工作区,索引区和版本库的概念。这三个选项参数就是影响对三个区的不同处理。简单说,选项参数就是reset
的程度:
-
soft
: 程度最轻,仅仅是把版本库的重置,索引区和工作区没做任何修改。 -
mixed
: 程度中,也是默认操作,它把版本区和索引区都重置了,但是工作区没有重置。 -
hard
: 程度最强,三个区全部重置了。
人们习惯
--hard
,为什么呢?因为重置后,人们常常需要用肉眼去核对,核对的方式往往是打开某个目录看看或文件看看,而这些都是在工作区
的状态。
三选项对比
以下图例:绿色的表示
reset
的了;红色表示没有reset
。
soft
版本区被重置了,但是索引区和工作区都没有被重置。
mixed
版本区和索引区都被重置了,但是工作区都没有被重置。
hard
版本区、索引区和工作区都被重置了。
快速实验
快速做个实验验证一下,分别创建c1.txt
,c2.txt
和c3.txt
,三个文件分别提交三次,然后回退到中间那次提交:
- 连续3次提交
echo "c1" > c1.txt
git add c1.txt && git commit -m 'c1'
echo "c2" > c2.txt
git add c2.txt && git commit -m 'c2'
echo "c3" > c3.txt
git add c3.txt && git commit -m 'c3'
- 回退到中间
git reset HEAD~1
我们选择的是默认的--mixed
模式。
- 查看状态
$ git status
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)
c3.txt
nothing added to commit but untracked files present (use "git add" to track)
$ ls
c1.txt c2.txt c3.txt
的确从文件系统上看,文件c3.txt
并没有消失,但是从索引区看,它消失了(就是回退到中间了)。
查看日志
查看日志时候,现在只能看到最前面的两条commit:
$ git log
c4b06a0 c2
81ee32e c1
但是这并不是表示git
就放弃对reset
的追踪了,实际上,我们还可以对刚才的reset
进行回滚,我们查看更详细的日志:
$ git reflog
c4b06a0 HEAD@{0}: reset: moving to HEAD~1
c51558f HEAD@{1}: commit: c3
c4b06a0 HEAD@{2}: commit: c2
81ee32e HEAD@{3}: commit (initial): c1
如果我们回到之前的c3,也完全可以。
git revert 与 git reset 的区别
刚才,git reset
后,可能因为权限的问题无法强行git push --force
。但是难道如果程序员误操作提交了一次错误的东西到master
就没法回滚了(指不需要gitlab
管理员来回滚)?
可以用git revert HEAD
,它跟git reset
的不同主要有两点:
-
git reset
是指HEAD
指针,重新指向某个commit-id的位置。并且它后续的commit-id会被删除。git revert
会产生一条新的commit,原有的commit-log并不会发生任何变化。 -
git reset
只是改变HEAD的指向。而git reset
是真的 回收,它不仅可以回收最后一次的,还可以回收中间的,它回收中间的,中间之后的并不回收。比如,连续三次提交了三个文件,分别是c1.txt, c2.txt和c3.txt,如果回收第二个,那么会剩2个文件,分别是c1.txt和c3.txt,而不会只有c1.txt。 -
连续三次提交3个文件
mkdir revertlab && cd revertlab && git init
echo "c1" > c1.txt
git add c1.txt && git commit -m 'c1'
echo "c2" > c2.txt
git add c2.txt && git commit -m 'c2'
echo "c3" > c3.txt
git add c3.txt && git commit -m 'c3'
然后撤销中间那次:
$ git revert HEAD~1
会自动编写 commit
Revert "c2"
This reverts commit c86b27c706d6a88082958818643e6b26db8b7300.
# 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: c2.txt
#
查看日志,发现的确是产生一条新的commit:
$ git log --oneline
c94de9d Revert "c2"
a6681f2 c3
c86b27c c2
402e6df c1
查看本地文件:
$ ls
c1.txt c3.txt
很惊讶的是,它是精准的撤销中间那次操作c2,c3.txt文件依然保留了。
总结
- 撤销工作区:
git checkout -- <files>
- 撤销版本区(重新编辑最后一次提交):
git commit --amend <files>
- 撤销版本区(真的撤销再提交):
git reset --soft HEAD^
接着git commit <files> -m ''
-
git reset
与git revert
的区别。
网友评论