简体中文 ▾ 主题 ▾ 最新版本 ▾ git-read-tree 上次更新于 2.52.0

名称

git-read-tree - 将树信息读取到索引中

概要

git read-tree [(-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>)
		[-u | -i]] [--index-output=<file>] [--no-sparse-checkout]
		(--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])

描述

<tree-ish> 指定的树信息读取到索引中,但实际上并不会更新它“缓存”的任何文件。(参见:git-checkout-index[1]

可选地,它可以使用 -m 标志将一棵树合并到索引中,执行快进(即 2 路)合并,或 3 路合并。当与 -m 一起使用时,-u 标志会同时根据合并结果更新工作树中的文件。

git read-tree 本身只执行简单的合并。当 git read-tree 返回时,只有冲突的路径会处于未合并状态。

选项

-m

执行合并,而不仅仅是读取。如果索引文件中有未合并的条目,命令将拒绝运行,这表明您尚未完成之前开始的合并。

--reset

与 -m 相同,不同之处在于未合并的条目将被丢弃而不是失败。当与 -u 一起使用时,会导致工作树更改或未跟踪文件或目录丢失的更新不会中止操作。

-u

成功合并后,使用合并结果更新工作树中的文件。

-i

通常,合并需要索引文件以及工作树中的文件与当前 HEAD 提交保持最新,以避免丢失本地更改。此标志禁用与工作树的检查,旨在用于将与当前工作树状态不直接相关的树的合并创建到临时索引文件中。

-n
--dry-run

检查命令是否会出错,而不实际更新索引或工作树中的文件。

-v

显示检出文件的进度。

--trivial

git read-tree 的三路合并限制为仅在不需要文件级别合并时发生,而不是解析简单的合并情况并将冲突的文件保留为未解决状态。

--aggressive

通常,git read-tree 的三路合并会解析非常简单的情况,并将其他情况保留为未解决状态,以便 porcelain 可以实现不同的合并策略。此标志使命令在内部解析更多情况

  • 当一方删除一个路径而另一方保持该路径不变时。解析结果是删除该路径。

  • 当双方都删除一个路径时。解析结果是删除该路径。

  • 当双方都以相同方式添加一个路径时。解析结果是添加该路径。

--prefix=<prefix>

保留当前的索引内容,并在 <prefix> 指定的目录下读取指定的 tree-ish 的内容。该命令将拒绝覆盖原始索引文件中已存在的条目。

--index-output=<file>

不将结果写入 $GIT_INDEX_FILE,而是将生成的索引写入指定的文件。在命令操作期间,原始索引文件会像通常一样被锁定。该文件必须允许从在索引文件旁边创建的临时文件中重命名(rename(2))到该文件;通常这意味着它需要与索引文件位于同一文件系统上,并且您需要对索引文件和索引输出文件所在的目录具有写入权限。

--recurse-submodules
--no-recurse-submodules

使用 --recurse-submodules 将通过递归调用 read-tree 来更新所有活动子模块的内容,使其与超级项目中记录的提交一致,同时还将子模块的 HEAD 设置为分离状态(detached)。

--no-sparse-checkout

即使 core.sparseCheckout 为 true,也禁用稀疏检出支持。

--empty

不将树对象读取到索引中,而是将其清空。

-q
--quiet

安静模式,抑制反馈消息。

<tree-ish#>

要读取/合并的树对象的 ID。

合并

如果指定了 -mgit read-tree 可以执行 3 种合并:如果只给了一个树,则为单树合并;如果给 2 个树,则为快进合并;如果给 3 个或更多树,则为 3 路合并。

单树合并

如果只指定了一个树,git read-tree 的行为就如同用户未指定 -m,但如果原始索引中有给定路径名的条目,并且该路径的内容与正在读取的树匹配,则使用索引中的 stat 信息。(换句话说,索引的 stat() 优先于合并树的。)

这意味着,如果您执行 git read-tree -m <newtree>,然后执行 git checkout-index -f -u -agit checkout-index 只会检出真正发生更改的内容。

这用于避免在 git diff-filesgit read-tree 之后运行时出现不必要的误报。

两树合并

通常,这被调用为 git read-tree -m $H $M,其中 $H 是当前仓库的 HEAD 提交,而 $M 是外部树的 HEAD,它仅仅是领先于 $H(即,我们处于快进情况)。

当指定两棵树时,用户告知 git read-tree 以下信息:

  1. 当前索引和工作树源自 $H,但用户可能自 $H 以来对其进行了本地更改。

  2. 用户希望快进到 $M。

在这种情况下,git read-tree -m $H $M 命令确保“合并”的结果不会丢失任何本地更改。以下是“向前传递”规则,其中“I”表示索引,“clean”表示索引和工作树一致,“exists”/“nothing”表示路径在指定提交中的存在情况:

	I                   H        M        Result
       -------------------------------------------------------
     0  nothing             nothing  nothing  (does not happen)
     1  nothing             nothing  exists   use M
     2  nothing             exists   nothing  remove path from index
     3  nothing             exists   exists,  use M if "initial checkout",
				     H == M   keep index otherwise
				     exists,  fail
				     H != M

        clean I==H  I==M
       ------------------
     4  yes   N/A   N/A     nothing  nothing  keep index
     5  no    N/A   N/A     nothing  nothing  keep index

     6  yes   N/A   yes     nothing  exists   keep index
     7  no    N/A   yes     nothing  exists   keep index
     8  yes   N/A   no      nothing  exists   fail
     9  no    N/A   no      nothing  exists   fail

     10 yes   yes   N/A     exists   nothing  remove path from index
     11 no    yes   N/A     exists   nothing  fail
     12 yes   no    N/A     exists   nothing  fail
     13 no    no    N/A     exists   nothing  fail

	clean (H==M)
       ------
     14 yes                 exists   exists   keep index
     15 no                  exists   exists   keep index

        clean I==H  I==M (H!=M)
       ------------------
     16 yes   no    no      exists   exists   fail
     17 no    no    no      exists   exists   fail
     18 yes   no    yes     exists   exists   keep index
     19 no    no    yes     exists   exists   keep index
     20 yes   yes   no      exists   exists   use M
     21 no    yes   no      exists   exists   fail

在所有“保留索引”的情况下,索引条目将保持与原始索引文件相同。如果条目不是最新的,git read-tree 在使用 -u 标志运行时会保持工作树中的副本不变。

当此形式的 git read-tree 成功返回后,您可以通过运行 git diff-index --cached $M 来查看您所做的“本地更改”中哪些已被向前传递。请注意,这不一定与此类两树合并之前的 git diff-index --cached $H 所产生的结果匹配。这是因为情况 18 和 19——如果您已经有了 $M 中的更改(例如,您可能通过电子邮件收到了补丁形式),git diff-index --cached $H 会在这次合并之前告知您更改,但它在两树合并后的 git diff-index --cached $M 输出中将不会显示。

情况 3 有点棘手,需要解释。此规则的结果逻辑上应该是在用户暂存了路径的删除,然后切换到新分支时删除该路径。然而,这会阻止初始检出发生,因此规则被修改为仅在使用 M(新树)时,当索引的内容为空时。否则,只要 $H 和 $M 相同,路径的删除就会保留。

3 路合并

每个“索引”条目都有两个位的“阶段”状态。阶段 0 是正常的,也是在任何正常使用中您都会看到的唯一状态。

然而,当您使用三个树进行 git read-tree 时,“阶段”从 1 开始。

这意味着您可以执行

$ git read-tree -m <tree1> <tree2> <tree3>

然后您将得到一个索引,其中 <tree1> 的所有条目都处于“stage1”,<tree2> 的所有条目都处于“stage2”,<tree3> 的所有条目都处于“stage3”。当执行将另一个分支合并到当前分支时,我们将公共祖先树作为 <tree1>,当前分支 HEAD 作为 <tree2>,另一个分支 HEAD 作为 <tree3>。

此外,git read-tree 具有特殊情况逻辑,如下:如果您看到一个文件在以下状态中与所有方面都匹配,它将“折叠”回“stage0”

  • 阶段 2 和 3 相同;选择其中一个(没有区别——我们的分支在阶段 2 中做了相同的工作,他们的分支在阶段 3 中也做了相同的工作)。

  • 阶段 1 和阶段 2 相同,而阶段 3 不同;选择阶段 3(我们的分支在阶段 2 中自阶段 1 的祖先以来没有做任何工作,而他们的分支在阶段 3 中对其进行了操作)。

  • 阶段 1 和阶段 3 相同,而阶段 2 不同;选择阶段 2(我们做了一些工作,而他们没有)。

git write-tree 命令拒绝写入无意义的树,如果它看到一个不是阶段 0 的单个条目,它将抱怨未合并的条目。

好的,这一切听起来都像是一堆完全无意义的规则,但实际上这就是您想要快速合并的确切内容。不同的阶段代表“结果树”(阶段 0,即“已合并”),原始树(阶段 1,即“orig”),以及您正在尝试合并的两棵树(分别为阶段 2 和 3)。

当您使用已经填充的索引文件开始 3 路合并时,阶段 1、2 和 3 的顺序(因此命令行的三个 <tree-ish> 参数的顺序)很重要。以下是算法工作方式的概述:

  • 如果一个文件以相同的格式出现在所有三棵树中,git read-tree 将自动将其折叠为“已合并”状态。

  • 在三棵树中存在任何细微差别的文件将保留为索引中的独立条目。由“porcelain policy”来确定如何删除非 0 阶段,以及插入合并版本。

  • 索引文件会保存并恢复所有这些信息,因此您可以增量地进行合并,但只要它包含阶段 1/2/3 的条目(即“未合并的条目”),您就无法写入结果。因此,现在合并算法变得非常简单:

    • 您按顺序遍历索引,并忽略所有阶段 0 的条目,因为它们已经处理完毕。

    • 如果您找到一个“stage1”,但没有匹配的“stage2”或“stage3”,您就知道它已从两棵树中删除(它只存在于原始树中),并且您将删除该条目。

    • 如果您找到匹配的“stage2”和“stage3”树,您将删除其中一个,并将另一个转换为“stage0”条目。如果存在匹配的“stage1”条目,也将其删除……所有正常的简单规则……

您通常会使用 git merge-index 并提供 git merge-one-file 来执行此最后一步。脚本在合并每个路径时会更新工作树中的文件,并在成功合并结束时也这样做。

当您使用已经填充的索引文件开始 3 路合并时,假定它代表您工作树中文件的状态,并且您甚至可以拥有未在索引文件中记录更改的文件。进一步假设此状态“派生”自阶段 2 树。3 路合并如果发现原始索引文件中与阶段 2 不匹配的条目,将拒绝运行。

这样做是为了防止您丢失正在进行的工作,并将您的随机更改混入不相关的合并提交中。为了说明,假设您从存储库中最后一次提交的状态开始:

$ JC=`git rev-parse --verify "HEAD^0"`
$ git checkout-index -f -u -a $JC

您进行随机编辑,而不运行 git update-index。然后您注意到“上游”树的尖端自您从他那里拉取以来已经前进:

$ git fetch git://.... linus
$ LT=`git rev-parse FETCH_HEAD`

您的工作树仍然基于您的 HEAD ($JC),但自那时以来您进行了一些编辑。三路合并可确保您自 $JC 以来没有添加或修改索引条目,如果没有,则会进行正确的操作。因此,通过以下序列:

$ git read-tree -m -u `git merge-base $JC $LT` $JC $LT
$ git merge-index git-merge-one-file -a
$ echo "Merge with Linus" | \
  git commit-tree `git write-tree` -p $JC -p $LT

您将提交的是 $JC 和 $LT 之间的纯合并,而不包含您正在进行的更改,并且您的工作树将更新为合并结果。

但是,如果您在工作树中有将被此合并覆盖的本地更改,git read-tree 将拒绝运行,以防止您的更改丢失。

换句话说,无需担心仅存在于工作树中的内容。当您在项目未参与合并的部分有本地更改时,您的更改不会干扰合并,并且会保持不变。当它们确实干扰时,合并甚至不会开始(git read-tree 会大声抱怨并在不修改任何内容的情况下失败)。在这种情况下,您可以继续进行您正在做的事情,当您的工作树准备就绪时(即,您已完成正在进行的工作),再次尝试合并。

稀疏检出

注意:git-update-index[1]read-tree 中的 skip-worktree 功能早于 git-sparse-checkout[1] 的引入。鼓励用户优先使用 sparse-checkout 命令来处理与稀疏检出/skip-worktree 相关的需求。但是,以下信息可能对试图理解 sparse-checkout 命令的非锥形模式中使用的模式风格的用户有所帮助。

“稀疏检出”允许稀疏地填充工作目录。它使用 skip-worktree 位(参见 git-update-index[1])来告诉 Git 工作目录中的文件是否值得查看。

git read-tree 和其他基于合并的命令(git mergegit checkout……)可以帮助维护 skip-worktree 位图和工作目录更新。$GIT_DIR/info/sparse-checkout 用于定义 skip-worktree 参考位图。当 git read-tree 需要更新工作目录时,它会根据此文件重置索引中的 skip-worktree 位,该文件使用的语法与 .gitignore 文件相同。如果条目匹配此文件中的模式,或者条目对应于工作树中存在的文件,则不会在该条目上设置 skip-worktree。否则,将设置 skip-worktree。

然后它比较新的 skip-worktree 值与之前的。如果 skip-worktree 从设置变为未设置,它将重新添加相应的文件。如果它从未设置变为设置,则该文件将被删除。

虽然 $GIT_DIR/info/sparse-checkout 通常用于指定哪些文件包含在内,但您也可以使用否定模式指定哪些文件包含在内。例如,要删除文件 unwanted

/*
!unwanted

另一个棘手的问题是,当您不再想要稀疏检出时,完全重新填充工作目录。您不能仅仅禁用“稀疏检出”,因为 skip-worktree 位仍然在索引中,并且您的工作目录仍然是稀疏填充的。您应该使用 $GIT_DIR/info/sparse-checkout 文件内容重新填充工作目录,如下所示:

/*

然后您可以禁用稀疏检出。git read-tree 和类似命令中的稀疏检出支持默认是禁用的。您需要将 core.sparseCheckout 设置为 true 才能启用稀疏检出支持。

GIT

Git[1] 套件的一部分