Skip to main content

Git Subtree Engineering Guide

Embedding External Repositories Without a Separate Pointer File

Git subtree lets you merge another repository into a subdirectory of your project, while retaining the ability to pull upstream updates and push changes back. Unlike git submodule, no .gitmodules file is needed — the foreign code lives inside your own history.


Core Concepts

How Subtree Works

At its core, git subtree merges an external repository into a designated prefix directory within your project. The foreign commits can either be preserved in your history, or squashed into a single commit per import when using --squash to keep your log clean.


Basic Operations

Adding a Subtree

# General syntax
git subtree add --prefix=<path> <repository_url> <branch> [--squash]

# Example: embed a utility library under vendor/tools
git subtree add --prefix=vendor/tools https://github.com/example/tools.git main --squash
FlagPurpose
--prefixTarget directory in the working tree (created if it does not exist)
--squashSquash external history into a single merge commit (recommended for a cleaner log)
<branch>Branch of the remote to import (main, master, develop, etc.)

Pulling Upstream Updates

# Pull latest changes from the remote into your prefix
git subtree pull --prefix=vendor/tools https://github.com/example/tools.git main --squash
info

git subtree pull internally performs a git merge of the remote branch into your prefix directory. Conflicts, if any, follow standard Git conflict resolution rules.


Development Workflow

Scenario: Importing a Library and Making Local Changes

  1. Add subtree (one-time setup): git subtree add --prefix=vendor/log https://github.com/user/log.git main --squash

  2. Modify library locally: Edit files in vendor/log/.

  3. Commit local changes: git add vendor/log && git commit -m "vendor(log): patch memory leak in parser"

  4. Push upstream (if you have write access): git subtree push --prefix=vendor/log https://github.com/user/log.git main


Best Practices

Common Patterns

  • Keep Prefix Paths Predictable: Organize subtrees under vendor/ or packages/.
  • Use --squash Consistently: Mixing squash and non-squash causes confusing histories.
  • Avoid Deep Nesting: Subtrees within subtrees are technically possible but lead to unpredictable merge behavior.
  • Commit Message Convention: Prefixing with vendor(<name>): makes it easy to filter history.

Troubleshooting

IssueSolution
subtree push is extremely slowUse git subtree push on a fresh clone or split first.
Merge conflicts on subtree pullResolve conflicts normally (git add <file> && git commit).
Wrong prefix directoryRe-add with the correct prefix and remove the old one.
Need to remove a subtreegit rm -r <prefix> && git commit -m "remove subtree <name>".

Migration

From Submodule to Subtree

# 1. Remove the submodule
git submodule deinit vendor/library
git rm vendor/library
rm -rf .git/modules/vendor/library
git commit -m "chore: remove submodule vendor/library"

# 2. Add as subtree
git subtree add --prefix=vendor/library https://github.com/user/library.git main --squash

References