This repository has been archived on 2026-10-25. You can view files and clone it. You cannot open issues or pull requests or push a commit.
simonfrankenandClaude Sonnet 5.5 e8d366d88f Compare pins with a target branch and flag pins off it
The ref of a pin was the closest branch containing it, so a pin on a
feature branch looked like a perfect one, and the ref changed with the
branch checked out in the submodule. Split it in two:

- Pin.Target is the branch the pin should be on: `branch =` of
  .gitmodules (none if it does not exist locally), else the remote's
  default branch.
- Pin.Ref is shown: the target if the pin is on it, else the branch
  containing the pin with the fewest commits after it. A tie goes to the
  upstream of the checked-out branch, then to the first name; no branch is
  a favorite.

A pushed pin that is not on its target is `off target`, a new issue at the
new level Info (blue), counted in the header and below collapsed rows. The
spine shows the target's tip, labelled with its branch name, so the
distance from it is visible; a diverged one is the fork. The role words in
a spine label are styled separately and `you` is bold.

Add Status.TargetTip, TargetDrift and Bases.Target, Pin.Target and
Pin.Branches; drop RefSource.IsTarget. The detail pane's label column is 11
wide for the word `off target`.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 17:35:16 +02:00
2026-10-21 15:10:58 +02:00
2026-10-22 14:38:14 +02:00
2026-10-21 21:15:24 +02:00
2026-10-21 21:15:24 +02:00

submarine

A terminal UI for git repositories with many submodules. See what needs your attention. Ignore the rest.

git submodule status gives you a wall of commit hashes. submarine answers the questions you actually have:

  • Which branch is this submodule on, and is the pin current?
  • Which submodules need me, and which are fine?
  • Is anything dangerous, like a pin nobody else can fetch?

submarine showing a tree with nested submodules and the detail pane of an off-pin submodule that is one commit behind its branch

Why you might like it

  • Only submodules, as a tree. Nested submodules at any depth, grouped by directory, collapsed until you open them.
  • Branch plus distance, not hashes. origin/main ↓2 says more than b7c8d9e. A pin on a feature branch shows that branch.
  • Problems are loud, healthy is quiet. Submodules without issues are muted in sync; a staged pin shows as blue staged (or a ● after an issue word). A collapsed parent counts the problems hidden below it (·1) and shows ● if a staged pin is hidden.
  • Words, not symbols. Every state is a short word (unpushed, conflict, missing, off pin, changed, off target), so nothing depends on color alone.
  • Live. Follows changes you make in other shells within about two seconds. It never fetches on its own: the detail pane shows when a repository was last fetched, f fetches everything.
  • Fix it from the same screen. Restore a repository to exactly what git records (x), or switch it to another branch (b). Both ask first if work would be lost.
  • Built for dozens to hundreds of submodules. Each status loads asynchronously, so the UI never freezes.

Install

curl -fsSL https://git.simon-franken.de/simonfranken/submarine/raw/branch/main/install.sh | sh

Installs sub and submarine (linux/macOS, amd64/arm64) to ~/.local/bin. Both names start the same program; the examples below use the shorter sub. Update later with sub update.

Try it

sub                  # the repository in the current directory
sub ~/code/project   # any directory inside a repository
sub --mock           # built-in sample data, no repository needed

submarine works everywhere sub does, e.g. submarine --mock.

Examples

Find what needs attention

Problems stand out: unpushed in red, off pin and changed in yellow, off target (the pin is not on the branch it should be, usually main) in blue, everything else muted. Select a submodule to see the details on the right: every issue in words, the resolved ref and why, where the pin, the tip and your checkout are in the history, and a preview of uncommitted changes.

 src/                                    │ src/
     core        off pin   origin/main   │ net
   ▸ net [2]     unpushed  origin/main ↑1│
     ui          in sync   origin/main   │ unpushed  pin not pushed to any remote
                                         │
                                         │ Ref       origin/main · via checked out branch
                                         │ pin · you ● b7c8d9e Hotfix null pointer
                                         │           ┊ 1 commit
                                         │ tip       ● 6f5e4d3 Release 2.1.0

Jump to a submodule with /

Press / and type. Only matching submodules (and the parents that lead to them) stay. Your selection moves to the next match if it was filtered out.

 libs/
   ▾ log          in sync   origin/main
     modules/
         nested   changed   origin/main
 / nest                                          enter navigate • esc close

enter keeps the filter and gives you the normal keys back, esc removes it and leaves your selection where it is.

Copy a path

Select a submodule and press c. Its absolute directory lands in your clipboard, ready for cd <paste>. It works over SSH too (OSC 52; in tmux, set -g set-clipboard on).

Restore with x

Select a submodule (or the header for the whole superproject) and press x. Its worktree becomes exactly what git records, and so do all submodules below it: changes are reset, untracked files removed, merges and rebases ended, missing submodules cloned. Staged pins are kept. If something would be lost, you are asked first:

 Restore src/app? Discards 7 changed files in 3 repositories.      y restore · n cancel

y restores, any other key cancels. Without a loss it runs at once. For a submodule with a staged pin, x first asks whether to restore to the staged pin (s) or the committed one (c, which unstages it).

Switch a branch with b

Press b on a submodule (or the header for the superproject). A popup lists the local and remote branches, newest first; type to filter.

 ╭──────────────────────────────────────────────────────────╮
 │ Switch libs/feature                                      │
 │ / sea                                                    │
 │ ──────────────────────────────────────────────────────── │
 │   origin/feat/search                    58a2bf1  2h ago  │
 │ ↑↓ select · enter switch · esc cancel                    │
 ╰──────────────────────────────────────────────────────────╯

↑ ↓ select, enter switches, esc cancels. The tip of the branch becomes the submodule's staged pin and it is restored to it, with HEAD on the branch. Picking origin/feat creates a local feat that tracks it; an existing local branch of that name is never moved. Like restore, it asks first if work would be lost. The list is read from disk, so press f first to see new remote branches.

Fetch and reload

f fetches all repositories again, r reloads everything after you edited files.

Keys

Key Action
↑ ↓ / j k Move
← → / h l Fold / unfold, parent / first child
H L / ⇧← ⇧→ Collapse / expand all
space Toggle the selected submodule
home end / g G, pgup pgdn Jump, page
/ Filter
! Problems only
c Copy path
b Switch branch: pick a local or remote branch; its tip is staged and the repository restored to it
x Restore: make the worktree exactly what git records (asks if work would be lost)
f / r Fetch / reload
? Full help
q Quit

The mouse works too: click to select, click ▸ to toggle, wheel to move. In the branch popup, click a branch to switch to it, use the wheel to move, and click outside to close it.

Status

A prototype. Browsing is complete. Two write actions exist: restore (x) and switch branch (b). More (stage pin) are being specified in docs/actions.md.

Documentation

Requirements, state rules, UX guide, architecture and the decision log are in docs/.

Development

go run ./cmd/submarine --mock   # run on mock data
scripts/make-fixture.sh DIR     # build a test superproject with every state
go vet ./... && go test ./...
S
Description
A terminal UI for git repositories with many submodules. See what needs your attention. Ignore the rest.
Readme
1.6 MiB
v0.13.0
Latest
2026-10-25 21:44:53 +00:00
Languages
Go 96.4%
Shell 2.4%
Makefile 1.2%