如何将Git存储库组合成线性历史记录?

Dio*_*lis 15 git merge git-rewrite-history git-branch-sculpting

我有两个Git仓库R1R2,包含从产品发展的两个时期承诺:1995 - 1997年和1999年至2013年.(我通过将现有的RCS和CVS存储库转换为Git来创建它们.)

R1:
A---B---C---D

R2:
K---L---M---N
Run Code Online (Sandbox Code Playgroud)

如何将两个存储库合并为一个包含项目线性历史的准确视图的存储库?

A---B---C---D---K---L---M---N
Run Code Online (Sandbox Code Playgroud)

请注意,之间R1R2文件已添加,删除和重命名.

我尝试创建一个空的存储库,然后将它们的内容合并到它上面.

git remote add R1 /vol/R1.git
git fetch R1

git remote add R2 /vol/R2.git
git fetch R2

git merge --strategy=recursive --strategy-option=theirs R1
git merge --strategy=recursive --strategy-option=theirs R2
Run Code Online (Sandbox Code Playgroud)

但是,这会留下最新的文件D,但不是修订版K.我可以制作一个合成提交来删除合并之间的额外文件,但这对我来说似乎不优雅.此外,通过这种方法,最终结果包含实际上没有发生的合并.

Mar*_*ato 14

使用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
Run Code Online (Sandbox Code Playgroud)

接下来,将"master"(当前分支)指向R2的"master"的尖端.

git reset --hard R2/master
Run Code Online (Sandbox Code Playgroud)

现在我们可以将R1的"大师"的历史嫁接到一开始.

git filter-branch --parent-filter 'sed "s_^\$_-p R1/master_"' HEAD
Run Code Online (Sandbox Code Playgroud)

换句话说,我们之间插入一个假的父提交D,K因此新的历史记录如下所示:

A---B---C---D---K---L---M---N
Run Code Online (Sandbox Code Playgroud)

到唯一的变化K通过N在于K的父指针的变化,并且因此所有的SHA-1的标识符改变.提交消息,作者,时间戳等保持不变.

将两个以上的存储库与filter-branch一起合并

如果你有两个以上的存储库要做,比如R1(最旧)到R5(最新),只需按时间顺序重复git resetgit 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
Run Code Online (Sandbox Code Playgroud)

使用移植物

作为使用--parent-filter选项的替代方法filter-branch,您可以改为使用移植机制.

考虑R2/master作为孩子追加的原始情况(即比新的更新)R1/master.和以前一样,首先将当前branch(master)指向R2/master.

git reset --hard R2/master
Run Code Online (Sandbox Code Playgroud)

现在,而不是在运行filter-branch命令,创建"接枝"(假亲)的.git/info/grafts链接的"根"(最老的)提交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
Run Code Online (Sandbox Code Playgroud)

此时,您可以查看您的历史记录(例如,通过gitk)以查看它是否正确.如果是这样,您可以通过以下方式永久更改:

git filter-branch
Run Code Online (Sandbox Code Playgroud)

最后,您可以通过删除移植文件来清理所有内容.

rm .git/info/grafts
Run Code Online (Sandbox Code Playgroud)

使用移植物可能比使用更多的工作--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
Run Code Online (Sandbox Code Playgroud)

那个git rebase怎么样?

其他几个人建议使用git rebase R1/master而不是git filter-branch上面的命令.这将采用空提交之间的差异K,然后尝试将其应用于D,从而导致:

A---B---C---D---K'---L'---M'---N'
Run Code Online (Sandbox Code Playgroud)

这很可能会导致合并冲突,甚至可能导致正在建立一个虚假的文件K',如果一个文件被删除之间DK.在此将工作的唯一情况是,如果的树木DK是相同的.

(另一种微小的差别是,git rebase改变用于提交者信息K'通过N',而git filter-branch没有.)