Git 🌙
简体中文 ▾ Topics ▾ Latest version ▾ git-read-tree last updated in 2.43.0

名称

git-read-tree - 将树信息读入索引

概述

git read-tree [(-m [--trivial] [--aggressive] | --reset | --prefix=<前缀>)
		[-u | -i]] [--index-output=<文件>] [--no-sparse-checkout]
		(--empty | <目录树对象 1> [<目录树对象 2> [<目录树对象 3>]])

描述

将 <树状对象> 提供的树信息读入索引,但实际上不会 更新 其 “缓存” 的任何文件。(参见:git-checkout-index[1]

可以选择使用 -m 标志将工作区合并到索引中、执行快进(即双向)合并或三向合并。 当与 -m 一起使用时,-u 标志会使它用合并的结果更新工作区中的文件。

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

选项

-m

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

--reset

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

-u

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

-i

通常情况下,合并需要索引文件和工作区中的文件都与当前的头提交保持一致,以免丢失本地变更。 此标记会禁用与工作区的检查,用于将与当前工作树状态无直接关系的目录树合并到临时索引文件中。

-n
--dry-run

检查该命令是否会出错,而不会真正更新索引或工作区中的文件。

-v

显示签出文件的进度。

--trivial

限制使用 git read-tree 进行三向合并,只有在不需要文件级合并的情况下才会发生,而不是在琐碎的情况下解决合并问题,并在索引中留下未解决的冲突文件。

--aggressive

通常情况下,通过 git read-tree 进行的三方合并会解决真正琐碎的合并问题,而将其他问题保留在索引中不予解决,这样就可以实现不同的合并策略。 这个标记会让命令在内部解决更多情况:

  • 当一方删除一条路径,而另一方不修改路径时。 解决方法就是删除该路径。

  • 当双方都删除一条路径时。 解决方法就是移除该路径。

  • 当双方添加的路径相同时。 解决方法就是增加这条路径。

--prefix=<前缀>

保留当前索引内容,并读取位于 <前缀> 目录下的命名树的内容。 该命令将拒绝覆盖原始索引文件中已存在的条目。

--index-output=<文件>

不将结果写入 $GIT_INDEX_FILE ,而是将生成的索引写入指定文件。 在执行命令时,原始索引文件会以与平常相同的机制被锁定。 该文件必须允许从通常的索引文件旁边创建的临时文件重命名 (2) 进入;这通常意味着它需要与索引文件本身位于同一文件系统,并且需要获得索引文件和索引输出文件所在目录的写入权限。

--[no-]recurse-submodules

使用 --recurse-submodules 会通过递归调用 read-tree 来根据超项目中记录的提交更新所有活动子模块的内容,并在该提交时将子模块的 HEAD 设置为分离。

--no-sparse-checkout

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

--empty

与其在索引中读取目录树对象,不如直接清空索引。

-q
--quiet

静默,压制反馈信息。

<树状对象#>

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

合并

如果指定了 -m 选项,git read-tree 可以执行 3 种合并:如果只提供了 1 个目录树,则执行单棵目录树合并;如果提供了 2 棵目录树,则执行快进合并;如果提供了 3 棵或更多的目录树,则执行 3 方合并。

单棵树合并

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

这就意味着,如果先执行 git read-tree -m <新目录树>,然后再执行 git checkout-index -f -u -agit checkout-index 只会检查出真正发生变化的内容。

当在 git read-tree 之后运行 git diff-files 时,可避免不必要的误报。

两棵目录树合并

通常的调用方式是 git read-tree -m $H $M,其中 $H 是当前仓库的头提交,$M 是另一棵目录树的头提交,它只是在 $H 之前(即我们处于快速合并状态)。

当指定了两棵目录树时,用户就是在告诉 git read-tree 以下内容:

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

  2. 用户希望快速合并到 $M。

在这种情况下,git read-tree -m $H $M 命令会确保 "merge" (合并)不会丢失任何本地变更。 以下是 “结转” 规则,其中 "I" 表示索引,"clean" 表示索引和工作区重合,"exists"/"nothing" 表示指定提交中存在路径:

	I                   H        M        结果
       -------------------------------------------------------
     0  无             无  无  (不会出现这种情况)
     1  无             无  有   使用 M
     2  无             有   无  从索引中移除路径
     3  无             有   有,  如果是 “初次检出”,则使用 M
				     H == M   keep index otherwise
				     exists,  fail
				     H != M

        clean I==H  I==M
       ------------------
     4  yes   N/A   N/A     无  无  保持索引
     5  no    N/A   N/A     无  无  保持索引

     6  yes   N/A   yes     无  有   保持索引
     7  no    N/A   yes     无  有   保持索引
     8  yes   N/A   no      无  有   失败
     9  no    N/A   no      无  有   失败

     10 yes   yes   N/A     有   无  从索引中移除目录
     11 no    yes   N/A     有   无  失败
     12 yes   no    N/A     有   无  失败
     13 no    no    N/A     有   无  失败

	clean (H==M)
       ------
     14 yes                 有   有   保持索引
     15 no                  有   有   保持索引

        clean I==H  I==M (H!=M)
       ------------------
     16 yes   no    no      有   有   失败
     17 no    no    no      有   有   失败
     18 yes   no    yes     有   有   保持索引
     19 no    no    yes     有   有   保持索引
     20 yes   yes   no      有   有   使用 M
     21 no    yes   no      有   有   失败

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

当这种形式的 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 相同,就会保留路径的删除。

三方合并

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

但是,当您使用 git read-tree 读取三棵树时,“阶段” 会从 1 开始。

这意味着您可以

$ git read-tree -m <目录树 1> <目录树 2> <目录树 3>

最终,您将得到一个包含 “阶段 1” 中所有 <目录树 1> 条目,“阶段 2” 中所有 <目录树 2> 条目和 “阶段 3” 中所有 <目录树 3> 条目的索引。 在将另一个分支合并到当前分支时,我们使用共同祖先树作为 <目录树 1>,当前分支头作为 <目录树 2>,另一个分支头作为 <目录树 3>。

此外,git read-tree 还有一个特殊情况逻辑:如果你看到一个文件在以下状态中各方面都符合要求,它就会 “折叠” 回 “阶段 0” :

  • 第 2 阶段和第 3 阶段是相同的,可以选择其中之一(这没有区别—​第 2 阶段我们的分支和第 3 阶段他们的分支所做的工作是相同的)

  • 阶段 1 和阶段 2 相同,阶段 3 不同;取阶段 3(我们在阶段 2 中的分支没有对阶段 1 中的祖先做任何事情,而他们在阶段 3 中的分支在做这件事)

  • 第 1 阶段和第 3 阶段相同,第 2 阶段与第 2 阶段不同(我们做了一些事情,而他们什么也没做)

git write-tree 命令会拒绝写入无意义的树,如果它看到一个非第 0 阶段的条目,就会抱怨存在未合并条目。

好吧,这听起来像是一堆毫无意义的规则,但实际上这正是快速合并所需要的。不同的阶段分别代表 “结果树”(第 0 阶段,又名 “合并”)、原始树(第 1 阶段,又名 “原始”)和你要合并的两棵树(分别为第 2 阶段和第 3 阶段)。

当你使用一个已经填充好的索引文件开始 3 向合并时,第 1、2 和 3 阶段的顺序(因此是三个 <树状对象> 命令行参数的顺序)非常重要。 以下是该算法的工作原理概要:

  • 如果一个文件以完全相同的格式存在于三棵目录树中,它将通过 git read-tree 自动折叠为 “合并” 状态。

  • 如果文件在三棵目录树中存在任何差异,都将作为独立条目保留在索引中。至于如何删除非 0 阶段,并插入合并版本,则由 “上层命令的策略” 决定。

  • 索引文件在保存和恢复时会包含所有这些信息,因此可以逐步合并,但只要索引文件中的条目还处于第 1/2/3 阶段(即 “未合并条目”),就无法写入合并结果。因此,现在的合并算法非常简单:

    • 你会按顺序执行索引,并忽略第 0 阶段的所有条目,因为它们已经执行过了。

    • 如果找到了 “阶段 1”,但没有找到匹配的 “阶段 2” 或 “阶段 3”,就说明它已从两棵目录树中删除(它只存在于原始目录树中),因此要删除该条目。

    • 如果找到匹配的 “阶段 2” 和 “阶段 3” 树,则删除其中一个,并将另一个变成 “阶段 0” 条目。如果还存在匹配的“阶段 1” 条目,则将其删除。 …​ 这些就是所有琐碎但正常的规则 …​

通常,你会使用 git merge-indexgit merge-one-file 来完成最后一步。 脚本会在合并每个路径和合并成功后更新工作区中的文件。

当你使用一个已填充的索引文件开始 3 向合并时,我们假定它代表了工作区中文件的状态,你甚至可以拥有索引文件中未记录更改的文件。 此外,我们还假定这种状态是从第二阶段树中 “派生” 出来的。 如果在原始索引文件中发现与第二阶段不匹配的条目,三方合并就会拒绝运行。

这样做是为了防止丢失正在进行中的更改,以及将随机更改混入不相关的合并提交中。 举例说明,假设你从上次提交到仓库的内容开始:

$ 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 会大声抱怨,并在不修改任何内容的情况下失败)。 在这种情况下,你只需继续做你正在做的事情,当你的工作区准备就绪时(即你已经完成了正在进行的工作),再尝试合并。

SPARSE CHECKOUT

注意:git-update-index[1]read-tree 中的跳过工作区功能早于 git-sparse-checkout[1] 的引入。 我们鼓励用户优先使用 sparse-checkout 命令,而不是这些底层命令来满足稀疏校验/跳转工作树的相关需求。 不过,以下信息对于试图理解 sparse-checkout 命令的非锥形模式中使用的模式样式的用户可能会有所帮助。

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

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

然后将新的 skip-worktree 值与之前的值进行比较。如果 skip-worktree 从已设变成未设,它就会重新添加相应的文件。如果从未设改为已设,则会删除该文件。

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

/*
!unwanted

另一个棘手的问题是,当你不再需要稀疏检出时,如何完全重新填充工作目录。你不能直接禁用 “稀疏检出”,因为跳过的工作区位仍在索引中,你的工作目录仍是稀疏填充的。你应该用 $GIT_DIR/info/sparse-checkout 文件内容重新填充工作目录,如下所示:

/*

然后就可以禁用稀疏签出。git read-tree 和类似命令中的稀疏签出支持默认是禁用的。你需要打开 core.sparseCheckout 才能获得稀疏签出支持。

GIT

属于 git[1] 文档

scroll-to-top