Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to concatenate two git histories?

I have two git repositories that are tangentially related. Namely, content of one was a predecessor of the other. I would like to somehow prepend the full history of depository A to the depository B, so that tip of A would be a parent of the very first changeset of repository B? Histories in both are pretty linear.

Is this possible?

like image 585
SilentGhost Avatar asked Jul 10 '10 15:07

SilentGhost


1 Answers

You could try using the graft file (.git/info/grafts) where you could overwrite the parenthood of a commit (like the first of projectB having for parent the latest of projectA)

See also "What are .git/info/grafts for?" and "How to prepend the past to a git repository?" for more on this manipulation.


skalee comments about the article "Git: Grafting repositories" (from SO user Ben Straub) for a concrete example.

Now what we want to do is change the first commit in the “nuevo” repo (“New commit #1”) so that its parent is the last commit in the “old” repo (“Old #3”). Time for some voodoo:

git fetch ../old master:ancient_history

Git lets you fetch from any other git repository, whether this repo is related to it or not! Brilliant! This leaves us with this:

enter image description here

Note how we renamed the old master branch to ancient_history. If we hadn’t, git would have tried to merge the two, and probably given up in disgust.

Now we still have a problem.
The two trees aren’t connected, and in fact a git pull won’t even get the ancient_history branch at all. We need a way to make a connection between the two.

Git has a facility called a graft, which basically fakes up a parent link between two commits.
To make one, just insert a line into the .git/info/grafts file in this format:

[ref] [parent]

Both of these need to be the full hash of the commits in question. So let’s find them:

$ git rev-list master | tail -n 1
d7737bffdad86dc05bbade271a9c16f8f912d3c6

$ git rev-parse ancient_history
463d0401a3f34bd381c456c6166e514564289ab2

$ echo d7737bffdad86dc05bbade271a9c16f8f912d3c6 \
       463d0401a3f34bd381c456c6166e514564289ab2 \
       > .git/info/grafts

(in one line, as suggested by ssokolow)

echo $(git rev-list master | tail -n 1) $(git rev-parse ancient_history) > .git/info/grafts 

There. Now our history looks like this:

enter image description here

Cloning this repo results in this:

enter image description here

Woops. It turns out that grafts only take effect for the local repository. We can fix this with judicious application of git fast-import:

$ git fast-export --all > ../export

$ mkdir ../nuevo-complete

$ cd ../nuevo-complete

$ git init

$ git fast-import < ../export
git-fast-import statistics: [...]

(in one line, as suggested by ssokolow)

git filter-branch $(git rev-parse ancient_history)..HEAD 

This effectively converts our “fake” history link into a real one.
All the engineers will have to re-clone from this new repository, since the hashes will all be different, but that’s a small price to pay for no downtime and a complete history.

enter image description here

As Qix comments below:

fast-import appears to just import the git information, but doesn't check anything out.
git init originally puts you on master, so you need a git reset --hard HEAD to actually check the files out after you fast-import.

like image 162
VonC Avatar answered Sep 20 '22 06:09

VonC