Search code examples
gitmergerepositoryrebase

Merge two Git repos with full history preserved / grafted (not rewritten)


I want to extend on another question I had: Merge two Git repositories and keep the master history

I have succeeded in merging 2 different repo's into one repo. I needed a rebase to do this successfully. The master is correct, but I also want to keep the merge history. Is this possible?

I have 2 repositories:

Repo A

Repo B

This is the result after rebasing. The times of the top repo are the rebase-time. The original date is lost!

Rebase

This is how I did it:

# Assume the current directory is where we want the new repository to be created
# Create the new repository
git init

# Before we do a merge, we have to have an initial commit, so we'll make a dummy commit
dir > Read.md
git add .
git commit -m "initial commit"

# Add a remote for and fetch the old RepoA
git remote add -f RepoA https://github.com/DimitriDewaele/RepoA

# Do the same thing for RepoB
git remote add -f RepoB https://github.com/DimitriDewaele/RepoB

# Rebase the working branch (master) on top of repoB
git rebase RepoB/master

# Rebase the working branch (master with RepoB) on top op repoA
git rebase RepoA/master

Is it possible to have something like this? (painted solution!!!)

Painted

I would like to keep the original time + the merge history.

UPDATE - ANSWER

The answer that worked best for me, was working with graft points. But other answers are also very usable in other use cases. I have added my results on github, so everybody can evaluate.

Answer 1: Best working in my case The 'graft' did reveal the correct working answer for me.

GitHub: RepoGraft

enter image description here

Answer 2 the "replace" option from "LeGEC" also gives good results for some use cases. One anomaly stayed for me:

GitHub: RepoHistory

enter image description here

Answer 3: Worth adding The answer from 'VonC'. I could not get the option '--preserve-merges working' in my case. This might work in other scenario's, but I did not test this furtner.


Solution

  • As you've discovered, rebase isn't the command you want to use to stitch histories together (because it actually rewrites history). Early Git had a feature (hack) designed specifically for what you're trying to do: graft points. Even better, since 1.6.5 you can use git replace --graft instead:

    git checkout master
    git replace --graft $(git log RepoB/master --format=%H | tail -1) HEAD
    git replace --graft $(git log RepoA/master --format=%H | tail -1) RepoB/master
    git reset --hard RepoA/master
    

    (git log RepoA/master --format=%H | tail -1 returns the initial commit from RepoA)

    Technically you could skip the first replace if you don't actually have anything of value yet in master, yielding just history with RepoB + RepoA.

    These commands create entries in refs/replace/* that can be pushed and pulled to share your revised history with others. Or, if you don't care about preserving the SHAs of RepoA/RepoB, you can make the replacements permanent by running git filter-branch --all to produce a "real" set of commits of the desired lineage.