Skip to content

Commit e692903

Browse files
authored
Merge pull request #2 from skunkworker/readme-demo-assets
Add a recorded demo GIF and screenshot to the README
2 parents 3f47768 + 32209c2 commit e692903

10 files changed

Lines changed: 307 additions & 1 deletion

File tree

Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ BINARY := git_pruner
44
BINDIR ?= $(HOME)/shared/bin
55
TARGET := $(BINDIR)/$(BINARY)
66

7-
.PHONY: build install test vet clean
7+
.PHONY: build install test vet clean assets
88

99
build: $(TARGET)
1010

@@ -22,3 +22,7 @@ vet:
2222

2323
clean:
2424
rm -f $(TARGET)
25+
26+
# Re-records the README's demo GIF and screenshot; needs vhs on PATH.
27+
assets:
28+
./assets/record.sh

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ matching remote branch.
88
You can also view a branch's changes as a colorized diff, and fetch-and-prune to find branches
99
whose upstream has been deleted so they can be cleaned up in one step.
1010

11+
![git_pruner: prune gone branches, arm a remote deletion, confirm, delete](assets/demo.gif)
12+
13+
Above: `p` fetches and prunes, marking three branches whose upstream was deleted and selecting
14+
them; `space` and `r` add a merged branch and arm its remote deletion too; `d` opens the
15+
confirmation, where `R` deletes local **and** remote.
16+
1117
## Install
1218

1319
Requires Go 1.26+ and git on your PATH.
@@ -65,6 +71,8 @@ In the diff view: `↑`/`↓` scroll, `space`/`ctrl+d` page down, `ctrl+u`/`pgup
6571

6672
## Row format
6773

74+
![The branch list: selection, track and merge columns, dates, hashes and subjects](assets/branches.png)
75+
6876
```
6977
> [x] R * feature/foo ↑2↓1 ✓ 3 days ago a1b2c3d Fix the thing
7078
```
@@ -144,11 +152,20 @@ make build # build straight to $BINDIR (default ~/shared/bin), skipping ins
144152
make test # go test ./...
145153
make vet # go vet ./...
146154
make clean # remove the binary from $BINDIR
155+
make assets # re-record the README's demo GIF and screenshot (needs vhs)
147156
```
148157

149158
CI runs `gofmt`, `go build`, `go vet`, and `go test -race` on Linux and macOS for every push to
150159
`master` and every pull request (`.github/workflows/ci.yml`).
151160

161+
The README's `assets/demo.gif` and `assets/branches.png` are generated, not hand-captured — rerun
162+
`make assets` (needs [vhs](https://github.com/charmbracelet/vhs): `brew install vhs`) after any
163+
change to the UI. It builds a throwaway repo under `/tmp/git_pruner-demo` with branches in every
164+
interesting state — merged, stale upstream, unmerged work, no upstream — then records both assets
165+
against that one repo so their commit hashes agree. The two tapes share their terminal settings
166+
and setup via `assets/common.tape`, since the GIF and the still sit side by side in this file and
167+
would look mismatched if the width or theme drifted between them.
168+
152169
[`docs/improvements.md`](docs/improvements.md) records the codebase analysis, the reasoning behind
153170
the current safety behavior, and the roadmap of remaining work.
154171

assets/branches.png

80.1 KB
Loading

assets/common.tape

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Shared by demo.tape and screenshot.tape via `Source`. The GIF and the PNG sit
2+
# next to each other in the README, so the geometry and theme below have to
3+
# agree between them — that is why they live here rather than in each tape.
4+
# Height is deliberately NOT set here: it is the one setting the two recordings
5+
# genuinely differ on.
6+
#
7+
# Sourced paths resolve against vhs's cwd, which record.sh pins to the repo root.
8+
9+
Set Shell "bash"
10+
Set FontSize 15
11+
Set Width 1180
12+
Set Padding 24
13+
Set Theme "Catppuccin Mocha"
14+
15+
# Stage the throwaway repo and binary that record.sh built. Kept hidden: the
16+
# tapes Show once the TUI is on screen. This path is the contract with
17+
# record.sh's $DEMO, which checks that the two still agree.
18+
Hide
19+
Type "export PATH=/tmp/git_pruner-demo/bin:$PATH"
20+
Enter
21+
Type "cd /tmp/git_pruner-demo/orbital"
22+
Enter

assets/demo.gif

354 KB
Loading

assets/demo.tape

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# The README demo. Regenerate with assets/record.sh (not by running vhs directly:
2+
# the tape assumes the throwaway repo and binary that record.sh stages).
3+
# Story: list -> prune stale upstreams -> arm a remote delete -> confirm -> results.
4+
5+
Output "assets/demo.gif"
6+
7+
# Tall enough for the confirmation screen, the longest of the views recorded
8+
# here. Must precede the Source — see the note in screenshot.tape.
9+
Set Height 580
10+
Set TypingSpeed 55ms
11+
12+
Source "assets/common.tape"
13+
14+
# Unlike the screenshot tape, the shell prompt is on camera here.
15+
Type "PS1='$ ' && clear"
16+
Enter
17+
Show
18+
19+
Sleep 800ms
20+
Type "git_pruner"
21+
Sleep 500ms
22+
Enter
23+
Sleep 2.5s
24+
25+
# Move around the list. `g` first: on launch the cursor sits on whatever branch
26+
# git listed first, so jump to the top to start from a known row.
27+
Type "g"
28+
Sleep 700ms
29+
Type "j"
30+
Sleep 400ms
31+
Type "j"
32+
Sleep 400ms
33+
Type "j"
34+
Sleep 1.2s
35+
36+
# Fetch & prune: the three stale upstreams become "gone" and are auto-selected.
37+
Type "p"
38+
Sleep 3s
39+
40+
# Add a merged branch and arm its remote deletion too.
41+
Type "G"
42+
Sleep 700ms
43+
Space
44+
Sleep 800ms
45+
Type "r"
46+
Sleep 1.8s
47+
48+
# Confirmation screen: the prompt splits because a remote delete is armed.
49+
Type "d"
50+
Sleep 3.2s
51+
52+
# R = local + remote.
53+
Type "R"
54+
Sleep 4s
55+
56+
# Rest on the results screen.
57+
Sleep 3s
58+
Type "q"
59+
Sleep 1.5s

assets/make-demo-repo.sh

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#!/usr/bin/env bash
2+
# Builds a throwaway repo + bare "origin" so git_pruner has realistic branch
3+
# state to render: merged branches, gone upstreams, unmerged work, no-upstream.
4+
#
5+
# macOS only: the date arithmetic below uses BSD `date -v`.
6+
set -euo pipefail
7+
8+
ROOT="${1:?usage: make-demo-repo.sh <dir>}"
9+
rm -rf "$ROOT"
10+
mkdir -p "$ROOT"
11+
REMOTE="$ROOT/origin.git"
12+
WORK="$ROOT/orbital"
13+
14+
git init --quiet --bare -b main "$REMOTE"
15+
git init --quiet -b main "$WORK"
16+
cd "$WORK"
17+
18+
git config user.name "Ada Reyes"
19+
git config user.email "ada@example.com"
20+
git config commit.gpgsign false
21+
git remote add origin "$REMOTE"
22+
23+
# Every commit gets an explicit date so the relative-time column reads naturally.
24+
commit() { # commit <days-ago> <subject> <file>
25+
local days="$1" subject="$2" file="$3"
26+
local when
27+
when=$(date -u -v-"${days}"d +"%Y-%m-%dT%H:%M:%S")
28+
mkdir -p "$(dirname "$file")"
29+
printf '// %s\npackage orbital\n' "$subject" >> "$file"
30+
git add -A
31+
GIT_AUTHOR_DATE="$when" GIT_COMMITTER_DATE="$when" git commit --quiet -m "$subject"
32+
}
33+
34+
branch_from_main() { git checkout --quiet -B "$1" main; }
35+
36+
merge() { # merge <days-ago> <branch>
37+
local when
38+
when=$(date -u -v-"$1"d +"%Y-%m-%dT%H:%M:%S")
39+
GIT_AUTHOR_DATE="$when" GIT_COMMITTER_DATE="$when" \
40+
git merge --quiet --no-ff -m "Merge branch '$2'" "$2"
41+
}
42+
43+
# --- main line -------------------------------------------------------------
44+
commit 210 "Initial commit: orbital service skeleton" README.md
45+
commit 190 "Add HTTP router and health endpoint" src/router.go
46+
commit 150 "Wire Postgres connection pool" src/store/pool.go
47+
commit 96 "Add structured logging middleware" src/middleware/log.go
48+
commit 61 "Support cursor pagination on /events" src/api/events.go
49+
git push --quiet -u origin main
50+
51+
# --- merged into main, remote still present (safe -d) ----------------------
52+
branch_from_main feature/rate-limiter
53+
commit 44 "Add token-bucket rate limiter" src/middleware/ratelimit.go
54+
commit 43 "Rate limiter: per-tenant buckets" src/middleware/ratelimit.go
55+
git push --quiet -u origin feature/rate-limiter
56+
57+
branch_from_main chore/bump-deps
58+
commit 38 "Bump golang.org/x/net to 0.38.0" go.mod
59+
git push --quiet -u origin chore/bump-deps
60+
61+
branch_from_main fix/timezone-parsing
62+
commit 30 "Parse RFC3339 offsets without truncating" src/api/time.go
63+
git push --quiet -u origin fix/timezone-parsing
64+
65+
git checkout --quiet main
66+
merge 23 feature/rate-limiter
67+
merge 22 chore/bump-deps
68+
merge 21 fix/timezone-parsing
69+
commit 20 "Cache tenant lookups for 30s" src/store/tenant.go
70+
git push --quiet origin main
71+
72+
# --- upstream deleted on the remote (shows as "gone" after p) --------------
73+
branch_from_main feature/webhook-retries
74+
commit 27 "Retry webhooks with exponential backoff" src/webhook/retry.go
75+
commit 26 "Cap webhook retries at 5 attempts" src/webhook/retry.go
76+
git push --quiet -u origin feature/webhook-retries
77+
78+
branch_from_main fix/session-leak
79+
commit 24 "Close idle sessions on shutdown" src/store/session.go
80+
git push --quiet -u origin fix/session-leak
81+
82+
branch_from_main release/v2.4.0
83+
commit 18 "Release v2.4.0" CHANGELOG.md
84+
git push --quiet -u origin release/v2.4.0
85+
86+
# Merge them so they carry no unique work, then delete the remote refs: this is
87+
# exactly the state `p` (fetch --prune) is meant to surface.
88+
git checkout --quiet main
89+
merge 17 feature/webhook-retries
90+
merge 16 fix/session-leak
91+
merge 15 release/v2.4.0
92+
git push --quiet origin main
93+
94+
# Delete the upstreams inside the bare repo rather than with `push --delete`,
95+
# which would also drop the local remote-tracking refs and make the branches read
96+
# as gone before the demo ever runs. This way they stay "stale but not yet
97+
# pruned" — the state pressing `p` is there to resolve.
98+
git -C "$REMOTE" branch -q -D feature/webhook-retries fix/session-leak release/v2.4.0
99+
100+
# --- unmerged work, upstream alive (ahead > 0, needs -D) -------------------
101+
branch_from_main feature/oauth-device-flow
102+
commit 9 "Add device authorization grant" src/auth/device.go
103+
commit 7 "Poll token endpoint with backoff" src/auth/device.go
104+
git push --quiet -u origin feature/oauth-device-flow
105+
commit 3 "WIP: verification_uri_complete" src/auth/device.go
106+
107+
branch_from_main feature/audit-log
108+
commit 12 "Append-only audit log writer" src/audit/writer.go
109+
git push --quiet -u origin feature/audit-log
110+
commit 5 "Redact PII from audit entries" src/audit/redact.go
111+
112+
# --- no upstream at all ----------------------------------------------------
113+
branch_from_main spike/graphql-gateway
114+
commit 34 "Spike: graphql gateway in front of REST" src/gateway/schema.go
115+
116+
branch_from_main refactor/storage-adapter
117+
commit 2 "Extract storage behind an adapter interface" src/store/adapter.go
118+
119+
git checkout --quiet main
120+
commit 1 "Emit request IDs on every response header" src/middleware/reqid.go
121+
git push --quiet origin main
122+
# Deliberately no `fetch --prune` here: the deleted upstreams must stay
123+
# unpruned so pressing `p` in the demo is what reveals them as gone.
124+
echo "demo repo ready: $WORK"

assets/record.sh

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/usr/bin/env bash
2+
# Regenerates assets/demo.gif and assets/branches.png. Run after any UI change.
3+
#
4+
# Both tapes record against a single build of the throwaway repo so the commit
5+
# hashes agree between the two assets — the screenshot tape is read-only, the
6+
# demo tape deletes branches, so the order below matters.
7+
set -euo pipefail
8+
9+
cd "$(dirname "$0")/.."
10+
DEMO=/tmp/git_pruner-demo
11+
STILL=assets/branches.png
12+
13+
command -v vhs >/dev/null 2>&1 || {
14+
printf '%s\n' "record.sh: vhs is not on PATH (brew install vhs)" >&2
15+
exit 1
16+
}
17+
# The tapes cannot interpolate $DEMO, so they hardcode it; catch the drift.
18+
grep -q "$DEMO" assets/common.tape || {
19+
printf '%s\n' "record.sh: assets/common.tape no longer refers to $DEMO" >&2
20+
exit 1
21+
}
22+
23+
./assets/make-demo-repo.sh "$DEMO"
24+
go build -o "$DEMO/bin/git_pruner" .
25+
26+
# vhs has been observed to skip a Screenshot and still exit 0, which would leave
27+
# the previous still in place and pass silently. Force the failure to be visible.
28+
rm -f "$STILL"
29+
vhs assets/screenshot.tape
30+
[ -f "$STILL" ] || {
31+
printf '%s\n' "record.sh: vhs did not write $STILL" >&2
32+
exit 1
33+
}
34+
35+
# The capture is true-colour but only ever shows terminal text, so a 256-colour
36+
# palette is visually indistinguishable and about a third of the size.
37+
if command -v magick >/dev/null 2>&1; then
38+
magick "$STILL" -strip -colors 256 -dither None \
39+
-define png:compression-level=9 "$STILL"
40+
fi
41+
42+
vhs assets/demo.tape
43+
rm -f "$DEMO/still.gif"
44+
printf '%s\n' "wrote $STILL and assets/demo.gif"

assets/screenshot.tape

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Static list-view screenshot for the README. Regenerate with assets/record.sh.
2+
# Read-only: this tape never deletes a branch, so it can share a repo build with
3+
# demo.tape as long as it runs first.
4+
5+
Output "/tmp/git_pruner-demo/still.gif"
6+
7+
# Sized to the list view alone so the still has no dead space below it. Must
8+
# precede the Source: vhs derives the terminal grid from the Set block as a
9+
# whole, and a Height applied after it silently yields the wrong geometry.
10+
Set Height 360
11+
12+
Source "assets/common.tape"
13+
14+
Type "git_pruner"
15+
Enter
16+
Sleep 3s
17+
# `g` first: on launch the cursor sits on whatever branch git listed first.
18+
Type "g"
19+
Sleep 300ms
20+
Type "jjj"
21+
Sleep 500ms
22+
Show
23+
Sleep 300ms
24+
Screenshot "assets/branches.png"
25+
# vhs has been seen to skip a Screenshot and still exit 0, so record.sh checks
26+
# that this file actually appeared rather than trusting the exit status.
27+
Sleep 300ms

docs/improvements.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,15 @@ delete `performDeletions`.
111111
(`visibleRows` is `height-5`; actual emission is `height+1`).
112112
- ANSI and control characters in commit subjects and branch names render raw into the terminal.
113113
- `applyBranches` silently discards the user's existing selections on `p`.
114+
- The cursor starts on an arbitrary row. `sortBranches` preserves the cursor by name
115+
unconditionally, but at startup `cursor` is 0 and `branches` is still in `for-each-ref`
116+
(alphabetical) order, so it pins the cursor to wherever the alphabetically-first branch
117+
lands after sorting — row 10 of 11 on the demo repo. Skip the preserve when there is no
118+
prior cursor to restore.
119+
- The confirmation screen warns `⚠ not merged into <default>` for every gone branch, because
120+
`remoteMerged` tests the upstream ref and a gone branch no longer has one. Branches that were
121+
merged and pushed before their upstream was deleted are flagged as if they held unique work;
122+
`riskWarning` already reports the real cost correctly.
114123
- `stateDeleting`'s ctrl+c quits while `git push --delete` children are still running.
115124

116125
### Tier 3 — features for the tool's actual job

0 commit comments

Comments
 (0)