Vendoring a third-party library
Pin an external dependency to an exact commit inside your repo, and move to a newer version only when you choose to.
Git · Command
git submodule lets you add, initialize, update, inspect and manage submodules. A submodule is another Git repository nested inside a superproject and pinned to a specific commit.
Last updated
The superproject records each submodule in two places: a commit pointer (gitlink) in its tree, and a name/path/URL entry in the versioned .gitmodules file. The init subcommand copies URLs from .gitmodules into your local .git/config. The update subcommand then clones any missing submodules, fetches commits that are missing, and moves each submodule's working tree to match what the superproject expects. By default it does this by checking out the recorded commit on a detached HEAD. Other subcommands handle status, URL syncing, branch tracking, unregistering submodules and running a command in every submodule.
git submodule [--quiet] add [<options>] [--] <repository> [<path>]
git submodule [--quiet] status [--cached] [--recursive] [--] [<path>…]
git submodule [--quiet] init [--] [<path>…]
git submodule [--quiet] deinit [-f|--force] (--all|[--] <path>...)
git submodule [--quiet] update [<options>] [--] [<path>…]
git submodule [--quiet] set-branch [<options>] [--] <path>
git submodule [--quiet] set-url [--] <path> <newurl>
git submodule [--quiet] summary [<options>] [--] [<path>…]
git submodule [--quiet] foreach [--recursive] <command>
git submodule [--quiet] sync [--recursive] [--] [<path>…]
git submodule [--quiet] absorbgitdirs [--] [<path>…]| Name | Type | Description |
|---|---|---|
<repository> | URL | Used by add. This is the origin URL of the new submodule. It can be absolute, or relative (starting with ./ or ../) to the superproject's default remote. |
<path>... | path(s) | Limits the command to the submodules at these paths. It is required for add, where it sets where the clone goes and, by default, the submodule's logical name. |
--recursive | flag | Also processes nested submodules. Only valid for foreach, update, status and sync. |
--init | flag | Used with update. Initializes any submodules that have not been initialized yet before updating them. |
--remote | flag | Used with update. Updates the submodule to its remote-tracking branch instead of the commit recorded in the superproject. |
--checkout | --rebase | --merge | flag | Chooses how update applies the target commit: a detached checkout (the default), rebasing the current branch, or merging into it. |
-b, --branch=<branch> | string | Used with add. Sets the branch to track, which is stored as submodule.<name>.branch in .gitmodules for update --remote. |
-f, --force | flag | Forces add, deinit or update to go ahead even when it would normally fail. |
-j, --jobs=<n> | integer | Used with update. Clones new submodules in parallel. Defaults to submodule.fetchJobs. |
--depth=<depth> | integer | Used with add or update. Makes a shallow clone with history truncated to this many revisions. |
-q, --quiet | flag | Prints only error messages. |
# Add a library as a submodule and commit it
git submodule add -b main https://example.com/libfoo.git vendor/libfoo
git commit -m "Add libfoo submodule"
# After cloning the superproject elsewhere: init + fetch everything, including nested ones
git submodule update --init --recursive --jobs 4
# Inspect state (- uninitialized, + differs from index)
git submodule status --recursive
# Pull upstream changes from the tracked branch, then record the new pointer
git submodule update --remote vendor/libfoo
git add vendor/libfoo
git commit -m "Bump libfoo"
# Run a command in each submodule
git submodule foreach 'echo $sm_path $(git rev-parse HEAD)'This walks through the usual cycle: add a submodule, set it up after a fresh clone, check its status, move it to a newer upstream commit and record that change in the superproject.
Pin an external dependency to an exact commit inside your repo, and move to a newer version only when you choose to.
Run update --init --recursive with --jobs (and optionally --depth) so a build agent gets every nested repo at the exact commits recorded.
After a submodule's hosting location changes in .gitmodules, run sync (or set-url) so existing local clones fetch from the new address.
Use foreach to run commands such as git fetch or git clean in every checked-out submodule at once.
The superproject records a specific commit for each submodule, not a branch. The default update procedure, checkout, checks out that recorded commit on a detached HEAD. If you want to stay on a branch, use --rebase or --merge, or set submodule.<name>.update.
Follow-up: What is the difference between 'update --merge' and 'update --remote --merge'? The plain version merges the commit recorded in the superproject's gitlink into the submodule's current branch. With --remote, it merges the commit from the submodule's upstream tracking branch instead.
init copies each submodule's URL (and its update setting, if safe) from .gitmodules into .git/config, which marks the submodule as one you want. update then clones missing submodules, fetches missing commits and puts each working tree at the expected commit. You can combine both steps with update --init.
Follow-up: Why doesn't init copy a custom '!command' update setting from .gitmodules? .gitmodules comes from whoever publishes the repo, so copying an arbitrary command would let a remote repo run code on your machine. Git skips it for security reasons.
git submodule deinit <path> removes the submodule's section from .git/config and its working tree, but leaves the project's history alone. To remove the submodule from the repository for everyone, run git rm <path>, which also removes its .gitmodules entry, and commit the change.
Follow-up: What happens to the submodule's git directory after deinit? It stays under the superproject's $GIT_DIR/modules, so running git submodule init again can bring the submodule back without re-cloning.
gitsubmodulesgitmodulesgit-rmgit-clonegit-configgit-diffgit-status