Git使用教程三

人生没有回头路,生命不会给谁第二次选择的机会,一瞬间的犹豫有时会完全改变一个人以后的一生。正因如此,所以人生脚下的每一步才需要倍感珍惜。

如果使用Git则不同了,Git不仅可以让你回到过去,而且还可以从过去回到那个你曾经到过的未来,简直是开挂的存在。Git的上面的能力,来自于两个命令reset和checkout,本文重点就是介绍这两个命令的使用,而且reset和checkout两个命令也是需要熟练使用的命令,平常开发中也是非常常用的命令。

git reset

git reset命令用于将当前HEAD复位到指定状态。一般用于撤消之前的一些操作(如:git add,git commit等)。

git reset [-q] [<tree-ish>] [--] <paths>…?
git reset (--patch | -p) [<tree-ish>] [--] [<paths>…?]
EXPERIMENTAL: git reset [-q] [--stdin [-z]] [<tree-ish>]
git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]

一般可以使用git reset命令的操作,意味着文件曾被commit值Git仓库。如果文件只是在暂存区则不需要使用git reset命令。

--mixed 默认

在复位位置的同时,会只保留工作目录的内容,将暂存区和Git仓库的内容更改为和reset目标节点一致。会将reset复位之前节点和reset目标节点之间的差异放入工作目录中。

假设一个文件commit了3次,每次提交内容一次顺序添加01,02,03,然后第4次只在工作目录做了更改,没有add至暂存区。

$ git log
commit 56013bbaa7cccf98089912ca88e4ee0e74cb5bbc (HEAD -> master)
Author: Mike <Mike@git.com>
Date:   Sun Sep 1 13:49:51 2019 +0800

    03 commit

commit 60b46da362627174b739dc7d73894e98d88f1514
Author: Mike <Mike@git.com>
Date:   Sun Sep 1 13:49:24 2019 +0800

    02 commit

commit 12097924846658cc03c1f13a0ce1fa918f12c105
Author: Mike <Mike@git.com>
Date:   Sun Sep 1 13:48:58 2019 +0800

    01 commit
	
# 复位至第2次提交后的位置
$ git reset 60b46da362627174b739dc7d73894e98d88f1514
Unstaged changes after reset:
M       hello.txt

使用git diff查看工作目录和暂存区差异性。

$ git diff
diff --git a/hello.txt b/hello.txt
index f284b82..c005301 100644
--- a/hello.txt
+++ b/hello.txt
@@ -1,3 +1,4 @@
 01
 02
-03
\ No newline at end of file
+03
+04
\ No newline at end of file

在reset命令执行成功后,使用git log查看提交记录,那么当前复位reset节点后续的提交记录也会被清除,这是跟reset HEAD不同的地方

$ git log
commit 60b46da362627174b739dc7d73894e98d88f1514
Author: Mike <Mike@git.com>
Date:   Sun Sep 1 13:49:24 2019 +0800

    02 commit

commit 12097924846658cc03c1f13a0ce1fa918f12c105
Author: Mike <Mike@git.com>
Date:   Sun Sep 1 13:48:58 2019 +0800

    01 commit

--hard

在复位的同时,会将暂存区和工作目录同时同步至reset节点相同内容。暂存区和工作目录里的内容会被完全重置为和reset新位置相同的内容。换句话说,就是你的没有commit的修改会被全部擦掉。

$ git reset --hard 60b46da362627174b739dc7d73894e98d88f1514
HEAD is now at edbe7aa 02 commit

--soft

该命令跟--mixed有些相似,不同的地方是,它会将Git仓库与暂存区不同的地方保存至暂存区,而不是保存至工作目录。

HEAD

有两个字符~和^常与HEAD连用,有关~和^的差异,当介绍merge命令后再细作分析,HEAD与~组合表示需要回退的步数。

$ git log --pretty=oneline
0b23fe59640cc449678f304e49cd37decdb501bd (HEAD -> master) 04 commit
32e055904d42b4cfcae81c51d4b5e9a6c618fb1a 03 commit
047edbce21192b584052c1640bc33087a70711ca 02 commit
b6c96148f624cfd9412652993437be65513f5794 01 commit

假设有上述4次commit,那么:

  • HEAD~或者HEAD~1表示HEAD前一次提交,也即是03 commit;
  • HEAD~~或者HEAD~2表示HEAD的前二次提交,也即是02 commit;
  • HEAD~~~或者HEAD~3表示HEAD的前3次提交,也即是01 commit;

上面介绍的reset结合commit id的复位操作,仍然可以结合HEAD和~实现相同的逻辑。

$ git reset --hard HEAD~2
HEAD is now at 047edbc 02 commit

$ git log --pretty=oneline
047edbce21192b584052c1640bc33087a70711ca (HEAD -> master) 02 commit
b6c96148f624cfd9412652993437be65513f5794 01 commit

除了上述介绍的几个常用命令外,还有git reset --merge命令,这个命令在介绍merge后再做介绍。

在使用git reset命令时,不仅可以复位某一次提交,即根据commit id或者HEAD复位,而且还可以复位某个文件。

把文件hello.txt从暂存区的索引中去除,但是已经提交至Git仓库的索引值不会被删除,只会删除当前一次没有commit的索引记录。

$ git reset -- hello.txt

回到未来

使用git reset命令复位回到了过去,但是发现回滚错了,原来的提交是正确的,那么还可以使用git reset重新回到未来。唯一的难题就是如何确定未来的某个标识,使用git log是无法得到所有的commit id的,这时候需要借助于git reflog命令查看所有的提交。

$ git reflog
aef9ccb (HEAD -> master) HEAD@{0}: commit: 1.txt
3f85350 HEAD@{1}: reset: moving to HEAD
3f85350 HEAD@{2}: reset: moving to HEAD
3f85350 HEAD@{3}: commit: 1.txt
047edbc HEAD@{4}: reset: moving to HEAD~2
0b23fe5 HEAD@{5}: commit: 04 commit
32e0559 HEAD@{6}: reset: moving to HEAD~
586a303 HEAD@{7}: commit: 04 commit
32e0559 HEAD@{8}: commit: 03 commit
047edbc HEAD@{9}: commit: 02 commit
b6c9614 HEAD@{10}: commit (initial): 01 commit

$ git reset HEAD 0b23fe5

$ git reset 0b23fe5
Unstaged changes after reset:
M       hello.txt

$ git log --pretty=oneline
0b23fe59640cc449678f304e49cd37decdb501bd (HEAD -> master) 04 commit
32e055904d42b4cfcae81c51d4b5e9a6c618fb1a 03 commit
047edbce21192b584052c1640bc33087a70711ca 02 commit
b6c96148f624cfd9412652993437be65513f5794 01 commit

git checkout

git checkout用于切换分支或者恢复目录树文件。git checkout是最常用的命令,也是一个很危险的命令,因为这条命令会重写工作区。重写工作区意味着工作区现有的文件会被覆盖,而且找不回了,因为在工作区文件的最新更改并没有纳入Git仓库。

git checkout [-q] [-f] [-m] [<branch>]
git checkout [-q] [-f] [-m] --detach [<branch>]
git checkout [-q] [-f] [-m] [--detach] <commit>
git checkout [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]
git checkout [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>…?
git checkout [<tree-ish>] [--] <pathspec>…?
git checkout (-p|--patch) [<tree-ish>] [--] [<paths>…?]

示例一

假设当前在master分支,而且master分支中有一个文件名称为hello,但是也有一个分支名称是hello。如下是git checkout命令的执行情况。

$ git branch
  dev01
  dev02
  hello
* master
$ git checkout hello
Switched to branch 'hello'

从命令执行结果可以知道,这时候并没有将hello文件恢复至工作区,而是执行了一个切换分支的命令,将分支切换至了hello分支。如果想在这种情况下从Git版本库中检索出hello文件,可以使用如下命令:

$ git checkout -- hello

示例二

如果一个文件demo.txt已经提交至Git仓库,这时候在工作区对demo.txt做了更改,但是更改后并没有add至暂存区,git checkout命令执行如下:

$ git checkout demo.txt
Updated 1 path from the index

命令执行后会将暂存区demo.txt覆盖工作区的demo.txt。

仍然借助上述场景,在更改后将demo.txt文件add至暂存区,然后再次进行第三次更改,但是本次只在工作区更改而不add至暂存区,这时候再次执行git checkout命令,仍然是将暂存区demo.txt文件覆盖工作区的demo.txt。

还可以使用如下命令,将暂存区的所有文件恢复至工作区。

$ git checkout .

示例三

如下命令可以直接将分支切换到一个新建的分支上。

$ git checkout -b dev03
Switched to a new branch 'dev03'

示例四

假设一个文件有3次commit,如果想从Git仓库检索出第1次提交的文件到暂存区和工作区,这时候可以使用如下命令:

$ git log --pretty=oneline
9f6039f6be8531a4ae0891ac96eddb40013ce657 (HEAD -> master) 03 commit
f63e8fc63c6f71339d8ae116afe850443e43e5e0 02 commit
bf407c97c7a35ea844d73a276dc33d3c215b7c0e 01 commit

$ git checkout master~2 hello.txt
Updated 1 path from de602c6
#或者
$ git checkout HEAD~2 hello.txt
Updated 1 path from de602c6
#或者
$ git checkout bf407c hello.txt
Updated 1 path from de602c6

示例五

如果想要恢复某次commit的文件至暂存区和工作区。

$ git log --pretty=oneline
9f6039f6be8531a4ae0891ac96eddb40013ce657 (HEAD -> master) 03 commit
f63e8fc63c6f71339d8ae116afe850443e43e5e0 02 commit
bf407c97c7a35ea844d73a276dc33d3c215b7c0e 01 commit

$ git checkout f63e8fc63c6f71339d8ae116afe850443e43e5e0 hello.txt
Updated 1 path from 0d024bd
#或者
$ git checkout 8af5b8 hello.txt
Updated 1 path from de602c6

在使用checkout从commit记录中恢复文件时,可以使用commit id的整个标识,也可以选择id的前面5~6位,只要可以作为唯一标识即可。

其它

git rm --cached

当将新文件add至暂存区时,但是还未commit至Git仓库,使用git status命令查看状态,命令执行后提示如下:

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

        new file:   1.txt
        new file:   2.txt

通过上述命令提示,可以知道使用git rm --cached命令可以将暂存区文件还原至工作目录,恢复至add之前的状态。

$ git rm --cached 1.txt 2.txt
rm '1.txt'
rm '2.txt'

git checkout

当将已经提交至暂存区的文件在工作目录再次被更改时,仍然没有commit值Git仓库,这时候使用git status命令查看状态。

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:   1.txt
        modified:   2.txt

这里提示可以使用checkout命令摒弃工作目录的更改,即表示将暂存区的文件覆盖已更改的工作目录的源文件。注意,这里使用checkout命令是有风险的,有可能将费了很久的更改内容一下就全部被覆盖了,而且是不可恢复。

$ git checkout 1.txt 2.txt
Updated 2 paths from the index

git reset HEAD

如果文件已经在Git仓库了,这时候对工作目录的文件更改,然后add至暂存区,使用git status查看状态如下:

$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   1.txt
        modified:   2.txt

如果使用git reset HEAD命令,命令执行结果如下:

$ git reset HEAD 1.txt 2.txt
Unstaged changes after reset:
M       1.txt
M       2.txt

小结

本文介绍内容不多,就两个命令reset和checkout的使用,这两个命令可以说是Git的强大所在,需要熟练使用。重点需要掌握,如何从暂存区恢复至工作区,如何从Git仓库恢复至暂存区或者工作目录。其实,Git的工作主要就是对工作目录、暂存区和Git仓库的操作,如果可以在这三个区间游刃有余,可以说是大师级别的存在了。

接下来会继续介绍merge的操作和冲突解决方式,以及如何对比差异。

参考资料

Git教程

Git Community Book 中文版

评论

您确定要删除吗?删除之后不可恢复