此文在阅读前需要有一定的git命令基础,若基础尚未掌握,建议先阅读这篇文章Git命令播报详版
在利用git协作过程中,经常需要进行代码的撤销操作,这个行为可能发生在工作区,暂存区或者仓库区(或版本库)。
我们先讨论在工作区与暂存区发生的撤销行为,这里会有两个命令提供帮助,git restore
与git checkout
。
后面我们会讨论在仓库区发生的撤销行为,这里同样会有两个命令提供帮助,git reset
与git revert
。
git restore
主要是在工作区与暂存区之间进行撤销操作。
我们在工作区修改了某个文件,然后我们想放弃此次修改,那么可以使用git restore pathspec
修改文件后,我们查看文件状态,可以看到文件状态已经变化
git status -s
输出:
M README.md
撤销修改后查看状态,可看到工作区已经干净
git restore README.md
git status
输出:
...
nothing to commit, working tree clean
如果我们同时修改了多个文件,想全部撤销,则可以执行git restore .
命令
结论:git restore pathspec
指令使得在工作空间但是不在暂存区的文件撤销更改(内容恢复到没修改之前的状态)。
git restore --staged pathspec
指令将暂存区的文件撤回到工作区,但不会更改文件的内容。
我们将某个文件进行修改并添加到暂存区,查看状态
git status
输出:
...
Changes to be committed:
...
modified: README.md
进行撤销后,再进行状态的查看
git restore --staged README.md
git status
输出:
...
Changes not staged for commit:
...
modified: README.md
...
如果我们想撤销暂存区的所有文件改动到工作区,则可以执行git restore --staged .
命令
从上面我们能看出git restore
命令的局限性:
接下来我们可以看看git checkout
的表现。
git checkout [
语法:
checkout
命令主要是覆盖工作区(如果
不省略,也会替换暂存区中相应的文件),且不会改变 HEAD
指针。
是可选项,如果省略则默认是从暂存区检出。这和下面将要讲述的 git reset
重置大不相同,重置的默认值是HEAD
,即等于git reset HEAD
。
和引用(或者提交)
同名而冲突,可以在
前用两个连续的短线减号作为分隔。如git checkout HEAD --
语法示例:
git checkout .
或者 git checkout
命令时,会用暂存区全部或指定的文件替换工作区的文件。这个操作很危险,会清除工作区中改动。git checkout HEAD .
或者 git checkout HEAD
命令时,会用 HEAD
提交的全部或者部分文件替换暂存区以及工作区中的文件。这个命令也是极具危险性的,因为不但会清除工作区中的改动,也会清除暂存区中的改动。git revert -n commit-id
:只会反做commit-id对应的内容,然后重新commit一个信息,不会影响其他的commit内容
比如,我们commit了三个版本(版本一、版本二、 版本三),突然发现版本二不行(如:有bug),想要撤销版本二,但又不想影响撤销版本三的提交,就可以用 git revert
命令来反做版本二,生成新的版本四,这个版本四里会保留版本三的东西,但撤销了版本二的东西。如下图所示:
git revert -n commit-idA..commit-idB
:反做commit-idA到commit-idB之间的所有commit
注意:使用-n
是应为revert后,需要重新提交一个commit信息,然后在推送。如果不使用-n
,指令后会弹出编辑器用于编辑提交信息
适用场景:如果我们想撤销之前的某一版本,但是又想保留该目标版本后面的版本,记录下这整个版本变动流程,就可以用这种方法。
在git revert
操作过程中,冲突难以避免,下面有几种解决方式
git revert --abort
:冲突发生后,当前的操作会回到指令执行之前的样子,回到原始的状态,相当于什么事没有发生git revert --quit
:冲突发生后,从反做操作行为中退出,该指令会保留文件冲突git revert --skip
:冲突发生后,跳过此次反做操作git revert --continue
:冲突发生后,解决冲突并add
后,执行该命令,会继续之前的操作流程 git add .
git commit -m "提交的信息"
git push
先查看历史提交记录
git log --oneline
输出:
0681044 (HEAD -> dev, origin/dev) v3
3a854db v2
fc00739 v1
8785cc6 dev提交
709627d Initial commit
对提交信息为v1的进行反做
git revert -n fc00739
输出:
Auto-merging README.en.md
CONFLICT (content): Merge conflict in README.en.md
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
error: could not revert fc00739... v1
...
出现冲突,进行修改,重新添加文件到暂存区,并进行提交
git add .
git revert --continue
输出:
hint: Waiting for your editor to close the file... unix2dos: converting file D:/project/personal/gittestproject/.git/COMMIT_EDITMSG to DOS format...
dos2unix: converting file D:/project/personal/gittestproject/.git/COMMIT_EDITMSG to Unix format...
[dev 972bfce] Revert "v2"
2 files changed, 2 insertions(+), 2 deletions(-)
推送到远程
git push
查看提交历史记录
git log --oneline
输出:
77adce6 (HEAD -> dev, origin/dev) Revert "v1"
0681044 v3
3a854db v2
fc00739 v1
8785cc6 dev提交
709627d Initial commit
先通过举个例子,来一个感性的认识。下面这两条命令让 hotfix 分支向后回退两个提交。
git checkout hotfix
git reset HEAD~2
仓库区HEAD
指针指向当前分支最新提交,回退两个提交后,hotfix 分支末端的两个提交现在变成了孤儿提交,如果你的提交还没有提交到远程,可以如上所示,直接用git reset
撤销这些提交。
若之前提交已经提交到远程,使用git reset
进行回退后,再使用git push
提交更改,此时会报错,因为我们本地库HEAD
指向的版本比远程库的要旧。所以我们要用git push -f
强制推上去,就可以了,那么回退就成功了!
适用场景: 如果想恢复到之前某个提交的版本,且那个版本之后提交的版本我们都不要了,就可以用这种方法。
为了演示下面这些例子,假设我们修改了 file.txt 文件并第三次提交它。 现在的历史看起来是这样的:
现在,假设我们运行git reset HEAD~
(后面可能会跟不同的参数)。
reset
做的第一件事是移动所在分支 HEAD
的指向。
运行 git reset HEAD~ –-soft
将会指向 9e5e6a4,这与checkout
通过切换分支来改变 HEAD
自身不同。
结合上图,我们理解一下发生的事情:它本质上是撤销了上一次 git commit
命令。 当你在运行 git commit
时,Git 会创建一个新的提交,并移动当前所在分支 HEAD
的指向,使之指向最新提交。 当你将它 reset --soft
回 HEAD~
(HEAD
的父结点)时,其实就是把该分支移回原来的位置,而不会改变暂存区和工作区。
具体表现是:撤销上一次git commit
的文件,将其放在暂存区,并与暂存区文件进行合并(如果有相同文件改动的话),工作区没有改变
接下来,git reset HEAD~ –-mixed
会用 HEAD
指向的当前快照的内容来更新暂存区。
这也是默认行为,即可以忽略选项,git reset HEAD~
。
现在再看一眼上图,理解一下发生的事情:它依然会撤销一上次提交,但还会取消所有暂存。 于是,我们回滚到了所有 git add
和 git commit
的命令执行之前。
具体表现是:撤销上一次git commit
的文件,并取消所有暂存区文件,将其与工作区文件进行合并(如果有相同文件改动的话)
如果使用 –-hard
选项,git reset HEAD~ --hard
要做的的第三件事情就是让工作区看起来像暂存区。
现在让我们回想一下刚才发生的事情:你撤销了最后的提交(git commit
)、git add
和工作区中的所有工作。
必须注意,–hard
标记是 reset
命令唯一的危险用法,它也是 Git 会真正地销毁数据的仅有的几个操作之一。其他任何形式的 reset
调用都可以轻松撤消,但是 –hard
选项不能,因为它强制覆盖了工作区中的文件。
具体表现是:撤销暂存区与工作区所有文件改动
reset 命令会以特定的顺序重写这三棵树,在你指定以下选项时停止:
--soft
,则到此停止)--mixed
,则到此停止)--hard
)-f