Before this:Your first repository
Submodules, hooks & worktrees
Key takeaways
Three features you won’t reach for daily but will be glad to know. Submodules nest one
repo inside another at a pinned commit (git submodule add, clone with
--recurse-submodules) — flexible but full of gotchas, with subtree as a simpler
alternative. Hooks are scripts Git runs at events like pre-commit and
pre-push; they live in .git/hooks, so they aren’t committed by default — share
them via core.hooksPath or the pre-commit framework. Worktrees
(git worktree add ../feature feature) check out multiple branches at once from one
repo, no re-cloning.
These three solve specific problems: depending on another repo, automating checks at Git events, and juggling several branches simultaneously. Each is optional — but each turns an awkward workflow into a smooth one when its problem is yours.
Submodules: a repo inside a repo
A submodule embeds one Git repository inside another at a fixed path. The outer “superproject” doesn’t copy the inner files — it records the submodule’s URL and the exact commit to check out, so the dependency is versioned by reference:
$ git submodule add https://github.com/acme/shared-ui.git vendor/shared-ui
Cloning into 'vendor/shared-ui'...
$ git status
new file: .gitmodules
new file: vendor/shared-ui
Adding a submodule creates a tracked .gitmodules file (mapping path → URL) and stages
the submodule as a single entry pointing at one commit. When someone clones the
superproject, the submodule directory is empty until they fetch it:
$ git clone --recurse-submodules https://github.com/acme/app.git # clone + fetch submodules
$ git submodule update --init --recursive # for an already-cloned repo
The gotchas are real:
- A submodule is pinned to one commit. To bump it,
cdin,git pull, then commit the new pointer in the superproject — easy to forget. - A plain
git clone(no--recurse-submodules) leaves submodules empty, surprising teammates. - Branch switches in the superproject don’t automatically update submodule contents.
If that’s more ceremony than you want, git subtree is the common alternative — it
merges the dependency’s files directly into your repo, so clones just work with no extra
commands (at the cost of a bigger repo and trickier upstream contributions). For most
language ecosystems, a proper package manager beats both.
Hooks: scripts that fire on Git events
Hooks are scripts Git runs automatically at points in its workflow. They live in
.git/hooks, where Git ships disabled samples:
$ ls .git/hooks
applypatch-msg.sample pre-commit.sample pre-push.sample
commit-msg.sample pre-rebase.sample prepare-commit-msg.sample
Remove the .sample suffix and make the file executable to activate it. The handy ones:
| Hook | Fires | Typical use |
|---|---|---|
pre-commit |
before a commit is created | run linters/formatters; reject on failure |
commit-msg |
after the message is written | enforce a message format (e.g. Conventional Commits) |
pre-push |
before a push uploads | run the test suite; block a broken push |
A minimal pre-commit that blocks the commit (non-zero exit) when linting fails:
#!/bin/sh
npm run lint || {
echo "Lint failed — commit aborted."
exit 1
}
The catch: .git is never committed or cloned, so hooks are strictly local — your
teammates won’t get them automatically. Two ways to share:
$ git config core.hooksPath .githooks # track scripts in .githooks/ and point Git at them
Keep the scripts in a committed .githooks/ directory and have everyone run that one
config line (or set it in a setup script). Better still for teams, adopt the pre-commit
framework (pre-commit): you commit a .pre-commit-config.yaml listing the checks, and
pre-commit install wires up the hook for each developer from that shared config.
Worktrees: multiple branches checked out at once
Need to fix an urgent bug while your feature branch sits mid-edit? The old answer was
git stash or a second clone. Worktrees are cleaner: one extra working directory
attached to the same repository, on a different branch:
$ git worktree add ../hotfix main
Preparing worktree (checking out 'main')
HEAD is now at 9f8e7d6 Release 1.4
Now ../hotfix is a full working directory on main while your original directory stays
on feature — both backed by one shared .git store, so it’s far lighter than cloning
again. Create a directory and a new branch in one step, and tidy up when done:
$ git worktree add -b experiment ../experiment # new branch "experiment" in ../experiment
$ git worktree list
/home/me/app a1b2c3d [feature]
/home/me/hotfix 9f8e7d6 [main]
/home/me/experiment a1b2c3d [experiment]
$ git worktree remove ../hotfix # remove when finished
The one rule: a branch can be checked out in only one worktree at a time, which keeps them from stepping on each other. Worktrees pair nicely with bisect or a long-running build you don’t want to interrupt your main work.
Quick check: why don't your teammates automatically get the pre-commit hook you set up?
Recap
- Submodules nest a repo at a pinned commit:
git submodule add, clone with--recurse-submodules, fetch later withgit submodule update --init. Mind the gotchas; consider subtree or a package manager. - Hooks run scripts on events like
pre-commit,commit-msg,pre-push; they live in.git/hooksand aren’t committed — share viacore.hooksPathor the pre-commit framework. - Worktrees (
git worktree add ../dir branch) check out multiple branches at once from one repo; a branch can live in only one worktree.
Next up: choosing how your team branches and ships — branching strategies: trunk, GitHub Flow & Git Flow.
Frequently asked questions
What is a Git submodule?
A submodule is a Git repository nested inside another Git repository at a specific path. The outer (super) project doesn’t store the submodule’s files directly — it records the submodule’s URL and the exact commit it should be checked out at. This lets you include and version a dependency or shared library by reference while keeping its history separate. Submodules are powerful but have sharp edges, so many teams prefer a package manager or git subtree instead.
Why aren't my Git hooks shared with my team?
Hooks live in .git/hooks, and the .git directory is never committed or cloned, so hooks are strictly local to each clone. To share them, keep the scripts in a tracked directory (for example .githooks) and point Git at it with git config core.hooksPath .githooks, or adopt a framework like pre-commit that installs hooks from a committed config file.
What is a Git worktree?
A worktree is an additional working directory attached to the same repository, letting you have multiple branches checked out at once without cloning again. git worktree add ../hotfix main creates a second directory on the main branch while your original directory stays on its feature branch. All worktrees share one .git store, so it’s far lighter than a second clone and they can’t both check out the same branch.
When should I use a submodule versus a subtree?
Use a submodule when you want to track a dependency by reference, keep its history fully separate, and contribute back to it easily — at the cost of extra commands and a steeper learning curve for the whole team. Use git subtree when you’d rather vendor the dependency’s files directly into your repo so clones just work with no special steps, accepting a larger repo and trickier upstream contribution.