git-worktrees 0.5.0__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.
- git_worktrees-0.6.0/.github/workflows/codeql.yml +48 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/.github/workflows/publish.yml +31 -14
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/.github/workflows/testpypi.yml +6 -1
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/.github/workflows/tests.yml +11 -6
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/AGENTS.md +33 -12
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/CHANGELOG.md +62 -0
- git_worktrees-0.6.0/Makefile +9 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/PKG-INFO +76 -42
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/README.md +74 -40
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/completions/gw.fish +1 -1
- git_worktrees-0.6.0/completions/gwbc.fish +13 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/completions/gwbs.fish +2 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/completions/gwrot.fish +1 -1
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/completions/gws.fish +2 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/docs/DEVELOPMENT.md +26 -19
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/docs/REFERENCE.md +13 -9
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/pyproject.toml +3 -3
- git_worktrees-0.6.0/src/worktrees/branch_cli.py +311 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/src/worktrees/branches.py +29 -6
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/src/worktrees/cli.py +242 -119
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/src/worktrees/forge.py +10 -7
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/src/worktrees/git.py +17 -5
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/src/worktrees/new_branch.py +8 -3
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/src/worktrees/pick.py +5 -3
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/src/worktrees/prune.py +16 -8
- git_worktrees-0.6.0/src/worktrees/render.py +259 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/src/worktrees/repo.py +68 -3
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/src/worktrees/rotate.py +78 -16
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/src/worktrees/worktree.py +11 -8
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/tests/test_branches.py +34 -1
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/tests/test_cli.py +147 -4
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/tests/test_completions.py +2 -1
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/tests/test_gates.py +1 -1
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/tests/test_new_branch.py +13 -12
- git_worktrees-0.6.0/tests/test_render.py +211 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/tests/test_rotate.py +110 -10
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/tests/test_shims.py +2 -2
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/uv.lock +1 -1
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/zsh/plugins/worktrees/completions/_gw +1 -0
- git_worktrees-0.5.0/zsh/plugins/worktrees/completions/_gwnb → git_worktrees-0.6.0/zsh/plugins/worktrees/completions/_gwbc +2 -2
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/zsh/plugins/worktrees/completions/_gwbs +2 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/zsh/plugins/worktrees/completions/_gwrot +1 -1
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/zsh/plugins/worktrees/completions/_gws +2 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/zsh/plugins/worktrees/worktrees.plugin.zsh +1 -1
- git_worktrees-0.5.0/completions/gwnb.fish +0 -13
- git_worktrees-0.5.0/src/worktrees/branch_cli.py +0 -212
- git_worktrees-0.5.0/src/worktrees/render.py +0 -94
- git_worktrees-0.5.0/tests/test_render.py +0 -75
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/.claude/settings.json +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/.gitignore +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/.pre-commit-config.yaml +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/.python-version +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/.releasetools.yaml +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/LICENSE +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/completions/gwa.fish +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/completions/gwbd.fish +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/completions/gwh.fish +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/completions/gwl.fish +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/completions/gwm.fish +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/completions/gwp.fish +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/completions/gwr.fish +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/completions/worktrees.fish +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/conf.d/worktrees.fish +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/docs/BENCHMARK.md +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/docs/commands/branch-delete.md +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/docs/commands/branch-status.md +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/functions/gwa.fish +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/functions/gwl.fish +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/functions/gwm.fish +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/functions/gwr!.fish +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/functions/gwr.fish +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/hatch_build.py +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/renovate.json +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/scripts/README.md +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/scripts/benchmark.py +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/scripts/verify-publish.bash +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/src/worktrees/__init__.py +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/src/worktrees/branch_forge.py +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/src/worktrees/errors.py +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/src/worktrees/layout.py +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/src/worktrees/merged.py +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/src/worktrees/progress.py +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/src/worktrees/verdicts.py +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/tests/conftest.py +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/tests/test_benchmark.py +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/tests/test_branch_forge.py +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/tests/test_fixture.py +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/tests/test_forge.py +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/tests/test_spec.py +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/tests/test_version.py +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/tests/test_worktree.py +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/zsh/plugins/worktrees/completions/_gwa +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/zsh/plugins/worktrees/completions/_gwbd +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/zsh/plugins/worktrees/completions/_gwh +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/zsh/plugins/worktrees/completions/_gwl +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/zsh/plugins/worktrees/completions/_gwm +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/zsh/plugins/worktrees/completions/_gwp +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/zsh/plugins/worktrees/completions/_gwr +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/zsh/plugins/worktrees/functions/gwa +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/zsh/plugins/worktrees/functions/gwl +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/zsh/plugins/worktrees/functions/gwm +0 -0
- {git_worktrees-0.5.0 → git_worktrees-0.6.0}/zsh/plugins/worktrees/functions/gwr +0 -0
- {git_worktrees-0.5.0 → 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-
|
|
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-
|
|
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
|
|
@@ -152,25 +152,27 @@ jobs:
|
|
|
152
152
|
name: dist
|
|
153
153
|
path: dist/
|
|
154
154
|
|
|
155
|
+
# Sign with this job's OIDC identity. uv uploads the attestations with
|
|
156
|
+
# their distributions.
|
|
157
|
+
- name: Attest
|
|
158
|
+
run: uvx pypi-attestations@0.0.30 sign dist/*
|
|
159
|
+
|
|
155
160
|
- name: Publish
|
|
156
161
|
run: uv publish --trusted-publishing always
|
|
157
162
|
|
|
158
|
-
#
|
|
159
|
-
#
|
|
160
|
-
# choosing: 0.2.1 took longer than the script waited, and a release that had
|
|
161
|
-
# published correctly went red and had to be re-run by hand. This job holds
|
|
162
|
-
# the second claim, `release` needs only the first, and a red `verify` says
|
|
163
|
-
# PyPI is slow rather than that the version is not out.
|
|
163
|
+
# PyPI can take time to serve an accepted upload. Verification runs in a
|
|
164
|
+
# separate job so that delay does not block the GitHub release.
|
|
164
165
|
verify:
|
|
165
|
-
name: Verify
|
|
166
|
-
runs-on:
|
|
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]
|
|
167
172
|
needs: publish
|
|
168
173
|
permissions:
|
|
169
174
|
contents: read
|
|
170
175
|
steps:
|
|
171
|
-
# The script reads the name and version from pyproject.toml through
|
|
172
|
-
# `uv version`, and installs from the index rather than from the
|
|
173
|
-
# artifact, so there is nothing to download here.
|
|
174
176
|
- name: Checkout code
|
|
175
177
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
176
178
|
|
|
@@ -183,9 +185,24 @@ jobs:
|
|
|
183
185
|
- name: Verify it installs from PyPI
|
|
184
186
|
run: scripts/verify-publish.bash --prod
|
|
185
187
|
|
|
188
|
+
# The filenames identify the distributions to fetch from PyPI.
|
|
189
|
+
- name: Download distributions
|
|
190
|
+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
191
|
+
with:
|
|
192
|
+
name: dist
|
|
193
|
+
path: dist/
|
|
194
|
+
|
|
195
|
+
# The install check waits for PyPI to serve the files.
|
|
196
|
+
- name: Verify attestations on PyPI
|
|
197
|
+
run: |
|
|
198
|
+
for f in dist/*.whl dist/*.tar.gz; do
|
|
199
|
+
uvx pypi-attestations@0.0.30 verify pypi \
|
|
200
|
+
--repository "https://github.com/$GITHUB_REPOSITORY" "pypi:${f#dist/}"
|
|
201
|
+
done
|
|
202
|
+
|
|
186
203
|
release:
|
|
187
204
|
name: Release
|
|
188
|
-
runs-on: ubuntu-
|
|
205
|
+
runs-on: ubuntu-24.04
|
|
189
206
|
needs: publish
|
|
190
207
|
permissions:
|
|
191
208
|
contents: write
|
|
@@ -28,7 +28,7 @@ permissions:
|
|
|
28
28
|
jobs:
|
|
29
29
|
snapshot:
|
|
30
30
|
name: Publish a snapshot
|
|
31
|
-
runs-on: ubuntu-
|
|
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:
|
|
@@ -59,6 +59,11 @@ jobs:
|
|
|
59
59
|
- name: Build
|
|
60
60
|
run: uv build
|
|
61
61
|
|
|
62
|
+
# Sign with this job's OIDC identity. uv uploads the attestations with
|
|
63
|
+
# their distributions.
|
|
64
|
+
- name: Attest
|
|
65
|
+
run: uvx pypi-attestations@0.0.30 sign dist/*
|
|
66
|
+
|
|
62
67
|
- name: Publish
|
|
63
68
|
# `always` rather than the default `automatic`: fail loudly if OIDC is
|
|
64
69
|
# unavailable instead of quietly looking for a token.
|
|
@@ -22,8 +22,12 @@ concurrency:
|
|
|
22
22
|
|
|
23
23
|
jobs:
|
|
24
24
|
lint:
|
|
25
|
-
name: Lint
|
|
26
|
-
runs-on:
|
|
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:
|
|
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`, `
|
|
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
|
|
78
|
+
## Two commands start branches
|
|
79
79
|
|
|
80
|
-
`
|
|
81
|
-
`origin rotate` do
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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 `
|
|
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,68 @@ 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
|
+
|
|
72
|
+
## 0.5.1 - 2026-09-29
|
|
73
|
+
|
|
74
|
+
### Added
|
|
75
|
+
|
|
76
|
+
The wheel and source distribution on PyPI carry PEP 740 attestations signed
|
|
77
|
+
by the GitHub workflow that built them. PyPI shows the provenance on the
|
|
78
|
+
release's page, and `uvx pypi-attestations verify pypi --repository
|
|
79
|
+
https://github.com/MihaiBojin/worktrees pypi:<file>` checks a file against
|
|
80
|
+
this repository before you install it.
|
|
81
|
+
|
|
20
82
|
## 0.5.0 - 2026-09-29
|
|
21
83
|
|
|
22
84
|
### 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.
|
|
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 ::
|
|
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`, `
|
|
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
|
-
| `
|
|
50
|
-
| `gwrot` |
|
|
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
|
-
| `
|
|
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
|
-
|
|
84
|
-
|
|
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`, `
|
|
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`, `
|
|
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
|
-
##
|
|
431
|
+
## gwbc and gwrot
|
|
425
432
|
|
|
426
|
-
Both
|
|
427
|
-
under the command table.
|
|
433
|
+
Both start branches rather than worktrees.
|
|
428
434
|
|
|
429
435
|
```console
|
|
430
|
-
$
|
|
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
|
|
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 `
|
|
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.
|
|
475
|
-
|
|
476
|
-
COMMAND GW
|
|
477
|
-
gws gw status (s, st)
|
|
478
|
-
gwp gw prune (p)
|
|
479
|
-
gwa NAME [BASE] gw add (a)
|
|
480
|
-
gwl [QUERY] gw list (l, ls)
|
|
481
|
-
gwm NEW gw move (m, mv)
|
|
482
|
-
gwr [QUERY] gw remove (rm)
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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`,
|
|
503
|
-
| `gwp` |
|
|
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`, `
|
|
506
|
-
| `
|
|
507
|
-
| `
|
|
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 `
|
|
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`, `
|
|
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 '
|
|
599
|
+
$ bash -c 'gwbc spike --json' | jq -r .base
|
|
566
600
|
refs/remotes/origin/main
|
|
567
601
|
```
|
|
568
602
|
|