编辑 MERGE_HEAD 来伪造合并是否安全?

Is it safe to edit MERGE_HEAD to fake a merge?

我有两个分支,由于某些原因,它们的合并非常繁琐。有时我想添加一个合并提交来表明我已经从一个分支获取了一些信息到另一个分支,例如我签出了一个文件。

因为使用git merge会产生很多合并冲突,我唯一感兴趣的是创建一个有两个父项的提交,这样做安全吗编辑 MERGE_HEAD 并提交?我的意思是,如果我将来使用 git rebasegit cherry-pick,是否有出现奇怪错误的风险?

据我了解,您只是想 "pick" 来自另一个分支的一些信息,是吗?例如:我在 feature[=] 中更改了文件 foobarbaz 51=] 并且想 "merge" foo 进入 master 而不带 barbaz .我认为有比 MERGE_HEAD.

更好的可能性

使用git cherry-pick

git cherry-pick --no-commit -x feature
git reset -p # Interactive reset similar to git add -p
Unstage foo? N
Unstage bar? Y
Unstage baz? Y
git commit

你最终得到了 foo 的提交,其中有一条消息说 cherry-picked from... 然而,git reset -p 允许取消暂存任何文件的任何块。您也可以只从另一个分支获取一行。

使用git checkout

git checkout feature -- path/to/foo
git commit

这会检查分支 feature 中可用版本中的 foo。 在这种情况下,您将不得不编写自己的提交消息。但是您可以通过将以下内容添加到 .git/config 文件来为其创建一个 git 别名。

[alias]
    pick-file = !git checkout  --  && git commit -i --edit -m \"Picked  from \"

用法:git pick file branch file

使用git show

例如:

git show feature:path/to/file > file

然后您可以使用 git add 暂存整个文件或 git add -p 来决定您需要哪些行。同样,您必须在那之后提交。您还可以为此任务创建一个别名。

我不会使用 MERGE_HEAD 来执行此操作,因为没有关于它在未来将如何继续工作的承诺。

但是,您可以使用 git commit-tree 构建您想要的任意历史记录,这是一个管道命令,需要:

  • 树哈希 ID(为新提交记录的源树),
  • 零个或多个父哈希 ID(新提交的父哈希),以及
  • 提交消息(针对新提交)。

结果是具有给定父项的提交对象。例如,给定一棵部分看起来像这样的树:

        o--o--o   <-- br1
       /
o--o--o--o   <-- br2
    \
     o--o--o   <-- br3

我们可以选择任意两个、三个、四个或我们喜欢的任何数量的提交哈希并进行新的合并提交。让我们选择顶行中间的提交和底行最右边的提交。我们将使用 br2 顶端的树作为实际树:

$ hash=$(git commit-tree -m 'mystery merge' -p br1~1 -p br3 br2)

我们现在在 $hash 中有了这个新合并提交的哈希 ID。没有 b运行ch 名称指向它,所以让我们创建一个新的:

$ git branch br4 $hash

给予:

        o--o--o   <-- br1
       /    \
o--o--o--o   o   <-- br4
    \       /
     o--o--o   <-- br3

(B运行ch br2还在里面指向中间的一个节点,我只是运行没地方画了。)

由于我们使用br2作为(快照)的来源来保存,如果我们现在运行:

$ git diff br2 br4

我们将一无所获:这两个提交具有相同的树(但父哈希 ID 不同,当然 br4 的提交消息是 "mystery merge")。

这影响未来操作的方式是,当 Git 去计算新提交的合并基础,或新提交的任何提交下游时,Git 将跟随父 links 我们刚刚作为新提交的一部分。对于大多数 cherry-pick 操作,这几乎没有影响,因为 cherry-picking 提交差异是针对其直接父级提交的。

请注意,当您进行正常的日常合并时,Git 正在做的是:

  • 进行两个特定的提交,一个是你当前的 b运行ch tip 提交 L(本地或左侧或 --ours),另一个是提交你的名字,通常是另一个 b运行ch tip,R (remote or right or --theirs);
  • 通过提交图找到他们的合并基础 B
  • 运行宁二git diffs: git diff <em>B L</em>, git diff <em>B R</em>
  • 合并两个差异结果并将其应用于 B 以获得新的 tree(Git 将此树存储在一个扁平化的形式,在索引中,然后使用相当于 git write-tree 的 C 代码来编写树对象);
  • 编写合并提交,就像 git commit-tree,使用上一步的树哈希和两个父哈希 LR 按此顺序;最后
  • 移动当前的 b运行ch name 以便它指向新的合并提交。

如果您通过任何方式制作自己的树,Git 相信这是合并其他两个提交的完整且正确的结果。未来 git merge 看到 this 合并并使用它来通知它关于 next 合并的合并基础提交 B 应该是。如果我们回到上面的图纸,而不是制作 new b运行ch 名称 br4,而是使用 b运行ch 名称 br3 移动以适应新的提交,我们得到:

        o--o--o   <-- br1
       /    \
o--o--o--o   \   <-- br2
    \         \
     o--o--o---o   <-- br3

这意味着就Git而言,正确将左上角中间提交(br1~1)合并到上一个提示中的结果br3 的(现在是 br3^2——这有点奇怪;请参阅下面的注释)是位于 br2 顶端的源代码树。这意味着如果我们现在 运行:

git merge br1

而在 br3 上,Git 将通过从 br3 的尖端向后走来找到合并基础,以找到 b运行 上的第一个提交下棋。那是我们的 br1~1。从那时起有一个提交,即 br1 的提示,所以我们将 br1~1br3 进行比较(产生这两个提交之间的不同之处)作为 "what's in br3 since the merge base",然后进行比较br1~1 反对 br1 为 "what's in br1 since the merge base"。 Git 然后尝试像往常一样组合这些,然后在 br3.

上进行新的合并提交

简而言之,您需要知道自己在做什么:您告诉 Git 使用您喜欢的任何树作为您构建的合并的 正确 结果,这会影响未来的合并。

注意:当我们进行合并时,我们选择 br1~1 作为 第一个 父级和 br3 (它的先前值)作为 。如果我们要像这样将合并坚持到 br3,那是一个不寻常的顺序:它有点倒退。实际上,它声明 br1~1 是某人稍后应该遵循的 "main line"。这个 "backwards merge" 从根本上没有错—— 是我们选择的任何东西,不管合并采用哪个 "direction"——但是如果你打算稍后查看使用 --first-parent 跟随工作的主线,你会看到 br1~1 就像这样 --first-parent link.

( git pull 命令 进行这些类型的 "reversed" 合并,有些人称之为 。当然它的合并是使用正常的合并基础和合并过程,而不是一些任意选择的树。但这意味着 git pull 进行的合并是 "backwards" 当试图遵循 --first-parent link s,这是人们可能希望避免 git pull.)

的另一个原因