Git 是否可以从主存储库中只合并一个子存储库(子树)?

Git 是否可以从主存储库中只合并一个子存储库(子树)?,git,git-subtree,Git,Git Subtree,假设有一个包含子树存储库的MainRepo:subRepoA、subRepoB、SubRepoC。 如果我在所有存储库中进行了更改,但只希望合并和推送在subRepoB中进行的更改。可能吗? 看来MainRepo的行为就像一个大型存储库,无法区分其子存储库。这里的答案是否定和肯定。也就是说,你可以实现你的要求,但是: 不使用单个简单的git merge命令(它需要额外的命令);及 这通常是个坏主意。小心!你以后可能会后悔的。但是,如果您通读了下面的所有内容,并考虑合并是如何工作的,您就可以做到

假设有一个包含子树存储库的MainRepo:subRepoA、subRepoB、SubRepoC。 如果我在所有存储库中进行了更改,但只希望合并和推送在subRepoB中进行的更改。可能吗?
看来MainRepo的行为就像一个大型存储库,无法区分其子存储库。

这里的答案是否定和肯定。也就是说,你可以实现你的要求,但是:

  • 不使用单个简单的
    git merge
    命令(它需要额外的命令);及
  • 这通常是个坏主意。小心!你以后可能会后悔的。但是,如果您通读了下面的所有内容,并考虑合并是如何工作的,您就可以做到这一点,并且可以在必要时找出以后如何更新
但要做到这一点,请使用:

git merge --no-commit
然后使用
git checkout
或(从git2.23开始)
git restore
来“撤销”一些合并。然后使用
git merge完成合并--继续
git commit
。有关更多信息,请参阅下面的详细信息

背景 要理解这一切是如何工作的(以及为什么这是一个坏主意),请记住关于Git的这一点:Git是关于提交的。Git与文件无关,甚至与分支无关。确实,提交包含文件,这就是为什么我们有提交,保存文件和分支名称查找提交,这就是为什么我们有分支名称。但最终,Git是关于提交的

  • 提交被编号。这些不是简单的计数:我们不是从提交1开始,然后是提交2、3等等。相反,每一个都有一个看起来随机的(但实际上根本不是随机的)唯一散列ID,它显示为一个丑陋的字母和数字的大字符串,通常缩写,因为人类通常只会在它们上面出现一点光点(是
    dca3c76df9bb99b0…
    dca3c76dfb9b99b0…
    ?)

  • 一旦提交,任何提交的任何部分都不能更改。原因是散列ID实际上是提交的每一位的加密校验和。如果您确实取出一个,进行一些更改,然后将其放回,您将得到一个具有新的和不同的哈希ID的新提交。具有唯一编号的旧提交仍然存在,任何查找该编号的人都将获得旧提交

  • 每个提交存储两个内容:

    • Git知道的每个文件都有一个完整的快照。这些文件以特殊的、只读的、仅Git的、压缩的和消除重复的格式存储。(重复数据消除会立即处理这样一个事实,即大多数提交中的大多数文件与以前提交中相同文件的版本完全相同。)

    • 同时,每个提交存储一些元数据,即关于提交本身的信息。这包括谁制作的名称、电子邮件地址和时间,以及解释制作原因的日志消息。在这个元数据中,Git存储了Git本身需要的东西:在我们这里看到的提交之前提交的提交数。Git将其称为父提交


    每个提交都存储其父项的哈希ID的数字,这意味着,如果我们可以在提交字符串中找到最后一个提交,Git可以使用它来向后工作。也就是说,假设我们使用单个大写字母来代表实际的哈希ID,并绘制以下内容:

     ... <-F <-G <-H
    
    进行新提交需要Git将新提交的哈希ID存储到分支名称中:

     ...--F--G--H--I   <-- master
    
    这里,两个名称都将commit
    H
    标识为它们的最后一次提交。因此,所有提交都在两个分支上

    技术术语是可达性。我们将在下面的合并中简单地使用它,但是考虑从提交开始,然后向后工作,一次提交一次。如果不移动,我们已到达提交
    H
    。我们后退一步,就到了提交
    G
    。后退两步,我们将进入提交
    F
    ,依此类推

  • 请注意,Git可以比较任意两个提交,而不仅仅是父子对。我们将较早的提交放在左侧(好吧,通常无论如何),将较晚的提交放在右侧。Git然后比较两个提交的快照。对于相同的文件,Git什么也没说。对于不同的文件,Git指出了我们可以做的一些更改:在第42行之后添加这些行,删除第86行。这是一个差异:它显示了如何将左侧文件更改为右侧文件

    如果我们比较父项和子项,这个差异列表通常就是我们所做的。但请注意,Git只会找到一组更改。在某些情况下,我们并不是这样改变的。diff-Git查找将起作用,即使我们做的事情有点不同,但有时(请参见下面的合并),这可能会导致较小但恼人的合并冲突,如果Git在这里做得更好,这是不会发生的

  • 当我们使用
    git push
    (或者
    git fetch
    ,因此也使用
    git pull
    )时,git使用提交。推送操作发送整个提交。这包括快照和元数据。这两个Git通过比较这些散列ID就知道彼此有哪些提交:这就是为什么散列ID是提交的加密校验和。每个Git要么有一个提交,要么没有。无论哪个Git发送提交,都会向接收Git提供哈希ID,该ID要么说“是的,我需要那个,发送它”,要么说“不,谢谢,我已经有那个了”

git merge
将合并提交并进行合并提交
git merge
命令本身合并提交。我们喜欢将其与分支名称一起使用。也就是说,我们从以下内容开始:

          I--J   <-- branch1 (HEAD)
         /
...--G--H
         \
          K--L   <-- branch2
然后Git将根据我们命名的另一个提交区分相同的合并基础:

git diff --find-renames <hash-of-H> <hash-of-L>   # what they changed
请注意,名称
branch1
已照常更新。现在它指向n
          I--J   <-- branch1 (HEAD)
         /
...--G--H
         \
          K--L   <-- branch2
git diff --find-renames <hash-of-H> <hash-of-J>   # what we changed
git diff --find-renames <hash-of-H> <hash-of-L>   # what they changed
          I--J
         /    \
...--G--H      M   <-- branch1 (HEAD)
         \    /
          K--L   <-- branch2
git checkout branch1
git merge --no-commit branch2
git checkout HEAD -- subdir2 subdir3
git restore -iw --source HEAD subdir2 subdir3
          I--J
         /    \
...--G--H      M   <-- branch1 (HEAD)
         \    /
          K--L   <-- branch2
          I--J
         /    \
...--G--H      M   <-- branch1 (HEAD)
         \    /
          K--L   <-- branch2
          I--J
         /    \
...--G--H      M--N   <-- branch1 (HEAD)
         \    /
          K--L--O--P   <-- branch2
git merge branch2