Another repository inside yours, pinned to a commit: a shared UI kit, a vendored library, a dataset. zev fetches and updates it on clone, checkout and pull by itself, keeps the pin in a file you review, and never throws away work done inside it. What Git's submodules should have been.
Sometimes a project needs another project inside it at an exact version, such as a UI kit shared by three apps, a library you patch, or a dataset. Git's answer is submodules, and most people who've used them avoid them:
- forget `--recursive` and the folder is empty;
- the code inside sits on a detached HEAD, so work done there is easy to lose;
- the version you depend on is an invisible entry that reviews don't show.
zev's **subrepositories** keep the idea and drop the traps.
```diagram
type: cards
title: How subrepositories differ from submodules
---
📄 | The pin is a file you read | .zevsubs lists each one's address, branch and commit; a change to it shows in every diff and review
🔁 | Nothing to remember | zev clone, checkout and pull fetch subrepositories and put them at their pins by themselves
🛟 | Your work is safe | a subrepository with unsaved changes or commits of its own is never moved; zev says so instead
🌿 | Always on a branch | no detached HEAD: commit inside it like in any repository
```
## Add one
```bash
zev sub add zaryn/ui-kit lib/ui
```
zev clones the repository into `lib/ui`, writes its address, branch and current commit to `.zevsubs`, and stages that file. Commit it:
```bash
zev commit -m "Use the shared UI kit"
```
```text
# Subrepositories: see them with zev sub, fetch them with zev sub update.
[lib/ui]
url = zaryn/ui-kit
branch = main
commit = 8fef7c70fd8de3da1fed658b65bab8d1c3296c6f07f4e550274e43a3fde39a9c
```
The parent repository never tracks the subrepository's files. `zev status` and `zev add .` leave them alone, because they belong to the subrepository's own history.
## Everyone gets it, at the same version
```bash
zev clone you/app
sub lib/ui: fetched, at its pin
```
A clone fetches each subrepository and puts it at its pinned commit. So do `zev checkout`, `zev switch` and `zev pull` whenever the pin changes: switch to a branch that pins an older version and the subrepository follows, then switch back and it returns. There's no `--recursive` to remember and no separate update step.
## Move to a newer version
```bash
zev sub pull # pull each subrepository's branch, then pin what you got
zev commit -m "UI kit 2.4"
```
`zev sub pin` records whatever a subrepository is at now, for example after you committed a fix inside it. Push that fix from inside the subrepository too, so others can fetch the commit you pinned.
## See where they are
```bash
$ zev sub
✓ lib/ui zaryn/ui-kit @ 8fef7c70
at its pin
! vendor/parser zaryn/parser @ 1c2d3e4f
has commits newer than the pin: zev sub pin records them
```
Each subrepository is at its pin, behind it, ahead of it (commits of your own), elsewhere, missing, or has unsaved changes, and zev says what to run for each. `--brief` gives one line each and `--json` gives the path, address, branch, pin, current commit, state and number of changes.
## Your work inside one is never lost
A subrepository is an ordinary repository on an ordinary branch. `cd lib/ui`, edit, commit and push as usual. zev moves it only when that loses nothing:
- **unsaved changes** inside it: zev never moves it and says so;
- **commits of your own** beyond the pin: switching the parent leaves it alone and says why. `zev sub pin` records them; `zev sub update --force` puts it back at the pin, and `zev undo` inside the subrepository takes that back.
## Commands
```text
zev sub where each one is and what it needs
zev sub add <address> <folder> [--branch B] bring a repository in, pinned
zev sub update [<folder>…] [--force] fetch, and put each at its pin
zev sub pin [<folder>…] record each one's current commit
zev sub pull [<folder>…] pull each one's branch, then pin
zev sub remove <folder> stop tracking it here (its files stay)
```
## Coming from Git submodules
`zev import-git` lists the submodules it found. Once their repositories are on zev (`zev import-git` them too), run `zev sub add <address> <folder>` for each.
| Git submodules | zev subrepositories |
|---|---|
| `git clone --recursive`, or `git submodule update --init` after | `zev clone`; nothing after |
| A tree entry and `.gitmodules`; the version isn't shown in reviews | `.zevsubs`, one readable file with the commit in it |
| Detached HEAD inside | On a branch |
| `git submodule update` can discard commits made inside | Never moved over unsaved work or your own commits without `--force`, and undoable |
| `git status` shows little about the state inside | `zev sub` says where each one is and what to run |
See also: [Branches and merging](/docs/en/branches), [Moving from Git](/docs/en/from-git).
Sometimes a project needs another project inside it at an exact version, such as a UI kit shared by three apps, a library you patch, or a dataset. Git's answer is submodules, and most people who've used them avoid them:
forget --recursive and the folder is empty;
the code inside sits on a detached HEAD, so work done there is easy to lose;
the version you depend on is an invisible entry that reviews don't show.
zev's subrepositories keep the idea and drop the traps.
How subrepositories differ from submodules
📄
The pin is a file you read
.zevsubs lists each one's address, branch and commit; a change to it shows in every diff and review
🔁
Nothing to remember
zev clone, checkout and pull fetch subrepositories and put them at their pins by themselves
🛟
Your work is safe
a subrepository with unsaved changes or commits of its own is never moved; zev says so instead
🌿
Always on a branch
no detached HEAD: commit inside it like in any repository
zev clones the repository into lib/ui, writes its address, branch and current commit to .zevsubs, and stages that file. Commit it:
Terminal
zev commit -m "Use the shared UI kit"
Text
# Subrepositories: see them with zev sub, fetch them with zev sub update.[lib/ui]url = zaryn/ui-kitbranch = maincommit = 8fef7c70fd8de3da1fed658b65bab8d1c3296c6f07f4e550274e43a3fde39a9c
The parent repository never tracks the subrepository's files. zev status and zev add . leave them alone, because they belong to the subrepository's own history.
A clone fetches each subrepository and puts it at its pinned commit. So do zev checkout, zev switch and zev pull whenever the pin changes: switch to a branch that pins an older version and the subrepository follows, then switch back and it returns. There's no --recursive to remember and no separate update step.
zev sub pull # pull each subrepository's branch, then pin what you gotzev commit -m "UI kit 2.4"
zev sub pin records whatever a subrepository is at now, for example after you committed a fix inside it. Push that fix from inside the subrepository too, so others can fetch the commit you pinned.
$ zev sub✓ lib/ui zaryn/ui-kit @ 8fef7c70 at its pin! vendor/parser zaryn/parser @ 1c2d3e4f has commits newer than the pin: zev sub pin records them
Each subrepository is at its pin, behind it, ahead of it (commits of your own), elsewhere, missing, or has unsaved changes, and zev says what to run for each. --brief gives one line each and --json gives the path, address, branch, pin, current commit, state and number of changes.
A subrepository is an ordinary repository on an ordinary branch. cd lib/ui, edit, commit and push as usual. zev moves it only when that loses nothing:
unsaved changes inside it: zev never moves it and says so;
commits of your own beyond the pin: switching the parent leaves it alone and says why. zev sub pin records them; zev sub update --force puts it back at the pin, and zev undo inside the subrepository takes that back.
zevsubwhere each one is and what it needszevsubadd<address><folder>[--branch B]bringarepositoryin,pinnedzevsubupdate[<folder>…][--force]fetch, and put each at its pinzevsubpin[<folder>…]record each one's current commitzevsubpull[<folder>…]pull each one's branch, then pinzevsubremove<folder>stop tracking it here (its files stay)
zev import-git lists the submodules it found. Once their repositories are on zev (zev import-git them too), run zev sub add <address> <folder> for each.
Git submodules
zev subrepositories
git clone --recursive, or git submodule update --init after
zev clone; nothing after
A tree entry and .gitmodules; the version isn't shown in reviews
.zevsubs, one readable file with the commit in it
Detached HEAD inside
On a branch
git submodule update can discard commits made inside
Never moved over unsaved work or your own commits without --force, and undoable