nowox
nowox

Reputation: 29178

How to merge history from unrelated repositories

I have an old project on SVN organized like this:

I migrated using GitHub tools git-import-svn-raw these three repositories under three Git repositories:

I used this command to get rid of the path

git filter-branch --prune-empty --subdirectory-filter \
/some/sub-directories/a/trunk/foo master

Now I would like to import the history of all these three projects into the very same repository where I have three directories a, b, c.

bundle.git/
   a/
   b/
   c/

The idea is not to merge the sub-tree but to interlace the history of each individual sub-directories together.

Is that possible?

Note: It would have been easier to convert the whole SVN repository into a single repository, but it doesn't work. I get many issues with the git-import-svn-raw tool. The same issue appear with git svn clone --stdlayout with requires a single repository which is not the case. I have also tried svn2git but it doesn't work either.

Upvotes: 3

Views: 1067

Answers (2)

Joern
Joern

Reputation: 2123

See also https://stackoverflow.com/a/34861819/933106 for another approach to "zipper up" two repositories based on their commit timestamp.

Upvotes: 1

Mark Adelsberger
Mark Adelsberger

Reputation: 45819

I would recommend against using "bundle" as the name of a repo, because bundle means something else to git. To avoid confusing the issue I'll use your terms throughout this answer, but I would use a different name.

So it's easy to get all the commits into the same repo.

cd bundle.git
git remote add a ../a.git
git remote add b ../b.git
git remote add c ../c.git
git fetch --all

Now you have refs like remote/a/master, remote/b/master, remote/c/master.

A1 -- A2 -- A3 -- A4 <--(a/master)

B1 -- B2 -- B3 -- B4 <--(b/master)

C1 -- C2 -- C3 -- C4 <--(c/master)

(master)

Note that I'm assuming you started with master having no commits ("unborn" branch) and it is still in that state here.

Next, getting a single commit with the combined content would also not be difficult. For example you could do this:

git reset --hard a/master
get merge --allow-unrelated-histories b/master
git merge --allow-unrelated-histories c/master

which would give you

               (a/master)
                   v
A1 -- A2 -- A3 -- A4 -- M1 ------------- M2 <--(master)
                       /                /
B1 --- B2 --- B3 --- B4 <--(b/master)  /
                                      /
C1 -------- C2 -------- C3 -------- C4 <--(c/master)

This history is complete and correct, but the historical commits are not interleaved and they don't have combined content (TREE objects). So if you check out B3 you will not see what a or c looked like at the corresponding time, for example.

That may be good enough. The output from log will default to reverse-chronological, and you can provide ordering options to influence exactly how this history is ordered (see git log docs). But it's not what you asked for...

So for the next step you could

git rebase -i --root master

This will bring up a "TODO" list that shows all the commits; but you'd have to manually figure out the order in which you want them interleaved. (The default order follows each branch, one at a time.) So that could be quite tedious. You can use git log output to figure out the correct order, and then re-arrange the TODO list accordingly.

UPDATE : It occurs to me that I should add some caveats about the rebase step.

First, the above only speaks explicitly about master branches. Of course each repo could have additional branches, and you'd have to decide how you want those reflected. Maybe you would decide that the branch point (the first commit from master reachable from the branch) should be unchanged, and the branch would simply not have changes from the other repos interleaved into it (assuming this wouldn't lead to branch name collisions). Or maybe you would decide to combine corresponding branches from each source repo in some way.

Second, if there are merges in the original histories, then you'll have to decide how to handle them in the rebase. This is especially a problem if the merges either contain conflict resolutions, or are "evil merges" (i.e. merges that introduce changes relative to the default merge result). The above procedure will try to produce a single, linear history. If that's not what you want, then it's quite difficult to make this work, because should you tell rebase to keep the merges, then it will keep the three histories separate as well. (Besides, --preserve-merges doesn't mix well with --interactive / -i.)

So the above works for relatively simple histories, and for more complex histories it may just not be a practical thing to do.

Upvotes: 1

Related Questions