W3docs

Git Subtree

On this page, you will find a brief introduction to Git subtrees, get introduced with its pros and cons, and the difference between subtree and submodule.

As mentioned on the previous page, Git Submodule is useful for specific cases. For tracking software dependencies inside a single repository, many developers prefer Git Subtree instead.

This page explains what a Git subtree is, when to use one over a submodule, and how to add, update, and contribute changes back to a subtree. It also covers rebasing a repository that contains subtrees and the command options you will use most.

What is Git Subtree

Git Subtree is an alternative to Git Submodule. It lets you nest one repository inside another as a subdirectory while keeping the history of the embedded ("sub") project. It is one of the ways to track the history of software dependencies.

The key difference from submodules is that a subtree is just a directory. Unlike submodules, subtrees do not need a .gitmodules file or special gitlinks in your repository. The files live directly in your working tree, get committed with everything else, and travel with every clone, branch, and merge automatically. Anyone who clones your repository gets the dependency's code without running any extra command.

Under the hood, git subtree is a porcelain command (a wrapper) around standard plumbing such as git merge, git read-tree, and git filter-branch/split. You do not need to learn a new storage format — only a few new commands.

Info

With submodules, a clone gives you an empty directory until you run git submodule update --init. With subtrees, the files are already there. That single difference drives most of the pros and cons below.

Subtree vs. Submodule

Both approaches embed one repository inside another, but they make opposite trade-offs. Choose based on who consumes the repository and how often code flows back upstream.

AspectGit SubtreeGit Submodule
Files after cloneAlready presentEmpty until submodule update --init
Extra metadataNone.gitmodules + gitlinks
Pins an exact upstream commitNo (history is merged in)Yes (records a SHA)
Contributing back upstreamManual (git subtree split + push)Natural (commit inside the submodule)
Repository sizeLarger (sub-project history is copied)Smaller (only a pointer)
Learning curve for consumersNoneMust learn submodule commands

A rough rule: prefer subtrees when you mostly consume a dependency and want every clone to just work; prefer submodules when the sub-project is actively co-developed and you need to pin or push to an exact upstream commit.

Why to Use Git Subtree

Pros

  • Supported by Git 1.7.10 and later (the subtree command ships with Git itself).
  • Simple workflow for people who clone your repo — no extra commands to learn.
  • Sub-project code is present immediately after cloning the super-project.
  • Does not add new metadata files (e.g., .gitmodules).
  • Lets you modify the dependency in place without a separate repository checkout.

Cons

  • Requires learning a new merge strategy and a few subtree-specific commands.
  • Contributing code back upstream is a multi-step process (git subtree split, then push).
  • Super-project and sub-project code are mixed in the same repository, which inflates its size and can clutter the history.

How to Add a Subtree

Suppose there is an external project, and you want to add it to your repository under a specific directory.

For example, to add a Vim extension into a repository that stores your Vim setup, run:

git subtree add --prefix .vim/bundle/example https://github.com/Example/vim-example.git master --squash

The pieces of this command:

  • --prefix .vim/bundle/example — the directory where the sub-project will live. This option is mandatory for every subtree command.
  • https://github.com/Example/vim-example.git — the source repository (a URL or, later, a named remote).
  • master — the branch (or commit/tag) to pull from.
  • --squash — collapse the sub-project's full history into a single commit, so it does not pollute your log. Omit it if you want the complete upstream history merged in.

With --squash, Git records the SHA-1 of master at that moment for future reference and produces two commits — the squashed import and the merge:

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: b999b09cd9d69f359fa5668e81b09dcfde455cca

Updating a Subtree

To update the sub-folder to the latest version of the child repository, run a subtree pull with the same prefix and source:

git subtree pull --prefix .vim/bundle/example https://github.com/Example/vim-example.git master --squash

This fetches the upstream branch and merges it into your subtree directory, creating a new merge commit. Always use the same --prefix and the same --squash/no---squash choice you used in subtree add, or Git will not align the histories correctly.

Note that git subtree stores the sub-project's commit IDs in the commit message metadata, not symbolic references. To find the branch or tag name connected with a stored commit, query the remote:

git ls-remote https://github.com/Example/vim-example.git | grep <commit-sha>

Replace <commit-sha> with the actual commit hash from the git-subtree-split: line of your import commit.

Rebasing After Git Subtree

To rebase a repository that contains subtrees, use the --interactive mode of git rebase:

git rebase --interactive HEAD~5

In the editor, you can drop or squash the subtree merge commits, then save and run:

git rebase --continue

Because rebasing rewrites history, the subtree merge commits may be removed or reshaped. After such a rewrite you typically need to re-establish the subtree by re-running git subtree add or git subtree pull. Be aware that the changed commit structure can also trigger merge conflicts during the rebase, so prefer rebasing branches that have not been pushed and shared.

Common Options

OptionDescription
-q, --quietSuppresses unnecessary result messages on stderr.
-d, --debugProduces additional debug messages on stderr.
-P <prefix>, --prefix=<prefix>Defines the path in the repository to the subtree you want to manipulate. Mandatory for all commands.
-m <message>, --message=<message>Specifies <message> as the commit message for the merge commit. Valid for add, merge, and pull.
--squashImports the sub-project as a single commit instead of merging its full history.

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 --squash

The 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 --squash

Contributing Changes Back Upstream

Because the sub-project's code is mixed into your repository, you cannot simply push it back. You first need to extract the changes that touched the subtree directory into a standalone branch with git subtree split:

git subtree split --prefix .vim/bundle/vim-double-upon -b split-branch

This rewrites only the commits affecting .vim/bundle/vim-double-upon into a new branch (split-branch) whose paths are relative to the sub-project root. You can then push that branch to the upstream repository:

git push https://hostname.org/example/vim-plugins.git split-branch:master

This one-way extraction is why "contributing back upstream" is listed as a con above — there is no built-in bidirectional sync.

To make the everyday add/pull/push commands shorter, add the sub-project as a remote.

Adding Sub-project as a Remote

Registering the source as a named remote shortens every later command — you reference vim-double-upon instead of the full URL. The -f flag fetches it immediately:

git remote add -f vim-double-upon https://hostname.org/example/vim-plugins.git

Add the subtree:

git subtree add --prefix .vim/bundle/vim-double-upon vim-double-upon master --squash

Update the sub-project like this:

git fetch vim-double-upon master
git subtree pull --prefix .vim/bundle/vim-double-upon vim-double-upon master --squash

Summary

Git Subtree is an alternative to Git Submodule. While a submodule keeps the sub-project as a separate pointer that must be initialized, a subtree keeps the sub-project as a regular directory that is present in every clone. Use git subtree add to import a dependency, git subtree pull to update it, and git subtree split + push to send changes upstream — there is no native bidirectional sync. To learn more, explore Git Branch and Git Merge, which power the subtree commands under the hood.

Practice

Practice
What are the features and usage of Git subtree?
What are the features and usage of Git subtree?
Was this page helpful?