如果我从一个分支中挑选一个提交,然后稍后合并整个分支,git 历史会发生什么?

If I cherry-pick a commit from a branch and then merge the whole branch later what happens to the git history?

所以我遇到了这样一种情况,我只想要来自 branchA 的特定提交,因为该分支中的其他提交尚未准备好合并。因此,如果我从 branchA 中挑选 commitX 到 master,然后将 branchA 合并到 master(比方说,中间有一些提交),就 git 历史而言,commitX 会发生什么?它是否因为已经存在于 master 中而被忽略,或者是否发生某种重复?

git merge 操作不会像以前那样查看 "merge path" 中的单个提交。相反,它只查看开始和结束快照。但是请注意,有 两个 个结束快照(和一个开始):

...--o--B--o--...--o--L
         \
          o--...--o--R

这里,B是合并基础提交,LR是左(或本地或--ours)和右(或其他,远程,或 --theirs) 分别提交。同时,每一轮 o 代表一些 Git 甚至没有看的提交。1 Git 只是做了,实际上:

git diff --find-renames B L    # figure out what we did
git diff --find-renames B R    # figure out what they did

这对您的 git cherry-pick 来说意味着 "what we did" 和 "what they did" 可能包含对 相同 的一些更改 same files—但这没问题,因为 git merge 在获得这两个 en-masse diffs 之后所做的步骤是 combine更改,完全复制出现在合并的左侧和右侧 "sides" 中的任何更改的副本。

但是,假设您挑选了一个提交,然后沿着一条路径再次将其还原,例如:

...--o--B--X'--!X'--L
         \
          X--o--R

X' 有勾号的原因是它是提交 Xcopy:当你挑选一个提交时,新的副本获得不同的哈希 ID。)

这里,你为分支(图中下行)写了 commit X,cherry-picked 到主线(上行),意识到它还没有准备好或者被破坏了,还原它(添加!X'),然后在主线中进行最终提交L。现在当你合并时,X 的副本进入,然后又出去,这一事实是不可见的:Git 比较 BL 并且根本看不到任何迹象X'--!X' 序列。因此,它仍然需要一份由提交 X.

引入的更改的副本

如果提交 X 已在以 R 结尾的分支上准备就绪,这是正确的操作。

如果提交 X 仍然失败,这是错误的操作——但正确的解决方法可能是在合并之前在分支上恢复 X


1除了找到B,即:Git必须同时从LR开始并且工作通过图形向后查找合并基础。这意味着 Git 必须遍历一些不感兴趣的提交节点。

没有什么特别的事情发生,但让我们分解一下。

         M
-o---o---o master
  \
   \----o--o--o branchA
           X

现在你来挑选吧。那么情况是:

         M  MX
-o---o---o--o master
  \
   \----o--o--o branchA
           X

到目前为止一切顺利。然后你再做一些提交...

         M  MX
-o---o---o---o---o---o---o master
  \
   \----o---o---o---o---o---o branchA
            X

现在合并...

         M  MX
-o---o---o---o---o---o---o-----o master
  \                           /
   \----o---o---o---o---o---o branchA
            X

一切如常。 Git 不存储提交 MX 是 cherry-pick 的结果这一事实,它也不需要。 cherry-pick 操作不同于合并,因为选择的提交 X 和新提交 MX 彼此没有任何关系。它们也不能,因为(通过合并)"parent-child" 关系的语义在末尾 master 包含 all branchA 的历史,不仅更改引入了单个提交。

文件级别的实际更改就像您手动编辑它们一样。也就是说,如果 cherry-pick 引入的变化停留在 master,git 会注意到(在简单的情况下,通过在相关行的合并期间没有注意到任何差异)并且事情只会被合并。

编辑:关于您在评论中提出的问题...

lets say my commitX had a message "Foobarbaz". Given the scenario, would I have two commits now in my master branch with commit messages "Foorbarbaz" or just 1.

每个提交在提交级别都有一个消息,在文件级别有一些内容。精选在内容层面有效;也就是说,它从一次提交中获取文件更改,并将其应用于当前工作目录中的任何内容。可能令人困惑的是命令 git cherry-pick 确实在应用该更改后为您创建一个新提交(本例中为 MX)。这个新提交只是一个普通的旧提交——它与原始提交 X 没有任何关系,除了 git cherry-pick 复制旧提交消息(您可以编辑)以方便使用。

澄清一下,您可以 git cherry-pick -n 并避免 git 为您执行提交 - 这将使您有机会在自己提交之前编辑 cherry-pick 所做的任何事情。

所以,cherry-pick 实际上只是一种方便的方法,就像您自己编辑更改并自己提交一样。新提交消息可能与旧提交消息相似或相同这一事实对 git 根本无关紧要,稍后在合并时。