将git父指针设置为不同的父级

如果我在过去提到了一个指向父母的承诺,但是我想改变它指向的父母,那么我该如何去做呢?

使用git rebase 。 在Git中,它是通用的“在commit(s)并将其放在不同的父(基)”命令上。

有些事情要知道,但是:

  1. 由于提交SHA涉及他们的父母,所以当你改变给定提交的父对象时,其SHA将改变 – 在开发线中的所有提交(比它更新)的SHA也将改变。

  2. 如果您正在与其他人一起工作,并且已经将公开提交的内容推送到了他们所在的位置,修改提交可能是一个糟糕的想法™。 这是由#1引起的,因此当试图找出由于SHA不再与“相同”提交相匹配而发生的事情时,其他用户的存储库将会遇到混淆。 (有关详细信息,请参阅链接的手册页上的“从上游重新启动”部分)。

也就是说,如果您目前正在某个分支上提交您想要移动到新父项的提交,则会看起来如下所示:

 git rebase --onto <new-parent> <old-parent> 

这会将当前分支中的<old-parent> 之后的所有内容移动到<new-parent>之上。

如果事实certificate,你需要避免重新绑定后续的提交(例如,因为历史重写将是站不住脚的),那么你可以使用git replace (在Git 1.6.5及更高版本中可用)。

 # …---o---A---o---o---… # # …---o---B---b---b---… # # We want to transplant B to be "on top of" A. # The tree of descendants from B (and A) can be arbitrarily complex. replace_first_parent() { old_parent=$(git rev-parse --verify "${1}^1") || return 1 new_parent=$(git rev-parse --verify "${2}^0") || return 2 new_commit=$( git cat-file commit "$1" | sed -e '1,/^$/s/^parent '"$old_parent"'$/parent '"$new_parent"'/' | git hash-object -t commit -w --stdin ) || return 3 git replace "$1" "$new_commit" } replace_first_parent BA # …---o---A---o---o---… # \ # C---b---b---… # # C is the replacement for B. 

在build立上述replace的情况下,对于对象B的任何请求将实际上返回对象C.除了第一父对象(相同的父对象(除了第一对外),同一棵树外,C的内容与B的内容完全相同,相同的提交消息)。

replace在默认情况下处于活动状态,但可以通过使用--no-replace-objects选项(在命令名称之前)或通过设置GIT_NO_REPLACE_OBJECTS环境variables来进行GIT_NO_REPLACE_OBJECTS 。 replace可以通过推送refs/replace/* (除了正常的refs/heads/* )来共享。

如果你不喜欢commit-munging(用上面的sed完成),那么你可以使用更高级别的命令创build你的replace提交:

 git checkout B~0 git reset --soft A git commit -CB git replace B HEAD git checkout - 

最大的区别在于,如果B是合并提交,则这个序列不会传播额外的父项。

请注意,在Git中更改提交需要更改其后的所有提交。 如果你已经发表了这部分历史,这是不鼓励的,有人可能会build立他们的历史上的工作,这是在变化之前。

在Amber的回应中提到的git rebase替代解决scheme是使用移植机制(参见Git术语表中的Git移植的定义以及Git Repository Layout文档中的.git/info/grafts文件的文档)来更改提交的父项,检查它是否正确的东西与一些历史浏览器( gitkgit log --graph等),然后使用git filter-branch (如其手册页的“示例”部分所述),使其永久(然后删除嫁接,并可以删除由git filter-branch或reclone repository备份的原始参考):

 echo“$ commit-id $ graft-id”>> .git / info / grafts
 git filter-branch $ graft-id..HEAD

注意 !!! 这个解决scheme与rebase解决scheme的不同之处在于git rebase改变 /移植变化 ,而基于移植的解决scheme只是按照原样重新提交提交,而没有考虑到旧父母和新父母之间的差异!

为了澄清上面的答案,并无耻地插入我自己的脚本:

这取决于你是否想要“变基”或“重新”。 正如Amber所build议的,一个rebase会围绕diff进行移动。 正如Jakub和Chris所build议的那样,重新组合了整棵树的快照 。 如果你想重新提交,我build议使用git reparent而不是手动完成工作。

对照

假设你有左边的图片,你想让它看起来像右边的图片:

  C' / A---B---C A---B---C 

重新装修和重新装修将产生相同的情况,但是C'的定义不同。 使用git rebase --onto ABC'不会包含由B引入的任何更改。 使用git reparent -p AC'将与C相同(除了B将不在历史中)。