Git
English ▾ 主题 ▾ 最新版本 ▾ git-read-tree 最后更新于 2.43.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]

可选地,它可以将树合并到索引中,执行快进(即 2 路)合并或使用 -m 标志进行 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 执行的三路合并会解决真正简单情况的合并,并将其他情况保留在索引中未解决状态,以便瓷器可以实现不同的合并策略。此标志使命令在内部解决更多情况

  • 当一方删除路径而另一方未修改路径时。解决方案是删除该路径。

  • 当双方都删除路径时。解决方案是删除该路径。

  • 当双方都以相同的方式添加路径时。解决方案是添加该路径。

--prefix=<prefix>

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

--index-output=<file>

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

--[no-]recurse-submodules

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

--no-sparse-checkout

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

--empty

不将树对象读入索引,只需清空它。

-q
--quiet

静默,抑制反馈消息。

<tree-ish#>

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

合并

如果指定了 -m,则 git read-tree 可以执行 3 种合并,如果只给出一个树,则执行单树合并,如果给出两个树,则执行快进合并,或者如果提供 3 个或更多树,则执行 3 路合并。

单树合并

如果只指定了一个树,则 git read-tree 的操作方式就像用户没有指定 -m 一样,除非原始索引中给定路径名的条目,并且路径的内容与正在读取的树匹配,则使用索引中的 stat 信息。(换句话说,索引的 stat() 优先于合并树的 stat())。

这意味着如果您执行 git read-tree -m <newtree>,然后执行 git checkout-index -f -u -a,则 git 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相同,就会保留路径的删除。

三路合并

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

但是,当你使用三个树进行git read-tree时,“阶段”从1开始。

这意味着你可以执行

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

最终你会得到一个索引,其中所有<tree1>条目位于“阶段1”,所有<tree2>条目位于“阶段2”,所有<tree3>条目位于“阶段3”。在将另一个分支合并到当前分支时,我们使用公共祖先树作为<tree1>,当前分支头作为<tree2>,另一个分支头作为<tree3>。

此外,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,即“orig”)以及你试图合并的两个树(分别为阶段2和3)。

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

  • 如果一个文件在所有三个树中都以相同的格式存在,它将由git read-tree自动折叠到“已合并”状态。

  • 在三个树中存在任何差异的文件都将保留为索引中的单独条目。“瓷器策略”将决定如何移除非0阶段,并插入合并版本。

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

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

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

    • 如果你找到一个匹配的“阶段2”和“阶段3”树,你将删除其中一个,并将另一个变成“阶段0”条目。如果存在匹配的“阶段1”条目,也将其删除。…所有正常的简单规则…

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

当你使用一个已经填充的索引文件开始一个三路合并时,假设它表示工作树中文件的状态,你甚至可以拥有在索引文件中未记录更改的文件。还假设此状态是“派生”自阶段2树。如果在原始索引文件中找到与阶段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才能获得稀疏检出支持。

Git

git[1]套件的一部分

scroll-to-top