## 命名

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

## 概要

    git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>]                [-u [--exclude-per-directory=<gitignore>] | -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

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

-i

通常，合并需要索引文件以及工作树中的文件与当前头提交保持同步，以避免丢失本地更改。该标志禁止使用工作树进行检查，并且用于创建与当前工作树状态不直接相关的树合并到临时索引文件中时使用。

-n --dry-run

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

-v

显示检查文件的进度。

--trivial

`git read-tree`只有在没有文件级合并所需的情况下，才会限制三路合并，而不是解决合并的问题，并在索引中留下冲突的文件。

--aggressive

通常是通过三种方式合并，通过`git read-tree`解决真正微不足道案件的合并，并使其他案例在索引中未解决，从而使瓷器可以实施不同的合并策略。该标志使命令可以在内部解决更多的情况：

- 当一方移除路径而另一方离开未修改的路径时。决议是消除这条道路。

- 当双方都消除一条路径时。决议是消除这条道路。

- 当双方相同地添加路径时。决议是增加这条道路。

--prefix=<prefix>/

保持当前的索引内容，并读取目录at下的命名树ish的内容`<prefix>`。该命令将拒绝覆盖原始索引文件中已经存在的条目。请注意，该`<prefix>/`值必须以斜杠结尾。

--exclude-per-directory=<gitignore>

使用`-u`和`-m`选项运行命令时，合并结果可能需要覆盖当前分支中未跟踪的路径。该命令通常拒绝继续合并以避免丢失这样的路径。但是这个安全阀有时会阻碍。例如，经常会发生另一个分支添加了一个文件，该文件曾经是分支中生成的文件，并且在您运行后，`make`但在运行`make clean`删除生成的文件之前尝试切换到该分支时触发安全阀。该选项告诉命令读取每个目录的排除文件（通常`.gitignore`），并允许覆盖这样一个未追踪但明确忽略的文件。

--index-output=<file>

除了将结果`$GIT_INDEX_FILE`写入外，将结果索引写入指定文件。在命令正在运行时，原始索引文件被锁定，其机制与往常一样。该文件必须允许从在通常索引文件旁边创建的临时文件重命名（2）; 通常这意味着它需要与索引文件本身位于相同的文件系统上，并且您需要对索引文件和索引输出文件所在目录的写入权限。

--no-recurse-submodules

使用--recurse子模块将根据超级项目中记录的提交，通过递归调用read-tree来更新所有已初始化的子模块的内容，同时还将子模块HEAD设置为在该提交时分离。

--no-sparse-checkout

即使`core.sparseCheckout`为真，也禁用稀疏结帐支持。

--empty

不要将树对象读入索引，只需将其清空即可。

<tree-ish#>

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

## 合并

如果`-m`被指定，则`git read-tree`可以执行3种合并，如果仅给出1棵树，则快速合并2棵树，或者如果提供3棵或更多树，则合并3种合并。

### 单树合并

如果只指定了1棵树，则`git read-tree`操作就像用户没有指定一样`-m`，除非如果原始索引具有给定路径名的条目，并且路径的内容与正在读取的树相匹配，则索引中的统计信息是用过的。（换句话说，索引的stat（）优先于合并树的）。

这意味着如果你做了一个`git read-tree -m <newtree>`跟随`git checkout-index -f -u -a`，`git checkout-index`唯一的检查出真正改变的东西。

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

### 两棵树合并

通常情况下，这被调用为`git read-tree -m $H $M`，其中$ H是当前存储库的头部提交，而$ M是外部树的头部，它仅仅在$ 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-Way Merge

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

但是，当你`git read-tree`使用三棵树时，“舞台”从1开始。

这意味着你可以做

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

并且您将以“stage1”中的所有<tree1>条目，“stage2”中的所有<tree2>条目和“stage3”中的所有<tree3>条目结束索引。当执行另一个分支到当前分支的合并时，我们使用共同祖先树作为<tree1>，当前分支头使用<tree2>，另一个分支头使用<tree3>。

此外，`git read-tree`还有一些特例逻辑：如果在下列状态中看到所有方面都匹配的文件，它将“折叠”回“stage0”：

- 阶段2和3是相同的; 采取一个或另一个（这没有什么区别 - 第二阶段我们的分支和第三阶段的分支已经完成了同样的工作）

- 阶段1和阶段2是相同的，阶段3是不同的; 进入第3阶段（我们在第2阶段的分支自第1阶段的祖先开始没有做任何事情，而第3阶段的分支在第3阶段进行分析）

- 第一阶段和第三阶段是相同的，第二阶段是不同的第二阶段（我们做了什么，而他们什么也没做）

该`git write-tree`命令拒绝写入一个无意义的树，并且如果它看到一个不是0级的单个条目，它会抱怨未合并的条目。

好吧，这听起来像是一个完全没有意义的规则集合，但它实际上正是你想要做的快速合并。不同的阶段代表“结果树”（阶段0，又名“合并”），原始树（阶段1，又名“orig”）以及您尝试合并的两棵树（分别为阶段2和3）。

当您使用已填充的索引文件开始3路合并时，阶段1,2和3的顺序（因此三个<tree-ish>命令行参数的顺序）非常重要。以下是该算法的工作原理概述：

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

- `any`在三棵树中有差别的文件将作为单独的条目保留在索引中。确定如何去除非0阶段并插入合并版本取决于“瓷器政策”。

- 索引文件会保存并恢复所有这些信息，因此您可以逐步合并，但只要它具有1/2/3阶段的条目（即“未合并条目”），则无法写入结果。所以现在合并算法变得非常简单：

    -  you walk the index in order, and ignore all entries of stage 0, since they’ve already been done.

    -  if you find a "stage1", but no matching "stage2" or "stage3", you know it’s been removed from both trees (it only existed in the original tree), and you remove that entry.

    -  if you find a matching "stage2" and "stage3" tree, you remove one of them, and turn the other into a "stage0" entry. Remove any matching "stage1" entry if it exists too. .. all the normal trivial rules ..

您通常会使用`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`大声抱怨和失败不进行任何修改）。在这种情况下，你可以简单地继续做你正在做的事情，当你的工作树准备好时（即你已经完成了正在进行的工作），再次尝试合并。

## 稀疏结帐

“稀疏结帐”允许稀疏地填充工作目录。它使用skip-worktree位（请参阅git-update-index [1]）来告诉Git工作目录中的文件是否值得关注。

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

然后它将新的skip-worktree值与前一个值进行比较。如果skip-worktree从设置变为未设置，它将添加相应的文件。如果从未设置变为设置，该文件将被删除。

虽然`$GIT_DIR/info/sparse-checkout`通常用于指定文件所在的位置，但也可以`not`使用取反模式来指定所处的文件。例如，要删除文件`unwanted`：

    /*

    !unwanted

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

    /*

然后你可以禁用稀疏结帐。`git read-tree`默认情况下，禁用和类似命令中的稀疏检出支持被禁用。您需要打开`core.sparseCheckout`才能拥有稀疏结帐支持。