Home / Alt manpages / git-subtree(1)

  • git-subtree(1)
  • User command
  • linux

Share a Git Subdirectory as a Subtree Without Losing Its History

By the end of this guide, a directory in an application repository will contain an imported project, and you will have a repeatable way to update it or export it as a standalone Git branch. The examples use Git 2.43.0 and the git-man manual installed on this machine. Allow 10 to 20 minutes for a first pass, plus the time needed to inspect the commits you are about to merge.

Before you start

You need a Git repository with a clean working tree, a remote repository or ref containing the project to import, and permission to push to the destination when you publish the split. The commands below do not need root privileges. Work from the top level of the application repository.

A subtree is an ordinary directory in the main repository. It does not create a .gitmodules entry or require users to initialise anything separately. That is the useful distinction from a submodule, but it also means the imported files are part of normal commits in the main project.

Checkpoint

Confirm that the repository is the one you intend to change.

git rev-parse --show-toplevel
git status --short
git --version

Expected version output for the installation covered here is similar to git version 2.43.0. The status command should print nothing. If it prints changes, commit them or make a clearly named temporary branch before continuing. Do not hide unrelated work with a broad reset.

Step 1: import the project

Choose the directory that will hold the project. It must not already contain files you need to preserve. The --squash option is a sensible default for a vendor-style import: it records the imported state as one merge commit instead of copying the complete upstream history into the application repository.

git subtree add   --prefix=vendor/widget   --squash   https://github.com/EXAMPLE/widget.git main

Replace both EXAMPLE/widget.git and main. The ref must exist in the repository you name. The command creates the vendor/widget directory and a commit in the current branch.

Verify the result before editing the imported code:

git log --oneline --decorate -3 -- vendor/widget
git ls-tree --name-only HEAD vendor/widget

The log should include a subtree merge commit, and the second command should print vendor/widget. If the import is wrong, stop here. Since the command created a commit, the safest reversal is to inspect its ID and use git revert <commit-id>, which preserves a record of the correction. Do not use a hard reset on a shared branch without first checking who has pulled it.

Step 2: update the subtree

When the upstream project changes, fetch and merge a new ref with pull. It combines the fetch and subtree merge, so the prefix remains explicit and the update is visible in your application history.

git subtree pull   --prefix=vendor/widget   https://github.com/EXAMPLE/widget.git main   --squash

Use --squash consistently if the original import used it. Squashed updates keep the application log smaller and can move forwards or backwards between upstream versions. They do not discard local changes in the subtree, although an overlapping update can still produce an ordinary merge conflict.

Checkpoint

Inspect the merge before starting local work.

git status --short
git log --oneline --decorate -3 -- vendor/widget
git diff HEAD^ HEAD --stat -- vendor/widget

Resolve a conflict in the normal Git way, then run your project tests and finish with git add and git commit. To abandon an unresolved merge before committing, use git merge --abort and check the status again. That recovery applies to the merge phase; it does not undo a completed earlier import.

Step 3: make and separate local changes

Keep changes that belong to the application separate from changes intended for the library where practical. This makes the synthetic history easier to review. It is not a hard requirement: git subtree split leaves out unrelated parts of a commit when it constructs the extracted project.

Commit the library change in the main repository as usual, for example:

git add vendor/widget
git commit -m "Fix widget configuration handling"

Check that the commit is present under the chosen prefix:

git log --oneline --all -- vendor/widget

Step 4: split the directory into a publishable branch

Run split with the same prefix. It creates synthetic commits whose project root is the contents of vendor/widget, then prints the new tip commit ID. Adding --branch gives that result a local branch you can inspect and push.

git subtree split   --prefix=vendor/widget   --annotate="(widget split) "   --branch widget-export

Git may print progress followed by a 40-character commit ID and a message such as Created branch 'widget-export'. The new branch must not already exist. Verify that its root contains the library files, not the vendor/widget directory itself:

git ls-tree --name-only widget-export
git log --oneline --decorate widget-export

Keep the exact --annotate value for future splits. Changing it changes the synthetic commit messages and removes the guarantee that repeated splits of the same history produce identical commit IDs. If you use --rejoin, Git can optimise later splits, but it also adds synthetic commits to the main history. Use it deliberately, and pair it with --squash when your merges are squashed.

Step 5: publish the split

Inspect the branch and remote name before pushing. The following command publishes the branch as main in the destination repository:

git push https://github.com/EXAMPLE/widget.git widget-export:main

Warning

A push changes a remote repository. Confirm the URL, destination ref and branch protection rules first. Do not add a leading force option just to make the command succeed. If the remote rejects the update, inspect its current history and decide whether a normal merge or an explicitly approved history rewrite is appropriate.

After a successful push, verify the remote-tracking state if that remote is configured locally:

git ls-remote https://github.com/EXAMPLE/widget.git refs/heads/main

Common traps

  • Wrong prefix: --prefix is mandatory and is the path inside the main repository. A typo can produce an empty or unrelated split.
  • Unexpected history size: omit --squash only when you deliberately want the upstream commit history imported.
  • Missing branch: --branch widget-export refuses to replace an existing branch. Choose a new name or inspect the existing branch first.
  • Repeated split differs: use the same annotation and other split settings. A prior --rejoin can be bypassed with --ignore-joins, but rebuilding all history may take much longer.

Done means

  • The application repository is clean and contains the intended subtree.
  • Updates arrive through an explicit git subtree pull with the chosen squash policy.
  • git subtree split creates a branch whose root is the subtree contents.
  • You inspected the split before pushing it to the standalone repository.
  • The split annotation and squash decision are recorded for the next update.