各种 git 状态,对 git 状态的低级理解(头、索引、工作状态)
various git states, low-level understanding of git status (head, index, working states)
假设您有一棵树和一个文件。假设我们对该文件只有两种可能的状态,a
和 b
。如果它丢失或不存在,ø
。我正在尝试构建一个 table 来理解所有可能的 git-status
es。我相信我所说的是有道理的,但是,我已经用 **
标记了问题区域:
head index working status
a a a no changes**
a a b unstaged:modified**
a a ø unstaged:deleted**
a b a staged:modified, unstaged:modified
a b b staged:modified
a b ø staged:modified, unstaged:deleted
a ø a no changes**
a ø b unstaged:modified**
a ø ø staged:deleted**
ø a a staged:new file
ø a b staged:new file, unstaged: modified
ø a ø staged:new file, unstaged: deleted
ø ø a untracked
对于任何 *, ø, *
我几乎觉得它取决于父树,以及它是否在索引中......例如,a, ø, ø
就好像你'我们从工作树中删除了 blob,还有索引。但是,从索引中删除是什么样子的呢?是否只是将父树添加到临时区域并删除了树条目?如果是这种情况,那么 blob 本身的索引中没有条目是有道理的。
对于 index = head, (a,a,a
, a,a,b
, a,a,ø
) 的任何记录,我假设这种状态实际上不会发生,除非你在玩管道命令。
如果您在我的 table 中看到错误,and/or 任何对此的启示都会很棒!提前致谢。
将索引视为 "the proposed next commit"
基于提交的版本控制系统,例如 Mercurial 和 Git 需要一种方法来区分 当前提交 中的内容——与任何提交一样,永远无法区分被改变——以及我们下一次提交的内容,在我们进行提交之前,这当然是可以改变的。 Mercurial 本质上为此使用了工作树,但是 Git 添加了一个额外的层,它称为 index。 Git 然后可以将一些额外的属性分配到索引中:例如,当且仅当文件在索引中时,它才会被 跟踪 。在合并期间,索引具有额外的属性(我们将在这里忽略 :-) )。最后一个并发症我会留到最后。
But, what does a removal from the index look like?
从索引中删除文件相当于(从字面上看)从索引中删除文件。尝试 运行ning git ls-files --stage
看看我的意思:对于第一行 (a, a, a = no changes
),您会发现索引中有一个名为 a
的文件。对于您的行 a, ø, a
,文件 a
根本不再在索引中(因此 不会 在您现在进行的新提交中)。
因此,调用文件 "staged" 可能有点误导。如果 a
根本不在索引中(但在 HEAD
中),则文件为 "staged for removal",但直接说 "not in the index" 更简单。一旦一个文件不在索引中,它也不会被跟踪,所以工作树版本变成一个未跟踪的文件!
这意味着您的 a, ø, b
条目也是错误的:此处文件已准备好删除,带有 b
的工作树变体是未跟踪的文件。
a, a, ø
条目可能是最难命名的条目。该文件仍在索引中,因此它将出现在您从此处开始的每次提交中,直到您将其从索引中删除。但是,该文件根本不在工作树中,因此您无法看到 它正在提交。如果您 运行 git add file
在此状态下,Git 通过删除索引条目将不存在的工作树文件复制到索引中。
(Mercurial 有类似的状态,因为有一个隐藏的内部数据结构称为 manifest,它与 Git 的索引起着相同的作用。如果该文件从工作树中丢失,但在清单中,Mercurial 调用文件 缺失 。Mercurial 尝试将工作树视为 进入下一次提交,所以你会认为如果文件像这样简单地消失了,它也应该从下一次提交中消失。根据文档,Mercurial 最初是这样的,但发现这太过分了容易出错。)
用于探索的低级工具
- 使用
git ls-tree -r HEAD
查看当前提交的整个树(如果只有一棵树则不需要-r
)。
- 使用
git ls-files --stage
查看当前索引的整棵树:索引就像一棵扁平化的树,如果你有一个名为dir
的子目录(子树)和文件d1
和 d2
,你得到名为 dir/d1
和 dir/d2
的索引条目(相对于提交,其中顶层树将有一个名为 dir
的子树,子树将有两个 blob命名为 d1
和 d2
).
- 使用OS的普通工具(例如
ls
)查看您的工作树。您的工作树本身对 git commit
意义不大,它只是将现有索引(无论其中包含什么)转换为一个或多个树对象以存储到新提交中。 (这一切都会改变你 运行 git commit
文件路径名参数或 -a
或类似的。这里 Git 可能会将文件添加到索引,甚至切换到临时备用索引它仅在提交完成之前使用。这取决于在提供其他路径时,您是使用 --include
还是 --only
。)
多一条皱纹
因为 Git 拥有并公开了索引,所以它可以而且确实以两种不同的方式公开了一项功能。索引的每个条目都有两个标志位,称为 assume-unchanged 和 skip-worktree。要使用 git ls-files
查看这些标志位,您必须添加 --debug
参数,但可以相对简单地描述它们的作用——事实证明有点太简单了——如:
如果在索引条目上设置了 assume-unchanged 或 skip-worktree 标志,Git 应该 "close its eyes" git status
.
这可以大大加快 Git 的速度,但有一定的副作用。副作用可能就是我们使用这些位的目的。
当你运行 git status
, Git 运行s 两个 git diff
s。一个比较 HEAD
与索引,第二个比较索引与工作树。第一个差异决定 git status --short
输出的第一列,第二个差异决定第二列。
assume-unchanged 和 skip-worktree 位告诉 Git 在第二次差异期间不要费心比较文件。1 请注意,要设置这些位, 索引必须有一个文件条目,即文件 必须 被跟踪才能像这样被跳过。我们大概可以假设索引条目与 HEAD
条目匹配(如果不匹配,它将在下一次提交之后!),因此这些标志位的 effect 是我们永远不会看到文件被修改,并且 git add
通常也会跳过该文件:它不会将工作树版本复制回索引中。
我们的假设——索引条目与提交匹配——在某些极端情况下使我们误入歧途,这就是有 两个 位的原因。有关详细信息,请参阅 Git - Difference Between 'assume-unchanged' and 'skip-worktree'
1第一个差异非常快,因为特殊形式的文件(blob)在存储在提交或索引中时具有。具体来说,Git 可以通过比较它们的哈希 ID 来判断任何一个文件的 内容 是否与任何其他文件的内容匹配。如果哈希 ID 匹配,则文件相同;如果不是,则文件不同。 Git 此时不是在寻找完整的差异,而只是寻找 --name-status
风格的差异:"are the files the same, or not?"
第二个差异要慢得多,因为 Git 在最坏的情况下必须打开并读取每个文件的全部内容。即使只是询问文件系统 关于 文件(调用 lstat
系统调用)也比 Git 的内部比较哈希 ID 技巧慢得多。
假设您有一棵树和一个文件。假设我们对该文件只有两种可能的状态,a
和 b
。如果它丢失或不存在,ø
。我正在尝试构建一个 table 来理解所有可能的 git-status
es。我相信我所说的是有道理的,但是,我已经用 **
标记了问题区域:
head index working status
a a a no changes**
a a b unstaged:modified**
a a ø unstaged:deleted**
a b a staged:modified, unstaged:modified
a b b staged:modified
a b ø staged:modified, unstaged:deleted
a ø a no changes**
a ø b unstaged:modified**
a ø ø staged:deleted**
ø a a staged:new file
ø a b staged:new file, unstaged: modified
ø a ø staged:new file, unstaged: deleted
ø ø a untracked
对于任何 *, ø, *
我几乎觉得它取决于父树,以及它是否在索引中......例如,a, ø, ø
就好像你'我们从工作树中删除了 blob,还有索引。但是,从索引中删除是什么样子的呢?是否只是将父树添加到临时区域并删除了树条目?如果是这种情况,那么 blob 本身的索引中没有条目是有道理的。
对于 index = head, (a,a,a
, a,a,b
, a,a,ø
) 的任何记录,我假设这种状态实际上不会发生,除非你在玩管道命令。
如果您在我的 table 中看到错误,and/or 任何对此的启示都会很棒!提前致谢。
将索引视为 "the proposed next commit"
基于提交的版本控制系统,例如 Mercurial 和 Git 需要一种方法来区分 当前提交 中的内容——与任何提交一样,永远无法区分被改变——以及我们下一次提交的内容,在我们进行提交之前,这当然是可以改变的。 Mercurial 本质上为此使用了工作树,但是 Git 添加了一个额外的层,它称为 index。 Git 然后可以将一些额外的属性分配到索引中:例如,当且仅当文件在索引中时,它才会被 跟踪 。在合并期间,索引具有额外的属性(我们将在这里忽略 :-) )。最后一个并发症我会留到最后。
But, what does a removal from the index look like?
从索引中删除文件相当于(从字面上看)从索引中删除文件。尝试 运行ning git ls-files --stage
看看我的意思:对于第一行 (a, a, a = no changes
),您会发现索引中有一个名为 a
的文件。对于您的行 a, ø, a
,文件 a
根本不再在索引中(因此 不会 在您现在进行的新提交中)。
因此,调用文件 "staged" 可能有点误导。如果 a
根本不在索引中(但在 HEAD
中),则文件为 "staged for removal",但直接说 "not in the index" 更简单。一旦一个文件不在索引中,它也不会被跟踪,所以工作树版本变成一个未跟踪的文件!
这意味着您的 a, ø, b
条目也是错误的:此处文件已准备好删除,带有 b
的工作树变体是未跟踪的文件。
a, a, ø
条目可能是最难命名的条目。该文件仍在索引中,因此它将出现在您从此处开始的每次提交中,直到您将其从索引中删除。但是,该文件根本不在工作树中,因此您无法看到 它正在提交。如果您 运行 git add file
在此状态下,Git 通过删除索引条目将不存在的工作树文件复制到索引中。
(Mercurial 有类似的状态,因为有一个隐藏的内部数据结构称为 manifest,它与 Git 的索引起着相同的作用。如果该文件从工作树中丢失,但在清单中,Mercurial 调用文件 缺失 。Mercurial 尝试将工作树视为 进入下一次提交,所以你会认为如果文件像这样简单地消失了,它也应该从下一次提交中消失。根据文档,Mercurial 最初是这样的,但发现这太过分了容易出错。)
用于探索的低级工具
- 使用
git ls-tree -r HEAD
查看当前提交的整个树(如果只有一棵树则不需要-r
)。 - 使用
git ls-files --stage
查看当前索引的整棵树:索引就像一棵扁平化的树,如果你有一个名为dir
的子目录(子树)和文件d1
和d2
,你得到名为dir/d1
和dir/d2
的索引条目(相对于提交,其中顶层树将有一个名为dir
的子树,子树将有两个 blob命名为d1
和d2
). - 使用OS的普通工具(例如
ls
)查看您的工作树。您的工作树本身对git commit
意义不大,它只是将现有索引(无论其中包含什么)转换为一个或多个树对象以存储到新提交中。 (这一切都会改变你 运行git commit
文件路径名参数或-a
或类似的。这里 Git 可能会将文件添加到索引,甚至切换到临时备用索引它仅在提交完成之前使用。这取决于在提供其他路径时,您是使用--include
还是--only
。)
多一条皱纹
因为 Git 拥有并公开了索引,所以它可以而且确实以两种不同的方式公开了一项功能。索引的每个条目都有两个标志位,称为 assume-unchanged 和 skip-worktree。要使用 git ls-files
查看这些标志位,您必须添加 --debug
参数,但可以相对简单地描述它们的作用——事实证明有点太简单了——如:
如果在索引条目上设置了 assume-unchanged 或 skip-worktree 标志,Git 应该 "close its eyes"
git status
.这可以大大加快 Git 的速度,但有一定的副作用。副作用可能就是我们使用这些位的目的。
当你运行 git status
, Git 运行s 两个 git diff
s。一个比较 HEAD
与索引,第二个比较索引与工作树。第一个差异决定 git status --short
输出的第一列,第二个差异决定第二列。
assume-unchanged 和 skip-worktree 位告诉 Git 在第二次差异期间不要费心比较文件。1 请注意,要设置这些位, 索引必须有一个文件条目,即文件 必须 被跟踪才能像这样被跳过。我们大概可以假设索引条目与 HEAD
条目匹配(如果不匹配,它将在下一次提交之后!),因此这些标志位的 effect 是我们永远不会看到文件被修改,并且 git add
通常也会跳过该文件:它不会将工作树版本复制回索引中。
我们的假设——索引条目与提交匹配——在某些极端情况下使我们误入歧途,这就是有 两个 位的原因。有关详细信息,请参阅 Git - Difference Between 'assume-unchanged' and 'skip-worktree'
1第一个差异非常快,因为特殊形式的文件(blob)在存储在提交或索引中时具有。具体来说,Git 可以通过比较它们的哈希 ID 来判断任何一个文件的 内容 是否与任何其他文件的内容匹配。如果哈希 ID 匹配,则文件相同;如果不是,则文件不同。 Git 此时不是在寻找完整的差异,而只是寻找 --name-status
风格的差异:"are the files the same, or not?"
第二个差异要慢得多,因为 Git 在最坏的情况下必须打开并读取每个文件的全部内容。即使只是询问文件系统 关于 文件(调用 lstat
系统调用)也比 Git 的内部比较哈希 ID 技巧慢得多。