如果我过去曾提交过指向父母一方的提交,但我想更改它指向的父级,我该如何做呢?
答案 0 :(得分:355)
使用git rebase
。它是Git中的通用“take commit(s)and plop it / them on a different parent(base)”命令。
但有些事情要知道:
由于提交SHA涉及其父项,因此当您更改给定提交的父项时,其SHA将更改 - 所有提交的SHA将更改为它(更新近于它在发展方面。
如果您正在与其他人合作,并且您已经将提交的问题公开推送到他们撤回的位置,那么修改提交可能是一个坏主意™。这是由于#1,因此在尝试弄清楚由于您的SHA不再与“相同”提交相匹配时发生的事情时,其他用户的存储库将会遇到混淆。 (有关详细信息,请参阅链接手册页中的“从上游重新恢复”部分。)
那就是说,如果你目前在某个分支上有一些你希望转移到新父级的提交,它看起来像这样:
git rebase --onto <new-parent> <old-parent>
这会将当前分支中 <old-parent>
之后的所有移动到<new-parent>
之上。
答案 1 :(得分:34)
如果事实证明您需要避免重新设置后续提交(例如,因为历史记录重写是站不住脚的),那么您可以使用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 B A
# …---o---A---o---o---…
# \
# C---b---b---…
#
# C is the replacement for B.
在建立了上述替换后,对对象B的任何请求都将实际返回对象C.除了第一个父项(相同的父项(第一个除外)之外,C的内容与B的内容完全相同)相同的树,相同的提交消息)。
默认情况下,替换处于活动状态,但可以使用 git 的--no-replace-objects
选项(在命令名称之前)或通过设置GIT_NO_REPLACE_OBJECTS
环境变量来启用替换。可以通过推送refs/replace/*
(除了正常refs/heads/*
)来共享替换。
如果您不喜欢commit-munging(使用上面的 sed 完成),那么您可以使用更高级别的命令创建替换提交:
git checkout B~0
git reset --soft A
git commit -C B
git replace B HEAD
git checkout -
最大的区别在于,如果B是合并提交,则此序列不会传播其他父项。
答案 2 :(得分:30)
请注意,更改Git中的提交要求必须更改其后的所有提交。如果你已经发布了这部分历史记录,那么就不鼓励这样做了。有些人可能已经在改变之前建立了他们的历史工作。
Amber's response中提及的git rebase
的替代解决方案是使用移植机制(请参阅Git Glossary中的Git移植定义和.git/info/grafts
的文档Git Repository Layout文档中的文件)来更改提交的父级,检查它是否与某个历史记录查看器(gitk
,git log --graph
等)一致,然后使用 {{ 3}} (如其联机帮助页的“示例”部分所述)使其永久化(然后删除移植,并可选择删除由git filter-branch
备份的原始引用,或重新定义存储库):< / p>
echo "$commit-id $graft-id" >> .git/info/grafts git filter-branch $graft-id..HEAD
注意!!! 此解决方案<与rebase解决方案不同,因为git rebase
会重新/移植 更改 ,而基于移植的解决方案只是简单地重新提交 提交 ,而不考虑旧父级和新父级之间的差异!
答案 3 :(得分:22)
澄清上述答案并无耻地插入我自己的剧本:
这取决于你是否想要“变基”或“重新表现”。 Amber建议的 rebase 会移动 diffs 。 Jakub和Chris建议的 reparent 会移动整个树的快照。如果你想重新表达,我建议使用git reparent
而不是手动完成工作。
假设您在左侧有图片,并且您希望它看起来像右图:
C'
/
A---B---C A---B---C
重新定位和重新定位都会生成相同的图片,但C'
的定义不同。使用git rebase --onto A B
时,C'
不会包含B
引入的任何更改。使用git reparent -p A
时,C'
将与C
相同(除了B
不在历史记录中)。
答案 4 :(得分:3)