Git Subtree
As mentioned on our previous page, Git Submodule is useful for specific cases. For tracking software dependencies, many developers prefer Git Subtree.
What is Git Subtree
Git Subtree is an alternative to Git Submodule. It allows nesting one repository inside another one as a subdirectory. It is one of the ways to track the history of software dependencies. But subtrees should not be confused with submodules. Unlike submodules, subtrees do not need .gitmodules files or gitlinks in the repository. A subtree is just a subdirectory that you can commit to, branch, and merge along with your project.
Why to Use Git Subtree
Pros
- Supported by Git 1.7.10 and later.
- Simple workflow management.
- Available sub-project code after the super project is done.
- No new Git knowledge required to use
git subtree. - Does not add new metadata files (e.g.,
.gitmodules). - Allows modifying content without a separate repository copy of the dependency.
Cons
- Requires learning a new merge strategy.
- Complicated process for contributing code back upstream to sub-projects.
- Super and sub-project code are mixed in the same repository.
How to Use Git Subtrees
Let’s assume there is an external project, and you want to add it to your repository.
For example, to add a vim extension in a repository that stores your vim setup do the following:
git subtree add --prefix .vim/bundle/example https://github.com/Example/vim-example.git master --squashThis will squash the entire history of the vim-example project into your folder .vim/bundle/example, recording the SHA-1 of master at the time for future reference. The output is the following two commits:
commit 6d7054b3acea64e2e31f4d6fb2e3be12e5865e87
Merge: 87fa91e ef86deb
Author: Ann Smith<[email protected]m>
Date: Tue Jun 10 13:37:03 2016 +0200
Merge commit 'fe67ddf158faccff4082d78a25c45d8cd93e8ba8' as '.vim/bundle/example'
commit fe67ddf158faccff4082d78a25c45d8cd93e8ba8
Author: Ann Smith<[email protected]m>
Date: Tue May 12 13:37:03 2015 +0200
Squashed '.vim/bundle/example/' content from commit b999b09
git-subtree-dir: .vim/bundle/example
git-subtree-split: b999b09cd9d69f359fa5668e81b09dcfde455ccaTo update the sub-folder to the latest version of the child repository, run the following:
git subtree pull --prefix .vim/bundle/example https://github.com/Example/vim-example.git master --squashBut, git subtree stores subproject commit ids and not references in the meta-data. Find the symbolic name connected with a commit:
git ls-remote https://github.com/Example/vim-example.git | grep <commit-sha>Replace <commit-sha> with the actual commit hash.
Rebasing After Git Subtree
To rebase a repository containing subtrees, use the --interactive mode of git rebase. You can drop or squash the subtree merge commits, then run git rebase --continue. Note that rewriting history requires re-executing git subtree add or git subtree pull afterward. Be aware that history rewriting may cause merge conflicts when rebasing subtrees, as the commit structure changes.
OPTIONS
| -q, --quiet | Suppresses unnecessary result messages on stderr. |
|---|---|
| -d, --debug | Produces additional debug messages on stderr. |
-P <prefix>, --prefix=<prefix> | Defines the path in the repository to the subtree you want to manipulate. It is mandatory for all commands. |
-m <message>, --message=<message> | Specifies <message> as the commit message for the merge commit. It is only valid for add, merge and pull. |
Using Git Subtree Without Remote Tracking
Add the git subtree at a specified prefix folder. Use the --squash flag to preserve the whole subproject history in your main repository:
git subtree add --prefix .vim/bundle/vim-double-upon https://hostname.org/example/vim-plugins.git master --squashThe command performs a fetch and squashes the history. The output typically shows the fetch progress followed by the addition confirmation:
git fetch https://hostname.org/example/vim-plugins.git master
warning: no common commits
remote: Counting objects: 325, done.
remote: Compressing objects: 100% (145/145), done.
remote: Total 325 (delta 101), reused 313 (delta 89)
Receiving objects: 100% (325/325), 61.47 KiB, done.
Resolving deltas: 100% (110/110), done.
From https://hostname.org/vim-plugins.git
* branch master -> FETCH_HEAD
Added dir '.vim/bundle/vim-double-upon'This creates a merge commit by squashing the entire history of the subproject into a single one:
3bca0ad [4 minutes ago] (HEAD, stree) Merge commit 'fa2f5dc4f1b94356bca8a440c786a94f75dc0a45' as '.vim/bundle/vim-double-upon' [John Brown]
fa2f5dc [4 minutes ago] Squashed '.vim/bundle/vim-double-upon/' content from commit 13189ec [John Brown]For updating the code of the plugin from the upstream repository, do a git subtree pull:
git subtree pull --prefix .vim/bundle/vim-double-upon https://hostname.org/example/vim-plugins.git master --squashTo contribute changes back to the upstream repository, extract the subtree history using git subtree split:
git subtree split --prefix .vim/bundle/vim-double-upon -b split-branch
git push split-branchThis creates a new branch containing only the subtree's history, which can then be pushed to the upstream repository.
To make the commands shorter, add the sub-project as a remote.
Adding Sub-project as a Remote
Adding as a remote shortens the process:
git remote add -f vim-double-upon https://hostname.org/example/vim-plugins.gitAdd the subtree:
git subtree add --prefix .vim/bundle/vim-double-upon vim-double-upon master --squashUpdate the sub-project like this:
git fetch vim-double-upon master
git subtree pull --prefix .vim/bundle/vim-double-upon vim-double-upon master --squashGit Subtree is an alternative to submodules. While submodules place another project in a directory and keep the remote repository in sync, Git Subtree keeps the subproject as a regular directory. Contributing changes back to the upstream repository requires using git subtree split to extract the subproject history, as direct bidirectional sync is not natively supported.
Practice
What are the features and usage of Git subtree?