gitの月光宝盒
git作为时下世界最流行的代码版本管理工具,我们无论在工作中还是日常编码时几乎都离不开 git 的使用。但是有时候因为我们自身的误操作,或者其他原来未料到突发事件,需要撤销某个操作或者修改曾经提交的操作,那么我们就需要使用上 git 强大的撤销功能了。
我们在进行撤销操作之前,首先了解两个 git 里面最基本但是很重要的概念:文件状态和reflog。
1 基础概念
1.1 文件状态(file states)
文件状态(file states)概念是 git 中相比较于以往的 CVSs 类软件不同的地方。按官方描述,文件被追踪
之后有三种状态,加上被追踪
之前的状态,这里认为文件有4种状态。如下图:
<div style="text-align:center;">(图1-1)</div>
-
untrack
-- 文件刚新建,还没有被 git 追踪前的状态。 -
tracked
-- 已经被 git 追踪,git 能检测到文件状态是否有改变。(https://git-scm.com/book/en/v2/Getting-Started-Git-Basics)。 -
staged
-- 文件(改变)已经进入'暂存'状态,下一次提交会把此改变提交到本地仓库。 -
commited
-- 文件(改变)已经提交到本地仓库,但是还没有推送到远程仓库,生成一次文件快照。
Ps:文件快照概念是 git 和其他版本管理软件相区分的重要概念之一。
1.2 reflog
reflog显示整个本地仓储的 commit 记录, 包括所有 branch 的 commit, 已经撤销的 commit, 只要 HEAD 发生了变化, 就会在 reflog 里面看得到。同一个改动,在 reflog 里可能出现多次。reflog 在撤销所有影响了本地仓库的改动时非常有用。
2 使用月光宝盒
2.1 tracked -> untrack
命令:git rm --cache <file>
一个新建的文件被记录在 git 中了,但是突然发现我们并不需要把它提交到仓库中,就需要让它回到“自由自在”的状态。比如我刚开始用 mac 的时候,在输入git status
命令查看时才发现一不小心就把.DS_Store文件提交到了 git 里面去,这时候就需要让这个意外的文件回到 untrack
状态。
~ $ touch .DS_Store
~ $ git status
On branch master
Untracked files:
.DS_Store
~ $ git add -N .DS_Store
~ $ git status
On branch master
Changes not staged for commit:
new file: .DS_Store
~ $ git rm --cache .DS_Store
rm '.DS_Store'
~ $ git status
On branch master
Untracked files:
.DS_Store
2.2 staged -> untrack
命令1:git rm --cache <file>
命令2:git reset HEAD <file>
使用上一个命令,同样可以使一个在 staged
状态的文件回到 untrack
状态,无论文件原来是否已经 tracked
。
git rm --cache <file>
命令还可以使已经暂存的文件回到untrack
状态
~ $ git status
On branch master
Changes to be committed:
new file: .DS_Store
modified: file1
~ $ git rm --cache .DS_Store file1
rm '.DS_Store'
rm 'file1'
~ $ git status
On branch master
Changes to be committed:
deleted: file1
Untracked files:
.DS_Store
file1
2.3 staged -> tracked
命令:git reset HEAD <file>
从暂存态回到追踪态,一般情况下是文件本来存在于 git 版本库中,经过用户修改并且使用git-add
命令添加到待提交队列中。如果文件较少,这个过程的撤销意义并不大,因为完全可以把文件修改回原始状态并且再次使用git-add
就可以了,但是如果文件较多,就使用命令比较方便了。
~ $ git status
On branch master
Changes to be committed:
modified: file1
~ $ git reset HEAD file1
Unstaged changes after reset:
M file1
~ $ git status
On branch master
Changes not staged for commit:
modified: file1
从上面可见,虽然文件从staged
回到了tranked
,但是本地修改还是保留了,如果加上 --hard
选项,则会让文件回退到最后一次 commit 时的状态,丢弃掉本地修改。
~ $ git status
On branch master
Changes to be committed:
modified: file1
~ $ git reset HEAD --hard
HEAD is now at a580a0e init
~ $ git status
On branch master
nothing to commit, working tree clean
2.4 undo commit
命令:git reset --option <commit>
想撤销一个 commit
操作其实和撤销 stage 操作一样,就是使用 reset
命令使本地目录回退到项目“曾经的某个状态”,这里的“某个状态”就是指曾经在本地项目做过的操作,可以使用上述提到的 git reflog
查看
~ $ git reflog
a580a0e (HEAD -> master) HEAD@{0}: reset: moving to a580a0e
9b953c1 HEAD@{1}: reset: moving to HEAD@{3}
a580a0e (HEAD -> master) HEAD@{2}: reset: moving to HEAD
a580a0e (HEAD -> master) HEAD@{3}: reset: moving to HEAD
a580a0e (HEAD -> master) HEAD@{4}: reset: moving to HEAD~1
9b953c1 HEAD@{5}: commit: test
a580a0e (HEAD -> master) HEAD@{6}: reset: moving to HEAD~1
eeb881f HEAD@{7}: commit: test
a580a0e (HEAD -> master) HEAD@{8}: reset: moving to HEAD
a580a0e (HEAD -> master) HEAD@{9}: reset: moving to HEAD
a580a0e (HEAD -> master) HEAD@{10}: commit (initial): init
把 git reset --option <commit>
中的 <commit>
替换为上面输出的第一列的HASH值或者HEAD@{number},即可让项目本地仓库回退到指定的状态,此状态之后的文件变化,会根据 --option
值的不同应用到本地文件中,我们使用 git reset --help
可以看到详细的例子介绍
~ $ git reset --help
In these tables, A, B, C and D are some different states of a file. For example, the first line of the first table means that if
a file is in state A in the working tree, in state B in the index, in state C in HEAD and in state D in the target, then "git
reset --soft target" will leave the file in the working tree in state A and in the index in state B. It resets (i.e. moves) the
HEAD (i.e. the tip of the current branch, if you are on one) to "target" (which has the file in state D).
working index HEAD target working index HEAD
----------------------------------------------------
A B C D --soft A B D
--mixed A D D
--hard D D D
--merge (disallowed)
--keep (disallowed)
working index HEAD target working index HEAD
----------------------------------------------------
A B C C --soft A B C
--mixed A C C
--hard C C C
--merge (disallowed)
--keep A C C
working index HEAD target working index HEAD
----------------------------------------------------
B B C D --soft B B D
--mixed B D D
--hard D D D
--merge D D D
--keep (disallowed)
working index HEAD target working index HEAD
----------------------------------------------------
B B C C --soft B B C
--mixed B C C
--hard C C C
--merge C C C
--keep B C C
working index HEAD target working index HEAD
----------------------------------------------------
B C C D --soft B C D
--mixed B D D
--hard D D D
--merge (disallowed)
--keep (disallowed)
working index HEAD target working index HEAD
----------------------------------------------------
B C C C --soft B C C
--mixed B C C
--hard C C C
--merge B C C
--keep B C C
总结
以上所提供的并不是唯一的方法,只是其中一种或多种实现撤销的手段,欢迎一起讨论更多好用的方法。
鉴于 git 的对整个项目做快照的特性,所有操作对于 git 来说都是ADD操作(增加一个“删除”操作,增加一个“修改”操作等),所以我们只需要善用 git reset
和 git reflog
,几乎能完成所有的撤销操作。
参考连接
https://git-scm.com/book/id/v2/Git-Basics-Recording-Changes-to-the-Repository
https://git-scm.com/book/en/v2/Getting-Started-Git-Basics
https://git-scm.com/docs/git-reflog
版权声明:本文为博主原创文章,未经博主允许不得转载,本文首发于xlaoyu