使用 git filter-branch
直接使用来自 git-filter-branch 手册中的技巧:
首先,创建一个新的仓库,并将两个原始仓库作为远程仓库添加,就像之前一样。 我假设它们都使用分支名称“master”。
git init repo
cd repo
git remote add R1 /vol/R1.git
git fetch R1
git remote add R2 /vol/R2.git
git fetch R2
接下来,将“master”(当前分支)指向R2的“master”的末端。
git reset --hard R2/master
现在我们可以将R1“主”分支的历史记录嫁接到开头。
git filter-branch --parent-filter 'sed "s_^\$_-p R1/master_"' HEAD
换句话说,我们在D
和K
之间插入一个虚假的父提交,使得新的历史记录看起来像:
A---B---C---D---K---L---M---N
仅有的变化是 K
到 N
的父指针发生了改变,因此所有的 SHA-1 标识符都会改变。提交消息、作者、时间戳等保持不变。
使用 filter-branch 将多个存储库合并在一起
如果你有超过两个要处理的存储库,比如从最老的 R1 到最新的 R5,只需按照时间顺序重复执行 git reset
和 git filter-branch
命令即可。
PARENT_REPO=R1
for CHILD_REPO in R2 R3 R4 R5; do
git reset --hard $CHILD_REPO/master
git filter-branch --parent-filter 'sed "s_^\$_-p '$PARENT_REPO/master'"' HEAD
PARENT_REPO=$CHILD_REPO
done
使用移植点
作为使用--parent-filter
选项对filter-branch
进行替代的一种方法,您可以使用移植点机制。
考虑将R2/master
附加为(即比)R1/master
更新后的子级的原始情况。与之前一样,首先将当前分支(master
)指向R2/master
的末端。
git reset --hard R2/master
现在,不要运行filter-branch
命令,而是在.git/info/grafts
中创建一个“graft”(虚假的父级),将R2/master
(K
)的“根”(最早的)提交与R1/master
(D
)的最新提交链接起来。(如果R2/master
有多个根,则以下内容仅链接其中一个。)
ROOT_OF_R2=$(git rev-list R2/master | tail -n 1)
TIP_OF_R1=$(git rev-parse R1/master)
echo $ROOT_OF_R2 $TIP_OF_R1 >> .git/info/grafts
此时,您可以查看您的历史记录(例如通过 gitk
)来确定是否正确。如果是,则可以通过以下方式将更改永久保存:
git filter-branch
最后,您可以通过删除移植文件来清理所有内容。
rm .git/info/grafts
使用嫁接方法比使用 --parent-filter
更费力,但它有一个优点,即可以使用单个 filter-branch
将多个历史记录进行嫁接。 (使用 --parent-filter
也可以实现相同的功能,但脚本会变得非常丑陋。) 它还可以让您在更改永久生效之前查看更改的效果; 如果看起来不好,只需删除嫁接文件即可中止。
使用嫁接将多个存储库合并在一起
要使用嫁接方法将 R1 (最旧的) 到 R5 (最新的) 进行嫁接,只需在嫁接文件中添加多行即可。(运行 echo
命令的顺序无关紧要。)
git reset --hard R5/master
PARENT_REPO=R1
for CHILD_REPO in R2 R3 R4 R5; do
ROOT_OF_CHILD=$(git rev-list $CHILD_REPO/master | tail -n 1)
TIP_OF_PARENT=$(git rev-parse $PARENT_REPO/master)
echo "$ROOT_OF_CHILD" "$TIP_OF_PARENT" >> .git/info/grafts
PARENT_REPO=$CHILD_REPO
done
Git rebase怎么样?
有些人建议使用git rebase R1/master
代替上面的git filter-branch
命令。这将会取空提交和K
之间的差异,然后尝试将其应用到D
上,结果如下:
A---B---C---D---K'---L'---M'---N'
如果在 D
和 K
之间删除了文件,这很可能会导致合并冲突,并且甚至可能会在 K'
中创建虚假文件。唯一能够奏效的情况是 D
和 K
的树是相同的。
(另一个轻微的区别是,git rebase
通过 N'
改变了 K'
的提交者信息,而 git filter-branch
没有改变。)