【问题标题】:Are "git add file" and "git checkout -- file" symmetric?“git add file”和“git checkout --file”是对称的吗?
【发布时间】:2013-12-22 05:13:50
【问题描述】:

我对@9​​87654321@和git checkout -- file有如下理解(但不确定是否正确)。

每当我们使用文本编辑器编辑文件时,我们都会在工作目录中进行。每次我们可以通过执行git add file_name 将文件移动到所谓的staging area。如果我们再次编辑文件(在git add 之后),我们会更改工作目录中的文件,这样,在工作目录中,文件处于“新”状态,而在staging area 中,文件位于“旧”状态。

当我们再次使用git add 时,我们会将暂存区中的文件带入“新”状态(来自工作目录的状态)。

如果我们执行git checkout -- file_name,我假设我们从暂存区获取一个文件并使用它来覆盖工作目录中的文件。通过这种方式,我们可以将工作目录中的文件带入“旧”状态。对吗?

我也不清楚,如果我们从暂存区复制或移动文件。换句话说,git checkout -- file 是否改变了staging area 中文件的状态。我们可以说在git checkout -- file之后暂存区的文件将文件的状态更改为它在暂存区的先前状态吗?

【问题讨论】:

    标签: git git-checkout git-add


    【解决方案1】:

    当您通过git add 添加文件时,您标记了要提交具有该状态的文件。 Git 会记住文件的状态,并在您提交或重置时保持不变。因此,暂存后对文件的每次操作都将使用工作目录中的文件,而不是暂存。
    当您运行git checkout 时,git 只会将未暂存的文件更改为 HEAD 修订版。要将暂存文件更改为您的 HEAD 版本,您需要运行 git reset

    【讨论】:

    • 对不起,我不明白答案,因为我不知道什么是 HEAD 修订版。
    【解决方案2】:

    几乎是对称的,但不完全是对称的。

    git add file 确实将文件复制到舞台(又名“索引”)。但是,它这样做的方式有点奇怪。

    在 git repo 中,所有内容都存储为 git“对象”。每个对象都有一个唯一的名称,它的 SHA-1(那些 40 个字符的字符串,例如 753be37fca1ed9b0f9267273b82881f8765d6b23——来自我这里的实际 .gitignore)。该名称是通过计算文件内容的哈希来构造的(或多或少——有一些噱头可以确保您不会从目录树或提交中创建文件,并导致哈希冲突,例如)。 Git 假设无论内容如何,​​SHA-1 都是唯一的:没有两个不同的文件、树、提交或带注释的标签会哈希到相同的值。

    文件(和符号链接)是“blob”类型的对象。因此,git repo 中的文件被散列,并且在某个地方,git 有一个映射:“名为 .gitignore 的文件”到“散列值 753be37fca1ed9b0f9267273b82881f8765d6b23”)。

    在 repo 中,目录树存储为“tree”类型的对象。树对象包含名称列表(如 .gitignore)、模式、对象类型(另一棵树或 blob)和 SHA-1:

    $ git cat-file -p HEAD:
    100644 blob 753be37fca1ed9b0f9267273b82881f8765d6b23    .gitignore
    [snip]
    

    提交对象为您(或 git)提供一个树对象,最终为您提供 blob ID。

    另一方面,暂存区(“索引”)只是一个文件,.git/index。此文件包含1 名称(以一种有趣的略微压缩的形式,使目录树变平)、合并冲突情况下的“阶段编号”和 SHA-1。同样,实际文件内容是 git repo 中的一个 blob。 (Git 不在索引中存储目录:索引只有实际文件,使用扁平化格式。)

    所以,当你这样做时:

    git add file_name
    

    git 这样做(或多或少,我故意掩盖过滤器):

    1. 计算文件 file_name (git hash-object -t blob) 内容的哈希值。
    2. 如果该对象不在 repo 中,请将其写入 repo(使用 -w 选项到 hash-object)。
    3. 更新.git/index(或$GIT_INDEX_FILE),使其在名称file_name 下映射到来自git hash-object 的名称。这始终是“阶段 0”条目(这是正常的、无合并冲突的版本)。

    因此,文件并不是真正“在”暂存区,而是真正“在”存储库本身!暂存区域中的内容是名称到 SHA-1 映射。

    相比之下,git checkout [<tree-ish>] -- file_name 这样做:

    1. 如果给定 <tree-ish>(提交名称、树对象 ID 等——基本上任何 git 可以解析为树的东西),通过将参数转换为树对象来从找到的树中查找名称。使用如此定位的对象 ID,将索引中的哈希更新为阶段 0。(如果 file_name 命名树对象,git 会递归处理树所代表的目录中的所有文件。)通过创建阶段 0 条目,任何合并file_name 上的冲突现已解决。

      否则,在索引中进行名称查找(不确定如果file_name 是一个目录会发生什么,可能是 git 读取了工作目录)。将 file_name 转换为对象 ID(此时将是一个 blob)。如果没有第 0 阶段条目,则会出现“未合并”消息,除非给出 -m--ours--theirs 选项。使用 -m 将“取消合并”文件(删除阶段 0 条目并重新创建冲突的合并2),而 --ours--theirs 保留任何阶段 0 条目(已解决的冲突保持已解决)。

    2. 在任何情况下,如果这还没有出错,请使用由此定位的 blob SHA-1(s) 将 repo 副本(或副本,如果 file_name 命名一个目录)提取到工作目录中。

    所以,简短的版本是“是和否”:git checkout 有时会修改索引,有时只使用它。但是,文件本身永远不会存储在索引中,只会存储在 repo 中。如果您 git add 一个文件,再对其进行更改,然后再次 git add 它,这会留下 git fsck 将发现的“悬空 blob”:一个没有引用的对象。


    1我故意省略了索引中的许多其他内容,以使 git 表现良好,并允许 --assume-unchanged 等。(这些与添加/结帐无关在这里行动。)

    2此重新创建尊重对merge.conflictstyle 的任何更改,因此如果您决定喜欢diff3 输出并且在没有diff3 样式的情况下已经有冲突合并,您可以更改git config 并使用git checkout -m 获得一个新的工作目录与新样式合并。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-06-01
    • 2019-10-16
    • 2012-07-17
    • 2022-11-10
    • 1970-01-01
    • 2011-01-02
    • 2019-06-18
    相关资源
    最近更新 更多