如何在Git中为提交添加父级?

3
我最近进行了重新分支操作——也就是,在主分支master的基础上创建一个新分支,并将功能分支(例如TASK-123)合并到新分支TASK-123-b中,代替已经被合并的功能分支。这是我们Git工作流程中的常规操作,每当一个功能分支与master冲突时,我们都会进行这样的操作。
当我将TASK-123合并到TASK-123-b时,自然会出现合并冲突,我解决了它并提交了。新的分支TASK-123-b和合并TASK-123的提交只有一个父节点:master
我该如何修改提交记录,使得其同时有masterTASK-123两个父节点?我猜测需要使用git rebase命令,但除此之外,我不知道如何将另一个父分支/提交添加到一个提交中。
我的提交历史目前看起来像这样:
    master -*---*-----* ...
             \         \
  TASK-123    *---*-*   \
                         \   
TASK-123-b                * ...

我希望我的提交历史看起来像这样:
    master -*---*-----* ...
             \         \
  TASK-123    *---*-*   \
                     \   \   
TASK-123-b            \---* ...

注意:我还没有将 TASK-123-b 推送到远程,所以在它上面变基的提交不应该对我的同事造成任何问题。
2个回答

1
你需要重新合并TASK-123到TASK-123-b,因为看起来它没有按照你想要的方式工作。为此,请在TASK-123-b中找到合并前的提交哈希,并在那里重置你的分支指针:
$ git checkout -B TASK-123-b abc123

然后重复合并:
$ git merge TASK-123

如果您重置分支指针,旧的合并提交将被孤立,并且在日志中不会显示,除非您特别将其作为参数提及。如果您想要找回它,哈希值将被记录在reflog中,或者您可以将哈希值复制到某个地方,并使用“checkout -B”命令将其放回,如果您决定需要它的话。
如果您不想再次解决冲突,那么不要重置分支指针,只需再次合并即可:
$ git checkout TASK-123-b
$ git merge TASK-123

并且删除旧的分支:

$ git branch -d TASK-123

你的回答的第一部分对我有用。原来我不知怎么地告诉了git我不再尝试合并,而我还在解决冲突。当我重新做时,我做对了。出于好奇,是否有一种方法可以给提交任意的父提交/分支? - Martin
2
@MartinCarney:如果你使用低级别的git commit-tree命令,你可以创建一个带有任何父提交的提交(它们来自于你的-p参数)。然后你必须手动引用新的提交。一个停止的合并(例如,冲突)会留下一个名为MERGE_HEAD的文件,其中包含要合并的提交的ID,而git commit将以HEAD作为第一父提交,以MERGE_HEAD中的内容作为第二父提交进行下一次提交。听起来你不知何故丢失了MERGE_HEAD文件。 - torek
您可以手动编写一个提交对象:http://git-scm.com/book/en/v2/Git-Internals-Git-Objects#Commit-Objects 或者您可以检出您想作为第一个父级的内容,然后像这样运行合并:git merge parent-2 parent-3 ... parent-n,然后使用checkout --ours修复所有合并冲突。我认为这会起作用。 - Jonathan Swinney
@torek 这是否意味着,如果我创建一个带有第二个父提交哈希的MERGE_HEAD文件,然后进行提交,无论提交中实际包含什么内容,git都会将该提交视为HEADMERGE_HEAD的合并? - Martin
@MartinCarney:是的,尽管这并没有在任何地方记录(因此它可能会发生变化——但实际上这与如果它被记录下来并没有太大的不同 :-))。 - torek

1
为了方便解释,假设你的提交记录被标识如下:
    master -a---c-----f-----h ...
             \         \
  TASK-123    b---d-e   \
                         \   
TASK-123-b                g---i ...

TL;DR

  1. 将代码库变基(rebase),移除合并提交(commit g),并编辑合并前的提交(commit f)。

  2. 不要在f上执行git commit --amend,而是使用git merge e --no-commit

  3. 使用git checkout g -- .检出来自提交g的所有文件。

  4. 提交并继续变基过程。


详细答案

如果您的提交树可以这样表示:

首先,使用合并提交之前第二个提交哈希进行变基(合并提交是g,因此将使用提交哈希是<commit-c-sha>

$ git rebase --interactive <commit-c-sha>

这将生成如下的待办事项列表:
pick <commit-f-sha>  <commit-f-message>   -----> change this to edit
pick <commit-g-sha>  <commit-g-message>   -----> remove this line
pick <commit-i-sha>  <commit-i-message>
...

所以你的待办事项列表将会是这样的:
edit <commit-f-sha>  <commit-f-message>
pick <commit-i-sha>  <commit-i-message>
...

第二步,创建一个没有提交的合并。
$ git merge <commit-e-sha> --no-commit

这里,我们不想实际“编辑”提交f。相反,我们为提交g的替换准备了一个提交。
这为我们提供了合并提交格式和灵活性,可以在合并提交上进行一些操作(例如:修改文件、修改提交属性等)。
第三步,我们将所有文件从提交g检出到当前索引,而不移动HEAD
$ git checkout <commit-g-sha> -- .

这将确保要提交的文件与提交 g 中的文件完全相同。 第四步,持久化更改并继续变基过程。例如:
$ git commit
$ git rebase --continue

你可以添加任何提交选项(但不要添加--amend),或者用git commit-tree命令替换它。
此后,提交g将有两个父级,并且所有在g之后的提交也会跟随。
    master -a---c-----f-----h ...
             \         \
  TASK-123    b---d-e   \
                     \   \   
TASK-123-b            \---g---i ... and all of the following commits

注释1:指定时间戳

当您提交更改时,git默认使用当前时间设置提交日期。如果您想指定时间戳,可以查看GIT_AUTHOR_DATEGIT_COMMITTER_DATE变量以及--date选项。

参考链接:https://git-scm.com/docs/git-commit#_date_formats


注意事项 2: 提交-树 (未尝试过)

如果您想要更加灵活地指定提交的父节点,您可以使用commit-tree命令进行checkout并创建提交,而不是使用merge -> checkout -> commit模式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接