Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Merge two Git repos and keep the history

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:

  • https://github.com/DimitriDewaele/RepoA
  • https://github.com/DimitriDewaele/RepoB

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!

  • https://github.com/DimitriDewaele/RepoMerged

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.

like image 703
Dimitri Dewaele Avatar asked Feb 17 '17 07:02

Dimitri Dewaele


3 Answers

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.

like image 128
dahlbyk Avatar answered Oct 11 '22 08:10

dahlbyk


There are two options in git rebase that should be of interest in your case:

p
--preserve-merges

Recreate merge commits instead of flattening the history by replaying commits a merge commit introduces.

--committer-date-is-author-date 

(from git am)

By default the command records the date from the e-mail message as the commit author date, and uses the time of commit creation as the committer date. This allows the user to lie about the committer date by using the same value as the author date.

Test if the second rebase doesn't yield a better result with:

git rebase -p --committer-date-is-author-date RepoA/master
like image 41
VonC Avatar answered Oct 11 '22 09:10

VonC


This answer suggests a different way to use RepoB as the active repo, and still have access to RepoA history :

use git replace

# start with a regular clone of the active repo :
$ git clone RepoB

# add repoA as a remote :
$ git remote add -f history https://github.com/DimitriDewaele/RepoA

# get hash of *initial* commit on repoB :
$ git log --oneline origin/master | tail -1
abcdef Initial commit

# get hash of last commit on repoA :
$ git log --oneline history/master | head -1
12345 Merge branch 'develop'

# use 'git replace' to tell git to stitch histories in the log :
$ git replace abcdef 12345

Note : this operation is done on your machine, not on the remote repositories, so should be repeated on all new clones.

Variant :

You may push RepoA:master to RepoB under a new name (e.g : RepoB:history/master), then you can use git replace abcdef history/master, on commits which are all stored in RepoB.

like image 27
LeGEC Avatar answered Oct 11 '22 10:10

LeGEC