使用 git 过滤器分支
直接使用 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 的唯一变化是 K 的父指针发生了变化,因此所有 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,您还可以使用grafts 机制。
考虑将R2/master 附加为R1/master 的子(即比)R1/master 的原始情况。和以前一样,首先将当前分支 (master) 指向 R2/master 的尖端。
git reset --hard R2/master
现在,不要运行filter-branch 命令,而是在.git/info/grafts 中创建一个“嫁接”(假父),将R2/master(K)的"root" (oldest) commit 链接到提示(最新)提交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 会更改K' 到N' 的提交者信息,而git filter-branch 不会。)