【问题标题】:How can I combine Git repositories into a linear history?如何将 Git 存储库组合成线性历史?
【发布时间】:2013-03-24 17:48:03
【问题描述】:

我有两个 git 存储库 R1R2,其中包含提交 产品开发的两个时期:1995-1997 年和 1999-2013 年。 (我通过将现有的 RCS 和 CVS 存储库转换为 Git 来创建它们。)

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

R2:
K---L---M---N

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

A---B---C---D---K---L---M---N

请注意,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

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

【问题讨论】:

  • 这听起来像是一次性问题,不是吗?如果是这样,我认为您可以只合成提交并忘记那种感觉是多么的不雅。 (从某种意义上说,所有导入的提交都已经是合成的了,所以我认为没有那么糟糕)。
  • 在我描述的案例中实际上有两个以上的repos,并且我可以看到这个问题将来会再次发生(我正在尝试将不同项目的历史重新创建为git repo )。
  • 这里是生成的存储库:github.com/dspinellis/unix-history-repo

标签: git merge git-rewrite-history git-branch-sculpting


【解决方案1】:

使用 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

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

A---B---C---D---K---L---M---N

KN 的唯一变化是 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

使用移植物

除了使用--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/masterK)的"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'

这很可能会导致合并冲突,如果在DK 之间删除文件,甚至可能导致在K' 中创建虚假文件。唯一可行的情况是 DK 的树相同。

(另一个细微的区别是git rebase 会更改K'N' 的提交者信息,而git filter-branch 不会。)

【讨论】:

  • 最后一步可以是git rebase R1/master
  • @vonbrand,我更新了我的答案以解释为什么这不起作用。
  • 效果很好,谢谢!我必须集成多个存储库(14000 次提交),所以我选择了你提到的移植选项。我正在编辑您的条目,因为提案假设我正在合并分支而不是 repos。
  • 太棒了——很高兴它成功了!我将答案改写为“repos”而不是“branches”,将“multiple repos with grafts”移至其自己的部分,并稍微澄清了“two repos with grafts”部分。 (之前,我的命令使用所有根提交进行了移植;新命令仅移植了一个根提交。)
  • 我对你关于 rebase 解决方案的说法持怀疑态度,即“这很可能会导致合并冲突,甚至可能导致在 K 中创建虚假文件,如果文件在DK 之间被删除。唯一可行的情况是DK 的树相同。 DK 的工作目录树,所以 K' 匹配 K...我需要稍后再查找。如果它不是自动合并策略,那么替换树的低级管道可能会起作用。
【解决方案2】:

原海报说:

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

R2:
K---L---M---N

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

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

A---B---C---D---K---L---M---N

请注意,R1R2 之间的文件已被添加、删除和 改名了。

所以我肯定知道,如果较新存储库的第一次提交 K 与旧存储库的最后一次提交 D 相同或略有修改,那么您可以简单地获取 R1 的历史记录到R2,然后将R2 的提交图重新定位到R1 的图上:

# From R2
git fetch R1
git checkout master
git rebase --onto R1/master --root

非线性历史(当你有合并提交时)

假设R2 的图表是线性的。如果它有合并提交,您可以通过指定要保留合并提交来尝试做同样的事情,

git rebase --preserve-merges --onto R1/master --root

但是,如果您必须解决任何您正在变基的合并中的冲突,您可能需要再次重新解决它们,这可能会很麻烦。

结合两种截然不同的历史?

原发帖人说:

请注意,R1R2 之间的文件已被添加、删除和 改名了。

正如我在上面所指出的,如果新仓库K 的第一次提交与旧仓库D 的最后一次提交相同或仅略有不同,那么简单的变基应该可以工作。如果K 实际上与D 有很大不同,我不确定相同的变基是否会干净地工作。我想在最坏的情况下,您可能必须在 rebase 期间第一次应用 K 时解决很多冲突。

文档

【讨论】:

  • 自我注意,添加如何在 rebase 的第一个补丁中出现痛苦冲突的情况下更喜欢来自较新 repo 的冲突解决方案。
【解决方案3】:

这就是我所做的工作:

git init
git remote add R1 /vol/R1.git
git fetch R1
git remote add R2 /vol/R2.git
git fetch R2
git co -B master R2/master
git rebase R1/master
git push -f

【讨论】:

    【解决方案4】:

    您只需要: git rebase 后面跟着你要变基的分支。

    简而言之,rebase 会回滚分支的所有提交,并将它们与您正在变基的分支的提交合并。

    根据两个分支之间的差异程度,您可能会遇到冲突。但是使用任何其他方法都无法避免相同的冲突。

    祝你好运!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-12-11
      • 1970-01-01
      • 1970-01-01
      • 2017-04-19
      • 2016-04-18
      • 2023-03-19
      • 1970-01-01
      相关资源
      最近更新 更多