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]

可选地,它可以将一个树合并到索引中,执行快速转发(即双向)合并,或者执行三向合并,使用 -m 标志。与 -m 一起使用时,-u 标志会导致它也使用合并结果更新工作树中的文件。

只有简单的合并由 *git read-tree* 本身完成。只有冲突的路径在 *git read-tree* 返回时才会处于未合并状态。

选项

-m

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

--reset

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

-u

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

-i

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

-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 种合并,如果只给出了 1 个树,则为单树合并,如果有 2 个树,则为快进合并,如果有 3 个或更多树,则为三向合并。

单树合并

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

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

这用于避免在 *git read-tree* 之后运行 *git diff-files* 时出现不必要的错误命中。

双树合并

通常,这会作为 git read-tree -m $H $M 调用,其中 $H 是当前存储库的 head commit,而 $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 相同,路径的删除就会被保留。

三向合并

每个 "index" 条目都有两位用于表示 "stage" 状态。stage 0 是正常状态,也是你在任何正常使用情况下唯一会看到的。

然而,当你使用三个树进行 git read-tree 操作时,"stage" 会从 1 开始。

这意味着你可以执行以下操作:

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

你最终会得到一个索引,其中 <tree1> 中的所有条目都在 "stage1" 中,<tree2> 中的所有条目都在 "stage2" 中,<tree3> 中的所有条目都在 "stage3" 中。当将另一个分支合并到当前分支时,我们将公共祖先树用作 <tree1>,当前分支头用作 <tree2>,另一个分支头用作 <tree3>。

此外,git read-tree 具有特殊逻辑,它表示:如果你看到一个文件在以下状态中完全匹配,它会“折叠”回 “stage0”。

  • stage 2 和 3 相同;选择其中一个(没有区别 - 在 stage 2 中和 stage 3 中我们分支所做的工作是相同的)

  • stage 1 和 stage 2 相同,而 stage 3 不同;选择 stage 3(我们在 stage 2 的分支没有做任何事情,因为 stage 1 中的祖先没有变化,而他们在 stage 3 的分支上进行了工作)

  • stage 1 和 stage 3 相同,而 stage 2 不同;选择 stage 2(我们做了一些事情,而他们什么都没做)

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

好的,这一切听起来像是一堆完全无意义的规则,但实际上这正是你想要进行快速合并所需的。不同的 stage 代表“结果树”(stage 0,又名“已合并”)、原始树(stage 1,又名“orig”)以及你尝试合并的两个树(分别为 stage 2 和 3)。

stage 1、2 和 3 的顺序(因此是三个 <tree-ish> 命令行参数的顺序)在你开始使用已填充的索引文件进行三向合并时非常重要。以下是该算法的工作原理概要:

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

  • 在三个树中具有任何差异的文件将作为单独的条目保留在索引中。由“瓷器策略”决定如何删除非 0 stage,并插入合并后的版本。

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

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

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

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

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

当你使用已填充的索引文件开始三向合并时,假定它代表工作树中文件的状态,你甚至可以拥有在索引文件中未记录更改的文件。进一步假定此状态是从 stage 2 树“派生”的。如果三向合并在原始索引文件中找到一个与 stage 2 不匹配的条目,则拒绝运行。

这样做是为了防止你丢失正在进行中的工作更改,并将你的随机更改混入无关的合并提交中。为了说明这一点,假设你从上次提交到你的存储库的内容开始:

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

你进行随机编辑,而不运行 git update-index。然后你注意到你的 “upstream” 树的顶端在你从他那里拉取之后已经前进了:

$ 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 命令,而不是这些底层命令,来满足与稀疏检出/跳过工作树相关的需求。 但是,以下信息可能对试图理解 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