Skip to content
This repository was archived by the owner on Feb 24, 2026. It is now read-only.

Commit 7c84f67

Browse files
feat: add repo setup and visibility recipes (#30)
Add just recipes for repo lifecycle management: - repo-clone: clone a GitHub repo as bare .git with main worktree - repo-init: initialize a fresh bare repo with main worktree - repo-create: create a GitHub repo and push (respects gh git_protocol) - repo-public/repo-private: change repo visibility (verifies repo exists) Also rewrites README Usage to use recipe-based workflow, adds a "Developing with worktrees" walkthrough, and removes the resolved Starship known issue. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5c8553d commit 7c84f67

2 files changed

Lines changed: 150 additions & 38 deletions

File tree

README.md

Lines changed: 46 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,32 @@ each worktree is a full working copy of a branch.
1212
## Prerequisites
1313

1414
- git
15+
- [gh](https://cli.github.com/) (GitHub CLI, for repo setup recipes)
1516
- [uv](https://docs.astral.sh/uv/) (Python package manager, used to run just and copier)
1617

1718
## Usage
1819

19-
Set up the bare repo and main worktree yourself, then run copier to drop skeleton files:
20+
Clone an existing GitHub repo:
2021

2122
```bash
2223
mkdir ~/repos/my-project && cd ~/repos/my-project
23-
gh repo clone owner/my-project .git -- --bare
24-
git worktree add main main
2524
uvx copier copy gh:dpoblador/projectuner .
25+
just repo-clone owner/my-project
2626
```
2727

28-
For a brand-new repo with no remote:
28+
Or start a fresh repo with no remote:
2929

3030
```bash
3131
mkdir ~/repos/my-project && cd ~/repos/my-project
32-
git init --bare .git
33-
git -C .git commit --allow-empty -m "Initial commit"
34-
git worktree add main main
3532
uvx copier copy gh:dpoblador/projectuner .
33+
just repo-init
34+
```
35+
36+
To publish a fresh repo to GitHub:
37+
38+
```bash
39+
just repo-create owner/my-project # private by default
40+
just repo-create owner/my-project public # or public
3641
```
3742

3843
## What you get
@@ -48,34 +53,45 @@ my-project/
4853
└── .copier-answers.yml
4954
```
5055

51-
## Worktree workflow
56+
## Developing with worktrees
57+
58+
All recipes are run from the project root. If you have
59+
[just](https://github.com/casey/just) installed, you can use `just` directly
60+
instead of `uv run --from just-bin just`.
5261

53-
All worktree operations are just recipes, run from the project root. If you have
54-
[just](https://github.com/casey/just) installed, you can use `just <recipe>` directly
55-
instead of the `uv run --from just-bin` prefix.
62+
Start a feature by creating a worktree:
5663

5764
```bash
58-
# Start a new feature
59-
uv run --from just-bin just wt-add my-feature
65+
just wt-add my-feature # creates branch from main
66+
cd my-feature/ # full working copy, ready to go
67+
```
6068

61-
# Work in it
62-
cd my-feature/
69+
Work normally (edit, commit, push) inside `my-feature/`. Meanwhile `main/` stays
70+
pristine, so you can diff against it anytime:
6371

64-
# List all worktrees
65-
uv run --from just-bin just wt-ls
72+
```bash
73+
git diff main...my-feature
74+
```
6675

67-
# Fetch latest and fast-forward main
68-
uv run --from just-bin just wt-update
76+
You can have multiple worktrees active at once (one per branch):
6977

70-
# Clean up when done (keeps remote branch)
71-
uv run --from just-bin just wt-rm my-feature
78+
```bash
79+
just wt-add bugfix # another branch, another directory
80+
just wt-ls # see all active worktrees
81+
```
7282

73-
# Or nuke everything: worktree + local + remote branch
74-
uv run --from just-bin just wt-destroy my-feature
83+
When a branch is merged, clean up:
84+
85+
```bash
86+
just wt-rm my-feature # removes worktree + local branch
87+
just wt-destroy bugfix # also deletes the remote branch
7588
```
7689

77-
The `main/` worktree stays pristine as a clean reference for diffing. Never edit files in it
78-
directly.
90+
To pull the latest changes into your local main:
91+
92+
```bash
93+
just wt-update # fetches all remotes, fast-forwards main
94+
```
7995

8096
## Updating a scaffolded project
8197

@@ -122,6 +138,11 @@ outside version control.
122138
| `wt-destroy` | `just wt-destroy <branch>` | Remove a worktree and delete both local and remote branches. |
123139
| `wt-ls` | `just wt-ls` | List all active worktrees. |
124140
| `wt-update` | `just wt-update` | Fetch all remotes and fast-forward `main`. |
141+
| `repo-clone` | `just repo-clone <owner/repo>` | Clone a GitHub repo as a bare `.git/` and create the `main` worktree. |
142+
| `repo-init` | `just repo-init` | Initialize a bare `.git/` repo with an empty commit and `main` worktree. |
143+
| `repo-create` | `just repo-create <owner/repo> [visibility]` | Create a GitHub repo (default: private) and push `main`. |
144+
| `repo-public` | `just repo-public` | Change the GitHub repo's visibility to public. |
145+
| `repo-private` | `just repo-private` | Change the GitHub repo's visibility to private. |
125146
| `template-update` | `just template-update [args]` | Update template files via copier. |
126147

127148
## Local excludes
@@ -130,18 +151,6 @@ Files at the project root are outside version control. They're excluded via
130151
`.git/info/exclude` (not `.gitignore`). To add your own patterns, edit
131152
`.git/info/exclude` directly.
132153

133-
## Known issues
134-
135-
### Starship shows branch name at the project root
136-
137-
[Starship](https://starship.rs/)'s `git_branch` module reads `.git/HEAD` and displays the
138-
branch name even at the bare repo root. The `ignore_bare_repo` option doesn't help because
139-
Starship uses [gitoxide](https://github.com/GitoxideLabs/gitoxide) which
140-
[has a bug detecting bare repos](https://github.com/GitoxideLabs/gitoxide/issues/2402).
141-
142-
The fix has been merged in gitoxide and is expected in the gitoxide release on 2026-02-22,
143-
after which Starship needs to bump its gitoxide dependency.
144-
145154
## Credit
146155

147156
Based on the workflow from Ahmed el Gabri:

template/justfile

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# ABOUTME: Recipes for managing git worktrees and updating template files.
1+
# ABOUTME: Recipes for managing git worktrees, GitHub repos, and template files.
22
# ABOUTME: Invoked via: uv run --from just-bin just <recipe>
33

44
# List available recipes
@@ -123,6 +123,109 @@ wt-update:
123123
exit 1
124124
fi
125125

126+
# Clone an existing GitHub repo as a bare repo with a main worktree
127+
repo-clone repo:
128+
#!/bin/bash
129+
set -euo pipefail
130+
131+
if [ -d .git ] || [ -f .git ]; then
132+
echo "Error: .git already exists." >&2
133+
exit 1
134+
fi
135+
136+
REPO="{{ repo }}"
137+
138+
gh repo clone "$REPO" .git -- --bare
139+
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
140+
git fetch origin --quiet
141+
git worktree add main main
142+
143+
echo "Cloned $REPO with main worktree."
144+
echo " cd main/"
145+
146+
# Initialize a new bare repo with a main worktree
147+
repo-init:
148+
#!/bin/bash
149+
set -euo pipefail
150+
151+
if [ -d .git ] || [ -f .git ]; then
152+
echo "Error: .git already exists." >&2
153+
exit 1
154+
fi
155+
156+
git init --bare .git
157+
TREE=$(git hash-object -t tree /dev/null)
158+
COMMIT=$(echo "Initial commit" | git commit-tree "$TREE")
159+
git update-ref refs/heads/main "$COMMIT"
160+
git worktree add main main
161+
162+
echo "Initialized bare repo with main worktree."
163+
echo " cd main/"
164+
165+
# Create a GitHub repo and push main
166+
repo-create repo visibility='private':
167+
#!/bin/bash
168+
set -euo pipefail
169+
170+
REPO="{{ repo }}"
171+
VISIBILITY="{{ visibility }}"
172+
173+
if git remote get-url origin &>/dev/null; then
174+
echo "Error: origin remote already exists ($(git remote get-url origin))." >&2
175+
exit 1
176+
fi
177+
178+
gh repo create "$REPO" --"$VISIBILITY"
179+
180+
PROTOCOL=$(gh config get git_protocol 2>/dev/null || echo "https")
181+
if [ "$PROTOCOL" = "ssh" ]; then
182+
URL="git@github.com:${REPO}.git"
183+
else
184+
URL="https://github.com/${REPO}.git"
185+
fi
186+
187+
git remote add origin "$URL"
188+
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
189+
git -C main push -u origin main
190+
191+
echo ""
192+
echo "Created $VISIBILITY repo and pushed main."
193+
echo " https://github.com/$REPO"
194+
195+
# Change GitHub repo visibility to public
196+
repo-public:
197+
@{{ just_executable() }} _repo-visibility public
198+
199+
# Change GitHub repo visibility to private
200+
repo-private:
201+
@{{ just_executable() }} _repo-visibility private
202+
203+
[private]
204+
_repo-visibility visibility:
205+
#!/bin/bash
206+
set -euo pipefail
207+
208+
VISIBILITY="{{ visibility }}"
209+
210+
REMOTE_URL=$(git remote get-url origin 2>/dev/null) || {
211+
echo "Error: no origin remote configured." >&2
212+
exit 1
213+
}
214+
215+
REPO=$(echo "$REMOTE_URL" | sed -E 's|.*github\.com[:/]||; s|\.git$||')
216+
if [ -z "$REPO" ]; then
217+
echo "Error: could not parse GitHub repo from $REMOTE_URL" >&2
218+
exit 1
219+
fi
220+
221+
gh repo view "$REPO" >/dev/null 2>&1 || {
222+
echo "Error: GitHub repo $REPO not found." >&2
223+
exit 1
224+
}
225+
226+
gh repo edit "$REPO" --visibility "$VISIBILITY"
227+
echo "$REPO is now $VISIBILITY."
228+
126229
# Update template files via copier (works around bare repo limitation)
127230
template-update *args:
128231
#!/bin/bash

0 commit comments

Comments
 (0)