使用git rebase
。这是Git中的通用命令,可以将提交(commit)移动到不同的父级(base)。
但需要知道以下几点:
由于提交(commit)包含其父级(parent)信息,因此当您更改给定提交的父级时,它的SHA值将发生更改,并且线性开发中位于其后面(比其更近的)所有提交的SHA值也将发生更改。
如果您正在与他人合作并且已经将要修改的提交公开推送到其他用户已经拉下来的地方,那么修改提交可能是一个糟糕的想法™。这是由于#1所述的原因,其他用户的存储库将遭遇的混乱结果,因为它们的“相同”提交的SHA值不再匹配于你们的存储库中的SHA值。(有关详细信息,请参见链接手册的“从上游重置恢复”部分。)
话虽如此,如果您当前在一条具有一些提交的分支上并希望将其移动到新父级,那么看起来会像这样:
git rebase --onto <new-parent> <old-parent>
这将会将当前分支中所有在 <old-parent>
之后的内容移动到 <new-parent>
上方。
如果你需要避免重新定位后续提交(例如因为历史重写是不可行的),你可以使用 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.
--no-replace-objects
选项或设置GIT_NO_REPLACE_OBJECTS
环境变量来关闭。可以通过推送refs/replace/*
(除了正常的refs/heads/*
)来共享替换。git checkout B~0
git reset --soft A
git commit -C B
git replace B HEAD
git checkout -
refs/replace/
引用层次结构。如果您只需要执行一次,则可以在源代码库中执行git push yourremote 'refs/replace/*'
,并在目标代码库中执行git fetch yourremote 'refs/replace/*:refs/replace/*'
。如果您需要多次执行,则可以将这些refspecs添加到remote.yourremote.push
配置变量(在源代码库中)和remote.yourremote.fetch
配置变量(在目标代码库中)。 - Chris Johnsengit replace --grafts
是一个更简单的方法。不确定这是什么时候被添加的,但它的整个目的是创建一个替代品,与提交B相同,但具有指定的父项。 - Paul Waglandgit rebase
的替代方案是使用grafts机制(请参阅Git Glossary中的Git grafts的定义以及Git Repository Layout文档中.git/info/grafts
文件的文档),以更改提交的父项,请使用一些历史查看器(gitk
,git log --graph
等)检查它是否做了正确的事情,然后使用git filter-branch
(如其手册的"Examples"部分所述)使其永久(然后删除嫁接点,可选地删除由git filter-branch
备份的原始引用或重新克隆存储库):
注意 !!! 这个解决方案与rebase解决方案不同,因为git rebase
会重新定位/移植更改,而基于嫁接的解决方案只会像原样重新父亲提交,不考虑旧父亲和新父亲之间的差异!
git-replace
+ git-filter-branch
?在我的测试中,filter-branch
似乎尊重替换,重新定位整个树。 - cdunn2001git filter-branch
重写历史,尊重嫁接和替换(但在重写的历史中,替换将无效);推送将导致非快进更改。git replace
创建就地可转移的替换;推送将是快进的,但您需要推送 refs/replace
来传输替换以纠正历史。希望对你有所帮助。 - Jakub Narębski为了澄清以上答案并且厚颜无耻地推销我的脚本:
这取决于您是想“rebase”还是“reparent”。如Amber所建议的那样,rebase会移动diffs。如Jakub和Chris所建议的那样,reparent会移动整个树的snapshots。如果要进行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
不在历史记录中)。
reparent
脚本,它运行得非常好;强烈推荐。我还建议创建一个新的 branch
标签,并在调用之前 checkout
该分支(因为分支标签会移动)。 - Rhubbarbgit filter-repo
需要 2.22 或更高版本。 - George
git rebase <新父提交记录>
来处理。 - clacke