有关 Git 中 commit 的原理 理解 及 reset、checkout 命令详解

对 Git 的学习一直处于学了忘,忘了学的状态。主要是第一次学会的时候没有好好进行运用,当想要使用的时,发现,大半都已经忘记了。从新拾起来的时候,又是发现了大量问题。而后再次学习→忘记→学习→忘记,死循环。因此,我仍是将本身一直陷进去的坑给填好吧,这样,下次忘记的时候容易→学习→忘记→学习。html

更好的阅读体验课移至此处
https://halfmoonvic.github.io...git

一、 三个区

看了许多关于 Git 的教程,大可能是先说工做目录、暂存目录(索引)和版本库(仓库或者HEAD)。都会配些图片,看来看去,我以为在Reset, Checkout, and Revert一文中就三个区的概念最为恰当。一图以蔽之github

  • Working Directory —— 工做目录

视力所及处皆是 Working Directoryide

  • Staged Snapshot

有的将这里称为暂存目录,有的称为索引区。而在Reset, Checkout, and Revert一文中就是简单称之为“快照区”,在下实在是认同。缘由如下讲。svg

  • Commit History

其它文章看到过有将其为版本库或是仓库的,这个让人糊涂。由于 Git 自己就是一个版本控制系统,私觉得这三者联合起来才能称之为版本库。而至于 HEAD,说的仍是比较贴切的。HEAD 只能在这里游动,跑不了。wordpress

而将其称为 Commit Histroy 最为贴切处是由于它很好的描述了这个区域的做用 —— 保存 commit 历史。

二、commit

初学 Git 时,只是简单的学习 add commit 等等基本操做命名,进而去学习各类远程库的推送等等操做,觉得这些好复杂。可等到学完的时候,才发现,本身仍是不怎么懂最为基本的 commit 概念。我本身在学习的过程当中逐渐认识到 commit 的厉害之处,对它的认识也是一直处于变化当中。到如今根本不在意它的中文意思应该是什么,而只是简单称它为 commit ,动词性的或者名词性质的,通通称之为 commit学习

而我就 Git 的我的理解,我认为其思想就是基于 commit 的各类控制操做ui

2.一、 基于 commit 的版本操做控制思想
  1. 最开始对 commit 的理解就是简单的认为其为中文释义——提交。

在咱们修改了文件以后进行git add <file_path>操做,进而进行git commit <file_path>。从 Staged SnapshotCommit History 的操做,这看起来很像一个提交。spa

  1. 继续学习当中,即涉及到第三个区 Commit History 中的 commit 时,让人沉醉 —— 快照。

咱们能够经过 checkout 命名轻松的移动HEAD到不一样的 <commitID> 上,在上一处对 commit 的理解仍是一个动词性的(提交)。这里 commit 却好像是变成了一个名词——快照(我的跟乐意用<commitID>来表示)。翻译

  1. 怀疑阶段——我究竟在 提交(git commit <file_path>)什么?

git commit <file_path>操做,开始会认为是提交了你在 Working Diradd的文件。这个很是对,没有半点错误。但是,你commit操做是 Staged SnapshotCommit History 二者之间的操做。这个叉跨的太大!

  1. 释疑阶段 —— 合体啊~

commit既是提交又是快照
这里所谓的既是提交又是快照指的仅仅是git commit <file_path>一处操做。什么意思?
我在将文件git add <file_path>操做后,我在进行git commit <file_path>操做时,事实上是先对addStaged Snapshot 来的文件进行拍照(快照),而后提交给 Commit History

  1. 到处皆是 commit

私觉得,快照这个动做是时时进行的。如你在 Working Dir 中对文件的add操做,是将 Working Dir 拍照了一下,传给了 Staged Snapshot 了。而后 Staged Snapshot 在将传过来的快照传给 Commit History。这样,从工做目录开始就是快照级别的操做。这些命令操做当中也就不用纠结这个文件究竟是怎样的一副状态了。由于它远在你add的时候就已经被固定为快照了。

2.二、 根据颜色辨别状态

git status时,那个 modified 是显示红色仍是绿色,是依据于 commit 所处状态来决定的。

  1. 显示 红色 就是 「Woring Dir 区的 commit」 与 「Staged Snapshot 的 commit / Commit History 的 commit」 有区别。

红色 modified 即为「Woring Dir 区的 commit」中内容还未被 add。

  1. 显示 绿色 是 「Woring Dir 区的 commit / Staged Snapshot 的 commit」 与 「Commit History 的 commit」 有区别。

绿色 modified 即为「Staged Snapshot 的 commit」还未被 commit。

印象加深图:
modified

2.三、 总结 commit

到如今,我已经不在意 commit 究竟是什么意思了,只是简单称为 commit,若是必定让我翻译一下。我更为乐意称之为「快照」。
至于那种 commit 的传递,我将其认为是 Git 本身内部命令的结果。
而涉及到「提交」一律念大概只有git commit <file_path>中的commit

三、reset 命令

checkout命令相比,我认为reset简单的多,但此命令有危险。除非,你知道本身在作什么。

reset 许多命令模式会变动 master(或者其它分支名,本文只以 master 分支举例)分支位置

3.一、 reset 的三种命令模式:
模式名称 master的位置 暂存区 工做目录
状况1.1 --mixed<file_path> 操控 Commit History 和 Staged Snapshot 修改 修改 不修改
状况1.2 --mixed<file_path> 操控 Staged Snapshot 不修改 修改 不修改
状况 2 --hard 操控 Commit History、Staged Snapshot、Working Directory 修改 修改 修改
状况 3 --soft 操控 Commit History 修改 不修改 不修改

示意以下三图,从左至右依次为:mixed(操做对象为HEAD、状况1.1)、hard、soft三种模式。

  • mixed模式的时候操做对象便可是 HEAD 也能够是 Staged Snapshop
  • soft hard模式针对的操做对象均是 HEAD

若是强行添加<file_path>,则会报错fatal: Cannot do (hard, soft) reset with paths

  • 三种模式操做HEAD时,都可以移动HEAD及其HEAD所处的当前<branch>mixed模式带有<file_path>时则不能够,其操做控制影响的 是 Staged Snapshop 区。



ᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂ 分割线 ᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂ

关于本人所作图片有两处须要阐述,请看下大图:
高清无码大图!!!

  • Working DirStaged Snapshot 两区,我依然画出了历史 commit。不过,未处于激活状态时,是以灰蒙状态般显示的。这个灰蒙状态实际意义上就是不存在的意思,此处是为理解上文中「到处皆 commit」之意而作。真正有多个 commit 的只有 Commit History区。
  • 在我多方多查看参考资料时,发现大可能是以箭头形式来指向,如 Commit HistoryStaged Snapshot。箭头性的示意图给了我一个误会,那就是 Commit History 控制着 Staged Snapshot 区,Staged Snapshot 区控制着 Working Dir 区。

    而事实上,我更乐意用<font style="color: red;">同步</font>一词来作比喻。

    • reset hard的时候,是三区的 commit 相同步的;
    • reset mixed的时候则是 Commit HistoryStaged Snapshot 之间的同步;
    • reset soft就只有 Commit History 本身玩耍了。

但是,光是这样还不够。如git reset hard HEAD^^模式的时候,是将HEAD^^所在的 <commitID>同步至 Staged SnapshotStaged Snapshot 区。看起来仍是用箭头来指示比较合适。
这里箭头容易形成的误导是这看起来像是 Commit History 在发号施令,故放弃使用箭头形式。那么是谁在发号施令呢?答:是 reset 命令(亦或者说是 reset 在使三者相同步)。
施术者不一样,职务不一样,采用的形式不一样。而我,我更偏向于这种同步性的符号,而非箭头这种指示性的符号。

  • 三副动图中,在 Commit History 区变化的是 master 对不一样的 commit 的指向。三个区的所依据的同步状态既是 HEAD 所处的位置。亦能够说是,HEAD指向哪里,master 跟着到哪里。主导的能够说是经过 master 分支 指向 commitHEAD
3.二、 状况1.1 状况1.2 --mixed模式

当为注明reset采用何种模式的时候,默认是采用--mixed模式
此命令经常被用于撤销 Staged Snapshot 中的 add 进来的文件,一些教程如是说。可事实上,这个命令从始至终都没 Working Dir 啥事。

1. git reset <commitID>

此种状况会移动master位置。
以下图所示,其做用是将 Commit HistoryHEAD^^处的 <commitID> 同步至 Staged Snapshot 区。

2. git reset <commitID> <file_path>

以下图所示,HEAD位置是不变的,天然HEAD所指向的分支位置也是不变的。变化的只有 Staged Snapshot 区。

如此,当你进行的add操做时,通常状况下,Working Dircommit 是会超前于 Commit History 区 的 commit
当进行 git reset <file_path>(当未注明 <commitID> 时,默认采用 HEAD) 操做时,Staged Snapshot 区的 commit 恢复到上个状态。
这看起来像是撤销操做,实质上是覆盖操做。便是如上图中的6跑到了如今的7'位置上

3. 其它无关杂项
  1. 事实上, HEAD <file_path> 都可省略书写,

    • git reset <file_path> 等价于 git reset HEAD <file_path>
    • git reset 等价于 git reset HEAD
  2. git reset <commitID> . 并不等价于 git reset <commitID>

git reset HEAD^^ . 也不会移动 HEAD,由于 . 就是表明了全部文件。这样,reset 的操做对象又变成了 Staged Snapshot 区。

3.3 状况2 hard —— git reset --hard <commitID>

此命令用于你作错了事情,想要完全抹除踪影。
采用hard模式的时候,意味着你想要丢弃在 Working Dir 区中的修改。以后继续进行你的工做。详细的 commit 步骤不讲了,对照第一条看便可。放张图给你。

  1. git reset --hard HEAD^^命令使得 master 分支状况发生改变,版本回退了两个,这里就有危险要发生了。

    假如你已经将最新的「6commit」推送到远程库。这个时候,你经过hard回退了两个版本,「5commit」「6commit」已被在本地 Commit History 区 删除掉(我亦用了灰蒙状态来表示)。这个时候,你再次进行git commit <file_path>操做时,新的 commit 将是「55commit」。至此,你已与远程库的 Commit History 历史相冲突了。反正,我能想到的解决办法只有将远程库删除掉,从新推送本地库,除此以外没有其它招了。

  2. git reset --hard HEAD命令

git reset --hard HEAD^^命令相比,git reset --hard HEAD命令要柔和的多。以下图所示:

这个是恢复当前 HEAD 所在的 commit 到如下两个区。也就不存在与远程库相冲突的问题了。

3.4 状况3 soft —— git reset --soft HEAD^^

soft 仅仅是移动 Commit History 区的 master 就不一样的 commit 的指向。没什么好说的了。

四、checkout 命令

  • 状况1.1: 切换HEAD到具体分支「如 git checkout dev
  • 状况1.2: 切换HEAD到匿名分支「如 git checkout <commitID>
  • 状况2: Staged Snapshop 区 <commitID>的同步操做,未有分支切换「如 git checkout <file_path>
  • 状况3: Commit History 区 <commitID>同步操做,未有分支切换 如「git checkout <commitID> <file_path>
4.一、 checkout 做用对象是谁?答:看你写谁
  1. checkout 用于「状况1.1」,或者「状况1.2」时,那么其做用对象就是 Commit History 中的 HEAD

其做用是让 HEAD 指向某一个 <commitID> 并将 此 <commitID> 的内容同步至 Staged SnapshotWorking Dir

  1. 而当 checkout 用于「状况2」,其做用对象为当前 Staged Snapshot 区的 commitID

其做用是让 当前 Staged Snapshop 区的 <commitID> 同步进 当前 Woring Dir

  1. 而当 checkout 用于「状况3」,其做用对象为 Commit History 中 的 <commitID>,而非 HEAD

其做用是 同步Commit History上某一个<commitID>的内容至当下的Staged SnapshopWoring Dir

4.二、 状况1.1 && 1.2

HEAD基本上是一直处于指向某一分支的状况的(HEAD指针指向发生过变更),如 HEAD → master、HEAD → dev 亦或是 HEAD → 匿名分支

  1. git checkout dev

咱们可使用诸如git branch dev般的命令达到切换分支的效果。这里能够注意一下,所谓的分支切换,其实质只不过是更改 HEAD 的指向。切换分支的时候就是在更改 HEAD 指向哪一个分支

  1. git checkout <commitID>

当咱们不写明具体的分支名称的时,而倒是写了一个 Commit History 中的一个 <commitID>。那么,HEAD 将被直接切换指向到这个 <commitID>。这个时候咱们能够称其为匿名分支
修改 bug 的时候会经常这么作吧。
以下图,此时,你能够经过git branch fix_bug命令新建分支,以后在merge到主分支。

4.三、 状况2: git checkout <file_path>

HEAD 未发生过变更,一直处于当前分支。

git checkout <file_path>命令经常被用于丢弃本地修改。
<span style="color:red">实质上这是一个 Staged Snapshot 区 与 Working Dir 区 两区相同步的一个操做。同步的方向是由 Staged Snapshot 区 → Working Dir 区。</span>

其做用效果与git reset --hard test.txt差很少,只是在git reset --hard test.txt中,Staged Snapshot 区的 commit 被更新了一次,但它和原来同样。

4.四、 状况3: 命令组合git checkout <commitID> <file_path>

HEAD 未发生过变更,一直处于当前分支。

<span style="color:red">git checkout HEAD^^ <file_path>命令是将 Commit History 中 的 commit 同步到 Staged Snapshot 和 Working Dir 区。而 Commit History 中的 HEAD 并未发生移动。</span>

git checkout HEAD^^ <file_path>命令形成的结果是 Staged Snapshot 区的 commit 与 Working Dir 区的 commit 保持一致,而与 Commit History 当前的 commit 不一样(HEAD未移动的缘由)
以下图,即 4' = 4'' ≠ 6

同时,咱们能够经过 git checkout HEAD^^ . 命令将过去的某一版本下的全部文件放置当前的文件目录下面,而不更改 HEAD 指针

4.5 总结

状况1.1,状况1.2,未有指定 <file_path> 这时,会发生 HEAD 的变更,其指向天然也是由 Commit History 指向 Staged SnapshotWoring Dir
状况2,状况3 指定了 <file_path>时(针对操做是特定文件),若是没有指定 Commit Id,则默认从 Staged SnapshotWoring Dir
若是指定了 <commitID>, 则是 在 <commitID>Staged Snapshot<commitID>Woring Dir

结语

我的对git的基本命令的理解就是这些。日常使用 Git 过程中,大体上能够为所欲为的控制各个 commit 版本了。

参考资料:

图解Git

細說git reset和git checkout的不一样之處

Reset, Checkout, and Revert

Why git can't do hard/soft resets by path?

相关文章
相关标签/搜索