Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

Merging git repositories, putting one of them into a subdirectory

For this you need git-filter-repo installed (filter-branch is discouraged).

cd a
git filter-repo --to-subdirectory-filter a
cd ..
cd b
git remote add -f a ../a
git merge --allow-unrelated-histories a/master
git remote remove a
git remote add -f a ../a

being equivalent to:

git remote add a ../a
git fetch a

An example of merging 2 big repositories, putting one of them into a subdirectory: https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731

How do you merge two Git repositories?
Merge two Git repositories without breaking file history
Merge bundler into rubygems repo
Understanding Git Filter-branch and the Git Storage Model
Integrating a submodule into the parent repository

#!/bin/sh
set -eux
mkcommit() {
local p=$1
touch "$p"
git add .
git commit -m "$p"
}
rm -rf sr sr2 r
(mkdir sr
cd sr
git init
mkcommit sr1
mkdir srd1
mkcommit srd1/sr2
git log --oneline --graph --decorate --all
git ls-files -s
)
git clone sr sr2
(cd sr2
git filter-repo --to-subdirectory-filter sr
git log --oneline --graph --decorate --all
git ls-files -s
git log -p --name-status --oneline
)
(mkdir r
cd r
git init
mkcommit r1
mkdir rd1
mkcommit rd1/r2
git log --oneline --graph --decorate --all
git ls-files -s
git remote add -f sr ../sr2
git merge --allow-unrelated-histories sr/master
git remote remove sr
git log --oneline --graph --decorate --all
git ls-files -s
git log -p --name-status --oneline
)
$ ./1.sh
+ rm -rf sr sr2 r
+ mkdir sr
+ cd sr
+ git init
Initialized empty Git repository in /home/yuri/_/git-filter-branch/sr/.git/
+ mkcommit sr1
+ local p=sr1
+ touch sr1
+ git add .
+ git commit -m sr1
[master (root-commit) 80775da] sr1
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 sr1
+ mkdir srd1
+ mkcommit srd1/sr2
+ local p=srd1/sr2
+ touch srd1/sr2
+ git add .
+ git commit -m srd1/sr2
[master 5e600a4] srd1/sr2
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 srd1/sr2
+ git log --oneline --graph --decorate --all
* 5e600a4 (HEAD -> master) srd1/sr2
* 80775da sr1
+ git ls-files -s
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 sr1
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 srd1/sr2
+ git clone sr sr2
Cloning into 'sr2'...
done.
+ cd sr2
+ git filter-repo --to-subdirectory-filter sr
Parsed 2 commitsHEAD is now at ed5b46d srd1/sr2
New history written in 0.04 seconds; now repacking/cleaning...
Repacking your repo and cleaning out old unneeded objects
Completely finished after 0.07 seconds.
+ git log --oneline --graph --decorate --all
* ed5b46d (HEAD -> master) srd1/sr2
* a2c2449 sr1
+ git ls-files -s
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 sr/sr1
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 sr/srd1/sr2
+ git log -p --name-status --oneline
ed5b46d srd1/sr2
A sr/srd1/sr2
a2c2449 sr1
A sr/sr1
+ mkdir r
+ cd r
+ git init
Initialized empty Git repository in /home/yuri/_/git-filter-branch/r/.git/
+ mkcommit r1
+ local p=r1
+ touch r1
+ git add .
+ git commit -m r1
[master (root-commit) 5e6aaa3] r1
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 r1
+ mkdir rd1
+ mkcommit rd1/r2
+ local p=rd1/r2
+ touch rd1/r2
+ git add .
+ git commit -m rd1/r2
[master b3768e7] rd1/r2
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 rd1/r2
+ git log --oneline --graph --decorate --all
* b3768e7 (HEAD -> master) rd1/r2
* 5e6aaa3 r1
+ git ls-files -s
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 r1
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 rd1/r2
+ git remote add -f sr ../sr2
Updating sr
From ../sr2
* [new branch] master -> sr/master
+ git merge --allow-unrelated-histories sr/master
Merge made by the 'recursive' strategy.
sr/sr1 | 0
sr/srd1/sr2 | 0
2 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 sr/sr1
create mode 100644 sr/srd1/sr2
+ git remote remove sr
+ git log --oneline --graph --decorate --all
* 148575f (HEAD -> master) Merge remote-tracking branch 'sr/master'
|\
| * ed5b46d srd1/sr2
| * a2c2449 sr1
* b3768e7 rd1/r2
* 5e6aaa3 r1
+ git ls-files -s
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 r1
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 rd1/r2
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 sr/sr1
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 sr/srd1/sr2
+ git log -p --name-status --oneline
148575f Merge remote-tracking branch 'sr/master'
b3768e7 rd1/r2
A rd1/r2
ed5b46d srd1/sr2
A sr/srd1/sr2
5e6aaa3 r1
A r1
a2c2449 sr1
A sr/sr1
@boxleytw
Copy link

Just what I was looking for, thank you!
Solutions based on subtree seemed to leave dangling commits so that git log projectA/README.md, even with --follow, did not include commits from the original external repository ("projectA"). This solution preserves tracing history for merged files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment