git-worktrees 0.5.1__tar.gz → 0.6.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.
Files changed (103) hide show
  1. git_worktrees-0.6.0/.github/workflows/codeql.yml +48 -0
  2. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/.github/workflows/publish.yml +9 -5
  3. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/.github/workflows/testpypi.yml +1 -1
  4. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/.github/workflows/tests.yml +11 -6
  5. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/AGENTS.md +33 -12
  6. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/CHANGELOG.md +52 -0
  7. git_worktrees-0.6.0/Makefile +9 -0
  8. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/PKG-INFO +76 -42
  9. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/README.md +74 -40
  10. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/completions/gw.fish +1 -1
  11. git_worktrees-0.6.0/completions/gwbc.fish +13 -0
  12. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/completions/gwbs.fish +2 -0
  13. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/completions/gwrot.fish +1 -1
  14. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/completions/gws.fish +2 -0
  15. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/docs/DEVELOPMENT.md +13 -12
  16. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/docs/REFERENCE.md +13 -9
  17. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/pyproject.toml +3 -3
  18. git_worktrees-0.6.0/src/worktrees/branch_cli.py +311 -0
  19. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/src/worktrees/branches.py +29 -6
  20. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/src/worktrees/cli.py +242 -119
  21. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/src/worktrees/forge.py +10 -7
  22. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/src/worktrees/git.py +17 -5
  23. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/src/worktrees/new_branch.py +8 -3
  24. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/src/worktrees/pick.py +5 -3
  25. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/src/worktrees/prune.py +16 -8
  26. git_worktrees-0.6.0/src/worktrees/render.py +259 -0
  27. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/src/worktrees/repo.py +68 -3
  28. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/src/worktrees/rotate.py +78 -16
  29. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/src/worktrees/worktree.py +11 -8
  30. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/tests/test_branches.py +34 -1
  31. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/tests/test_cli.py +147 -4
  32. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/tests/test_completions.py +2 -1
  33. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/tests/test_gates.py +1 -1
  34. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/tests/test_new_branch.py +13 -12
  35. git_worktrees-0.6.0/tests/test_render.py +211 -0
  36. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/tests/test_rotate.py +110 -10
  37. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/tests/test_shims.py +2 -2
  38. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/uv.lock +1 -1
  39. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/zsh/plugins/worktrees/completions/_gw +1 -0
  40. git_worktrees-0.5.1/zsh/plugins/worktrees/completions/_gwnb → git_worktrees-0.6.0/zsh/plugins/worktrees/completions/_gwbc +2 -2
  41. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/zsh/plugins/worktrees/completions/_gwbs +2 -0
  42. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/zsh/plugins/worktrees/completions/_gwrot +1 -1
  43. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/zsh/plugins/worktrees/completions/_gws +2 -0
  44. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/zsh/plugins/worktrees/worktrees.plugin.zsh +1 -1
  45. git_worktrees-0.5.1/completions/gwnb.fish +0 -13
  46. git_worktrees-0.5.1/src/worktrees/branch_cli.py +0 -212
  47. git_worktrees-0.5.1/src/worktrees/render.py +0 -94
  48. git_worktrees-0.5.1/tests/test_render.py +0 -75
  49. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/.claude/settings.json +0 -0
  50. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/.gitignore +0 -0
  51. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/.pre-commit-config.yaml +0 -0
  52. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/.python-version +0 -0
  53. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/.releasetools.yaml +0 -0
  54. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/LICENSE +0 -0
  55. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/completions/gwa.fish +0 -0
  56. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/completions/gwbd.fish +0 -0
  57. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/completions/gwh.fish +0 -0
  58. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/completions/gwl.fish +0 -0
  59. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/completions/gwm.fish +0 -0
  60. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/completions/gwp.fish +0 -0
  61. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/completions/gwr.fish +0 -0
  62. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/completions/worktrees.fish +0 -0
  63. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/conf.d/worktrees.fish +0 -0
  64. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/docs/BENCHMARK.md +0 -0
  65. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/docs/commands/branch-delete.md +0 -0
  66. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/docs/commands/branch-status.md +0 -0
  67. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/functions/gwa.fish +0 -0
  68. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/functions/gwl.fish +0 -0
  69. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/functions/gwm.fish +0 -0
  70. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/functions/gwr!.fish +0 -0
  71. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/functions/gwr.fish +0 -0
  72. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/hatch_build.py +0 -0
  73. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/renovate.json +0 -0
  74. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/scripts/README.md +0 -0
  75. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/scripts/benchmark.py +0 -0
  76. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/scripts/verify-publish.bash +0 -0
  77. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/src/worktrees/__init__.py +0 -0
  78. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/src/worktrees/branch_forge.py +0 -0
  79. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/src/worktrees/errors.py +0 -0
  80. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/src/worktrees/layout.py +0 -0
  81. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/src/worktrees/merged.py +0 -0
  82. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/src/worktrees/progress.py +0 -0
  83. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/src/worktrees/verdicts.py +0 -0
  84. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/tests/conftest.py +0 -0
  85. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/tests/test_benchmark.py +0 -0
  86. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/tests/test_branch_forge.py +0 -0
  87. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/tests/test_fixture.py +0 -0
  88. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/tests/test_forge.py +0 -0
  89. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/tests/test_spec.py +0 -0
  90. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/tests/test_version.py +0 -0
  91. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/tests/test_worktree.py +0 -0
  92. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/zsh/plugins/worktrees/completions/_gwa +0 -0
  93. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/zsh/plugins/worktrees/completions/_gwbd +0 -0
  94. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/zsh/plugins/worktrees/completions/_gwh +0 -0
  95. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/zsh/plugins/worktrees/completions/_gwl +0 -0
  96. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/zsh/plugins/worktrees/completions/_gwm +0 -0
  97. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/zsh/plugins/worktrees/completions/_gwp +0 -0
  98. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/zsh/plugins/worktrees/completions/_gwr +0 -0
  99. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/zsh/plugins/worktrees/functions/gwa +0 -0
  100. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/zsh/plugins/worktrees/functions/gwl +0 -0
  101. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/zsh/plugins/worktrees/functions/gwm +0 -0
  102. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/zsh/plugins/worktrees/functions/gwr +0 -0
  103. {git_worktrees-0.5.1 → git_worktrees-0.6.0}/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
@@ -198,7 +202,7 @@ jobs:
198
202
 
199
203
  release:
200
204
  name: Release
201
- runs-on: ubuntu-26.04
205
+ runs-on: ubuntu-24.04
202
206
  needs: publish
203
207
  permissions:
204
208
  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
@@ -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
 
@@ -240,12 +240,33 @@ on a terminal: both are what a script reads. The colour is applied at the
240
240
  call site that formats a table, and neither path passes a code to one.
241
241
 
242
242
  `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
243
+ checked, while `gws`, `gwp`, `gwr`, `gwbs` and `gwbd` fetch and judge. It runs only when
244
244
  stderr is a terminal that can clear a line, and never under `-q` or `-v`,
245
245
  since the verbose log writes to the line it redraws. Anything else printed on
246
246
  stderr while it spins goes through `progress.line`, which clears the spinner
247
247
  first.
248
248
 
249
+ Every message on stderr goes through `render.fail`, `render.warn` or
250
+ `render.note`, a usage error and a cancelled picker included. On a terminal
251
+ each leads with its symbol, `✗`, `!` or `→`, and its first line makes flags
252
+ and quoted commands bold, paths dim and shas yellow. A path is a word that
253
+ starts with `/`, `~/`, `./` or `../`, taken whole, so a directory named `-x`
254
+ is not a flag and a uuid inside one is not a sha. A failure prints every line
255
+ red; a warning or a note dims the lines after its first.
256
+
257
+ A branch name is wrapped in `render.branch` and a ref such as `origin/main` in
258
+ `render.ref` where the message is built, since no pattern tells a branch from
259
+ prose; the passes after leave those alone. A branch is bold magenta wherever
260
+ it prints: a table, the picker, a message, a result line. A prompt is
261
+ `render.ask`, the question bold and the answers it takes dim. The line that
262
+ puts a branch back is `render.restore`, painted the way a table paints a
263
+ branch and a sha, and it pastes the same. Off a terminal it is the words
264
+ alone, so a test or a log reads what the code wrote. `-v` prints each command once it has run, as
265
+ `render.trace` lays it out: the line that pastes back, what it printed,
266
+ indented, its exit code when that is not 0 or is the only answer, then a
267
+ blank line. Printing after the run keeps a command and its answer together
268
+ when eight threads are asking at once.
269
+
249
270
  ## A recent fetch, and a forge answer that cannot change, are reused
250
271
 
251
272
  The README's "What is reused, and for how long" table lists every case and
@@ -17,6 +17,58 @@ 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.0 - 2026-11-02
21
+
22
+ ### Changed
23
+
24
+ `gwnb` is now `gwbc`, and `gw new-branch` is now `gw branch create`. Update
25
+ the fish or zsh plugin so the new name completes. `gwbc` and `gwrot` are no
26
+ longer marked alpha.
27
+
28
+ `gwrot` prints the branch it left and the one it is on now, each with its
29
+ commit, and exits 1 when a rotation or catch-up would change nothing.
30
+
31
+ On a terminal, every command colours its output the same way: errors red
32
+ behind ✗, warnings behind !, notes behind →, branches bold magenta, refs
33
+ blue, commits yellow, paths dim, and each verdict in one colour wherever it
34
+ appears. Piped or redirected output, and any run with `NO_COLOR` set, stays
35
+ plain.
36
+
37
+ `gwl`, `gws`, `gwp` and `gwr` list the main checkout first, then the other
38
+ worktrees from oldest to newest.
39
+
40
+ `-v` prints each git and `gh` command after it runs, with what it printed and
41
+ its exit code indented beneath it.
42
+
43
+ `gwp` names the worktrees it removed.
44
+
45
+ ### Added
46
+
47
+ `gws` and `gwbs` take `--only VERDICT`, repeatable, to show only the rows
48
+ marked `remove`, `keep` or `unknown`, in the table and in `--json`. The counts
49
+ under the table still cover every row. Both also take `-f` for
50
+ `--force-refresh`.
51
+
52
+ `gwrot -f` is short for `gwrot --force-refresh`.
53
+
54
+ `gwbd` shows each branch's verdict in its selector and in the list it prints
55
+ before asking, and a `--dry-run` ends by saying how many branches would go.
56
+
57
+ `gwbs` and `gwbd` show a spinner on stderr while they judge, with a line
58
+ naming the branch, the check in progress and how far through the list they
59
+ are, such as `fix-parser: was it squash-merged into main? (4 of 18)`. `gwbd`
60
+ keeps it running while it rechecks each branch before deleting it. It stays
61
+ off when stderr is not a terminal, on `TERM=dumb`, and under `-q` or `-v`.
62
+
63
+ `gwl` names each remote above the picker as host and path, such as
64
+ `origin: github.com/org/repo`, for SSH and HTTPS remotes alike and without
65
+ any user name or token the URL carries.
66
+
67
+ ### Fixed
68
+
69
+ `gwbd` says why it kept each branch it was asked to delete, rather than
70
+ exiting 3 with nothing printed.
71
+
20
72
  ## 0.5.1 - 2026-09-29
21
73
 
22
74
  ### 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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: git-worktrees
3
- Version: 0.5.1
3
+ Version: 0.6.0
4
4
  Summary: Git worktree commands that refuse to lose work.
5
5
  Project-URL: Bug reports, https://github.com/MihaiBojin/worktrees/issues/new
6
6
  Project-URL: Source, https://github.com/MihaiBojin/worktrees
@@ -8,7 +8,7 @@ Author-email: Mihai Bojin <557584+MihaiBojin@users.noreply.github.com>
8
8
  License-Expression: Apache-2.0
9
9
  License-File: LICENSE
10
10
  Keywords: cli,git,worktree
11
- Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Development Status :: 4 - Beta
12
12
  Classifier: Intended Audience :: Developers
13
13
  Classifier: License :: OSI Approved :: Apache Software License
14
14
  Classifier: Natural Language :: English
@@ -31,7 +31,7 @@ Description-Content-Type: text/markdown
31
31
 
32
32
  Git worktree commands that refuse to lose work.
33
33
 
34
- `gws`, `gwp`, `gwbs`, `gwbd`, `gwnb`, `gwrot`, and `gwh` run as console
34
+ `gws`, `gwp`, `gwbs`, `gwbd`, `gwbc`, `gwrot`, and `gwh` run as console
35
35
  scripts. `gwa`, `gwl`, `gwm`, and `gwr` change the caller's directory and need
36
36
  a shell function of the same name.
37
37
 
@@ -46,8 +46,8 @@ a shell function of the same name.
46
46
  | `gwm NEW` | rename this worktree's branch and move the checkout to match |
47
47
  | `gwr [QUERY]` | remove a worktree whose branch is finished, and the branch |
48
48
  | `gwr! [QUERY]` | `gwr --delete-ignored`, a shell function in fish and zsh |
49
- | `gwnb NAME` | alpha. Fetch, then branch `NAME` off the head branch and check it out |
50
- | `gwrot` | alpha. Start the next branch after this one, or catch the head branch up |
49
+ | `gwbc NAME` | fetch, then branch `NAME` off the head branch and check it out |
50
+ | `gwrot` | start the next branch after this one, or catch the head branch up |
51
51
  | `gwh` | print that table, with every name each command answers to |
52
52
 
53
53
  `gw` takes the same commands as subcommands, each with shorthands, so `gwl`,
@@ -63,7 +63,7 @@ a shell function of the same name.
63
63
  | `gwl` | `gw list`, `ls`, `l` |
64
64
  | `gwm` | `gw move`, `mv`, `m` |
65
65
  | `gwr` | `gw remove`, `rm` |
66
- | `gwnb` | `gw new-branch`, `nb`, `new` |
66
+ | `gwbc` | `gw branch create` |
67
67
  | `gwrot` | `gw rotate`, `rot` |
68
68
  | `gwh` | `gw help`, `h`, and `gw` with no subcommand |
69
69
 
@@ -80,11 +80,8 @@ a worktree.
80
80
 
81
81
  `gws` and `gwp` are what this tool is for and their behaviour is settled.
82
82
 
83
- **`gwnb` and `gwrot` are alpha and may go.** They start branches rather than
84
- worktrees, which is a different job from the one this repository exists to do,
85
- and `origin new-branch` and `origin rotate` already do it. Only one of the two
86
- sets survives. Until that is decided the names, the flags and the output may
87
- change, and nothing should be built on top of them.
83
+ `gwbc` and `gwrot` start branches rather than worktrees: `gwbc` one you
84
+ name, `gwrot` the next in a series.
88
85
 
89
86
  ## Two holes in git
90
87
 
@@ -131,7 +128,7 @@ Python 3.11 or newer, and no dependencies. The standard library answers every
131
128
  question this asks, so an invocation pays for the interpreter and nothing
132
129
  else.
133
130
 
134
- That installs `gws`, `gwp`, `gwbs`, `gwbd`, `gwnb`, `gwrot`, and `gwh`. The
131
+ That installs `gws`, `gwp`, `gwbs`, `gwbd`, `gwbc`, `gwrot`, and `gwh`. The
135
132
  four that land you somewhere need a shell function too, and every command has
136
133
  a completion, both of which arrive through a plugin manager.
137
134
 
@@ -165,7 +162,7 @@ autoload -Uz compinit && compinit
165
162
  Each function runs `command <name>` and `cd`s to what it printed, provided
166
163
  that is a directory. `--json` and `--list` print what was asked for on the
167
164
  same stream a destination arrives on, so the shim asks whether what it has is
168
- somewhere to go and stays put when it is not. `gws`, `gwp`, `gwnb`, `gwrot` and `gwh`
165
+ somewhere to go and stays put when it is not. `gws`, `gwp`, `gwbc`, `gwrot` and `gwh`
169
166
  change no directory, so they ship as console scripts alone and answer the same
170
167
  from a prompt and from a script.
171
168
 
@@ -256,7 +253,7 @@ $ gwp
256
253
  squash-merged /home/you/git/.worktrees/squash-merged/repo
257
254
  restore with: git branch squash-merged a2b41ad1fb271bd7d84256f0d35ee5d58c29dd08
258
255
  remove 1 worktree(s)? [y/N] y
259
- removed 1 worktree(s)
256
+ removed 1 worktree(s): squash-merged
260
257
  ```
261
258
 
262
259
  It removes exactly the rows `gws` marks `remove`, under the same flags, and one
@@ -367,6 +364,7 @@ asks, however many there are:
367
364
 
368
365
  ```console
369
366
  $ gwl # standing in never-pushed
367
+ origin: github.com/you/repo
370
368
  # WORKTREE BRANCH PATH
371
369
  * never-pushed never-pushed /home/you/git/.worktrees/never-pushed/repo
372
370
  > 1 repo main /home/you/git/repo
@@ -375,6 +373,15 @@ $ gwl # standing in never-pushed
375
373
  which? [up/down and enter, 1-3, or esc to cancel]
376
374
  ```
377
375
 
376
+ The lines above the table name each remote and where it fetches from, as
377
+ host and path: `git@github.com:you/repo.git` and
378
+ `https://github.com/you/repo.git` are both `github.com/you/repo`, and a user,
379
+ token or port in the URL is not shown. `--list` and `--json` leave them out.
380
+
381
+ Rows run in the order the worktrees were made, the main checkout first and
382
+ the newest last. git keeps no date, so it is read off the file `git worktree
383
+ add` writes into `.git/worktrees/` and nothing changes afterwards.
384
+
378
385
  WORKTREE is the directory under `.worktrees`, or the main checkout's own
379
386
  directory name. It parts from BRANCH when somebody switches branch inside a
380
387
  worktree. Up and down move the highlight, enter takes it, a digit takes that
@@ -421,13 +428,12 @@ deleted, under `--delete-ignored` and under `--force` alike.
421
428
  to lose. It is a shell function in fish and zsh, not a console script, and it
422
429
  calls the `gwr` function so you still land where `gwr` says.
423
430
 
424
- ## gwnb and gwrot (alpha)
431
+ ## gwbc and gwrot
425
432
 
426
- Both are alpha and both start branches rather than worktrees. See the note
427
- under the command table.
433
+ Both start branches rather than worktrees.
428
434
 
429
435
  ```console
430
- $ gwnb fix-parser
436
+ $ gwbc fix-parser
431
437
  fix-parser from origin/main at 4a91c02
432
438
  ```
433
439
 
@@ -454,36 +460,56 @@ Three digits, so the sequence cannot be read as another field of the date. A
454
460
  number is free only when neither `refs/heads/` nor the remote holds it, or
455
461
  two people rotate into the same name.
456
462
 
463
+ It prints the branch it left and the one it made, each with its commit:
464
+
465
+ ```console
466
+ $ gwrot
467
+ was fix-parser 6d45e4f kept, 1 commit not on origin/main
468
+ now fix-parser-2026-09-11_001 910074d new, from origin/main
469
+ ```
470
+
471
+ A branch already at the head branch with no commits of its own is refused at
472
+ exit 1, because the next branch would be the same commit under another name.
473
+ Running `gwrot` twice without committing in between is how that happens.
474
+ `origin rotate` makes the branch anyway; this is the second place the two
475
+ differ, after exit codes.
476
+
457
477
  On the head branch there is no chain to continue, so it catches that up
458
478
  instead:
459
479
 
460
480
  ```console
461
481
  $ gwrot
462
- main is at origin/main
482
+ was main 6d45e4f
483
+ now main 910074d fast-forwarded by 3 commits to origin/main
463
484
  ```
464
485
 
486
+ A head branch already level with the remote is refused at exit 1 too, since
487
+ there is nothing to catch up.
488
+
465
489
  That is `merge --ff-only`, never a rebase: rewriting local commits on the
466
490
  head branch is the class this tooling refuses everywhere else. Commits the
467
491
  remote does not have are refused and listed, because they are a change of
468
- their own and `gwnb` puts them on a branch.
492
+ their own and `gwbc` puts them on a branch.
469
493
 
470
494
  ## gwh
471
495
 
472
496
  ```console
473
497
  $ gwh
474
- worktrees 0.2.0: git worktree commands that refuse to lose work
475
-
476
- COMMAND GW DOES
477
- gws gw status (s, st) which worktrees are finished, and why
478
- gwp gw prune (p) remove the ones status marks removable
479
- gwa NAME [BASE] gw add (a) create a worktree and land you in it
480
- gwl [QUERY] gw list (l, ls) pick a worktree and land you in it
481
- gwm NEW gw move (m, mv) rename this branch and move it
482
- gwr [QUERY] gw remove (rm) remove one whose branch is finished
483
- gwnb NAME gw new-branch (nb, new) branch off the head branch (alpha)
484
- gwrot gw rotate (rot) the next branch in a series (alpha)
485
- gwh gw help (h) every command and alias, this list
486
- — gw version (v) the version this package installs
498
+ worktrees 0.5.1: git worktree commands that refuse to lose work
499
+
500
+ COMMAND GW DOES
501
+ gws gw status (s, st) which worktrees are finished, and why
502
+ gwp gw prune (p) remove the ones status marks removable
503
+ gwa NAME [BASE] gw add (a) create a worktree and land you in it
504
+ gwl [QUERY] gw list (l, ls) pick a worktree and land you in it
505
+ gwm NEW gw move (m, mv) rename this branch and move it
506
+ gwr [QUERY] gw remove (rm) remove one whose branch is finished
507
+ gwrot gw rotate (rot) the next branch in a series
508
+ gwh gw help (h) every command and alias, this list
509
+ — gw version (v) the version this package installs
510
+ gwbc NAME gw branch create branch off the head branch
511
+ gwbs [NAME ...] gw branch status which branches are finished, and why
512
+ gwbd [NAME ...] gw branch delete delete local branches proved finished
487
513
  ```
488
514
 
489
515
  `gw help`, `gw h` and `gw` with no subcommand print the same thing. One table
@@ -499,19 +525,27 @@ print one thing each and have nothing to be quiet about, takes `--json`, `-q`,
499
525
 
500
526
  | | |
501
527
  | --- | --- |
502
- | `gws` | `--branch NAME`, `--no-fetch`, `--force-refresh`, `--delete-ignored`, `--no-forge` |
503
- | `gwp` | those five, and `-y` |
528
+ | `gws` | `--branch NAME`, `--no-fetch`, `-f`/`--force-refresh`, `--delete-ignored`, `--no-forge`, `--only VERDICT` |
529
+ | `gwp` | `--branch NAME`, `--no-fetch`, `--force-refresh`, `--delete-ignored`, `--no-forge`, and `-y` |
504
530
  | `gwr` | `-f`, `--delete-ignored`, `--no-fetch`, `--force-refresh`, `--no-forge`, `-y` |
505
- | `gwa`, `gwnb`, `gwrot` | `--no-fetch`, `--force-refresh` |
506
- | `gwbs` | `--no-fetch`, `--force-refresh`, `--no-forge` |
507
- | `gwbd` | those three, `--all`, `--dry-run` and `-y` |
531
+ | `gwa`, `gwbc` | `--no-fetch`, `--force-refresh` |
532
+ | `gwrot` | `--no-fetch`, `-f`/`--force-refresh` |
533
+ | `gwbs` | `--no-fetch`, `-f`/`--force-refresh`, `--no-forge`, `--only VERDICT` |
534
+ | `gwbd` | `--no-fetch`, `--force-refresh`, `--no-forge`, `--all`, `--dry-run` and `-y` |
508
535
  | `gwl` | `-l` |
509
536
  | `gwm`, `gwh` | none |
510
537
 
538
+ `--only` takes `remove`, `keep` or `unknown` and can be given more than
539
+ once. It narrows the table and `--json`; the counts under the table still
540
+ cover every row. `-f` is on the two status commands only, since on `gwp` and
541
+ `gwbd` it would read as forcing a removal.
542
+
511
543
  `gw <command> --help` prints one command's own list.
512
544
 
513
- While `gws`, `gwp` and `gwr` fetch and judge, a spinner on stderr names the
514
- branch and the check in progress.
545
+ While `gws`, `gwp`, `gwr`, `gwbs` and `gwbd` fetch and judge, a spinner on
546
+ stderr names the branch and the check in progress. `gwbs` and `gwbd` judge one
547
+ branch at a time, so their line also counts how far through the list they are:
548
+ `fix-parser: was it squash-merged into main? (4 of 18)`.
515
549
 
516
550
  ### What is reused, and for how long
517
551
 
@@ -521,7 +555,7 @@ is kept, and `--no-fetch` never fetches.
521
555
 
522
556
  | what | when | reused for |
523
557
  | --- | --- | --- |
524
- | a fetch, by `gws`, `gwp`, `gwr`, `gwbs`, `gwbd`, `gwa`, `gwnb`, `gwrot` | from any worktree of the repository | 10 minutes; the command says how old it is |
558
+ | a fetch, by `gws`, `gwp`, `gwr`, `gwbs`, `gwbd`, `gwa`, `gwbc`, `gwrot` | from any worktree of the repository | 10 minutes; the command says how old it is |
525
559
  | a request, merged or closed | on the commit it was asked about | until `--force-refresh` |
526
560
  | a request, open | a draft, a conflict, or on GitLab a needed rebase, with no auto-merge and no check running | 10 minutes |
527
561
  | a request, open | auto-merge or merge-when-pipeline-succeeds set | never |
@@ -562,7 +596,7 @@ squash-merged
562
596
  $ fish -c 'gws --json' | jq '.verdicts | length'
563
597
  4
564
598
 
565
- $ bash -c 'gwnb spike --json' | jq -r .base
599
+ $ bash -c 'gwbc spike --json' | jq -r .base
566
600
  refs/remotes/origin/main
567
601
  ```
568
602