Skip to main content

Git Composition Strategy

Choosing Between Submodule and Subtree for Dependency Management

Before implementing a specific technical solution, evaluate the development philosophy that best fits your project. The choice usually hinges on whether you treat external code as an independent dependency (like a reference) or as an integrated part of your project's source (like a managed fork).


Decision Logic

The following table outlines the primary trade-offs between the two approaches:

DimensionSubmodule (Separation)Subtree (Inclusion)
Mental ModelReference: A pointer to an external repoManaged Fork: Code is merged into your repo
Philosophy"Dependency as a pointer""Dependency as source code"
ModificationRare — dependency is an independent repoFrequent — dependency is part of the project
Update FrequencyLow — deliberate, infrequent upgradesHigh — changes happen alongside main development
Sync CostHigher — two-step (sub-repo + pointer)Lower — single commit covers both
OnboardingHigher — requires Submodule workflowsLower — standard Git workflows suffice
CI/CDNeeds recursive clone/initZero extra setup

Short Guidance

  • Prefer Submodule if the dependency is maintained independently (e.g., a stable library) and you only need to upgrade it occasionally. It works like a link to a specific version.
  • Prefer Subtree if your team frequently patches or evolves the dependency code while working on features. It works like forking the code directly into your repository, keeping everything in one place.

Architectural Fit

Use this guide to determine where your requirement falls within the Git ecosystem:


Comparison Matrix

DimensionSubmoduleSubtreePackage Manager
StoragePointer to commit hashFull code mergedBuild artifacts (usually)
WorkflowHigher (init/update)Lower (normal Git)Managed (npm/pip)
HistorySeparatedMergedNone (source hidden)
CI/CDNeeds recursive cloneZero extra setupNeeds registry/install
Push UpstreamTrivialPossible but slowVia release process