workforest 0.1.0__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.1.0/.github/workflows/ci.yml +47 -0
- workforest-0.1.0/.github/workflows/publish_pypi.yml +22 -0
- workforest-0.1.0/.github/workflows/release.yml +34 -0
- workforest-0.1.0/.gitignore +21 -0
- workforest-0.1.0/LICENSE +21 -0
- workforest-0.1.0/Makefile +33 -0
- workforest-0.1.0/PKG-INFO +188 -0
- workforest-0.1.0/README.md +169 -0
- workforest-0.1.0/completions/_workforest +23 -0
- workforest-0.1.0/packaging/AUR/.SRCINFO +19 -0
- workforest-0.1.0/packaging/AUR/PKGBUILD +37 -0
- workforest-0.1.0/pyproject.toml +77 -0
- workforest-0.1.0/src/workforest/__init__.py +3 -0
- workforest-0.1.0/src/workforest/__main__.py +4 -0
- workforest-0.1.0/src/workforest/cli.py +254 -0
- workforest-0.1.0/src/workforest/commands.py +284 -0
- workforest-0.1.0/src/workforest/completions.py +76 -0
- workforest-0.1.0/src/workforest/config.py +188 -0
- workforest-0.1.0/src/workforest/errors.py +34 -0
- workforest-0.1.0/src/workforest/examples/config.yaml +117 -0
- workforest-0.1.0/src/workforest/gitutil.py +194 -0
- workforest-0.1.0/src/workforest/hooks.py +146 -0
- workforest-0.1.0/src/workforest/integrations/__init__.py +2 -0
- workforest-0.1.0/src/workforest/integrations/claude.py +129 -0
- workforest-0.1.0/src/workforest/launch.py +107 -0
- workforest-0.1.0/src/workforest/output.py +75 -0
- workforest-0.1.0/src/workforest/shell/completion.bash +27 -0
- workforest-0.1.0/src/workforest/shell/completion.zsh +38 -0
- workforest-0.1.0/src/workforest/shell/workforest.sh +16 -0
- workforest-0.1.0/src/workforest/shellinit.py +27 -0
- workforest-0.1.0/src/workforest/tui.py +206 -0
- workforest-0.1.0/tests/__init__.py +0 -0
- workforest-0.1.0/tests/conftest.py +196 -0
- workforest-0.1.0/tests/test_claude.py +167 -0
- workforest-0.1.0/tests/test_cli.py +138 -0
- workforest-0.1.0/tests/test_commands.py +337 -0
- workforest-0.1.0/tests/test_config.py +256 -0
- workforest-0.1.0/tests/test_gitutil.py +153 -0
- workforest-0.1.0/tests/test_harness.py +34 -0
- workforest-0.1.0/tests/test_hooks.py +185 -0
- workforest-0.1.0/tests/test_launch.py +113 -0
- workforest-0.1.0/tests/test_shell.py +184 -0
- workforest-0.1.0/tests/test_tui.py +118 -0
- workforest-0.1.0/uv.lock +431 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
check:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
container: archlinux:latest
|
|
12
|
+
steps:
|
|
13
|
+
- name: Install system dependencies
|
|
14
|
+
run: pacman -Syu --noconfirm git python uv make zsh
|
|
15
|
+
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Sync dev environment
|
|
19
|
+
run: uv sync
|
|
20
|
+
|
|
21
|
+
- name: Lint, type-check, test
|
|
22
|
+
run: make check
|
|
23
|
+
|
|
24
|
+
package-smoke:
|
|
25
|
+
# Builds the wheel the AUR package would build and installs it into a
|
|
26
|
+
# clean venv — catches missing package data and broken entry points.
|
|
27
|
+
# The full `makepkg -si` + namcap check runs in the release workflow,
|
|
28
|
+
# where a source tarball exists.
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
container: archlinux:latest
|
|
31
|
+
steps:
|
|
32
|
+
- name: Install system dependencies
|
|
33
|
+
run: pacman -Syu --noconfirm git python python-build python-installer python-wheel python-hatchling
|
|
34
|
+
|
|
35
|
+
- uses: actions/checkout@v4
|
|
36
|
+
|
|
37
|
+
- name: Build wheel (no isolation, like the PKGBUILD)
|
|
38
|
+
run: python -m build --wheel --no-isolation
|
|
39
|
+
|
|
40
|
+
- name: Install into a clean venv and smoke test
|
|
41
|
+
run: |
|
|
42
|
+
python -m venv /tmp/venv
|
|
43
|
+
/tmp/venv/bin/pip install dist/*.whl
|
|
44
|
+
/tmp/venv/bin/workforest --version
|
|
45
|
+
/tmp/venv/bin/wf --version
|
|
46
|
+
/tmp/venv/bin/workforest shell-init bash | bash -n
|
|
47
|
+
/tmp/venv/bin/workforest shell-init zsh | head -1 | grep -q Workforest
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
environment: pypi
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write # OIDC for PyPI trusted publishing
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- uses: astral-sh/setup-uv@v5
|
|
17
|
+
|
|
18
|
+
- name: Build sdist and wheel
|
|
19
|
+
run: uv build
|
|
20
|
+
|
|
21
|
+
- name: Publish to PyPI
|
|
22
|
+
run: uv publish
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
release:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
|
|
13
|
+
- name: Read package version
|
|
14
|
+
id: version
|
|
15
|
+
run: |
|
|
16
|
+
version=$(sed -n 's/^__version__ = "\(.*\)"$/\1/p' src/workforest/__init__.py)
|
|
17
|
+
if [ -z "$version" ]; then
|
|
18
|
+
echo "Could not parse __version__ from src/workforest/__init__.py" >&2
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
22
|
+
|
|
23
|
+
- name: Create tag and release if missing
|
|
24
|
+
env:
|
|
25
|
+
# Must be a PAT, not github.token: releases created with the default
|
|
26
|
+
# token do not trigger the release-published publisher workflows.
|
|
27
|
+
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
|
28
|
+
run: |
|
|
29
|
+
tag="v${{ steps.version.outputs.version }}"
|
|
30
|
+
if git ls-remote --exit-code --tags origin "refs/tags/$tag" > /dev/null; then
|
|
31
|
+
echo "Tag $tag already exists — nothing to release."
|
|
32
|
+
else
|
|
33
|
+
gh release create "$tag" --target "$GITHUB_SHA" --generate-notes
|
|
34
|
+
fi
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
.venv/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
*.egg-info/
|
|
8
|
+
|
|
9
|
+
# Tooling caches
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
.mypy_cache/
|
|
12
|
+
.ruff_cache/
|
|
13
|
+
.coverage
|
|
14
|
+
htmlcov/
|
|
15
|
+
|
|
16
|
+
# Packaging artifacts
|
|
17
|
+
*.pkg.tar.zst
|
|
18
|
+
pkg/
|
|
19
|
+
|
|
20
|
+
# Tooling config
|
|
21
|
+
.workforest.yaml
|
workforest-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arkady Buryakov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Dev targets wrap `uv run`; `uv sync` is the only setup step.
|
|
2
|
+
|
|
3
|
+
.PHONY: check test lint type cov sync install uninstall
|
|
4
|
+
|
|
5
|
+
sync:
|
|
6
|
+
uv sync
|
|
7
|
+
|
|
8
|
+
test:
|
|
9
|
+
uv run pytest
|
|
10
|
+
|
|
11
|
+
lint:
|
|
12
|
+
uv run ruff check .
|
|
13
|
+
uv run ruff format --check .
|
|
14
|
+
|
|
15
|
+
type:
|
|
16
|
+
uv run mypy
|
|
17
|
+
|
|
18
|
+
check: lint type test
|
|
19
|
+
|
|
20
|
+
cov:
|
|
21
|
+
uv run pytest --cov-report=html
|
|
22
|
+
@echo "open htmlcov/index.html"
|
|
23
|
+
|
|
24
|
+
# Install the current checkout as a uv tool (~/.local/bin/workforest).
|
|
25
|
+
# --reinstall so re-running picks up changes even without a version bump.
|
|
26
|
+
install:
|
|
27
|
+
uv tool install --reinstall .
|
|
28
|
+
@echo
|
|
29
|
+
@echo 'workforest installed. Make sure your shell rc has:'
|
|
30
|
+
@echo ' eval "$$(workforest shell-init)"'
|
|
31
|
+
|
|
32
|
+
uninstall:
|
|
33
|
+
uv tool uninstall workforest
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: workforest
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Git worktree forest management: create, open, and clean up per-branch worktrees with project-defined setup hooks
|
|
5
|
+
Project-URL: Homepage, https://github.com/arkadyb/workforest
|
|
6
|
+
Author-email: Arkady Buryakov <arkady@buryakov.pro>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: cli,developer-tools,git,worktree
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
16
|
+
Requires-Python: >=3.14
|
|
17
|
+
Requires-Dist: pyyaml>=6.0
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# Workforest
|
|
21
|
+
|
|
22
|
+
Git worktree forest management: one main checkout plus any number of
|
|
23
|
+
disposable, per-branch worktrees in a predictable location — created, set up,
|
|
24
|
+
opened, and cleaned up with one command.
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
~/dev/
|
|
28
|
+
├── api/ # main checkout
|
|
29
|
+
└── worktrees/
|
|
30
|
+
└── api/
|
|
31
|
+
├── feature-x/ # wf create feature-x
|
|
32
|
+
└── fix-y/
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- **Create** a worktree for any branch (local, remote, or brand new) and have
|
|
36
|
+
it set up automatically: symlinks for untracked assets (`node_modules`,
|
|
37
|
+
`.env`, …) and project-defined setup scripts.
|
|
38
|
+
- **Open** it in your editor — in the current shell, or in a new terminal
|
|
39
|
+
window via a configurable command template.
|
|
40
|
+
- **Run** named project scripts with well-known `WF_*` environment variables.
|
|
41
|
+
- **Delete** worktrees safely, or **checkout**: collapse one back into the
|
|
42
|
+
main checkout.
|
|
43
|
+
- Drive everything from an interactive fzf **TUI** (`wf` with no arguments).
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
# Arch Linux
|
|
49
|
+
yay -S workforest # AUR
|
|
50
|
+
|
|
51
|
+
# anywhere else
|
|
52
|
+
uv tool install workforest # or: pipx install workforest
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This installs two commands: `workforest` and its alias `wf`. Then add one
|
|
56
|
+
line to your `~/.bashrc` / `~/.zshrc`:
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
eval "$(workforest shell-init)"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
This upgrades `wf` to a shell function (needed so `wf open` can change your
|
|
63
|
+
shell's directory — a plain binary cannot) and registers completions. Without
|
|
64
|
+
it everything still works, but "open in current shell" prints the `cd`
|
|
65
|
+
command instead of performing it.
|
|
66
|
+
|
|
67
|
+
Requirements: Linux, git ≥ 2.36, Python ≥ 3.14. Optional: `fzf` for the TUI.
|
|
68
|
+
|
|
69
|
+
## Quick start
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
wf create feature/login # create worktree + run hooks + open in $EDITOR
|
|
73
|
+
wf list # what's in the forest
|
|
74
|
+
wf open login -o 'lazygit' # open with any command instead
|
|
75
|
+
wf run test # run a named script from config
|
|
76
|
+
wf run make check -j2 # extra args are appended to the script command
|
|
77
|
+
wf checkout login # fold the branch back into the main checkout
|
|
78
|
+
wf delete fix-y # remove a worktree (asks about dirty changes)
|
|
79
|
+
wf # interactive TUI (fzf)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Any unknown first word is an opener shortcut: `wf edit api` ≡
|
|
83
|
+
`wf open api -o edit`.
|
|
84
|
+
|
|
85
|
+
## Configuration
|
|
86
|
+
|
|
87
|
+
Layered, YAML or JSON; later layers override earlier ones:
|
|
88
|
+
|
|
89
|
+
| Layer | Location | Typical content |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| system | `/etc/workforest/config.yaml` | org-wide defaults |
|
|
92
|
+
| user | `~/.config/workforest/config.yaml` | your terminal/editor setup |
|
|
93
|
+
| project (shared) | `.workforest.yaml` in the repo root | repo policy, committed |
|
|
94
|
+
| project (local) | `.vscode/` or `.idea/` `.workforest.yaml` | personal overrides, untracked |
|
|
95
|
+
|
|
96
|
+
Scalars and lists replace; the `scripts`/`openers` mappings merge per key
|
|
97
|
+
(`null` removes an entry). `workforest config` shows the merged result and
|
|
98
|
+
where each layer came from; `workforest init` scaffolds a project file
|
|
99
|
+
(`--local` for a personal one).
|
|
100
|
+
|
|
101
|
+
All keys, with defaults:
|
|
102
|
+
|
|
103
|
+
```yaml
|
|
104
|
+
worktrees_dir: "$WF_MAIN/../worktrees/$WF_NAME" # where the forest lives
|
|
105
|
+
opener: "" # default opener; "" → $VISUAL → $EDITOR
|
|
106
|
+
openers: {} # name -> command template, e.g. edit: "$EDITOR {path}"
|
|
107
|
+
window_command: "" # "" → current shell; or e.g.
|
|
108
|
+
# "kitty --title {title} --directory {path} {command}"
|
|
109
|
+
symlinks: [] # untracked assets linked from main into new worktrees
|
|
110
|
+
setup_scripts: [] # shell snippets run in a fresh worktree
|
|
111
|
+
scripts: {} # name -> snippet for `wf run NAME`
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Openers are command templates: `{path}` is replaced with the target path; a
|
|
115
|
+
template without `{path}` just runs with the worktree as working directory.
|
|
116
|
+
Environment variables expand in both openers and `window_command`.
|
|
117
|
+
|
|
118
|
+
Fully commented reference configs:
|
|
119
|
+
[`config.yaml`](src/workforest/examples/config.yaml) (user/system) and
|
|
120
|
+
[`.workforest.yaml`](src/workforest/examples/.workforest.yaml) (project) —
|
|
121
|
+
installed to `/usr/share/doc/workforest/examples/` by the Arch package.
|
|
122
|
+
|
|
123
|
+
### Script environment
|
|
124
|
+
|
|
125
|
+
`setup_scripts`, `scripts`, and hooks run via `$SHELL -c` with:
|
|
126
|
+
|
|
127
|
+
| Variable | Value |
|
|
128
|
+
|---|---|
|
|
129
|
+
| `WF_MAIN` | main worktree path |
|
|
130
|
+
| `WF_NAME` | repo name (main checkout directory name) |
|
|
131
|
+
| `WF_WORKTREE` | current/new worktree path |
|
|
132
|
+
| `WF_WORKTREES_DIR` | resolved worktrees directory |
|
|
133
|
+
| `WF_BRANCH` | branch of the current/new worktree |
|
|
134
|
+
|
|
135
|
+
`worktrees_dir` is a template using the same naming pattern: `$WF_MAIN` and
|
|
136
|
+
`$WF_NAME` (plus regular environment variables like `$HOME`) expand there —
|
|
137
|
+
the per-worktree variables don't, since no worktree exists yet when the base
|
|
138
|
+
directory is resolved.
|
|
139
|
+
|
|
140
|
+
### Example project config
|
|
141
|
+
|
|
142
|
+
```yaml
|
|
143
|
+
# .workforest.yaml — committed to the repo
|
|
144
|
+
symlinks: [node_modules, .env]
|
|
145
|
+
setup_scripts:
|
|
146
|
+
- npm install --prefer-offline
|
|
147
|
+
scripts:
|
|
148
|
+
test: npm test
|
|
149
|
+
migrate: npm run db:migrate
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Commands
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
workforest create [BRANCH] [-o OPENER] [-p PATH] [--no-hooks] [--no-open]
|
|
156
|
+
workforest open [NAME] [-o OPENER] [-p PATH]
|
|
157
|
+
workforest list [--porcelain]
|
|
158
|
+
workforest delete NAME... [--force] [--delete-branch | --keep-branch]
|
|
159
|
+
workforest checkout NAME [--force]
|
|
160
|
+
workforest run SCRIPT [ARGS...]
|
|
161
|
+
workforest tui [MODE]
|
|
162
|
+
workforest init [--local]
|
|
163
|
+
workforest config [--json]
|
|
164
|
+
workforest shell-init [bash|zsh]
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Exit codes: `0` ok · `1` error · `2` usage · `3` cancelled · `4` config error.
|
|
168
|
+
Human messages go to stderr; stdout carries only machine output (`cd`
|
|
169
|
+
directives for the `wf` wrapper, `--porcelain` listings, dumps).
|
|
170
|
+
|
|
171
|
+
## Development
|
|
172
|
+
|
|
173
|
+
```sh
|
|
174
|
+
uv sync # venv + dev dependencies (uv.lock)
|
|
175
|
+
make check # ruff + mypy --strict + pytest (coverage gate ≥ 90%)
|
|
176
|
+
make install # install this checkout as a uv tool (~/.local/bin/workforest)
|
|
177
|
+
make uninstall # remove it again
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Packaging recipes live under
|
|
181
|
+
`packaging/` (one directory per package manager; currently `packaging/AUR/`).
|
|
182
|
+
Release: bump `__version__`, tag, update `sha256sums` in
|
|
183
|
+
`packaging/AUR/PKGBUILD`, regenerate `.SRCINFO`
|
|
184
|
+
(`makepkg --printsrcinfo > .SRCINFO` inside `packaging/AUR/`), push to AUR.
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Workforest
|
|
2
|
+
|
|
3
|
+
Git worktree forest management: one main checkout plus any number of
|
|
4
|
+
disposable, per-branch worktrees in a predictable location — created, set up,
|
|
5
|
+
opened, and cleaned up with one command.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
~/dev/
|
|
9
|
+
├── api/ # main checkout
|
|
10
|
+
└── worktrees/
|
|
11
|
+
└── api/
|
|
12
|
+
├── feature-x/ # wf create feature-x
|
|
13
|
+
└── fix-y/
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- **Create** a worktree for any branch (local, remote, or brand new) and have
|
|
17
|
+
it set up automatically: symlinks for untracked assets (`node_modules`,
|
|
18
|
+
`.env`, …) and project-defined setup scripts.
|
|
19
|
+
- **Open** it in your editor — in the current shell, or in a new terminal
|
|
20
|
+
window via a configurable command template.
|
|
21
|
+
- **Run** named project scripts with well-known `WF_*` environment variables.
|
|
22
|
+
- **Delete** worktrees safely, or **checkout**: collapse one back into the
|
|
23
|
+
main checkout.
|
|
24
|
+
- Drive everything from an interactive fzf **TUI** (`wf` with no arguments).
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
# Arch Linux
|
|
30
|
+
yay -S workforest # AUR
|
|
31
|
+
|
|
32
|
+
# anywhere else
|
|
33
|
+
uv tool install workforest # or: pipx install workforest
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
This installs two commands: `workforest` and its alias `wf`. Then add one
|
|
37
|
+
line to your `~/.bashrc` / `~/.zshrc`:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
eval "$(workforest shell-init)"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
This upgrades `wf` to a shell function (needed so `wf open` can change your
|
|
44
|
+
shell's directory — a plain binary cannot) and registers completions. Without
|
|
45
|
+
it everything still works, but "open in current shell" prints the `cd`
|
|
46
|
+
command instead of performing it.
|
|
47
|
+
|
|
48
|
+
Requirements: Linux, git ≥ 2.36, Python ≥ 3.14. Optional: `fzf` for the TUI.
|
|
49
|
+
|
|
50
|
+
## Quick start
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
wf create feature/login # create worktree + run hooks + open in $EDITOR
|
|
54
|
+
wf list # what's in the forest
|
|
55
|
+
wf open login -o 'lazygit' # open with any command instead
|
|
56
|
+
wf run test # run a named script from config
|
|
57
|
+
wf run make check -j2 # extra args are appended to the script command
|
|
58
|
+
wf checkout login # fold the branch back into the main checkout
|
|
59
|
+
wf delete fix-y # remove a worktree (asks about dirty changes)
|
|
60
|
+
wf # interactive TUI (fzf)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Any unknown first word is an opener shortcut: `wf edit api` ≡
|
|
64
|
+
`wf open api -o edit`.
|
|
65
|
+
|
|
66
|
+
## Configuration
|
|
67
|
+
|
|
68
|
+
Layered, YAML or JSON; later layers override earlier ones:
|
|
69
|
+
|
|
70
|
+
| Layer | Location | Typical content |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| system | `/etc/workforest/config.yaml` | org-wide defaults |
|
|
73
|
+
| user | `~/.config/workforest/config.yaml` | your terminal/editor setup |
|
|
74
|
+
| project (shared) | `.workforest.yaml` in the repo root | repo policy, committed |
|
|
75
|
+
| project (local) | `.vscode/` or `.idea/` `.workforest.yaml` | personal overrides, untracked |
|
|
76
|
+
|
|
77
|
+
Scalars and lists replace; the `scripts`/`openers` mappings merge per key
|
|
78
|
+
(`null` removes an entry). `workforest config` shows the merged result and
|
|
79
|
+
where each layer came from; `workforest init` scaffolds a project file
|
|
80
|
+
(`--local` for a personal one).
|
|
81
|
+
|
|
82
|
+
All keys, with defaults:
|
|
83
|
+
|
|
84
|
+
```yaml
|
|
85
|
+
worktrees_dir: "$WF_MAIN/../worktrees/$WF_NAME" # where the forest lives
|
|
86
|
+
opener: "" # default opener; "" → $VISUAL → $EDITOR
|
|
87
|
+
openers: {} # name -> command template, e.g. edit: "$EDITOR {path}"
|
|
88
|
+
window_command: "" # "" → current shell; or e.g.
|
|
89
|
+
# "kitty --title {title} --directory {path} {command}"
|
|
90
|
+
symlinks: [] # untracked assets linked from main into new worktrees
|
|
91
|
+
setup_scripts: [] # shell snippets run in a fresh worktree
|
|
92
|
+
scripts: {} # name -> snippet for `wf run NAME`
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Openers are command templates: `{path}` is replaced with the target path; a
|
|
96
|
+
template without `{path}` just runs with the worktree as working directory.
|
|
97
|
+
Environment variables expand in both openers and `window_command`.
|
|
98
|
+
|
|
99
|
+
Fully commented reference configs:
|
|
100
|
+
[`config.yaml`](src/workforest/examples/config.yaml) (user/system) and
|
|
101
|
+
[`.workforest.yaml`](src/workforest/examples/.workforest.yaml) (project) —
|
|
102
|
+
installed to `/usr/share/doc/workforest/examples/` by the Arch package.
|
|
103
|
+
|
|
104
|
+
### Script environment
|
|
105
|
+
|
|
106
|
+
`setup_scripts`, `scripts`, and hooks run via `$SHELL -c` with:
|
|
107
|
+
|
|
108
|
+
| Variable | Value |
|
|
109
|
+
|---|---|
|
|
110
|
+
| `WF_MAIN` | main worktree path |
|
|
111
|
+
| `WF_NAME` | repo name (main checkout directory name) |
|
|
112
|
+
| `WF_WORKTREE` | current/new worktree path |
|
|
113
|
+
| `WF_WORKTREES_DIR` | resolved worktrees directory |
|
|
114
|
+
| `WF_BRANCH` | branch of the current/new worktree |
|
|
115
|
+
|
|
116
|
+
`worktrees_dir` is a template using the same naming pattern: `$WF_MAIN` and
|
|
117
|
+
`$WF_NAME` (plus regular environment variables like `$HOME`) expand there —
|
|
118
|
+
the per-worktree variables don't, since no worktree exists yet when the base
|
|
119
|
+
directory is resolved.
|
|
120
|
+
|
|
121
|
+
### Example project config
|
|
122
|
+
|
|
123
|
+
```yaml
|
|
124
|
+
# .workforest.yaml — committed to the repo
|
|
125
|
+
symlinks: [node_modules, .env]
|
|
126
|
+
setup_scripts:
|
|
127
|
+
- npm install --prefer-offline
|
|
128
|
+
scripts:
|
|
129
|
+
test: npm test
|
|
130
|
+
migrate: npm run db:migrate
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Commands
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
workforest create [BRANCH] [-o OPENER] [-p PATH] [--no-hooks] [--no-open]
|
|
137
|
+
workforest open [NAME] [-o OPENER] [-p PATH]
|
|
138
|
+
workforest list [--porcelain]
|
|
139
|
+
workforest delete NAME... [--force] [--delete-branch | --keep-branch]
|
|
140
|
+
workforest checkout NAME [--force]
|
|
141
|
+
workforest run SCRIPT [ARGS...]
|
|
142
|
+
workforest tui [MODE]
|
|
143
|
+
workforest init [--local]
|
|
144
|
+
workforest config [--json]
|
|
145
|
+
workforest shell-init [bash|zsh]
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Exit codes: `0` ok · `1` error · `2` usage · `3` cancelled · `4` config error.
|
|
149
|
+
Human messages go to stderr; stdout carries only machine output (`cd`
|
|
150
|
+
directives for the `wf` wrapper, `--porcelain` listings, dumps).
|
|
151
|
+
|
|
152
|
+
## Development
|
|
153
|
+
|
|
154
|
+
```sh
|
|
155
|
+
uv sync # venv + dev dependencies (uv.lock)
|
|
156
|
+
make check # ruff + mypy --strict + pytest (coverage gate ≥ 90%)
|
|
157
|
+
make install # install this checkout as a uv tool (~/.local/bin/workforest)
|
|
158
|
+
make uninstall # remove it again
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Packaging recipes live under
|
|
162
|
+
`packaging/` (one directory per package manager; currently `packaging/AUR/`).
|
|
163
|
+
Release: bump `__version__`, tag, update `sha256sums` in
|
|
164
|
+
`packaging/AUR/PKGBUILD`, regenerate `.SRCINFO`
|
|
165
|
+
(`makepkg --printsrcinfo > .SRCINFO` inside `packaging/AUR/`), push to AUR.
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#compdef workforest wf
|
|
2
|
+
# zsh completion for workforest / wf — installed to zsh site-functions.
|
|
3
|
+
# Dynamic candidates are delegated to `workforest --complete TOPIC`.
|
|
4
|
+
|
|
5
|
+
local topic cmd
|
|
6
|
+
local -a items
|
|
7
|
+
cmd="${words[2]:-}"
|
|
8
|
+
if (( CURRENT == 2 )); then
|
|
9
|
+
topic=commands
|
|
10
|
+
else
|
|
11
|
+
case "$cmd" in
|
|
12
|
+
create) topic=branches ;;
|
|
13
|
+
open|delete|checkout) topic=worktrees ;;
|
|
14
|
+
run) topic=scripts ;;
|
|
15
|
+
claude) topic=claude-sessions ;;
|
|
16
|
+
tui|list|init|config|shell-init) topic=none ;;
|
|
17
|
+
*) topic=worktrees ;;
|
|
18
|
+
esac
|
|
19
|
+
fi
|
|
20
|
+
if [[ "$topic" != none ]]; then
|
|
21
|
+
items=(${(f)"$(workforest --complete "$topic" 2>/dev/null)"})
|
|
22
|
+
(( ${#items} )) && compadd -a items
|
|
23
|
+
fi
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
pkgbase = workforest
|
|
2
|
+
pkgdesc = Git worktree forest management: per-branch worktrees with project-defined setup hooks
|
|
3
|
+
pkgver = 0.1.0
|
|
4
|
+
pkgrel = 1
|
|
5
|
+
url = https://github.com/arkadyb/workforest
|
|
6
|
+
arch = any
|
|
7
|
+
license = MIT
|
|
8
|
+
makedepends = python-build
|
|
9
|
+
makedepends = python-installer
|
|
10
|
+
makedepends = python-wheel
|
|
11
|
+
makedepends = python-hatchling
|
|
12
|
+
depends = python
|
|
13
|
+
depends = python-yaml
|
|
14
|
+
depends = git
|
|
15
|
+
optdepends = fzf: interactive TUI (workforest tui)
|
|
16
|
+
source = workforest-0.1.0.tar.gz::https://github.com/arkadyb/workforest/archive/v0.1.0.tar.gz
|
|
17
|
+
sha256sums = SKIP
|
|
18
|
+
|
|
19
|
+
pkgname = workforest
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Maintainer: Arkady Buryakov <arkady@buryakov.pro>
|
|
2
|
+
pkgname=workforest
|
|
3
|
+
pkgver=0.1.0
|
|
4
|
+
pkgrel=1
|
|
5
|
+
pkgdesc="Git worktree forest management: per-branch worktrees with project-defined setup hooks"
|
|
6
|
+
arch=(any)
|
|
7
|
+
url="https://github.com/arkadyb/workforest"
|
|
8
|
+
license=(MIT)
|
|
9
|
+
depends=(python python-yaml git)
|
|
10
|
+
makedepends=(python-build python-installer python-wheel python-hatchling)
|
|
11
|
+
optdepends=('fzf: interactive TUI (workforest tui)')
|
|
12
|
+
source=("$pkgname-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
|
|
13
|
+
sha256sums=('SKIP') # set the real checksum when cutting a release
|
|
14
|
+
|
|
15
|
+
build() {
|
|
16
|
+
cd "$pkgname-$pkgver"
|
|
17
|
+
python -m build --wheel --no-isolation
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
package() {
|
|
21
|
+
cd "$pkgname-$pkgver"
|
|
22
|
+
python -m installer --destdir="$pkgdir" dist/*.whl
|
|
23
|
+
|
|
24
|
+
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
|
|
25
|
+
|
|
26
|
+
# Shell completions (the wf function itself comes from `workforest shell-init`)
|
|
27
|
+
install -Dm644 src/workforest/shell/completion.bash \
|
|
28
|
+
"$pkgdir/usr/share/bash-completion/completions/workforest"
|
|
29
|
+
install -Dm644 completions/_workforest \
|
|
30
|
+
"$pkgdir/usr/share/zsh/site-functions/_workforest"
|
|
31
|
+
|
|
32
|
+
# Reference configs (DESIGN §4.3)
|
|
33
|
+
install -Dm644 src/workforest/examples/config.yaml \
|
|
34
|
+
"$pkgdir/usr/share/doc/$pkgname/examples/config.yaml"
|
|
35
|
+
install -Dm644 src/workforest/examples/.workforest.yaml \
|
|
36
|
+
"$pkgdir/usr/share/doc/$pkgname/examples/workforest.project.yaml"
|
|
37
|
+
}
|