Skip to main content

Git Subtree

Introduction

When working on complex projects, you'll often need to incorporate external code repositories into your own. While Git submodules provide one approach to this problem, Git subtree offers a more streamlined alternative that many developers prefer.

Git subtree allows you to include another repository within your own, treating the external code as if it were part of your project while still maintaining the ability to pull updates from (and push changes to) the original source. Unlike submodules, subtrees store the actual content of the external repository directly in your project, making it easier for other developers to clone and work with your repository.

In this guide, we'll explore how Git subtree works, when to use it, and how it can simplify your workflow when managing external dependencies.

What is Git Subtree?

Git subtree is a built-in Git command that lets you insert any repository as a subdirectory of your project. It accomplishes this by:

  1. Merging the entire history of the external repository into your own
  2. Preserving all commit history of the included repository
  3. Allowing you to treat the subtree content as part of your repository
  4. Enabling you to pull updates from the external repository
  5. Optionally allowing you to contribute changes back to the external repository

When to Use Git Subtree

Git subtree is particularly useful in the following scenarios:

  • When you want to include a library or framework in your project
  • When you need to share code across multiple projects
  • When you want other team members to have immediate access to dependencies without additional steps
  • When you prefer to have all project code in a single repository (a "monorepo" approach)
  • When the external repository rarely changes or when you plan to customize it heavily

Git Subtree vs. Git Submodule

Before diving deeper, let's understand how subtree differs from Git's other dependency management tool:

FeatureGit SubtreeGit Submodule
Repository contentCopied into main repositoryReferenced as a pointer
Clone processSingle step (everything included)Requires initialization and update
History preservationPreserved but mergedKept separate
Complexity for team membersLower (transparent)Higher (requires submodule knowledge)
Size impactIncreases repository sizeMinimal size impact
Update processMore complex commandsSimpler update commands

Basic Git Subtree Commands

Let's explore the fundamental Git subtree commands:

Adding a Subtree

To add a subtree to your repository:

bash
git subtree add --prefix=<folder> <repository-url> <branch> --squash

Example:

bash
git subtree add --prefix=libs/jquery https://github.com/jquery/jquery.git main --squash

This command:

  • Adds the jQuery repository to your project in the libs/jquery directory
  • Takes code from the main branch
  • Uses --squash to condense the external repository's history into a single commit (optional but recommended for large repositories)

Updating a Subtree

To pull the latest changes from the external repository:

bash
git subtree pull --prefix=<folder> <repository-url> <branch> --squash

Example:

bash
git subtree pull --prefix=libs/jquery https://github.com/jquery/jquery.git main --squash

Contributing Back to the Original Repository

If you've made changes to the subtree and want to push them back to the original repository:

bash
git subtree push --prefix=<folder> <repository-url> <branch>

Example:

bash
git subtree push --prefix=libs/jquery https://github.com/jquery/jquery.git my-jquery-fix

Real-world Example: Including a UI Component Library

Let's walk through a practical example of using Git subtree to include a UI component library in your web application.

Scenario

You're building a web application and want to include a lightweight UI component library called "mini-ui" that you might need to customize for your project.

Step 1: Initialize Your Project

bash
mkdir my-web-app
cd my-web-app
git init
echo "# My Web Application" > README.md
git add README.md
git commit -m "Initial commit"

Step 2: Add the UI Library as a Subtree

bash
git subtree add --prefix=src/lib/mini-ui https://github.com/example/mini-ui.git main --squash

Output:

git fetch https://github.com/example/mini-ui.git main
From https://github.com/example/mini-ui.git
* branch main -> FETCH_HEAD
Added dir 'src/lib/mini-ui'

Step 3: Use the Library in Your Project

Now you can use the components from the library in your application:

javascript
// src/App.js
import { Button, Card } from './lib/mini-ui/components';

function App() {
return (
<div className="app">
<Card title="Welcome">
<p>This is my application using the mini-ui library!</p>
<Button primary>Get Started</Button>
</Card>
</div>
);
}

export default App;

Step 4: Customize the Library

You can modify the library files directly:

bash
# Edit a component
vim src/lib/mini-ui/components/Button.js

# Commit your changes
git add src/lib/mini-ui/components/Button.js
git commit -m "Customize Button component for our brand colors"

Step 5: Pull Updates Later

When the original library releases updates:

bash
git subtree pull --prefix=src/lib/mini-ui https://github.com/example/mini-ui.git main --squash

If there are conflicts with your customizations, you'll need to resolve them as you would with any Git merge conflict.

Working with Subtrees in a Team Environment

When working with a team, it's important to document your subtree usage to help other developers understand the project structure:

  1. Document the subtree commands in your README.md
  2. Create scripts for common subtree operations
  3. Consider using Git aliases for frequently used subtree commands

Example documentation:

markdown
## External Dependencies

This project uses Git subtree to manage the following dependencies:

- Mini UI library: Located in `src/lib/mini-ui`
- Original repository: https://github.com/example/mini-ui.git
- To update: `git subtree pull --prefix=src/lib/mini-ui https://github.com/example/mini-ui.git main --squash`

Advanced Subtree Techniques

Splitting a Subtree

If you've made extensive changes to a subtree and want to extract them into their own repository:

bash
git subtree split --prefix=<folder> -b <branch-name>

Example:

bash
git subtree split --prefix=src/lib/mini-ui -b mini-ui-custom

This creates a new branch containing only the history of the specified folder, which you can then push to a new repository.

Using a Shorter Syntax with Remote Tracking

You can make subtree commands shorter by adding the external repository as a remote:

bash
# Add remote
git remote add mini-ui-remote https://github.com/example/mini-ui.git

# Add subtree using remote
git subtree add --prefix=src/lib/mini-ui mini-ui-remote main --squash

# Update later using remote
git subtree pull --prefix=src/lib/mini-ui mini-ui-remote main --squash

Visualizing Subtree History

To see the commit history related to your subtree:

bash
git log --oneline | grep -i "subtree"

This helps you track when subtree operations were performed.

Common Challenges and Solutions

Dealing with Merge Conflicts

When pulling updates that conflict with your local changes:

  1. Use standard Git merge conflict resolution tools
  2. Consider using a graphical merge tool: git mergetool
  3. After resolving conflicts, complete the merge with git commit

Managing Large Subtrees

For repositories with extensive history:

  1. Always use the --squash option to reduce history complexity
  2. Consider using Git LFS for large binary files
  3. Explore using a shallow clone for the subtree if you don't need full history

Keeping Track of Subtree Origins

Create a metadata file to track subtree information:

json
// subtrees.json
{
"subtrees": [
{
"prefix": "src/lib/mini-ui",
"repository": "https://github.com/example/mini-ui.git",
"branch": "main",
"added": "2023-03-15"
}
]
}

Comparison with Alternative Approaches

ApproachProsCons
Git Subtree- Single repository
- Full history available
- No special commands for team members
- Complex update process
- Increases repository size
Git Submodule- Smaller main repository
- Clear separation
- Additional clone steps
- Steeper learning curve for team
Package Manager (npm, etc.)- Standard dependency approach
- Version locking
- Less control over source
- External dependency
Copy-Paste- Simple
- Complete control
- No update path
- No history preservation

Summary

Git subtree provides a powerful way to incorporate external repositories into your project while maintaining a clean workflow. By merging the external repository's content directly into your own, you create a self-contained project that's easier for your team to work with.

Key benefits:

  • Single repository with no external dependencies
  • Full history preservation
  • No special knowledge required for team members
  • Ability to customize external code while still pulling updates

While subtree commands might seem complex at first, they provide flexibility that can significantly improve your dependency management workflow.

Exercises

  1. Add a subtree to a test repository and experiment with the basic commands
  2. Create a project that includes multiple subtrees and organize them logically
  3. Customize a subtree, then pull updates from the original repository
  4. Set up Git aliases for common subtree operations
  5. Try splitting a subtree into its own repository

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)