Git Subtrees
Git subtrees
Simple use of a library
Let us create a Git repository.
~/tmp $ mkdir mainrepo
~/tmp $ cd mainrepo
~/tmp/mainrepo $ git init
Initialized empty Git repository in /home/rat/tmp/mainrepo/.git/
Add some files and commits.
~/tmp/mainrepo $ touch feature1
~/tmp/mainrepo $ git add *; git commit -m "Add feature1"
~/tmp/mainrepo (master) $ touch feature2
~/tmp/mainrepo (master) $ git add *; git commit -m "Add feature2"
The log shows
~/tmp/mainrepo (master) $ git log --oneline
e2c7cdf Add feature2
f3deaf9 Add feature1
On the side, let us develop a library:
~/tmp/mainrepo (master) $ cd ..
~/tmp/librepo $ mkdir librepo; cd librepo
~/tmp/librepo $ git init
Initialized empty Git repository in /home/rat/tmp/librepo/.git/
~/tmp/librepo $ touch libfeature1
~/tmp/librepo $ git add *; git commit -m "Add libfeature1"
~/tmp/librepo $ touch libfeature2
~/tmp/librepo $ git add *; git commit -m "Add libfeature2"
~/tmp/librepo (master) $ git log --oneline
fb36e6b Add libfeature2
7b2b43e Add libfeature1
This is then published on a bare repository for sharing:
~/tmp $ mkdir lib.git
~/tmp $ cd lib.git
~/tmp/lib.git $ git init --bare
Initialized empty Git repository in /home/rat/tmp/lib.git/
~/tmp/librepo $ git push /home/rat/tmp/lib.git master
Counting objects: 9, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (7/7), done.
Writing objects: 100% (9/9), 814 bytes | 0 bytes/s, done.
Total 9 (delta 3), reused 0 (delta 0)
To /home/rat/tmp/lib.git
* [new branch] master -> master
Now, in order to use the library in our main project, we try to include it as a subtree. First, to make it more comfortable, we define a “remote” alias.
~/tmp/librepo (master) $ cd ../mainrepo
~/tmp/mainrepo (master) $ git remote add library /home/rat/tmp/librepo/.git
~/tmp/mainrepo (master) $ git remote add library /home/rat/tmp/lib.git # alternatively
Then add the library:
~/tmp/mainrepo (master) $ git subtree add -P lib library master --squash
git fetch library master
From /home/rat/tmp/librepo/
* branch master -> FETCH_HEAD
* Added dir 'lib'
The log now shows the creation of a subtree for the library, and merging this to the current master:
7bddfdd Merge commit '58945f907447e965eadc45c6074bff1a8cce782c' as 'lib'
58945f9 Squashed 'lib/' content from commit fb36e6b
e2c7cdf Add feature2
f3deaf9 Add feature1
Now two things happen. First, development in our repository continues.
~/tmp/mainrepo (master) $ touch feature3
~/tmp/mainrepo (master) $ git add *; git commit -m "Add feature3"
~/tmp/mainrepo (master) $ echo "changes" >> feature1
~/tmp/mainrepo (master) $ git add *; git commit -m "Update feature1"
~/tmp/mainrepo (master) $ git log --oneline
b13bf6d Update feature1
d0a6164 Add feature3
7bddfdd Merge commit '58945f907447e965eadc45c6074bff1a8cce782c' as 'lib'
58945f9 Squashed 'lib/' content from commit fb36e6b
e2c7cdf Add feature2
f3deaf9 Add feature1
This also happens in the library.
~/tmp/librepo (master) $ touch libfeature3
~/tmp/librepo (master) $ git add *; git commit -m "Add libfeature3"
~/tmp/librepo (master) $ git log --oneline
46b8c30 Add libfeature3
fb36e6b Add libfeature2
7b2b43e Add libfeature1
Let us integrate these changes in the library into our project!
~/tmp/mainrepo (master) $ git subtree pull -P lib library master --squash
remote: Counting objects: 2, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 2 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (2/2), done.
From /home/rat/tmp/librepo/
* branch master -> FETCH_HEAD
fb36e6b..46b8c30 master -> library/master
Merge made by the 'recursive' strategy.
lib/libfeature3 | 0
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 lib/libfeature3
~/tmp/mainrepo (master) $ git log --oneline
88251f1 Merge commit '9b0d431aaec6c321b456e336a625e0ee3fa8d651'
9b0d431 Squashed 'lib/' changes from fb36e6b..46b8c30
b13bf6d Update feature1
d0a6164 Add feature3
58945f9 Squashed 'lib/' content from commit fb36e6b
7bddfdd Merge commit '58945f907447e965eadc45c6074bff1a8cce782c' as 'lib'
e2c7cdf Add feature2
f3deaf9 Add feature1
This works as expected and we can investigate the changes
~/tmp/mainrepo (master) $ git diff b13bf6d
diff --git a/lib/libfeature3 b/lib/libfeature3
new file mode 100644
index 0000000..e69de29
Advanced use: contributing to a library
As before, development continues in both library and main repository.
~/tmp/librepo (master) $ touch libfeature4
~/tmp/librepo (master) $ git add *; git commit -m "Add libfeature4"
~/tmp/librepo (master) $ git log --oneline
570b0be Add libfeature4
46b8c30 Add libfeature3
fb36e6b Add libfeature2
7b2b43e Add libfeature1
~/tmp/mainrepo (master) $ touch feature4
~/tmp/mainrepo (master) $ git add *; git commit -m "Add feature4"
~/tmp/mainrepo (master) $ git log --oneline
ee67500 Add feature4
88251f1 Merge commit '9b0d431aaec6c321b456e336a625e0ee3fa8d651'
9b0d431 Squashed 'lib/' changes from fb36e6b..46b8c30
b13bf6d Update feature1
d0a6164 Add feature3
58945f9 Squashed 'lib/' content from commit fb36e6b
7bddfdd Merge commit '58945f907447e965eadc45c6074bff1a8cce782c' as 'lib'
e2c7cdf Add feature2
f3deaf9 Add feature1
Now we change something inside the library:
~/tmp/mainrepo (master) $ echo "bugfix" >> lib/libfeature2
~/tmp/mainrepo (master) $ git add *; git commit -m "Bugfix in library"
How do we push this to the library? The easiest case is if the library expects a pull-request. We then simply push a new branch which the library owners have to merge (somehow). This is great - it is not our responsibility!
~/tmp/mainrepo (master) $ git subtree push -P lib library branch-for-review --squash
git push using: library branch-for-review
Counting objects: 10, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (7/7), done.
Writing objects: 100% (10/10), 859 bytes | 0 bytes/s, done.
Total 10 (delta 3), reused 0 (delta 0)
To /home/rat/tmp/librepo/.git
* [new branch] 93510f81b90f7880b3b8cb086574824e2e653f5a -> branch-for-review
If we instead try to push the changes directly to the library, we will fail due to remote changes:
~/tmp/maincopy (master) $ git subtree push -P lib library master --squash
git push using: library master
To /home/rat/tmp/librepo/.git
! [rejected] 93510f81b90f7880b3b8cb086574824e2e653f5a -> master (fetch first)
error: failed to push some refs to '/home/rat/tmp/librepo/.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
So we perform a subtree merge first…
~/tmp/maincopy (master) $ git subtree pull -P lib library master --squash
remote: Counting objects: 2, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 2 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (2/2), done.
From /home/rat/tmp/librepo/
* branch master -> FETCH_HEAD
* [new branch] master -> library/master
Merge made by the 'recursive' strategy.
lib/libfeature4 | 0
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 lib/libfeature4
Then we can push this to the (bare) repository, where the bugfix and the merge are added to the history:
~/tmp/mainrepo (master) $ git subtree push -P lib library2 master --squash
git push using: library2 master
Counting objects: 5, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (5/5), 538 bytes | 0 bytes/s, done.
Total 5 (delta 2), reused 0 (delta 0)
To /home/rat/tmp/lib.git
570b0be..2872ed9 2872ed9313f0224a6a1eddc944a24b9343652e4a -> master
~/tmp/lib.git (master) $ git log --graph --oneline
* 2872ed9 Merge commit '37c4398e18eec96a68a5194384edf8be77e47c98'
|\
| * 570b0be Add libfeature4
* | 93510f8 Bugfix in library
|/
* 46b8c30 Add libfeature3
* fb36e6b Add libfeature2
* 7b2b43e Add libfeature1
Can we check beforehand what is send to the remote? Let us try this:
~/tmp/mainrepo (master) $ git subtree split -P lib -b pull-request --squash
Created branch 'pull-request'
2872ed9313f0224a6a1eddc944a24b9343652e4a
~/tmp/mainrepo (master) $ git log --graph pull-request --oneline
* 2872ed9 Merge commit '37c4398e18eec96a68a5194384edf8be77e47c98'
|\
| * 570b0be Add libfeature4
* | 93510f8 Bugfix in library
|/
* 46b8c30 Add libfeature3
* fb36e6b Add libfeature2
* 7b2b43e Add libfeature1
rat@m7480 ~/tmp/mainrepo (master) $ git log --graph --oneline
* 1ed0204 Merge commit '37c4398e18eec96a68a5194384edf8be77e47c98'
|\
| * 37c4398 Squashed 'lib/' changes from 46b8c30..570b0be
* | aa71d2f Bugfix in library
* | ee67500 Add feature4
* | 88251f1 Merge commit '9b0d431aaec6c321b456e336a625e0ee3fa8d651'
|\ \
| |/
| * 9b0d431 Squashed 'lib/' changes from fb36e6b..46b8c30
* | b13bf6d Update feature1
* | d0a6164 Add feature3
* | 7bddfdd Merge commit '58945f907447e965eadc45c6074bff1a8cce782c' as 'lib'
|\ \
| |/
| * 58945f9 Squashed 'lib/' content from commit fb36e6b
* e2c7cdf Add feature2
* f3deaf9 Add feature1
Even more advanced: using branches
This setup has us use a local clone of a bare repository into which we integrate another bare repository as a subtree library, using a development branch….