更改根提交父项以指向另一个提交(连接两个独立的 git 存储库)

Change the root commit parent to point to another commit (connecting two independent git repositories)

我有一个项目在 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

更新:

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

更新二:

我尝试了命令 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 上不起作用,但它在 Git Bash 和 windows 上起作用。

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

用最近的历史复制回购协议。添加具有旧历史记录的 repo 作为远程。我建议这个克隆是一个 "mirror" 并且你通过用这个替换你的原始仓库来完成。但是您也可以关闭 --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,CC'代表版本3,DD' 代表版本 4。 EF 是原始迁移后创建的作品。因此,您想将父级为 D(本例中为 E)的提交拼接到 D'.

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

改写最近的历史

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

如果真的只有一个分支,那么你可以直接使用rebase

git rebase --onto D' D master

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

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

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

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

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

这会创建您需要清理的 "backup" 个引用。最后你会得到

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

事实是 FF' 取代,这就需要硬切换(或多或少)。

现在,如果您在第 1 步制作了一个镜像克隆,您可以考虑擦除 reflog,删除遥控器,然后 运行 gc,然后这是一个新的准备就绪的-使用原始仓库。

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

使用 "replacement commit"

另一个选项不会造成硬切换,但会让您永远面临一些小麻烦。您可以使用 git replace。在您的合并仓库中

git replace `D` `D'`

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

有一些已知的故障。可能存在未知故障。默认情况下,不共享使这一切正常工作的 "replacement refs",因此您必须故意推送和获取它们。