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

lmc*_*iro 5 git git-rebase git-filter-branch git-history-graph

I have a project that has more than 3 years of history in the svn repository. It was migrated to git, but the guy who did this, just take the last version and throw out all these 3 years of history.

Now the project has the last 3-4 months of history in one repository, and I've imported the other 3 years of svn history into a new git repository.

Is there some way to connect the root commit of the second repository into the last commit of the first one?

It is something like this:

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

Update:

I just learn that I need to do a git rebase, but how? Please, let's consider the commit dates like it was the SHA-1 codes... The answer was to use git filter-branch with --parent-filter option, not a git rebase.

Update 2:

I tried the command git filter-branch --parent-filter 'test $GIT_COMMIT = 443aec8880e898710796a1c4fb4decea1ca5ff66 && echo "-p 98e2b95e07b84ad1e40c3231e66840ea910e9d66" || cat' HEAD and it didn't work:

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>...]'
Run Code Online (Sandbox Code Playgroud)

Update 3:

It didn't work on Windows CMD or PowerShell, but it did work in Git Bash on windows.

Mar*_*ger 6

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

使用最近的历史记录克隆 repo。添加具有旧历史记录的 repo 作为远程。我建议这个克隆是一个“镜像”,你通过用这个替换你的原始仓库来完成。但是,您也可以放弃--mirror,然后将所有参考推回原点(可能是强制推入,具体取决于您使用的方法)。

git clone --mirror url/of/current/repo
cd repo
git remote add history url/of/historical/repo
git fetch history
Run Code Online (Sandbox Code Playgroud)

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

Recent-History Repo

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

Old-History Repo

A --- B --- C' --- D'
Run Code Online (Sandbox Code Playgroud)

其中A表示版本1,B表示第2版,CC'表示第3版,并DD'代表4.版本 EF是原始迁移之后创建的工作。因此,您想将父项为DE在本例中)的提交拼接到D'.

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

重写最近的历史

如果您可以协调所有开发人员切换到新的存储库, IMO 是最好的方式(这意味着您安排一个时间,他们都同意推送所有未完成的工作,因此他们会丢弃他们的克隆;然后您进行转换;然后他们all re-clone)是(有效地)将最近的历史重新建立在旧的历史上。

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

git rebase --onto D' D master
Run Code Online (Sandbox Code Playgroud)

(其中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
Run Code Online (Sandbox Code Playgroud)

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

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

A --- B --- C' --- D' --- E' --- F' <--(master)
Run Code Online (Sandbox Code Playgroud)

正是F被替换的事实F'导致需要进行硬切换(或多或少)。

现在,如果您在步骤 1 中创建了镜像克隆,则可以考虑擦除 reflog、删除遥控器并运行gc,然后这是一个新的随时可用的原始存储库。

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

使用“替换提交”

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

git replace `D` `D'`
Run Code Online (Sandbox Code Playgroud)

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

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