git reset & checkout 详解

基础

首先,我们来了解下 git 存储的逻辑分区:

  • workspace(工作目录):本地直接编辑的结果
  • stage/index(索引区): workspace 内的内容可暂存到stage中
  • repository(仓库):将当前stage打包起来,作为一次归档,提交到repository中

git.png

这里涉及2个简单的基本命令:

  • git add:将文件从workspace添加到stage
  • git commit:将当前stage的所有内容添加到repository中

至于其逆向操作,有很多文章写的是reset和checkout,如下所示:

git2.png

之所以这么写,我理解主要是由于git reset的操作起始点是repository,而git checkout的落脚点是workspace,但这么写并不是很全面,掩盖了这两个命令的真正含义:

  • git reset:用respository中特定commit来重置head下的repository、stage、workspace
  • git checkout:用于分支切换&从respository(stage)中检出特定的commit覆盖当前的workspace,

下面我们展开细讲一下这两个命令

git reset

要理解reset,首先要先理解commit&head的概念:

  • commit:是指respoistory中每次保存(提交)后的结果,指向一个完整的版本记录
  • head:是一个指针,代表当前正在操纵的版本(commit),可以指向feature、master等有名分支,也只指向某一commit id 直接代表的版本

git3.png

开篇已经说了,git reset 实际上就是在修改head指向的commit,所以相当于会将head指向repository中特定的commit。同时根据修改head后,是否级联修改stage、workspace,可进一步的细分成三种模式hard、sort、mixed(默认),如下图所示:

git4.png

hard

修改范围

会同时重置repository、stage、workspace

场景

想放弃当前所有修改:

  1. 写了一段时间并且add & commit了
  2. 刚写的不大行,想放弃整个修改
  3. 这时候就可以用hard模式将repository、stage、workspace重置到修改之前

git reset --hard HEAD1 # HEAD1 代表上一个版本

git5.png

# 最初只有 a.txt 文件
sola@MB1 test % git status
On branch master
nothing to commit, working tree clean
sola@MB1 test % ls
a.txt
sola@MB1 test % git log
commit febd1475ef294c1563212b8f20c37d48f590a557
Author: sola 
Date:   Mon Nov 22 22:45:53 2021 +0800

    add a

# 创建、stage、commit b.txt 文件
sola@MB1 test % touch b.txt
sola@MB1 test % git add .
sola@MB1 test % git commit -m 'add b'
[master a3d7d32] add b
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 b.txt
# b.txt 已经在 respository 中
sola@MB1 test % git log
commit a3d7d3286ddb20c0a442233c43b53a025b5a8f97 (HEAD -> master)
Author: sola
Date:   Thu Dec 9 23:33:55 2021 +0800

    add b

commit febd1475ef294c1563212b8f20c37d48f590a557
Author: sola
Date:   Mon Nov 22 22:45:53 2021 +0800

    add a
    
# 发现 b.txt 是个无效的提交,还原到上次commit之前的最初状态
sola@MB1 test % git reset --hard head~1
HEAD is now at febd147 add a
sola@MB1 test % git status
On branch master
nothing to commit, working tree clean
sola@MB1 test % ls
a.txt
复制代码

soft

修改范围

会重置repository,但保留stage、workspace中的改动

场景

用于合并commit:

  1. 写了一段时间并且add&commit

  2. 然后发现一点小问题,修改了下,又add&commit

  3. 两次commit实际上都在完成一个feature,当前提交了两次就会显得commit message不清晰,期望合并

  4. 这时候就可以用soft模式将之前的两次commit重置掉

git reset --soft HEAD2 # HEAD2 代表2次commit之前的版本

  1. 直接commit即可合并

git6.png

# 最初只有 a.txt 文件
sola@MB1 test % git status
On branch master
nothing to commit, working tree clean
sola@MB1 test % ls
a.txt
sola@MB1 test % git log
commit 7e9f983f3821dda4a2a1bcdf990aed55538a4332
Author: sola
Date:   Thu Dec 9 23:44:18 2021 +0800

    add a

# 第一次修改 a.txt、stage、commit
sola@MB1 test % vim a.txt
sola@MB1 test % git add .
sola@MB1 test % git commit -m 'update a'
[master 78c37f9] update a
 1 file changed, 1 insertion(+)
 
# 第二次修改 a.txt、stage、commit
sola@MB1 test % vim a.txt
sola@MB1 test % git add .
sola@MB1 test % git commit -m 'update a'
[master 33a5438] update a
 1 file changed, 1 insertion(+)

# 查看当前 commit 日志
sola@MB1 test % git log
commit 33a54389433840eb8c42253e588b0e3bde85fd0c (HEAD -> master)
Author: sola
Date:   Thu Dec 9 23:46:16 2021 +0800

    update a

commit 78c37f933cd9f696c2b9cf5cd3e364670200d93d
Author: sola
Date:   Thu Dec 9 23:45:03 2021 +0800

    update a

commit 7e9f983f3821dda4a2a1bcdf990aed55538a4332
Author: sola
Date:   Thu Dec 9 23:44:18 2021 +0800

    add a
    
# 合并 33a5438 & 78c37f9 两次 commit,保证 commit log 简洁
sola@MB1 test % git reset --soft head~2
sola@MB1 test % git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   a.txt

sola@MB1 test % git add .
sola@MB1 test % git commit -m 'update a'
[master 4f78d11] update a
 1 file changed, 2 insertions(+), 1 deletion(-)
sola@MB1 test % git log
commit 4f78d110bb3ed603ecbf466cbe23d1714e26b191 (HEAD -> master)
Author: sola
Date:   Thu Dec 9 23:47:29 2021 +0800

    update a

commit 7e9f983f3821dda4a2a1bcdf990aed55538a4332
Author: sola
Date:   Thu Dec 9 23:44:18 2021 +0800

    add a
复制代码

PS:实际上可以用git commit --amend,git rebase & squash 来修正,更方便,这里仅做为演示

mixed

mixed

会重置repository、stage,只保留workspace中的改动

场景

显然易见,可以配合add,实现类似soft的合并commit的效果

更多场景是,用于丢弃当前已经add的结果

  1. 写了一段时间并且add
  2. 发现刚刚add时候,add了一些无关的文件,想删除
  3. 这时候就可以用mixed(默认)模式来重置stage

git reset --mixed HEAD # HEAD 当前版本

git reset HEAD # HEAD 当前版本

# 添加 b.txt 到 stage
sola@MB1 test % touch b.txt
sola@MB1 test % git add b.txt
sola@MB1 test % git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	new file:   b.txt

# 放弃本次 b.txt 的改动
sola@MB1 test % git reset head
sola@MB1 test % git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)

	b.txt
复制代码

总结

模式 是否重置repository 是否重置stage 是否重置workspace 常用场景
hard Y Y Y 放弃所有修改
soft Y N N 合并commit
mixed(默认) Y Y N 放弃stage中的修改

这里只是枚举了几个常用的场景,在熟练掌握reset的范围后,可以更加自由的搭配其他命令来管理整个git,避免思维定式

git checkout

git checkout 有两个特性:

  • 切换分支:不带具体的文件路径时,用于修改head当前指向的分支
  • 检出文件:带具体的文件路径时,从指定的respository 或者 stage 中恢复文件

切换分支

该feature相对简单,我们直接上代码

# 查看当前的所有分支
sola@MB1 test % git branch
  feature
* master
# 从 master 切换到 feature
sola@MB1 test % git checkout feature
Switched to branch 'feature'
# 创建并切换到一个新分支
sola@MB1 test % git checkout -b feature2
Switched to a new branch 'feature2'
复制代码

检出文件

对于检出文件,网上有两种说法,有的说是从stage中恢复文件,有的说是从respository中恢复文件,我们先来看下这两种恢复

从stage恢复文件

# 修改 a.txt & 添加到 stage
sola@MB1 test % cat a.txt
0
sola@MB1 test % echo 1 > a.txt
sola@MB1 test % git add .
sola@MB1 test % git status
On branch feature
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   a.txt
 
# 修改 a.txt
sola@MB1 test % echo 2 > a.txt
sola@MB1 test % git status
On branch a.txt
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   a.txt

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.txt

# 从 stage 中恢复
sola@MB1 test % git checkout -- a.txt
sola@MB1 test % git status
On branch feature
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   a.txt

# 最终版本与首次 add 到 stage 中的一致
sola@MB1 test % cat a.txt
1
复制代码

从respository恢复文件

# 修改 a.txt (未添加到缓存区)
sola@MB1 test % cat a.txt
0
sola@MB1 test % git status
On branch feature
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.txt

no changes added to commit (use "git add" and/or "git commit -a")
# 从 respository 中恢复
sola@MB1 test % git checkout -- a.txt
sola@MB1 test % git status
On branch feature
nothing to commit, working tree clean
# 最终版本与 respository 中的一致
sola@MB1 test % cat a.txt
0
复制代码

对于从respository恢复文件的场景,我个人是倾向于总结为从stage中恢复文件,因为在该场景下 respository 与 stage 内的版本其实一致的,你说从respository恢复也好,从stage恢复也罢,其实都是一样的效果,为了简化记忆,我们可以归纳为:

git checkout 是从 stage 中恢复文件

参考

这才是真正的GIT——GIT内部原理

用21张图,把Git 工作原理彻底说清楚

Git Reset 三种模式

Git 中 git checkout -- 的真正用法

git checkout命令

git checkout 命令详解

猜你喜欢

转载自juejin.im/post/7040638672584048671