workforest 0.5.0__tar.gz → 0.5.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {workforest-0.5.0 → workforest-0.5.2}/.claude/rules/architecture.md +12 -0
- {workforest-0.5.0 → workforest-0.5.2}/.github/workflows/ci.yml +4 -0
- {workforest-0.5.0 → workforest-0.5.2}/.github/workflows/publish_aur.yml +15 -5
- {workforest-0.5.0 → workforest-0.5.2}/.github/workflows/publish_homebrew.yml +1 -1
- {workforest-0.5.0 → workforest-0.5.2}/.gitignore +5 -1
- {workforest-0.5.0 → workforest-0.5.2}/PKG-INFO +77 -17
- {workforest-0.5.0 → workforest-0.5.2}/README.md +76 -16
- workforest-0.5.2/assets/logo-dark.svg +3 -0
- workforest-0.5.2/assets/logo-light.svg +3 -0
- workforest-0.5.2/assets/logo.svg +3 -0
- {workforest-0.5.0 → workforest-0.5.2}/man/workforest.1 +51 -16
- {workforest-0.5.0 → workforest-0.5.2}/man/workforest.5 +40 -4
- {workforest-0.5.0 → workforest-0.5.2}/packaging/homebrew/workforest.rb.template +10 -4
- {workforest-0.5.0 → workforest-0.5.2}/pyproject.toml +1 -1
- {workforest-0.5.0 → workforest-0.5.2}/src/workforest/__init__.py +1 -1
- {workforest-0.5.0 → workforest-0.5.2}/src/workforest/cli.py +7 -3
- {workforest-0.5.0 → workforest-0.5.2}/src/workforest/commands.py +43 -11
- {workforest-0.5.0 → workforest-0.5.2}/src/workforest/config.py +67 -10
- workforest-0.5.2/src/workforest/examples/.workforest.yaml +106 -0
- workforest-0.5.2/src/workforest/hooks.py +864 -0
- {workforest-0.5.0 → workforest-0.5.2}/src/workforest/output.py +5 -3
- {workforest-0.5.0 → workforest-0.5.2}/src/workforest/shell/completion.bash +1 -1
- {workforest-0.5.0 → workforest-0.5.2}/src/workforest/shell/completion.zsh +1 -1
- workforest-0.5.2/src/workforest/shell/workforest.sh +25 -0
- workforest-0.5.2/src/workforest/templates/project.yaml +21 -0
- {workforest-0.5.0 → workforest-0.5.2}/tests/test_cli.py +10 -0
- {workforest-0.5.0 → workforest-0.5.2}/tests/test_commands.py +41 -3
- {workforest-0.5.0 → workforest-0.5.2}/tests/test_config.py +71 -1
- {workforest-0.5.0 → workforest-0.5.2}/tests/test_hooks.py +422 -0
- {workforest-0.5.0 → workforest-0.5.2}/tests/test_shell.py +11 -0
- workforest-0.5.0/src/workforest/hooks.py +0 -493
- workforest-0.5.0/src/workforest/shell/workforest.sh +0 -18
- {workforest-0.5.0 → workforest-0.5.2}/.claude/rules/conventions.md +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/.claude/rules/tests.md +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/.github/workflows/publish_pypi.yml +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/.github/workflows/release.yml +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/LICENSE +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/Makefile +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/completions/_workforest +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/man/wf.1 +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/man/wf.5 +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/packaging/AUR/PKGBUILD.template +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/src/workforest/__main__.py +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/src/workforest/completions.py +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/src/workforest/errors.py +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/src/workforest/examples/config.yaml +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/src/workforest/gitutil.py +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/src/workforest/integrations/__init__.py +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/src/workforest/integrations/claude.py +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/src/workforest/jobs.py +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/src/workforest/launch.py +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/src/workforest/shellinit.py +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/src/workforest/tui.py +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/tests/__init__.py +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/tests/conftest.py +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/tests/test_claude.py +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/tests/test_gitutil.py +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/tests/test_harness.py +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/tests/test_jobs.py +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/tests/test_launch.py +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/tests/test_man.py +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/tests/test_output.py +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/tests/test_tui.py +0 -0
- {workforest-0.5.0 → workforest-0.5.2}/uv.lock +0 -0
|
@@ -19,3 +19,15 @@
|
|
|
19
19
|
environment variables, or exit codes updates both README.md and the
|
|
20
20
|
affected page in the same change. `tests/test_man.py` catches drift from
|
|
21
21
|
`cli.py`, not from prose — keep the wording in sync by hand.
|
|
22
|
+
- Script groups (`hooks.py`): a `bulk`/`pipeline` runs under a forked
|
|
23
|
+
supervisor that leads the process group and takes the tty exactly like a
|
|
24
|
+
command, so records, `wf stop`, `exclusive`, `cleanup`, and `-b` need no
|
|
25
|
+
group-specific code. Bulk members must never take the tty (only one
|
|
26
|
+
group can own it); they run in the supervisor's process group so a
|
|
27
|
+
signal to the group reaches them all, write to a pty when stderr is a
|
|
28
|
+
terminal (so their programs keep colors) and to a pipe otherwise, and
|
|
29
|
+
the supervisor relays their lines prefixed. A bulk always waits for
|
|
30
|
+
every member. An outcome of `-SIGINT` or exit 130 is an interruption
|
|
31
|
+
(warning, `wf` dies by SIGINT), never a failure. Forked-child bodies are
|
|
32
|
+
`# pragma: no cover`; the logic lives in tested pure helpers
|
|
33
|
+
(`_Prefixer`, `_pump`, `_bulk_outcome`, `_run_step`).
|
|
@@ -49,3 +49,7 @@ jobs:
|
|
|
49
49
|
# puts that directory on $MANPATH for venv installs
|
|
50
50
|
MANPATH=/tmp/venv/share/man man -w workforest wf 5 workforest wf
|
|
51
51
|
/tmp/venv/bin/workforest shell-init bash | grep -q 'MANPATH=/tmp/venv/share/man'
|
|
52
|
+
# the init starter is package data too
|
|
53
|
+
git init -q /tmp/repo
|
|
54
|
+
(cd /tmp/repo && /tmp/venv/bin/workforest init)
|
|
55
|
+
test -s /tmp/repo/.workforest.yaml
|
|
@@ -3,11 +3,18 @@ name: Publish to AUR
|
|
|
3
3
|
on:
|
|
4
4
|
release:
|
|
5
5
|
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
version:
|
|
9
|
+
description: "Release version to publish (e.g. 0.2.1)"
|
|
10
|
+
required: true
|
|
6
11
|
|
|
7
12
|
jobs:
|
|
8
13
|
publish:
|
|
9
14
|
runs-on: ubuntu-latest
|
|
10
15
|
container: archlinux:latest
|
|
16
|
+
env:
|
|
17
|
+
RELEASE_VERSION: ${{ github.event_name == 'workflow_dispatch' && inputs.version || github.ref_name }}
|
|
11
18
|
environment:
|
|
12
19
|
name: aur
|
|
13
20
|
url: https://aur.archlinux.org/packages/workforest
|
|
@@ -15,15 +22,18 @@ jobs:
|
|
|
15
22
|
contents: read
|
|
16
23
|
steps:
|
|
17
24
|
- name: Install system dependencies
|
|
18
|
-
run:
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
run: |
|
|
26
|
+
# The archlinux image drops man pages at install time
|
|
27
|
+
# (NoExtract = usr/share/man/*); the smoke test below needs them.
|
|
28
|
+
sed -i '/^NoExtract.*usr\/share\/man/d' /etc/pacman.conf
|
|
29
|
+
pacman -Syu --noconfirm base-devel git openssh pacman-contrib namcap man-db \
|
|
30
|
+
python python-yaml python-build python-installer python-wheel python-hatchling
|
|
21
31
|
|
|
22
32
|
- uses: actions/checkout@v4
|
|
23
33
|
|
|
24
34
|
- name: Render PKGBUILD from template
|
|
25
35
|
run: |
|
|
26
|
-
version="${
|
|
36
|
+
version="${RELEASE_VERSION#v}"
|
|
27
37
|
cd packaging/AUR
|
|
28
38
|
sed "s/@VERSION@/$version/g" PKGBUILD.template > PKGBUILD
|
|
29
39
|
# makepkg refuses to run as root
|
|
@@ -47,7 +57,7 @@ jobs:
|
|
|
47
57
|
env:
|
|
48
58
|
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
|
49
59
|
run: |
|
|
50
|
-
version="${
|
|
60
|
+
version="${RELEASE_VERSION#v}"
|
|
51
61
|
# Explicit paths only: ssh resolves ~ from the passwd entry (/root),
|
|
52
62
|
# while the shell resolves it from $HOME (/github/home) — they differ
|
|
53
63
|
# in container jobs. Host keys are pinned rather than keyscanned.
|
|
@@ -48,7 +48,7 @@ jobs:
|
|
|
48
48
|
test -f "$(brew --prefix)/share/man/man1/wf.1"
|
|
49
49
|
test -f "$(brew --prefix)/share/man/man5/workforest.5"
|
|
50
50
|
test -f "$(brew --prefix)/share/man/man5/wf.5"
|
|
51
|
-
# the formula
|
|
51
|
+
# the formula moves them out of the venv, so shell-init must not touch MANPATH
|
|
52
52
|
! workforest shell-init bash | grep -q MANPATH
|
|
53
53
|
|
|
54
54
|
- name: Push to tap
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: workforest
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: Git worktree forest management: create, open, and clean up per-branch worktrees with project-defined setup hooks
|
|
5
5
|
Project-URL: Homepage, https://github.com/ArkadyBuryakov/workforest
|
|
6
6
|
Author-email: Arkady Buryakov <arkady@buryakov.pro>
|
|
@@ -18,6 +18,13 @@ Requires-Python: >=3.14
|
|
|
18
18
|
Requires-Dist: pyyaml>=6.0
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
|
|
21
|
+
<p align="center">
|
|
22
|
+
<picture>
|
|
23
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ArkadyBuryakov/workforest/HEAD/assets/logo-dark.svg">
|
|
24
|
+
<img src="https://raw.githubusercontent.com/ArkadyBuryakov/workforest/HEAD/assets/logo-light.svg" alt="Workforest logo" width="160">
|
|
25
|
+
</picture>
|
|
26
|
+
</p>
|
|
27
|
+
|
|
21
28
|
# Workforest
|
|
22
29
|
|
|
23
30
|
## Elevator pitch
|
|
@@ -71,11 +78,12 @@ line to your `~/.bashrc` / `~/.zshrc`:
|
|
|
71
78
|
eval "$(workforest shell-init)"
|
|
72
79
|
```
|
|
73
80
|
|
|
74
|
-
This upgrades `wf` to
|
|
75
|
-
shell's directory — a plain binary cannot
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
This upgrades both `workforest` and `wf` to one shell function (needed so
|
|
82
|
+
`open` can change your shell's directory — a plain binary cannot; either
|
|
83
|
+
spelling, or an alias of either, behaves the same), registers completions,
|
|
84
|
+
and — for `uv tool`/`pipx` installs, whose files live in a venv — puts the
|
|
85
|
+
man pages on `$MANPATH`. Without it everything still works, but "open in
|
|
86
|
+
current shell" prints the `cd` command instead of performing it.
|
|
79
87
|
|
|
80
88
|
Reference: `man workforest` (commands) and `man 5 workforest` (the
|
|
81
89
|
configuration files); `wf` works in place of `workforest` for both.
|
|
@@ -110,8 +118,9 @@ Layered, YAML or JSON; later layers override earlier ones:
|
|
|
110
118
|
|
|
111
119
|
Scalars and lists replace; the `openers`/`wrappers`/`scripts` mappings
|
|
112
120
|
merge per key (`null` removes an entry). `workforest config` shows the
|
|
113
|
-
merged result and where each layer came from; `workforest init`
|
|
114
|
-
|
|
121
|
+
merged result and where each layer came from; `workforest init` writes a
|
|
122
|
+
short, fully commented starter (`--local` for a personal one) — inert until
|
|
123
|
+
you uncomment something. Nothing in the environment
|
|
115
124
|
changes the result — files and flags only.
|
|
116
125
|
|
|
117
126
|
All keys, with defaults:
|
|
@@ -124,14 +133,14 @@ openers: {} # name -> what `-o NAME` runs, and where
|
|
|
124
133
|
wrappers: {} # name -> command that runs $WF_COMMAND elsewhere (window, pane, direnv)
|
|
125
134
|
symlinks: [] # untracked assets linked from main into new worktrees
|
|
126
135
|
setup_scripts: [] # shell snippets run in a fresh worktree
|
|
127
|
-
scripts: {} # name -> command for `wf run NAME` (
|
|
136
|
+
scripts: {} # name -> command or group for `wf run NAME` (see Scripts)
|
|
128
137
|
stop_timeout: 30 # seconds a stopped script gets after SIGTERM before SIGKILL
|
|
129
138
|
```
|
|
130
139
|
|
|
131
140
|
### Openers
|
|
132
141
|
|
|
133
142
|
An opener is a shell command that runs with the worktree root as working
|
|
134
|
-
directory, either **in your terminal** (the default: the
|
|
143
|
+
directory, either **in your terminal** (the default: the shell function does
|
|
135
144
|
`cd` there and runs it) or **in the background** (`background: true`:
|
|
136
145
|
spawned detached, `wf` returns immediately — for GUI apps and commands that
|
|
137
146
|
hand off to a daemon or multiplexer). Optionally it runs **through a
|
|
@@ -215,7 +224,7 @@ installed to `/usr/share/doc/workforest/examples/` by the Arch package.
|
|
|
215
224
|
|
|
216
225
|
`wf run NAME [ARGS...]` runs a `scripts` entry from the root of the current
|
|
217
226
|
worktree, with any extra arguments shell-quoted and appended. An entry is a
|
|
218
|
-
shell command string, or a
|
|
227
|
+
shell command string, a mapping, or a group of other entries (see below):
|
|
219
228
|
|
|
220
229
|
```yaml
|
|
221
230
|
scripts:
|
|
@@ -236,9 +245,10 @@ terminal, so Ctrl-C and Ctrl-Z reach it directly; a signal sent to `wf`
|
|
|
236
245
|
itself (SIGINT, SIGTERM, SIGHUP) is forwarded to the whole group. `cleanup`
|
|
237
246
|
then runs — after a normal exit, a failure, or a kill — in the same
|
|
238
247
|
worktree with the same `WF_*` variables. `wf run` fails with the command's
|
|
239
|
-
status, or `128+N` when the command was killed by signal N
|
|
240
|
-
killed by SIGINT
|
|
241
|
-
|
|
248
|
+
status, or `128+N` when the command was killed by signal N. A command
|
|
249
|
+
killed by SIGINT — or exiting 130, as a shell does once its child was — is
|
|
250
|
+
reported as interrupted, a warning rather than an error, and ends `wf` the
|
|
251
|
+
way Ctrl-C would, so a shell loop around it aborts.
|
|
242
252
|
|
|
243
253
|
A `background` script (or `wf run -b NAME`) is detached instead: it runs
|
|
244
254
|
under a supervisor of its own — the same process-group and cleanup
|
|
@@ -267,6 +277,48 @@ orphan) is stopped and cleaned up by whoever stops it when `/proc`
|
|
|
267
277
|
confirms the pid was not recycled; elsewhere it is reported and left
|
|
268
278
|
alone.
|
|
269
279
|
|
|
280
|
+
#### Groups
|
|
281
|
+
|
|
282
|
+
A `bulk` entry runs other entries at once; a `pipeline` entry runs them one
|
|
283
|
+
after another. Members are named, not written inline, so each keeps its own
|
|
284
|
+
`exclusive`, `cleanup`, and `stop_timeout`, and a group may name another
|
|
285
|
+
group (cycles are a config error):
|
|
286
|
+
|
|
287
|
+
```yaml
|
|
288
|
+
scripts:
|
|
289
|
+
migrate: npm run db:migrate
|
|
290
|
+
dev:
|
|
291
|
+
bulk: [backend, frontend] # both at once; done when both have ended
|
|
292
|
+
fresh:
|
|
293
|
+
pipeline: [migrate, dev] # in order; the first failure ends it
|
|
294
|
+
background: true
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
A group is a script like any other: `wf run -b`, `background`,
|
|
298
|
+
`exclusive`, and `cleanup` apply to it, and `wf stop GROUP` stops every
|
|
299
|
+
member, waits for all their cleanups, then runs the group's own. A group
|
|
300
|
+
takes no extra arguments; its `stop_timeout` defaults to the longest of
|
|
301
|
+
its members'. Members stay individually visible: `wf stop MEMBER` stops
|
|
302
|
+
that member out from under a running group, and a member already running
|
|
303
|
+
in the worktree fails its step.
|
|
304
|
+
|
|
305
|
+
A pipeline's steps run like consecutive `wf run`s, each with the
|
|
306
|
+
terminal; a `background` member is started and left running while the
|
|
307
|
+
pipeline moves on. The pipeline ends with the first failing step's status.
|
|
308
|
+
|
|
309
|
+
A bulk relays its members' output line by line, each line prefixed with
|
|
310
|
+
the member's name:
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
backend | Attaching to db-1, api-1
|
|
314
|
+
frontend | VITE v5.4 ready in 312 ms
|
|
315
|
+
backend | api-1 | listening on :8000
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Members cannot read the terminal, but Ctrl-C reaches them all. A bulk
|
|
319
|
+
waits for every member — so `bulk: [lint, test, typecheck]` shows every
|
|
320
|
+
failure, not just the first — and fails if any of them did.
|
|
321
|
+
|
|
270
322
|
### Script environment
|
|
271
323
|
|
|
272
324
|
`setup_scripts`, `scripts`, and hooks run via `$SHELL -c` with:
|
|
@@ -303,6 +355,10 @@ scripts:
|
|
|
303
355
|
background: true
|
|
304
356
|
cleanup: docker compose down
|
|
305
357
|
stop_timeout: 60
|
|
358
|
+
dev:
|
|
359
|
+
bulk: [backend, frontend]
|
|
360
|
+
fresh:
|
|
361
|
+
pipeline: [migrate, dev]
|
|
306
362
|
```
|
|
307
363
|
|
|
308
364
|
## Commands
|
|
@@ -310,7 +366,7 @@ scripts:
|
|
|
310
366
|
```
|
|
311
367
|
workforest create [BRANCH] [-o OPENER] [-w WRAPPER] [-p PATH] [--no-hooks] [--no-open]
|
|
312
368
|
workforest open [NAME] [-o OPENER] [-w WRAPPER] [-p PATH]
|
|
313
|
-
workforest list [--porcelain]
|
|
369
|
+
workforest list [--porcelain | --json]
|
|
314
370
|
workforest delete NAME... [--force] [--delete-branch | --keep-branch]
|
|
315
371
|
workforest checkout NAME [--force]
|
|
316
372
|
workforest run [-b] SCRIPT [ARGS...]
|
|
@@ -338,7 +394,11 @@ which is not a stable interface, so any Claude Code update may break it.
|
|
|
338
394
|
Exit codes: `0` ok · `1` error · `2` usage · `3` cancelled · `4` config error
|
|
339
395
|
· `128+N` the `run` command was killed by signal N.
|
|
340
396
|
Human messages go to stderr; stdout carries only machine output (`cd`
|
|
341
|
-
directives for the
|
|
397
|
+
directives for the shell function, `--porcelain`/`--json` listings, dumps).
|
|
398
|
+
`list --json` describes the whole forest for programs — `main` (the main
|
|
399
|
+
checkout, in the same `name`/`branch`/`path`/`dirty` shape as each entry of
|
|
400
|
+
`worktrees`) and the resolved `worktrees_dir` — for editor integrations
|
|
401
|
+
and other programs.
|
|
342
402
|
|
|
343
403
|
## Development
|
|
344
404
|
|
|
@@ -353,7 +413,7 @@ Man pages are hand-written roff under `man/` (`workforest.1` and
|
|
|
353
413
|
`workforest.5`, plus `wf.1`/`wf.5` links to them); `tests/test_man.py` fails when they drift from
|
|
354
414
|
`cli.py`. They ship as `share/man` data in the wheel, which is how every
|
|
355
415
|
package gets them: the Arch package installs the wheel to `/usr`, the
|
|
356
|
-
Homebrew formula
|
|
416
|
+
Homebrew formula moves them out of its venv, and `uv tool`/`pipx` keep
|
|
357
417
|
them in the venv where `workforest shell-init` points `$MANPATH`.
|
|
358
418
|
|
|
359
419
|
Packaging templates live under `packaging/` (one directory per package
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ArkadyBuryakov/workforest/HEAD/assets/logo-dark.svg">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/ArkadyBuryakov/workforest/HEAD/assets/logo-light.svg" alt="Workforest logo" width="160">
|
|
5
|
+
</picture>
|
|
6
|
+
</p>
|
|
7
|
+
|
|
1
8
|
# Workforest
|
|
2
9
|
|
|
3
10
|
## Elevator pitch
|
|
@@ -51,11 +58,12 @@ line to your `~/.bashrc` / `~/.zshrc`:
|
|
|
51
58
|
eval "$(workforest shell-init)"
|
|
52
59
|
```
|
|
53
60
|
|
|
54
|
-
This upgrades `wf` to
|
|
55
|
-
shell's directory — a plain binary cannot
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
This upgrades both `workforest` and `wf` to one shell function (needed so
|
|
62
|
+
`open` can change your shell's directory — a plain binary cannot; either
|
|
63
|
+
spelling, or an alias of either, behaves the same), registers completions,
|
|
64
|
+
and — for `uv tool`/`pipx` installs, whose files live in a venv — puts the
|
|
65
|
+
man pages on `$MANPATH`. Without it everything still works, but "open in
|
|
66
|
+
current shell" prints the `cd` command instead of performing it.
|
|
59
67
|
|
|
60
68
|
Reference: `man workforest` (commands) and `man 5 workforest` (the
|
|
61
69
|
configuration files); `wf` works in place of `workforest` for both.
|
|
@@ -90,8 +98,9 @@ Layered, YAML or JSON; later layers override earlier ones:
|
|
|
90
98
|
|
|
91
99
|
Scalars and lists replace; the `openers`/`wrappers`/`scripts` mappings
|
|
92
100
|
merge per key (`null` removes an entry). `workforest config` shows the
|
|
93
|
-
merged result and where each layer came from; `workforest init`
|
|
94
|
-
|
|
101
|
+
merged result and where each layer came from; `workforest init` writes a
|
|
102
|
+
short, fully commented starter (`--local` for a personal one) — inert until
|
|
103
|
+
you uncomment something. Nothing in the environment
|
|
95
104
|
changes the result — files and flags only.
|
|
96
105
|
|
|
97
106
|
All keys, with defaults:
|
|
@@ -104,14 +113,14 @@ openers: {} # name -> what `-o NAME` runs, and where
|
|
|
104
113
|
wrappers: {} # name -> command that runs $WF_COMMAND elsewhere (window, pane, direnv)
|
|
105
114
|
symlinks: [] # untracked assets linked from main into new worktrees
|
|
106
115
|
setup_scripts: [] # shell snippets run in a fresh worktree
|
|
107
|
-
scripts: {} # name -> command for `wf run NAME` (
|
|
116
|
+
scripts: {} # name -> command or group for `wf run NAME` (see Scripts)
|
|
108
117
|
stop_timeout: 30 # seconds a stopped script gets after SIGTERM before SIGKILL
|
|
109
118
|
```
|
|
110
119
|
|
|
111
120
|
### Openers
|
|
112
121
|
|
|
113
122
|
An opener is a shell command that runs with the worktree root as working
|
|
114
|
-
directory, either **in your terminal** (the default: the
|
|
123
|
+
directory, either **in your terminal** (the default: the shell function does
|
|
115
124
|
`cd` there and runs it) or **in the background** (`background: true`:
|
|
116
125
|
spawned detached, `wf` returns immediately — for GUI apps and commands that
|
|
117
126
|
hand off to a daemon or multiplexer). Optionally it runs **through a
|
|
@@ -195,7 +204,7 @@ installed to `/usr/share/doc/workforest/examples/` by the Arch package.
|
|
|
195
204
|
|
|
196
205
|
`wf run NAME [ARGS...]` runs a `scripts` entry from the root of the current
|
|
197
206
|
worktree, with any extra arguments shell-quoted and appended. An entry is a
|
|
198
|
-
shell command string, or a
|
|
207
|
+
shell command string, a mapping, or a group of other entries (see below):
|
|
199
208
|
|
|
200
209
|
```yaml
|
|
201
210
|
scripts:
|
|
@@ -216,9 +225,10 @@ terminal, so Ctrl-C and Ctrl-Z reach it directly; a signal sent to `wf`
|
|
|
216
225
|
itself (SIGINT, SIGTERM, SIGHUP) is forwarded to the whole group. `cleanup`
|
|
217
226
|
then runs — after a normal exit, a failure, or a kill — in the same
|
|
218
227
|
worktree with the same `WF_*` variables. `wf run` fails with the command's
|
|
219
|
-
status, or `128+N` when the command was killed by signal N
|
|
220
|
-
killed by SIGINT
|
|
221
|
-
|
|
228
|
+
status, or `128+N` when the command was killed by signal N. A command
|
|
229
|
+
killed by SIGINT — or exiting 130, as a shell does once its child was — is
|
|
230
|
+
reported as interrupted, a warning rather than an error, and ends `wf` the
|
|
231
|
+
way Ctrl-C would, so a shell loop around it aborts.
|
|
222
232
|
|
|
223
233
|
A `background` script (or `wf run -b NAME`) is detached instead: it runs
|
|
224
234
|
under a supervisor of its own — the same process-group and cleanup
|
|
@@ -247,6 +257,48 @@ orphan) is stopped and cleaned up by whoever stops it when `/proc`
|
|
|
247
257
|
confirms the pid was not recycled; elsewhere it is reported and left
|
|
248
258
|
alone.
|
|
249
259
|
|
|
260
|
+
#### Groups
|
|
261
|
+
|
|
262
|
+
A `bulk` entry runs other entries at once; a `pipeline` entry runs them one
|
|
263
|
+
after another. Members are named, not written inline, so each keeps its own
|
|
264
|
+
`exclusive`, `cleanup`, and `stop_timeout`, and a group may name another
|
|
265
|
+
group (cycles are a config error):
|
|
266
|
+
|
|
267
|
+
```yaml
|
|
268
|
+
scripts:
|
|
269
|
+
migrate: npm run db:migrate
|
|
270
|
+
dev:
|
|
271
|
+
bulk: [backend, frontend] # both at once; done when both have ended
|
|
272
|
+
fresh:
|
|
273
|
+
pipeline: [migrate, dev] # in order; the first failure ends it
|
|
274
|
+
background: true
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
A group is a script like any other: `wf run -b`, `background`,
|
|
278
|
+
`exclusive`, and `cleanup` apply to it, and `wf stop GROUP` stops every
|
|
279
|
+
member, waits for all their cleanups, then runs the group's own. A group
|
|
280
|
+
takes no extra arguments; its `stop_timeout` defaults to the longest of
|
|
281
|
+
its members'. Members stay individually visible: `wf stop MEMBER` stops
|
|
282
|
+
that member out from under a running group, and a member already running
|
|
283
|
+
in the worktree fails its step.
|
|
284
|
+
|
|
285
|
+
A pipeline's steps run like consecutive `wf run`s, each with the
|
|
286
|
+
terminal; a `background` member is started and left running while the
|
|
287
|
+
pipeline moves on. The pipeline ends with the first failing step's status.
|
|
288
|
+
|
|
289
|
+
A bulk relays its members' output line by line, each line prefixed with
|
|
290
|
+
the member's name:
|
|
291
|
+
|
|
292
|
+
```
|
|
293
|
+
backend | Attaching to db-1, api-1
|
|
294
|
+
frontend | VITE v5.4 ready in 312 ms
|
|
295
|
+
backend | api-1 | listening on :8000
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Members cannot read the terminal, but Ctrl-C reaches them all. A bulk
|
|
299
|
+
waits for every member — so `bulk: [lint, test, typecheck]` shows every
|
|
300
|
+
failure, not just the first — and fails if any of them did.
|
|
301
|
+
|
|
250
302
|
### Script environment
|
|
251
303
|
|
|
252
304
|
`setup_scripts`, `scripts`, and hooks run via `$SHELL -c` with:
|
|
@@ -283,6 +335,10 @@ scripts:
|
|
|
283
335
|
background: true
|
|
284
336
|
cleanup: docker compose down
|
|
285
337
|
stop_timeout: 60
|
|
338
|
+
dev:
|
|
339
|
+
bulk: [backend, frontend]
|
|
340
|
+
fresh:
|
|
341
|
+
pipeline: [migrate, dev]
|
|
286
342
|
```
|
|
287
343
|
|
|
288
344
|
## Commands
|
|
@@ -290,7 +346,7 @@ scripts:
|
|
|
290
346
|
```
|
|
291
347
|
workforest create [BRANCH] [-o OPENER] [-w WRAPPER] [-p PATH] [--no-hooks] [--no-open]
|
|
292
348
|
workforest open [NAME] [-o OPENER] [-w WRAPPER] [-p PATH]
|
|
293
|
-
workforest list [--porcelain]
|
|
349
|
+
workforest list [--porcelain | --json]
|
|
294
350
|
workforest delete NAME... [--force] [--delete-branch | --keep-branch]
|
|
295
351
|
workforest checkout NAME [--force]
|
|
296
352
|
workforest run [-b] SCRIPT [ARGS...]
|
|
@@ -318,7 +374,11 @@ which is not a stable interface, so any Claude Code update may break it.
|
|
|
318
374
|
Exit codes: `0` ok · `1` error · `2` usage · `3` cancelled · `4` config error
|
|
319
375
|
· `128+N` the `run` command was killed by signal N.
|
|
320
376
|
Human messages go to stderr; stdout carries only machine output (`cd`
|
|
321
|
-
directives for the
|
|
377
|
+
directives for the shell function, `--porcelain`/`--json` listings, dumps).
|
|
378
|
+
`list --json` describes the whole forest for programs — `main` (the main
|
|
379
|
+
checkout, in the same `name`/`branch`/`path`/`dirty` shape as each entry of
|
|
380
|
+
`worktrees`) and the resolved `worktrees_dir` — for editor integrations
|
|
381
|
+
and other programs.
|
|
322
382
|
|
|
323
383
|
## Development
|
|
324
384
|
|
|
@@ -333,7 +393,7 @@ Man pages are hand-written roff under `man/` (`workforest.1` and
|
|
|
333
393
|
`workforest.5`, plus `wf.1`/`wf.5` links to them); `tests/test_man.py` fails when they drift from
|
|
334
394
|
`cli.py`. They ship as `share/man` data in the wheel, which is how every
|
|
335
395
|
package gets them: the Arch package installs the wheel to `/usr`, the
|
|
336
|
-
Homebrew formula
|
|
396
|
+
Homebrew formula moves them out of its venv, and `uv tool`/`pipx` keep
|
|
337
397
|
them in the venv where `workforest shell-init` points `$MANPATH`.
|
|
338
398
|
|
|
339
399
|
Packaging templates live under `packaging/` (one directory per package
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="26.677 49.555 48.277 47.922">
|
|
2
|
+
<path fill="#e6edf3" d="m 40.35,71.984 -10.673,17.25 14.26,0.002 -0.874,5.241 8.619,-0.001 -1.192,-5.47 6.692,0.001 -0.611,3.667 h 6.015 l -0.612,-3.667 9.98,-0.002 -7.47,-12.071 2.501,-0.041 -7.398,-13.555 -7.416,13.555 2.501,0.041 L 47.831,87.978 53.253156,77.719406 50.83,77.658 55.434,68.012 47.373,52.555 36.776,71.926 Z"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="26.677 49.555 48.277 47.922">
|
|
2
|
+
<path fill="#1f2328" d="m 40.35,71.984 -10.673,17.25 14.26,0.002 -0.874,5.241 8.619,-0.001 -1.192,-5.47 6.692,0.001 -0.611,3.667 h 6.015 l -0.612,-3.667 9.98,-0.002 -7.47,-12.071 2.501,-0.041 -7.398,-13.555 -7.416,13.555 2.501,0.041 L 47.831,87.978 53.253156,77.719406 50.83,77.658 55.434,68.012 47.373,52.555 36.776,71.926 Z"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="26.677 49.555 48.277 47.922">
|
|
2
|
+
<path fill="currentColor" d="m 40.35,71.984 -10.673,17.25 14.26,0.002 -0.874,5.241 8.619,-0.001 -1.192,-5.47 6.692,0.001 -0.611,3.667 h 6.015 l -0.612,-3.667 9.98,-0.002 -7.47,-12.071 2.501,-0.041 -7.398,-13.555 -7.416,13.555 2.501,0.041 L 47.831,87.978 53.253156,77.719406 50.83,77.658 55.434,68.012 47.373,52.555 36.776,71.926 Z"/>
|
|
3
|
+
</svg>
|
|
@@ -31,7 +31,7 @@ workforest, wf \- git worktree forest management
|
|
|
31
31
|
.IR PATH ]
|
|
32
32
|
.YS
|
|
33
33
|
.SY "workforest list"
|
|
34
|
-
.RB [ \-\-porcelain ]
|
|
34
|
+
.RB [ \-\-porcelain " | " \-\-json ]
|
|
35
35
|
.YS
|
|
36
36
|
.SY "workforest delete"
|
|
37
37
|
.IR NAME .\|.\|.
|
|
@@ -106,7 +106,7 @@ After
|
|
|
106
106
|
.B shell-init
|
|
107
107
|
(see
|
|
108
108
|
.BR "SHELL INTEGRATION" )
|
|
109
|
-
|
|
109
|
+
both are one shell function that can change the calling shell's directory.
|
|
110
110
|
.SH COMMANDS
|
|
111
111
|
.SS create [\fIBRANCH\fR]
|
|
112
112
|
Create (or reuse) a worktree for
|
|
@@ -188,6 +188,23 @@ and
|
|
|
188
188
|
(dirty) or
|
|
189
189
|
.B 0
|
|
190
190
|
(clean), separated by tabs.
|
|
191
|
+
.TP
|
|
192
|
+
.B \-\-json
|
|
193
|
+
The whole forest as a JSON object, for editor integrations and other
|
|
194
|
+
programs:
|
|
195
|
+
.B main
|
|
196
|
+
(the main checkout) and each entry of
|
|
197
|
+
.B worktrees
|
|
198
|
+
carry
|
|
199
|
+
.BR name ,
|
|
200
|
+
.B branch
|
|
201
|
+
(null when detached),
|
|
202
|
+
.BR path ,
|
|
203
|
+
and
|
|
204
|
+
.B dirty
|
|
205
|
+
(true/false);
|
|
206
|
+
.B worktrees_dir
|
|
207
|
+
is the resolved worktrees directory.
|
|
191
208
|
.SS delete \fINAME\fR...
|
|
192
209
|
Delete one or more worktrees.
|
|
193
210
|
Every name is resolved before anything is removed, so a typo fails the
|
|
@@ -216,7 +233,14 @@ Skip the dirty-worktree confirmation.
|
|
|
216
233
|
Run the named script from the merged configuration's
|
|
217
234
|
.B scripts
|
|
218
235
|
map, from the root of the current worktree.
|
|
219
|
-
Extra arguments are shell-quoted and appended to the script command
|
|
236
|
+
Extra arguments are shell-quoted and appended to the script command (a
|
|
237
|
+
group \(em a
|
|
238
|
+
.B bulk
|
|
239
|
+
or
|
|
240
|
+
.B pipeline
|
|
241
|
+
entry, see
|
|
242
|
+
.BR workforest (5)
|
|
243
|
+
\(em takes none).
|
|
220
244
|
See
|
|
221
245
|
.B ENVIRONMENT
|
|
222
246
|
for the variables scripts receive.
|
|
@@ -239,10 +263,17 @@ while it is already running in this worktree (see
|
|
|
239
263
|
.BR stop ).
|
|
240
264
|
The exit status is the command's, or 128+\fIN\fR when it was killed by
|
|
241
265
|
signal
|
|
242
|
-
.IR N
|
|
243
|
-
|
|
266
|
+
.IR N .
|
|
267
|
+
A command killed by SIGINT \(em or exiting 130, as a shell does once its
|
|
268
|
+
child was \(em is reported as interrupted, a warning rather than an
|
|
269
|
+
error, and ends
|
|
244
270
|
.B workforest
|
|
245
271
|
by SIGINT as well, so a shell loop around it aborts.
|
|
272
|
+
A group runs under a supervisor that takes the command's place in all of
|
|
273
|
+
the above: a pipeline runs its members in order and ends with the first
|
|
274
|
+
failing step's status; a bulk runs them at once, relays their output with
|
|
275
|
+
the member's name as a line prefix, and ends with the first failing
|
|
276
|
+
member's status once all have ended.
|
|
246
277
|
.TP
|
|
247
278
|
.BR \-b ", " \-\-background
|
|
248
279
|
Detach instead: the command runs under a supervisor of its own with its
|
|
@@ -265,6 +296,9 @@ foreground or background, started from any terminal \(em with SIGTERM,
|
|
|
265
296
|
then SIGKILL after
|
|
266
297
|
.B stop_timeout
|
|
267
298
|
seconds, and wait for its cleanup to finish.
|
|
299
|
+
For a group this stops every member and waits for all of their cleanups,
|
|
300
|
+
then the group's own; a member can also be stopped by name out from
|
|
301
|
+
under a running group.
|
|
268
302
|
Fails when nothing is running.
|
|
269
303
|
.TP
|
|
270
304
|
.B \-\-all
|
|
@@ -314,9 +348,12 @@ The
|
|
|
314
348
|
.B delete
|
|
315
349
|
tab stays open after each deletion for bulk cleanup.
|
|
316
350
|
.SS init
|
|
317
|
-
|
|
351
|
+
Write a short, fully commented
|
|
318
352
|
.B .workforest.yaml
|
|
319
|
-
|
|
353
|
+
starter in the main checkout. Every key is commented out, so the file
|
|
354
|
+
changes nothing until edited; see
|
|
355
|
+
.BR workforest (5)
|
|
356
|
+
for the reference.
|
|
320
357
|
.TP
|
|
321
358
|
.B \-\-local
|
|
322
359
|
Place it in
|
|
@@ -359,15 +396,14 @@ A subprocess cannot change its parent shell's directory, so add to your
|
|
|
359
396
|
eval "$(workforest shell-init)"
|
|
360
397
|
.EE
|
|
361
398
|
.PP
|
|
362
|
-
This defines
|
|
363
|
-
.
|
|
364
|
-
as
|
|
365
|
-
|
|
366
|
-
and evaluates the directive it prints (a
|
|
399
|
+
This defines both
|
|
400
|
+
.BR workforest " and " wf
|
|
401
|
+
as one shell function that runs the workforest binary and evaluates the
|
|
402
|
+
directive it prints (a
|
|
367
403
|
.B cd
|
|
368
|
-
into the worktree, followed by the opener command)
|
|
369
|
-
|
|
370
|
-
|
|
404
|
+
into the worktree, followed by the opener command), so either spelling
|
|
405
|
+
\(em or an alias of either \(em behaves the same; registers tab
|
|
406
|
+
completion for both;
|
|
371
407
|
and, when workforest was installed into a Python environment whose
|
|
372
408
|
manual pages are not on the default search path (a
|
|
373
409
|
.B uv tool
|
|
@@ -499,7 +535,6 @@ command was killed by signal
|
|
|
499
535
|
.PP
|
|
500
536
|
Human-readable messages go to stderr.
|
|
501
537
|
Stdout carries only machine output: directives for the
|
|
502
|
-
.B wf
|
|
503
538
|
shell function, listings, and configuration dumps.
|
|
504
539
|
.SH EXAMPLES
|
|
505
540
|
.TP
|