The Dependency Problem
Modern software is built on libraries. Usually, you use a package manager (npm, pip, cargo) to handle this. But sometimes, you need to depend on another Git repository directly—perhaps a private library or a C++ project that needs to be compiled from source.
Git offers two main ways to embed one repository inside another: Submodules and Subtrees.
Git Submodules
A submodule is a pointer to a specific commit in another repository. It stays in a subdirectory of your main project, but Git sees it as a separate entity.
Adding a Submodule
git submodule add https://github.com/libs/mylib.git libs/mylib
This creates a .gitmodules file (which tracks the mapping between URL and path) and downloads the code into libs/mylib.
Cloning with Submodules
When you clone a project that has submodules, the directories will be empty by default. You must initialize and update them.
git clone https://github.com/my/project.git
cd project
git submodule init
git submodule update
Or, do it all at once:
git clone --recurse-submodules https://github.com/my/project.git
The Detached HEAD Trap
When you enter a submodule directory, you are in a “Detached HEAD” state pointing to the commit recorded by the parent project. If you make changes and commit them, they exist only in that submodule. To update the parent project to use your new changes:
- Push changes inside the submodule.
- Go to the parent directory.
git add libs/mylib(this updates the pointer).git commit.
Git Subtree
Subtree is an alternative that allows you to nest one repository inside another as a normal subdirectory. Unlike submodules, the files are actually present in the parent repository’s history.
Adding a Subtree
git subtree add --prefix=libs/mylib https://github.com/libs/mylib.git main --squash
This merges the history of the library into your project under the libs/mylib folder.
Updating
To pull in new changes from the library:
git subtree pull --prefix=libs/mylib https://github.com/libs/mylib.git main --squash
Pros vs Cons
Submodules:
- Pros: Keeps repositories strictly separate. Good for large binary dependencies.
- Cons: Complex workflow. Easy to forget to push the submodule. Users must remember
--recurse-submodules.
Subtrees:
- Pros: Simple for users (it’s just files). No special clone commands needed.
- Cons: Complex for the maintainer. Mixing history can be confusing.
Best Practices
- Use Package Managers first: If you can use npm, Maven, or NuGet, do it. Use Git dependencies only when necessary.
- Automate: Use scripts to update submodules to ensure everyone is on the same version.
- Don’t modify submodules: Treat them as read-only build dependencies if possible.
Which file tracks the mapping of submodules to their URLs?
Cloning Recursively
# You want to clone a repo and automatically fetch all nested submodules. git clone https://host/repo.git