5

我有一个在 svn 存储库中有超过 3 年历史的项目。它被迁移到了 git,但是这样做的人,只是拿了最后一个版本,把这 3 年的历史全部扔掉了。

现在该项目在一个存储库中具有最近 3-4 个月的历史记录,并且我已将另外 3 年的 svn 历史记录导入到一个新的 git 存储库中。

有没有办法将第二个存储库的根提交连接到第一个存储库的最后一个提交?

它是这样的:

  *   2017-04-21 - last commit on master
  |   
  *   2017-03-20 - merge branch Y into master
  |\  
  | * 2017-03-19 - commit on branch Y
  | | 
  * | 2017-03-18 - merge branch X into master
 /| * 2017-02-17 - commit on another new branch Y
* |/  2017-02-16 - commit on branch X
| *   2017-02-15 - commit on master branch
* |   2017-01-14 - commit on new branch X
 \|   
  *   2017-01-13 - first commit on new repository
  |   
  *   2017-01-12 - init new git project with the last version of the code in svn repository
  .   
  .   
There is no relationship between the two different repositories yet, this is what I wanna
do. I want to connect the root commit of 2nd repository with the last commit of the first
one.
  .
  .   
  *   2017-01-09 - commit
  |   
  *   2017-01-08 - commit
  |   
  *   2017-01-07 - merge
 /|   
* |   2016-01-06 - 2nd commit the other branch
| *   2016-01-05 - commit on trunk
* |   2016-01-04 - commit on new branch
 \|   
  *   2015-01-03 - first commit
  |   
  *   2015-01-02 - beggining of the project

更新:

我刚知道我需要做 a git rebase,但是怎么做?请让我们考虑一下提交日期,就像它是 SHA-1 代码一样......答案是使用git filter-branchwith--parent-filter选项,而不是git rebase.

更新 2:

我尝试了该命令git filter-branch --parent-filter 'test $GIT_COMMIT = 443aec8880e898710796a1c4fb4decea1ca5ff66 && echo "-p 98e2b95e07b84ad1e40c3231e66840ea910e9d66" || cat' HEAD,但它不起作用:

PS D:\git\rebase-test\rep2cc> git filter-branch --parent-filter 'test $GIT_COMMIT = 443aec8880e898710796a1c4fb4decea1ca5ff66 && echo "-p 98e2b95e07b84ad1e40c3231e66840ea910e9d66" || cat' HEAD
fatal: ambiguous argument '98e2b95e07b84ad1e40c3231e66840ea910e9d66 || cat': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'

更新 3:

它在 Windows CMD 或 PowerShell 上不起作用,但在 Windows 上的 Git Bash 中确实有效。

4

1 回答 1

6

首先要做的事情是:您需要一个包含所有可用历史记录的单一存储库。

使用最近的历史克隆 repo。将具有旧历史记录的存储库添加为远程。我建议这个克隆是一个“镜像”,并且你通过用这个替换你的原始存储库来完成。但或者你可以离开--mirror,你将通过将所有参考推回原点来完成(可能是强制推,取决于你使用的方法)。

git clone --mirror url/of/current/repo
cd repo
git remote add history url/of/historical/repo
git fetch history

接下来你需要做的是弄清楚你将在哪里拼接历史。我认为描述这一点的术语有点模糊......你想要的是找到与两个历史都有提交的最新 SVN 修订相对应的两个提交。例如,您的 SVN 存储库包含版本 1、2、3 和 4。现在您有了

Recent-History Repo

C --- D --- E --- F <--(master)

Old-History Repo

A --- B --- C' --- D'

其中A代表版本 1,B代表版本 2,C代表C'版本 3,DD'代表版本 4。 EF是原始迁移后创建的作品。D因此,您想将父级为(E在此示例中)的提交拼接到D'.

现在,我可以想到两种方法,各有利弊。

改写最近的历史

IMO 是最好的方式,如果你可以协调所有开发人员到一个新的 repo(这意味着你安排一个时间,他们都同意推送所有未完成的工作,所以他们丢弃他们的克隆;然后你进行转换;然后他们所有重新克隆)是为了(有效地)将最近的历史重新定位到旧的历史上。

如果真的只有一个分支,那么你可以从字面上使用 rebase

git rebase --onto D' D master

(其中DD'被替换为提交的 SHA ID)。

在最近的历史中,您更有可能有一些分支和合并;在这种情况下,rebase 操作将很快开始成为问题。另一方面,您可以利用D具有相同树的事实D'-- 所以 rebase 和 re-parent 或多或少是等价的。

所以你可以使用git filter-branchwith a--parent-filter来进行重写。根据https://git-scm.com/docs/git-filter-branch文档中的示例,您可以执行类似的操作

git filter-branch --parent-filter 'test $GIT_COMMIT = D && echo "-p D'" || cat' HEAD

(再次替换为提交的 SHA ID)DD'

这会创建您需要清理的“备份”引用。最后你会得到

A --- B --- C' --- D' --- E' --- F' <--(master)

F被替换的事实是F'需要硬切换(或多或少)。

现在,如果您在步骤 1 中进行了镜像克隆,您可以考虑擦除 reflog,删除远程,然后运行gc​​,然后这是一个新的即用型原始存储库。

如果您进行了常规克隆,那么您将需要对push -f原点的所有参考,这可能会在原点回购中留下一些混乱。

使用“替换提交”

另一个选项不会造成硬切换,但它会让您永远处理一些小麻烦。您可以使用git replace. 在您的合并回购中

git replace `D` `D'`

默认情况下,在生成日志输出或其他任何内容时,如果 git 找到D,它将D'在输出中替换(及其历史记录)。

有一些已知的故障。可能存在未知故障。并且默认情况下,使这一切工作的“替换引用”是不共享的,因此您必须故意推送和获取它们。

于 2017-05-19T20:05:23.837 回答