Archived
Show the pin, tip and checkout as a spine in the detail pane
Replace the Pinned/Checkout/Tip rows and the list of commits between pin and tip with one line of history, newest on top: a node per commit with its roles (pin, tip, you, staged, HEAD) and the number of commits between two nodes. A staged pin shows the committed pin as its own node and notes when it moves back. All three on one commit read "in sync". Add Status.Drift, Committed and CommittedDrift and Status.Spine(); drop Status.Between and Repo.FetchedAt. The fetch age moves from the header to the detail pane (right-aligned in the name's line). The header summary is right-aligned, ordered by importance and cut with an ellipsis if too narrow. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -114,7 +114,8 @@ The mock provider (`internal/submodule/mock`) covers every state and sleeps
|
||||
`Status` calls run at once; the others wait on a semaphore and still
|
||||
respect `ctx` (D16).
|
||||
- Superproject: branch from `symbolic-ref`, ahead/behind against
|
||||
`@{upstream}`, "fetched" from the mtime of `FETCH_HEAD`.
|
||||
`@{upstream}`. Its fetch age (mtime of `FETCH_HEAD`) is `Status.FetchedAt`
|
||||
of the root node, like a submodule's.
|
||||
- `Fetch`: `git fetch --all --prune --no-recurse-submodules --quiet` (we
|
||||
fetch every submodule ourselves), at most 8 at once, 2 min timeout each.
|
||||
To never prompt, `GIT_SSH_COMMAND="ssh -o BatchMode=yes"` is set unless
|
||||
|
||||
+31
-3
@@ -18,7 +18,7 @@ transparent.
|
||||
pin, so a mismatch was invisible.
|
||||
**Decision.** Model all three commits. The tree's ref column always describes
|
||||
the **pin**; checkout drift is an issue (`off pin`).
|
||||
**Consequences.** The detail pane shows Pinned / Checkout / Tip rows.
|
||||
**Consequences.** The detail pane shows all three (see D25).
|
||||
|
||||
## D3 — Raw dimensions in the domain, summarized issues in the UI
|
||||
|
||||
@@ -303,8 +303,8 @@ not issues.
|
||||
submodules that is hundreds of network round trips (and credential or
|
||||
host-key failures) every time the tool opens, whether or not the user wants
|
||||
fresh remote data.
|
||||
**Decision.** Supersedes D18. Nothing is fetched on startup. The header and
|
||||
the detail pane show the last fetch (`FETCH_HEAD`, also from fetches made in
|
||||
**Decision.** Supersedes D18. Nothing is fetched on startup. The detail pane
|
||||
shows the last fetch of the selected repository (`FETCH_HEAD`, also from fetches made in
|
||||
other shells), so the user can judge how stale the remote refs are and press
|
||||
`f` to fetch. The `--no-fetch` flag is removed with it (it still had the
|
||||
side effect of switching off the update check; that has
|
||||
@@ -386,3 +386,31 @@ restores below, without staging.
|
||||
**Consequences.** `Switcher` is a new optional provider interface; the mock
|
||||
does not implement it. The switch runs as the restore does (one write
|
||||
operation at a time, same lines above the footer).
|
||||
|
||||
## D25 — Detail pane: a spine instead of three commit rows and a list
|
||||
|
||||
**Context.** `Pinned` / `Checkout` / `Tip` were three rows of SHAs to
|
||||
compare, the distance sat on the `Ref` line, and a list of up to 50 commits
|
||||
(two lines each) pushed the rest out of view. Where the pin, the checkout and
|
||||
the tip are had to be worked out by reading.
|
||||
**Decision.** Draw them as one line of history, newest on top: a node per
|
||||
commit, the roles in the label column, the number of commits between two
|
||||
nodes in a gap row. Roles on one commit share a node. The commit list is
|
||||
dropped (no cap, no popup): the gap count says how far, not what changed.
|
||||
The `Ref` line loses the distance suffix (the gap says it) and keeps
|
||||
`via <rule>`.
|
||||
**Consequences.** `Status.Between` is gone; `Status.Drift` (checkout ↔ pin,
|
||||
one `rev-list --left-right --count`, only when off pin) and `Status.Spine()`
|
||||
are new. Author and age of commits are no longer shown anywhere in the pane.
|
||||
Diverged tip or checkout show `diverged ↑n ↓m` in the gap; drawing a real
|
||||
fork needs the merge base and is left for later.
|
||||
|
||||
**Amendment (with D25).** The header no longer shows `fetched <age>`. It was
|
||||
the superproject's own fetch, which the tree's `↓n` do not depend on, and read
|
||||
as the age of everything. The age lives only in the detail pane, right-aligned
|
||||
in the name's line (the superproject's when the header is selected); the
|
||||
header keeps the progress and `n failed`. `Repo.FetchedAt` is gone.
|
||||
|
||||
**Amendment (with D25).** A staged pin shows both pins on the spine: `staged`
|
||||
(the index) and `pin` (the superproject's HEAD, `Status.Committed`), so a
|
||||
bump reads as a distance. Before, only the index pin was shown at all.
|
||||
|
||||
+12
-9
@@ -59,11 +59,14 @@ commit hashes. It doesn't answer the questions users actually have:
|
||||
|
||||
- **F20** Shows the selected submodule (or the superproject): full path, name, all issues in
|
||||
words, staged-pin and detached-HEAD notes.
|
||||
- **F21** Shows the resolved ref, how it was resolved, and three commits:
|
||||
**Pinned**, **Checkout** and **Tip** (SHA + subject). A checkout that
|
||||
differs from the pin is highlighted.
|
||||
- **F22** Lists the commits between pin and tip (SHA, subject, author, age)
|
||||
with a title stating ahead/behind/diverged.
|
||||
- **F21** Shows the resolved ref, how it was resolved, and the **pin** (and
|
||||
the **staged** pin, if there is one), the
|
||||
**tip** and the checkout (**you**) as one line of history, newest on top:
|
||||
a node per commit (SHA + subject) with its roles, and the number of
|
||||
commits between two nodes. Roles on the same commit share a node. A
|
||||
checkout that differs from the pin is highlighted.
|
||||
- **F22** *(dropped)* The commits between pin and tip are not listed; only
|
||||
their number is shown (F21).
|
||||
- **F23** Shows child count, issues below, and the remote URL.
|
||||
- **F24** For a submodule with uncommitted changes, previews the changed
|
||||
files so the user can remember what they were doing: a flat list with a
|
||||
@@ -78,8 +81,8 @@ commit hashes. It doesn't answer the questions users actually have:
|
||||
or untracked, untracked repositories (e.g. a submodule clone the current
|
||||
branch no longer has), conflicts, and HEAD not pushed. Its HEAD is compared
|
||||
with the upstream of the branch. Submodules do not count for it, their rows
|
||||
show them. The detail pane shows it like a submodule (HEAD and Tip instead
|
||||
of Pinned and Checkout, a summary of all submodules instead of Children).
|
||||
show them. The detail pane shows it like a submodule (HEAD and tip instead
|
||||
of pin and you, a summary of all submodules instead of Children).
|
||||
See [state-model.md](state-model.md#superproject).
|
||||
|
||||
### Header and footer
|
||||
@@ -100,8 +103,8 @@ commit hashes. It doesn't answer the questions users actually have:
|
||||
|
||||
### Fetch and refresh
|
||||
|
||||
- **F50** Nothing is fetched on startup. The header and the detail pane show
|
||||
when the repositories were last fetched (`FETCH_HEAD`), whoever did it.
|
||||
- **F50** Nothing is fetched on startup. The detail pane shows
|
||||
when the selected repository was last fetched (`FETCH_HEAD`), whoever did it.
|
||||
`f` fetches the superproject and every initialized submodule in the
|
||||
background (all remotes, with prune).
|
||||
- **F51** While fetching, the header shows progress (`fetching 12/174`)
|
||||
|
||||
+27
-2
@@ -31,7 +31,8 @@ in several at once.
|
||||
| `PinStaged` | Pin changed in the superproject index but not committed |
|
||||
| `Pin` | `Ref`, `Source`, `Ahead`, `Behind`, `Pushed` |
|
||||
| `Pinned`, `CheckedOut`, `Tip` | Commits (SHA, subject, author, time) |
|
||||
| `Between` | Commits that make up the pin ↔ tip difference |
|
||||
| `Drift` | Checkout compared with the pin: `Ahead` = commits only the checkout has, `Behind` = commits only the pin has |
|
||||
| `Committed`, `CommittedDrift` | With a staged pin: the pin in the superproject's HEAD (nil if it is not on disk or the submodule was only added) and how it compares with the staged pin, like `Drift` |
|
||||
| `Changes` | Changed files of the worktree (nil if clean), see below |
|
||||
| `FetchedAt` | Last fetch of the submodule (`FETCH_HEAD` mtime); zero if never |
|
||||
|
||||
@@ -172,7 +173,8 @@ are local; `<pin>` is the index gitlink.
|
||||
| `Pushed` | `for-each-ref --contains <pin> refs/remotes` is not empty |
|
||||
| `Pin.Ref` | rules above; the remote is `origin`, else the first remote |
|
||||
| `Ahead`, `Behind` | `rev-list --left-right --count <pin>...<ref>` |
|
||||
| `Between` | `log <pin>...<ref>`, at most 50 commits |
|
||||
| `Drift` | `rev-list --left-right --count <head>...<pin>`, only if the checkout differs from the pin |
|
||||
| `Committed`, `CommittedDrift` | `ls-tree HEAD` gitlink, if it differs from the pin and exists locally; `rev-list --left-right --count <committed>...<pin>` |
|
||||
|
||||
Details:
|
||||
|
||||
@@ -196,6 +198,29 @@ Details:
|
||||
fails with "pinned commit … not fetched" (shown as `error`) instead of
|
||||
guessing `unpushed`.
|
||||
|
||||
## Spine
|
||||
|
||||
`Status.Spine()` arranges the pin, the tip and the checkout as one line of
|
||||
history, newest on top, for the detail pane. Each node is a commit with its
|
||||
roles (`pin`, `tip`, `you`; `HEAD` for the superproject). It uses `Pin.Ahead` /
|
||||
`Pin.Behind` (pin ↔ tip) and `Drift` (checkout ↔ pin).
|
||||
|
||||
- Roles on the same commit share a node (`pin · tip · you`);
|
||||
`SpineNode.InSync()` is true if all of them are on it (`HEAD` and `tip` for
|
||||
the superproject).
|
||||
- Tip or checkout *ahead* of the pin (it is their ancestor) sit above it,
|
||||
*behind* below it; the `Link` between two nodes counts the commits
|
||||
separating them. The count is the distance, so a tip 4 commits ahead
|
||||
of the pin shows `4 commits`, and an adjacent one `1 commit`.
|
||||
- A diverged tip is placed above the pin, a diverged checkout below it,
|
||||
with a diverged `Link` (`Above` and `Below` count the commits only the
|
||||
upper or lower node has). The link between a diverged node and a node other
|
||||
than the pin is unknown (zero `Link`).
|
||||
- With a staged pin the node of `Pinned` (the index) is labelled `staged`, and
|
||||
the committed pin gets its own `pin` node, placed by `CommittedDrift` like
|
||||
the checkout. Both can share nodes with `tip` and `you`.
|
||||
- No tip (the pin resolved to a SHA) or no checkout commit: no node.
|
||||
|
||||
## Distance notation
|
||||
|
||||
| Display | Meaning |
|
||||
|
||||
+55
-21
@@ -15,17 +15,17 @@
|
||||
## Layout
|
||||
|
||||
```
|
||||
~/coding/my_project main 13 submodules · 1 unpushed · 1 missing · 2 off pin · … fetched 2m ago
|
||||
~/coding/my_project main 1 unpushed · 1 missing · 2 off pin · 2 changed · 13 submodules
|
||||
docs origin/main │ src/
|
||||
misc/tools/ │ my_submodule_3
|
||||
my_submodule_1 origin/main │
|
||||
my_submodule_2 missing a1b2c3d │ unpushed pin not pushed to any remote
|
||||
src/ │
|
||||
my_submodule_1 off pin origin/main │ Ref origin/main ↑1
|
||||
▸ my_submodule_2 [2] changed ·2 origin/main │ via checked out branch
|
||||
my_submodule_3 unpushed origin/main ↑1 │ Pinned b7c8d9e Hotfix null pointer
|
||||
libs/ │ Checkout b7c8d9e Hotfix null pointer
|
||||
logging origin/main ↓3 │ Tip 6f5e4d3 Release 2.1.0
|
||||
my_submodule_1 off pin origin/main │ Ref origin/main · via checked out branch
|
||||
▸ my_submodule_2 [2] changed ·2 origin/main │ pin · you ● b7c8d9e Hotfix null pointer
|
||||
my_submodule_3 unpushed origin/main ↑1 │ ┊ 1 commit
|
||||
libs/ │ tip ● 6f5e4d3 Release 2.1.0
|
||||
logging origin/main ↓3 │
|
||||
↑↓/jk move • ←→/hl fold • ⇧←→/HL fold all • space toggle • ? help • q quit
|
||||
```
|
||||
|
||||
@@ -75,23 +75,53 @@ indent ▸ name [n] status ref delta
|
||||
|
||||
### Detail pane
|
||||
|
||||
For the superproject (header selected), `Pinned` / `Checkout` become one
|
||||
`HEAD` line, the commit list says "HEAD is n commits behind …", and
|
||||
`Children` becomes `Modules` (count and issues of all submodules). See
|
||||
For the superproject (header selected), the `pin` and `you` roles become
|
||||
`HEAD`, and `Children` becomes `Modules` (count and issues of all submodules). See
|
||||
[Superproject](state-model.md#superproject).
|
||||
|
||||
1. Directory (muted) and name (bold)
|
||||
1. Directory (muted) and name (bold); `fetched <age>` of the submodule,
|
||||
muted, right-aligned in the name's line (left out if the pane is too
|
||||
narrow, or the submodule is missing)
|
||||
2. One line per issue: word (colored, 10 columns) + explanation;
|
||||
`in sync nothing to do` if there are none and no pin is staged;
|
||||
`staged` and `detached HEAD on pin` as information
|
||||
3. `Changes` (only with uncommitted changes), see
|
||||
[Change preview](#change-preview)
|
||||
4. `Ref` with distance and `via <rule>`; `Fetched <age>` of the submodule;
|
||||
4. `Ref` with `· via <rule>`, a blank line, then the [spine](#spine);
|
||||
`fetch failed: <git's message>` (yellow word) if the last fetch failed
|
||||
5. `Pinned`, `Checkout`, `Tip`: SHA + subject; the checkout SHA is yellow if it
|
||||
differs from the pin
|
||||
6. "Pin is n commits behind/ahead of …" / "Pin and … have diverged": commit list
|
||||
7. `Children` (count and issues below), `URL`
|
||||
5. `Children` (count and issues below), `URL`
|
||||
|
||||
#### Spine
|
||||
|
||||
```
|
||||
Ref origin/main · via .gitmodules branch
|
||||
|
||||
tip ● 3f1bf20 Fix: update package dependencies
|
||||
┊ 4 commits
|
||||
pin ● 9ac01de Add ethercat node
|
||||
┊ 2 commits
|
||||
you ● 48005ac Merge branch 'feat/ethercat…
|
||||
```
|
||||
|
||||
- Newest on top, like `git log`: where each commit is, is read from its
|
||||
position, not by comparing SHAs. The commits between two nodes are not
|
||||
listed, only counted (`┊ 4 commits`; `1 commit` if adjacent).
|
||||
- The label column holds the roles (`pin`, `tip`, `you`; `HEAD` for the
|
||||
superproject), joined by ` · ` if they sit on one commit
|
||||
(`pin · tip`, `pin · you`). If all of them sit on one commit it says
|
||||
`in sync` instead, which also keeps the commit column in line with the
|
||||
other values; the column only grows for a longer label.
|
||||
- With a staged pin the spine shows both: `staged` (blue label) is the pin
|
||||
in the index, `pin` the one in the superproject's HEAD, so the bump is
|
||||
visible as a distance between two nodes. The committed pin is left out if
|
||||
it is not on disk. If the staged pin is *older* than the committed one it
|
||||
sits below it, and the gap above it says `(staged pin moves back)` (blue).
|
||||
- The checkout (`you`) is yellow (label and SHA) when it is on its own node,
|
||||
i.e. off pin. Everything else is the default text color, the gap muted.
|
||||
- A diverged pair shows `┊ diverged ↑n ↓m` (arrows from the lower node's
|
||||
view, like the tree). A real fork is not drawn yet.
|
||||
- The nodes are `●` (`o` with `SUBMARINE_ASCII`), the gap `┊` (`:`).
|
||||
- A missing submodule has only the pin node (SHA, no subject).
|
||||
|
||||
#### Change preview
|
||||
|
||||
@@ -147,7 +177,11 @@ shows the superproject's own state word like a tree row (`in sync`,
|
||||
`changed`, `unpushed +1`, spinner, `error`). The counts that follow are
|
||||
about submodules only.
|
||||
|
||||
`path branch ↑↓ state · n submodules · n error · <count> <issue>… · n pin staged · n loading · <fetch>`
|
||||
`path branch ↑↓ state n error · <count> <issue>… · n pin staged · n loading · n submodules · <fetch>`
|
||||
|
||||
The summary and `<fetch>` form one group at the right edge; the path,
|
||||
branch and state stay at the left. If the line is too narrow it is cut with
|
||||
`…` at the right end, so the most important counts stay.
|
||||
|
||||
Issue counts are ordered by severity (unpushed, conflict, missing, off pin,
|
||||
changed), each in its level color. Zero counts are omitted. `n loading`
|
||||
@@ -160,12 +194,12 @@ superproject shows `(detached)` as its branch.
|
||||
| State | Shows |
|
||||
|---|---|
|
||||
| fetching | spinner + `fetching 12/174` (repositories done / total) |
|
||||
| done | `fetched just now`, then the age (`fetched 5m ago`) |
|
||||
| some failed | `fetched just now · 3 failed` (count in yellow) |
|
||||
| never fetched | `fetched never` |
|
||||
| done | nothing |
|
||||
| some failed | `3 failed` (count in yellow) |
|
||||
|
||||
The age is the newer of the superproject's `FETCH_HEAD` and the end of the
|
||||
application's own fetch.
|
||||
The header has no fetch age: the age belongs to one repository, and a header
|
||||
age would be read as the age of everything. It is in the detail pane, for
|
||||
the selected repository (the superproject's own when the header is selected).
|
||||
|
||||
## Colors
|
||||
|
||||
|
||||
Reference in New Issue
Block a user