git-worktrees 0.5.1__tar.gz → 0.6.1__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.
Files changed (106) hide show
  1. git_worktrees-0.6.1/.github/workflows/codeql.yml +48 -0
  2. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/.github/workflows/publish.yml +27 -8
  3. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/.github/workflows/testpypi.yml +1 -1
  4. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/.github/workflows/tests.yml +11 -6
  5. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/.releasetools.yaml +3 -1
  6. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/AGENTS.md +41 -12
  7. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/CHANGELOG.md +71 -0
  8. git_worktrees-0.6.1/Makefile +9 -0
  9. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/PKG-INFO +123 -47
  10. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/README.md +121 -45
  11. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/completions/gw.fish +1 -1
  12. git_worktrees-0.6.1/completions/gwbc.fish +13 -0
  13. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/completions/gwbs.fish +2 -0
  14. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/completions/gwrot.fish +1 -1
  15. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/completions/gws.fish +2 -0
  16. git_worktrees-0.6.1/conf.d/worktrees.fish +7 -0
  17. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/docs/DEVELOPMENT.md +24 -14
  18. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/docs/REFERENCE.md +13 -9
  19. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/hatch_build.py +2 -2
  20. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/pyproject.toml +3 -3
  21. git_worktrees-0.6.1/scripts/bump_version.py +77 -0
  22. git_worktrees-0.6.1/src/worktrees/branch_cli.py +311 -0
  23. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/src/worktrees/branches.py +29 -6
  24. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/src/worktrees/cli.py +301 -120
  25. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/src/worktrees/forge.py +10 -7
  26. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/src/worktrees/git.py +17 -5
  27. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/src/worktrees/new_branch.py +8 -3
  28. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/src/worktrees/pick.py +5 -3
  29. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/src/worktrees/prune.py +16 -8
  30. git_worktrees-0.6.1/src/worktrees/render.py +259 -0
  31. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/src/worktrees/repo.py +68 -3
  32. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/src/worktrees/rotate.py +78 -16
  33. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/src/worktrees/worktree.py +11 -8
  34. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/tests/test_branches.py +34 -1
  35. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/tests/test_cli.py +147 -4
  36. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/tests/test_completions.py +2 -1
  37. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/tests/test_gates.py +1 -1
  38. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/tests/test_new_branch.py +13 -12
  39. git_worktrees-0.6.1/tests/test_render.py +211 -0
  40. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/tests/test_rotate.py +110 -10
  41. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/tests/test_shims.py +38 -2
  42. git_worktrees-0.6.1/tests/test_version.py +233 -0
  43. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/uv.lock +1 -1
  44. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/zsh/plugins/worktrees/completions/_gw +1 -0
  45. git_worktrees-0.5.1/zsh/plugins/worktrees/completions/_gwnb → git_worktrees-0.6.1/zsh/plugins/worktrees/completions/_gwbc +2 -2
  46. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/zsh/plugins/worktrees/completions/_gwbs +2 -0
  47. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/zsh/plugins/worktrees/completions/_gwrot +1 -1
  48. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/zsh/plugins/worktrees/completions/_gws +2 -0
  49. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/zsh/plugins/worktrees/worktrees.plugin.zsh +7 -2
  50. git_worktrees-0.5.1/completions/gwnb.fish +0 -13
  51. git_worktrees-0.5.1/conf.d/worktrees.fish +0 -3
  52. git_worktrees-0.5.1/src/worktrees/branch_cli.py +0 -212
  53. git_worktrees-0.5.1/src/worktrees/render.py +0 -94
  54. git_worktrees-0.5.1/tests/test_render.py +0 -75
  55. git_worktrees-0.5.1/tests/test_version.py +0 -87
  56. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/.claude/settings.json +0 -0
  57. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/.gitignore +0 -0
  58. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/.pre-commit-config.yaml +0 -0
  59. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/.python-version +0 -0
  60. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/LICENSE +0 -0
  61. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/completions/gwa.fish +0 -0
  62. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/completions/gwbd.fish +0 -0
  63. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/completions/gwh.fish +0 -0
  64. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/completions/gwl.fish +0 -0
  65. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/completions/gwm.fish +0 -0
  66. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/completions/gwp.fish +0 -0
  67. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/completions/gwr.fish +0 -0
  68. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/completions/worktrees.fish +0 -0
  69. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/docs/BENCHMARK.md +0 -0
  70. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/docs/commands/branch-delete.md +0 -0
  71. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/docs/commands/branch-status.md +0 -0
  72. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/functions/gwa.fish +0 -0
  73. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/functions/gwl.fish +0 -0
  74. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/functions/gwm.fish +0 -0
  75. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/functions/gwr!.fish +0 -0
  76. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/functions/gwr.fish +0 -0
  77. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/renovate.json +0 -0
  78. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/scripts/README.md +0 -0
  79. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/scripts/benchmark.py +0 -0
  80. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/scripts/verify-publish.bash +0 -0
  81. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/src/worktrees/__init__.py +0 -0
  82. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/src/worktrees/branch_forge.py +0 -0
  83. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/src/worktrees/errors.py +0 -0
  84. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/src/worktrees/layout.py +0 -0
  85. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/src/worktrees/merged.py +0 -0
  86. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/src/worktrees/progress.py +0 -0
  87. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/src/worktrees/verdicts.py +0 -0
  88. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/tests/conftest.py +0 -0
  89. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/tests/test_benchmark.py +0 -0
  90. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/tests/test_branch_forge.py +0 -0
  91. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/tests/test_fixture.py +0 -0
  92. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/tests/test_forge.py +0 -0
  93. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/tests/test_spec.py +0 -0
  94. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/tests/test_worktree.py +0 -0
  95. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/zsh/plugins/worktrees/completions/_gwa +0 -0
  96. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/zsh/plugins/worktrees/completions/_gwbd +0 -0
  97. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/zsh/plugins/worktrees/completions/_gwh +0 -0
  98. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/zsh/plugins/worktrees/completions/_gwl +0 -0
  99. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/zsh/plugins/worktrees/completions/_gwm +0 -0
  100. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/zsh/plugins/worktrees/completions/_gwp +0 -0
  101. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/zsh/plugins/worktrees/completions/_gwr +0 -0
  102. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/zsh/plugins/worktrees/functions/gwa +0 -0
  103. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/zsh/plugins/worktrees/functions/gwl +0 -0
  104. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/zsh/plugins/worktrees/functions/gwm +0 -0
  105. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/zsh/plugins/worktrees/functions/gwr +0 -0
  106. {git_worktrees-0.5.1 → git_worktrees-0.6.1}/zsh/plugins/worktrees/functions/gwr! +0 -0
@@ -0,0 +1,48 @@
1
+ name: CodeQL
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - main
8
+ # New queries reach code nobody has pushed to.
9
+ schedule:
10
+ - cron: "17 4 * * 1"
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ concurrency:
16
+ group: ${{ github.workflow }}-${{ github.ref }}
17
+ cancel-in-progress: true
18
+
19
+ jobs:
20
+ analyze:
21
+ name: Analyze (${{ matrix.language }}, ${{ matrix.os }})
22
+ runs-on: ${{ matrix.os }}
23
+ permissions:
24
+ contents: read
25
+ security-events: write
26
+ strategy:
27
+ fail-fast: false
28
+ matrix:
29
+ os: [ubuntu-24.04, ubuntu-26.04]
30
+ language: [actions, python]
31
+ steps:
32
+ - name: Checkout code
33
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
34
+
35
+ # Neither language compiles, so build-mode none reads the source as it
36
+ # is and nothing has to be installed first.
37
+ - name: Initialize CodeQL
38
+ uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
39
+ with:
40
+ languages: ${{ matrix.language }}
41
+ build-mode: none
42
+
43
+ # The category carries the runner, so the two uploads for one language
44
+ # are two analyses rather than one replacing the other.
45
+ - name: Analyze
46
+ uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
47
+ with:
48
+ category: /language:${{ matrix.language }}/os:${{ matrix.os }}
@@ -36,7 +36,7 @@ jobs:
36
36
  # byte-for-byte what was tested and installed here.
37
37
  build:
38
38
  name: Build
39
- runs-on: ubuntu-26.04
39
+ runs-on: ubuntu-24.04
40
40
  permissions:
41
41
  contents: read
42
42
  # Reading another workflow's runs. Without it the tests guard below
@@ -127,7 +127,7 @@ jobs:
127
127
 
128
128
  publish:
129
129
  name: Publish to PyPI
130
- runs-on: ubuntu-26.04
130
+ runs-on: ubuntu-24.04
131
131
  needs: build
132
132
  # Binds this job to the trusted publisher registered for `pypi`. Add a
133
133
  # required reviewer on this environment for a manual gate before anything
@@ -163,8 +163,12 @@ jobs:
163
163
  # PyPI can take time to serve an accepted upload. Verification runs in a
164
164
  # separate job so that delay does not block the GitHub release.
165
165
  verify:
166
- name: Verify PyPI install and attestations
167
- runs-on: ubuntu-26.04
166
+ name: Verify PyPI install and attestations (${{ matrix.os }})
167
+ runs-on: ${{ matrix.os }}
168
+ strategy:
169
+ fail-fast: false
170
+ matrix:
171
+ os: [ubuntu-24.04, ubuntu-26.04]
168
172
  needs: publish
169
173
  permissions:
170
174
  contents: read
@@ -188,17 +192,32 @@ jobs:
188
192
  name: dist
189
193
  path: dist/
190
194
 
191
- # The install check waits for PyPI to serve the files.
195
+ # The install check resolves the wheel alone, so the sdist can still be
196
+ # missing from PyPI here. A file PyPI does not serve yet is asked again,
197
+ # backing off from 15s over eight attempts as the install check does;
198
+ # any other failure ends the job at once.
192
199
  - name: Verify attestations on PyPI
193
200
  run: |
194
201
  for f in dist/*.whl dist/*.tar.gz; do
195
- uvx pypi-attestations@0.0.30 verify pypi \
196
- --repository "https://github.com/$GITHUB_REPOSITORY" "pypi:${f#dist/}"
202
+ file="${f#dist/}"
203
+ delay=15
204
+ for attempt in 1 2 3 4 5 6 7 8; do
205
+ code=0
206
+ uvx pypi-attestations@0.0.30 verify pypi \
207
+ --repository "https://github.com/$GITHUB_REPOSITORY" "pypi:$file" 2>err.txt || code=$?
208
+ cat err.txt >&2
209
+ [ "$code" -ne 0 ] || break
210
+ grep -q "Could not find the artifact" err.txt || exit 1
211
+ [ "$attempt" -lt 8 ] || exit 1
212
+ echo "waiting ${delay}s for PyPI to serve $file (attempt $attempt/8)..." >&2
213
+ sleep "$delay"
214
+ delay=$((delay * 2))
215
+ done
197
216
  done
198
217
 
199
218
  release:
200
219
  name: Release
201
- runs-on: ubuntu-26.04
220
+ runs-on: ubuntu-24.04
202
221
  needs: publish
203
222
  permissions:
204
223
  contents: write
@@ -28,7 +28,7 @@ permissions:
28
28
  jobs:
29
29
  snapshot:
30
30
  name: Publish a snapshot
31
- runs-on: ubuntu-26.04
31
+ runs-on: ubuntu-24.04
32
32
  # Binds this job to the trusted publisher registered for `testpypi`.
33
33
  environment: testpypi
34
34
  permissions:
@@ -22,8 +22,12 @@ concurrency:
22
22
 
23
23
  jobs:
24
24
  lint:
25
- name: Lint
26
- runs-on: ubuntu-26.04
25
+ name: Lint (${{ matrix.os }})
26
+ runs-on: ${{ matrix.os }}
27
+ strategy:
28
+ fail-fast: false
29
+ matrix:
30
+ os: [ubuntu-24.04, ubuntu-26.04]
27
31
  steps:
28
32
  - name: Checkout code
29
33
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
@@ -33,7 +37,7 @@ jobs:
33
37
  with:
34
38
  python-version: "3.14"
35
39
  enable-cache: true
36
- cache-suffix: ${{ github.workflow }}-${{ github.job }}
40
+ cache-suffix: ${{ github.workflow }}-${{ github.job }}-${{ matrix.os }}
37
41
 
38
42
  - name: Install dependencies
39
43
  run: uv sync --all-extras --locked
@@ -44,13 +48,14 @@ jobs:
44
48
  run: uv run --no-sync pre-commit run --all-files --show-diff-on-failure
45
49
 
46
50
  test:
47
- name: Python ${{ matrix.python-version }}
48
- runs-on: ubuntu-26.04
51
+ name: Python ${{ matrix.python-version }} (${{ matrix.os }})
52
+ runs-on: ${{ matrix.os }}
49
53
  strategy:
50
54
  # One version failing is a fact about that version, so let the others
51
55
  # finish and say which.
52
56
  fail-fast: false
53
57
  matrix:
58
+ os: [ubuntu-24.04, ubuntu-26.04]
54
59
  python-version: ["3.11", "3.12", "3.13", "3.14"]
55
60
 
56
61
  steps:
@@ -67,7 +72,7 @@ jobs:
67
72
  with:
68
73
  python-version: ${{ matrix.python-version }}
69
74
  enable-cache: true
70
- cache-suffix: ${{ github.workflow }}-${{ github.job }}
75
+ cache-suffix: ${{ github.workflow }}-${{ github.job }}-${{ matrix.os }}
71
76
 
72
77
  - name: Install dependencies
73
78
  # --locked fails the run on a stale uv.lock, rather than quietly
@@ -10,7 +10,9 @@ projects:
10
10
  - path: ./
11
11
  manifest: pyproject.toml
12
12
  changelog: CHANGELOG.md
13
- bump: uv version {version}
13
+ # `uv version` for pyproject.toml and uv.lock, then the copies the shell
14
+ # plugins and the README's install lines carry.
15
+ bump: python3 scripts/bump_version.py {version}
14
16
 
15
17
  release:
16
18
  branch: main
@@ -40,7 +40,7 @@ function too, so this is the agent path as much as the human one.
40
40
 
41
41
  | | changes directory | ships as |
42
42
  | --- | --- | --- |
43
- | `gws`, `gwp`, `gwbs`, `gwbd`, `gwnb`, `gwrot`, `gwh` | no | a console script, and nothing else |
43
+ | `gws`, `gwp`, `gwbs`, `gwbd`, `gwbc`, `gwrot`, `gwh` | no | a console script, and nothing else |
44
44
  | a command that lands you somewhere | yes | a console script, plus a function of the same name |
45
45
 
46
46
  A binary cannot `cd` its caller. That is the only thing shell code is here for,
@@ -75,18 +75,18 @@ It calls the `gwr` function rather than `command gwr`, so the caller still
75
75
  lands where the binary says, and it has no console script of its own.
76
76
  `SHORTCUTS` in `tests/test_shims.py` lists them and holds each to one line.
77
77
 
78
- ## Two commands are alpha
78
+ ## Two commands start branches
79
79
 
80
- `gwnb` and `gwrot` start branches, not worktrees. `origin new-branch` and
81
- `origin rotate` do the same job, only one of the two sets survives, and
82
- nothing has decided which. So they match `origin` rather than improve on it:
83
- the name format, the stem-stripping and the remote-aware collision check are
84
- copied, and a difference between them is a decision somebody has to make
85
- later. Say so wherever they are documented.
80
+ `gwbc` and `gwrot` start branches, not worktrees, and do the job
81
+ `origin new-branch` and `origin rotate` do. The name format, the
82
+ stem-stripping and the remote-aware collision check are the same in both, so
83
+ a branch either one names is one the other would have named.
86
84
 
87
- Exit codes are the one deliberate divergence. `origin` has a single non-zero
88
- code and draws no line between a usage error and a runtime one; this CLI
89
- keeps argparse's 2 for a usage error.
85
+ Two divergences are deliberate. `origin` has a single non-zero exit code and
86
+ draws no line between a usage error and a runtime one; this CLI keeps
87
+ argparse's 2 for a usage error. And `gwrot` refuses, at exit 1, a rotation or
88
+ catch-up that would change nothing, where `origin rotate` makes a branch at
89
+ the same commit under a new name.
90
90
 
91
91
  ## Where shell code goes
92
92
 
@@ -105,6 +105,14 @@ zsh/plugins/worktrees/completions/_gwa
105
105
  Neither manager puts a binary on `$PATH`. The console scripts arrive through
106
106
  `uv tool install` from the published package, which is a separate step.
107
107
 
108
+ Both plugins export `WORKTREES_PLUGIN_VERSION`, the release they came from:
109
+ `conf.d/worktrees.fish` and `worktrees.plugin.zsh`, one assignment each.
110
+ `gwh` and `gw version` compare it with `__version__`, in Python; the shell
111
+ files hold the value and no logic. Nothing built can stamp a file a plugin
112
+ manager copies from git, so `scripts/bump_version.py` writes it at release,
113
+ along with the tag in the README's install lines, and `tests/test_version.py`
114
+ fails when a copy disagrees with `pyproject.toml`.
115
+
108
116
  Nothing installs this from a checkout on the user's disk. It is consumed as a
109
117
  third-party plugin and a third-party tool, so anything that assumes a local
110
118
  clone at a known path is wrong here.
@@ -240,12 +248,33 @@ on a terminal: both are what a script reads. The colour is applied at the
240
248
  call site that formats a table, and neither path passes a code to one.
241
249
 
242
250
  `progress.py` draws a spinner on stderr and one line saying what is being
243
- checked, while `gws`, `gwp` and `gwr` fetch and judge. It runs only when
251
+ checked, while `gws`, `gwp`, `gwr`, `gwbs` and `gwbd` fetch and judge. It runs only when
244
252
  stderr is a terminal that can clear a line, and never under `-q` or `-v`,
245
253
  since the verbose log writes to the line it redraws. Anything else printed on
246
254
  stderr while it spins goes through `progress.line`, which clears the spinner
247
255
  first.
248
256
 
257
+ Every message on stderr goes through `render.fail`, `render.warn` or
258
+ `render.note`, a usage error and a cancelled picker included. On a terminal
259
+ each leads with its symbol, `✗`, `!` or `→`, and its first line makes flags
260
+ and quoted commands bold, paths dim and shas yellow. A path is a word that
261
+ starts with `/`, `~/`, `./` or `../`, taken whole, so a directory named `-x`
262
+ is not a flag and a uuid inside one is not a sha. A failure prints every line
263
+ red; a warning or a note dims the lines after its first.
264
+
265
+ A branch name is wrapped in `render.branch` and a ref such as `origin/main` in
266
+ `render.ref` where the message is built, since no pattern tells a branch from
267
+ prose; the passes after leave those alone. A branch is bold magenta wherever
268
+ it prints: a table, the picker, a message, a result line. A prompt is
269
+ `render.ask`, the question bold and the answers it takes dim. The line that
270
+ puts a branch back is `render.restore`, painted the way a table paints a
271
+ branch and a sha, and it pastes the same. Off a terminal it is the words
272
+ alone, so a test or a log reads what the code wrote. `-v` prints each command once it has run, as
273
+ `render.trace` lays it out: the line that pastes back, what it printed,
274
+ indented, its exit code when that is not 0 or is the only answer, then a
275
+ blank line. Printing after the run keeps a command and its answer together
276
+ when eight threads are asking at once.
277
+
249
278
  ## A recent fetch, and a forge answer that cannot change, are reused
250
279
 
251
280
  The README's "What is reused, and for how long" table lists every case and
@@ -17,6 +17,77 @@ not in here, whatever it cost to build.
17
17
  A release closes with a `### Choices` section when a decision in it is worth
18
18
  the reader's time: what was chosen, and what the alternative failed to do.
19
19
 
20
+ ## 0.6.1 - 2026-11-02
21
+
22
+ ### Added
23
+
24
+ `gwh` says which release the shell plugin in this shell came from, and whether
25
+ it matches the commands. When the plugin is older, it names the tag to pin it
26
+ to, and when newer, it gives the `uv tool upgrade git-worktrees` to run.
27
+ `gw version` says the same on stderr, and its stdout is still the version
28
+ alone. Both plugins export `WORKTREES_PLUGIN_VERSION` for this. A shell whose
29
+ plugin predates this release reports none.
30
+
31
+ ### Changed
32
+
33
+ The README installs the plugins at the release's tag, `fisher install
34
+ MihaiBojin/worktrees@v0.6.1` for fish and `branch:v0.6.1` for Antidote, and
35
+ says how to move each to another release. Moving an existing install onto a
36
+ tag takes an edit to `fish_plugins` and `fisher update`, or `antidote purge
37
+ MihaiBojin/worktrees` before editing the tag.
38
+
39
+ ## 0.6.0 - 2026-11-02
40
+
41
+ ### Changed
42
+
43
+ `gwnb` is now `gwbc`, and `gw new-branch` is now `gw branch create`. Update
44
+ the fish or zsh plugin so the new name completes. `gwbc` and `gwrot` are no
45
+ longer marked alpha.
46
+
47
+ `gwrot` prints the branch it left and the one it is on now, each with its
48
+ commit, and exits 1 when a rotation or catch-up would change nothing.
49
+
50
+ On a terminal, every command colours its output the same way: errors red
51
+ behind ✗, warnings behind !, notes behind →, branches bold magenta, refs
52
+ blue, commits yellow, paths dim, and each verdict in one colour wherever it
53
+ appears. Piped or redirected output, and any run with `NO_COLOR` set, stays
54
+ plain.
55
+
56
+ `gwl`, `gws`, `gwp` and `gwr` list the main checkout first, then the other
57
+ worktrees from oldest to newest.
58
+
59
+ `-v` prints each git and `gh` command after it runs, with what it printed and
60
+ its exit code indented beneath it.
61
+
62
+ `gwp` names the worktrees it removed.
63
+
64
+ ### Added
65
+
66
+ `gws` and `gwbs` take `--only VERDICT`, repeatable, to show only the rows
67
+ marked `remove`, `keep` or `unknown`, in the table and in `--json`. The counts
68
+ under the table still cover every row. Both also take `-f` for
69
+ `--force-refresh`.
70
+
71
+ `gwrot -f` is short for `gwrot --force-refresh`.
72
+
73
+ `gwbd` shows each branch's verdict in its selector and in the list it prints
74
+ before asking, and a `--dry-run` ends by saying how many branches would go.
75
+
76
+ `gwbs` and `gwbd` show a spinner on stderr while they judge, with a line
77
+ naming the branch, the check in progress and how far through the list they
78
+ are, such as `fix-parser: was it squash-merged into main? (4 of 18)`. `gwbd`
79
+ keeps it running while it rechecks each branch before deleting it. It stays
80
+ off when stderr is not a terminal, on `TERM=dumb`, and under `-q` or `-v`.
81
+
82
+ `gwl` names each remote above the picker as host and path, such as
83
+ `origin: github.com/org/repo`, for SSH and HTTPS remotes alike and without
84
+ any user name or token the URL carries.
85
+
86
+ ### Fixed
87
+
88
+ `gwbd` says why it kept each branch it was asked to delete, rather than
89
+ exiting 3 with nothing printed.
90
+
20
91
  ## 0.5.1 - 2026-09-29
21
92
 
22
93
  ### Added
@@ -0,0 +1,9 @@
1
+ .PHONY: local-install reset
2
+
3
+ # The working copy, in place of anything published.
4
+ local-install:
5
+ uv tool install --force --from . git-worktrees
6
+
7
+ # Undoes local-install: the latest release from PyPI, and only its commands.
8
+ reset:
9
+ uv tool install --force git-worktrees