git暂存区知识梳理

暂存区是git的一个重要概念,经过了一段时间的项目实践,总结一下个人对暂存区的一些理解。

当我们开始对git的一些知识进行梳理的时候,可以盗用以下这张图:

add-commit-push.png

  • working directory为本地的工作区
  • stage area为暂存区,亦称作stage或者index
  • local branch代指某个本地分支
  • remote branch代指某个远程分支

正常情况下,本地分支 和 远程分支 存在着对应关系,比如,我们用本地的一个 main分支 对应远程的 main分支。

git的作用围绕着 “分支” 这么一个对象来展开的。

很明显这张图只考虑单分支(一对对应分支)的情形,这也是我们在工作中常见的情形。如果明白了单分支的玩法,多分支的玩法也是同理可得的。


使用 git 的最简单的一个流程就是:

git add .              ::::将 对本地工作区中所有的内容变动 保存到暂存区
git commit -m "描述"   ::::将 暂存区的内容 提交到本地分支
git push               ::::将 本地分支的变化 同步到远程分支

暂存区的作用 – 优化commit

git的规则是原子性提交 。原子,是化学世界不可再分的最小单位。

原子性提交 的意思是,由 多个文件的内容变动信息 组成一次提交(commit)后,每一个commit被视为一个不可再分的整体。

要么在同一个commit里的 多个文件的内容变动 全部成功提交到本地分支,要么全部失败。

原子性提交带来的好处是,我们能很简便地把分支还原到某个阶段或者时间节点。就像玩游戏,将游戏进度还原到某个存档的时间点上。

在存档形成的时候,各种各样的信息组成一个存档记录。一个存档记录在形成后,是不能再被细分的。

一个commit,相当于是游戏里的一个存档。


暂存区的存在,使得一个commit比一个游戏存档更加牛逼:在形成的时候,游戏存档会把该时间节点上的所有信息(人物等级、背包里面的物品、游戏任务进度等等)全部记录下来;然而在git中,有了暂存区,我们可以自己挑选记录哪些信息形成一个commit。

比如,我在本地工作区修改了a.txt、b.txt、c.txt三个文件的内容,但是我只想记录提交a.txt、c.txt两个文件的变动,那么我们只需要把a.txt和c.txt两个文件的变动保存到暂存区,然后将暂存区的内容形成一个commit就可以了。

git add的作用 – 选取变动保存到暂存区

由上文可知,我们能挑选自己想记录提交的内容,git add就是 用来选择记录的 一个指令。刚才的操作的指令是:

git add a.txt c.txt

就能把把a.txt和c.txt两个文件的变动保存到暂存区了。继而进行其他后续操作。

而一般情况下,就像游戏存档一样,我们会去选择保存所有的文件变动,而git add . 代表的就是这个意思。

github-desktop

用过GitHub Desktop的童鞋会发现它们是同样的道理的,只不过一个是git指令的形式,一个是图形化界面的形式,流程是一样的:

  • 挑选自己想记录提交的内容,形成一个commit
  • 将一个commit作为一个整体,添加到分支上

在过程其中的区别就是:git指令没有操作界面,所以需要用到暂存区这么一个概念;而图形化界面只需直观地在界面上勾选文件的变动项就可以了。

所以在GitHub Desktop上根本不存在暂存区的概念,直接在界面上勾选,就能起到同样的作用。

暂存区的延伸作用 – 文件快照,便于回退

暂存区的作用,解释了它被发明是为了解决什么样的痛点。

接着,人们发现,这种理念还能够发挥出其他额外的作用,这种作用就是文件快照

git checkout

比如,我对hello.js做了修改之后,git add hello.js ,把它的改动保存到暂存区作为一个快照。之后我们再继续对hello.js做修改,但是发现改得不满意,想回退到它上一次保存到暂存区时的状态。

这时候使用指令git checkout hello.js就可以实现回退到上一次快照了。

但是这个玩法就比较鸡肋了,因为我们在代码编辑器连续按 ctrl + z 也能实现撤销改动的嘛。

git diff

通常代码编辑器所做不到的功能是git diff:

git diff 显示当前工作区的文件和暂存区文件的差异

git diff --staged 显示暂存区和HEAD的文件的差异

git diff HEAD 显示工作区和HEAD的文件的差异

补充说明:

  • HEAD 指向的是分支上最新提交的版本
  • 英文stage area即为暂存区,亦称作stage或者index
  • git diff --cachedgit diff --staged的作用是一样的:前者是老版本git的写法,后者是新版本的写法。为了兼容老版本git,git diff --cached依然可以被git识别的。

git reset

git reset其实是一个用于回退HEAD的指令。但是取决于它--hard--mixed--soft三个程度参数的不同,也有可以顺带地影响到暂存区

例如,在用git add hello.js 将文件变动保存到暂存区后,之后我又想从暂存区删掉这项“保存”记录,我就可以使用git reset,而它同时不会撤销工作区中的文件改动:

git reset hello.js

补充:git reset默认参数是--mixed,即上式等价为:git reset hello.js --mixed

  • 设置为--hard时会影响到HEAD、暂存区、工作区
  • 设置为--mixed时只会影响到HEAD、暂存区
  • 设置为--soft时只会影响到HEAD

可以看到git reset和git checkout同样用于回退,但是它们作用是不同的。由于git reset主要针对的是HEAD,本文不展开讨论了,详情可参考其他资料。

总结对比Github Desktop:

git的暂存区的主要作用是优化commit;在Github Desktop界面上勾选记录,也能起到同样的作用。

而git的暂存区的延伸作用 – 文件快照:

  • git checkout
  • git diff的部分指令
  • git reset
  • 其他等等

它们这些功能是GitHub Desktop所无法匹敌的。

在后续的git主题的博文中,我们会继续总结如何使用git做一些比较实用和常见的操作,比如:

  • 如何撤销一个commit?
  • 已经push到远程了,怎么撤销一个commit?
  • git merge的原理?管理多分支需要什么样的基础知识?
  • 如何从一个分支抽取部分文件提交到另一个分支?
  • git submodule是什么,在项目中有什么实际的应用?

敬请期待。

文章目录
  1. 暂存区的作用 – 优化commit
    1. git add的作用 – 选取变动保存到暂存区
  2. 暂存区的延伸作用 – 文件快照,便于回退
    1. git checkout
    2. git diff
    3. git reset
  3. 总结对比Github Desktop: