了解git暂存区域

Git的暂存区允许你冻结你的工作树的状态,随后的git commit将采用该冻结状态并将其用作联系点。许多版本控制系统只允许你在提交的时候冻结,因此没有这个中间阶段;当你第一次使用Git时,你通常会习惯性地使用git add,紧接着就是git commit,甚至设置一个别名来完成所有操作。

那么,Git为什么要有暂存区的概念呢?嗯,记住Git使用可寻址内容的文件;换句话说,当你有一个特定的内容(比如空文件)时,它总是具有相同的标识- e69..391 -无论文件叫什么。运行git add时发生的事情是将对象添加到对象数据库中。除了添加对象之外,它需要一个指针指向它,因此有一个名为暂存区的虚拟树,其中包含一个指向其中包含的blob的树。

当你添加文件(或删除文件)时,你实际上修改了表示你想要执行的下一步操作的这个树。当你运行提交时,它获取该树,构建一个有效的树对象,并将其提交到数据库中(如果有的话,还更新分支)。

尽管这个特性可能一开始似乎不那么有用(有人认为这是Git相对于其他系统的复杂性的一个例子),但它对执行特定操作非常有益;例如:

将文件的部分暂存以将其分解为不同的提交(就像上一次一样)
处理大型合并,其中许多文件可能存在冲突(可以记录哪些文件没有冲突,以及你已经处理过的文件,然后运行git add;然后你就剩下了一些要处理的差异越来越少)
当你运行git status时,它会告诉你关于暂存区如何对应于当前工作树的所有信息,向你展示它发现的文件的不同消息。为了加速处理,Git通常使用时间戳来确定文件是否已更改,但在进行完整处理扫描时,将计算文件(因此,目录)内容的SHA1哈希以确定与暂存区的差异。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(master) $ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# deleted: deleteme
# renamed: same -> renamed
#
# 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: changed
#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# addme

暂存区的内容实际上是已经修改的文件的快照。例如,上面重命名和删除的更改显示了已经暂存(即添加到暂存区中)的更改,而未暂存的更改已被修改但尚未提交。暂存区还允许快速识别尚未添加的本地存储库中的更改。

因此,对于给定的文件,在处理它时可能有三个不同的副本。有先前提交的版本(即HEAD),有磁盘上的当前版本(工作树),以及第三个副本,即暂存区中的缓存版本。这就是为什么当你有一个本地更改并与已经存在的更改组合在一起时,你可能会在状态消息中看到相同的文件两次的原因:

1
2
3
4
5
6
7
8
9
10
11
12
13
(master) apple[example] $ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: three
#
# 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: three
#

你可以使用 git diff 来显示这两个文件之间的差异:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(master) $ git diff
diff --git a/three b/three
index bb87574..3179466 100644
--- a/three
+++ b/three
@@ -1 +1 @@
-This is the version in the index
+This is the version in the working tree
(master) $ git diff --cached
diff --git a/three b/three
index 48d0444..bb87574 100644
--- a/three
+++ b/three
@@ -1 +1 @@
-This is the previously committed version
+This is the version in the index

在第二个例子中,--cached 表示在暂存区和上一次提交之间进行比较(否则它会比较暂存区和工作树)。如果你愿意,你可以获取每个文件的完整内容,只要你知道哈希值(在上面的差异中显示):

1
2
3
4
5
6
(master) $ git show 48d0444
This is the previously committed version
(master) $ git show bb87574
This is the version in the index
(master) $ cat three
This is the version in the working tree