git-worktrees 0.2.0__tar.gz → 0.2.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.
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/.github/workflows/publish.yml +13 -12
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/AGENTS.md +5 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/CHANGELOG.md +23 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/PKG-INFO +6 -4
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/README.md +5 -3
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/completions/gw.fish +0 -1
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/completions/gwa.fish +0 -1
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/completions/gwh.fish +0 -1
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/completions/gwl.fish +0 -1
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/completions/gwm.fish +0 -1
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/completions/gwnb.fish +0 -1
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/completions/gwp.fish +0 -1
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/completions/gwr.fish +0 -1
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/completions/gwrot.fish +0 -1
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/completions/gws.fish +0 -1
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/pyproject.toml +1 -1
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/scripts/verify-publish.bash +36 -8
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/src/worktrees/cli.py +52 -9
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/tests/test_cli.py +31 -1
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/tests/test_completions.py +1 -1
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/tests/test_version.py +5 -5
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/uv.lock +1 -1
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/zsh/plugins/worktrees/completions/_gw +0 -1
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/zsh/plugins/worktrees/completions/_gwa +0 -1
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/zsh/plugins/worktrees/completions/_gwh +0 -1
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/zsh/plugins/worktrees/completions/_gwl +0 -1
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/zsh/plugins/worktrees/completions/_gwm +0 -1
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/zsh/plugins/worktrees/completions/_gwnb +0 -1
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/zsh/plugins/worktrees/completions/_gwp +0 -1
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/zsh/plugins/worktrees/completions/_gwr +0 -1
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/zsh/plugins/worktrees/completions/_gwrot +0 -1
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/zsh/plugins/worktrees/completions/_gws +0 -1
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/.agents/skills/release/SKILL.md +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/.claude/settings.json +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/.github/workflows/tests.yml +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/.gitignore +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/.pre-commit-config.yaml +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/.python-version +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/LICENSE +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/conf.d/worktrees.fish +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/docs/DEVELOPMENT.md +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/functions/gwa.fish +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/functions/gwl.fish +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/functions/gwm.fish +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/functions/gwr.fish +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/renovate.json +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/src/worktrees/__init__.py +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/src/worktrees/forge.py +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/src/worktrees/git.py +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/src/worktrees/layout.py +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/src/worktrees/merged.py +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/src/worktrees/new_branch.py +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/src/worktrees/pick.py +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/src/worktrees/prune.py +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/src/worktrees/render.py +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/src/worktrees/repo.py +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/src/worktrees/rotate.py +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/src/worktrees/verdicts.py +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/src/worktrees/worktree.py +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/tests/conftest.py +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/tests/test_forge.py +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/tests/test_gates.py +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/tests/test_new_branch.py +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/tests/test_render.py +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/tests/test_rotate.py +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/tests/test_shims.py +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/tests/test_spec.py +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/tests/test_worktree.py +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/zsh/plugins/worktrees/functions/gwa +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/zsh/plugins/worktrees/functions/gwl +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/zsh/plugins/worktrees/functions/gwm +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/zsh/plugins/worktrees/functions/gwr +0 -0
- {git_worktrees-0.2.0 → git_worktrees-0.2.1}/zsh/plugins/worktrees/worktrees.plugin.zsh +0 -0
|
@@ -74,15 +74,19 @@ jobs:
|
|
|
74
74
|
# release that should go ahead. The API needs no local history.
|
|
75
75
|
- name: Check the commit is on main
|
|
76
76
|
env:
|
|
77
|
-
|
|
78
|
-
GH_REPO
|
|
77
|
+
# GH_REPO carries the value the runner publishes as GITHUB_REPOSITORY: gh looks for
|
|
78
|
+
# GH_REPO and never at GITHUB_REPOSITORY. GITHUB_TOKEN is not a default variable at
|
|
79
|
+
# all, so the token has to be handed over rather than renamed.
|
|
80
|
+
GH_TOKEN: ${{ github.token }} # no runner default
|
|
81
|
+
GH_REPO: ${{ github.repository }} # = $GITHUB_REPOSITORY; gh reads GH_REPO
|
|
79
82
|
run: rt github::assert_on_branch main
|
|
80
83
|
|
|
81
84
|
- name: Wait for the tests on this commit
|
|
82
85
|
env:
|
|
83
|
-
GH_TOKEN: ${{ github.token }}
|
|
84
|
-
GH_REPO: ${{ github.repository }}
|
|
85
|
-
|
|
86
|
+
GH_TOKEN: ${{ github.token }} # no runner default
|
|
87
|
+
GH_REPO: ${{ github.repository }} # = $GITHUB_REPOSITORY; gh reads GH_REPO
|
|
88
|
+
SHA: ${{ github.sha }} # = $GITHUB_SHA
|
|
89
|
+
run: rt github::await_workflow "$SHA" tests.yml
|
|
86
90
|
|
|
87
91
|
- name: Install dependencies
|
|
88
92
|
run: uv sync --all-extras --locked
|
|
@@ -168,13 +172,10 @@ jobs:
|
|
|
168
172
|
- name: Publish
|
|
169
173
|
run: uv publish --trusted-publishing always
|
|
170
174
|
|
|
171
|
-
# An index serves what it has just accepted only after a delay,
|
|
172
|
-
#
|
|
173
|
-
#
|
|
174
|
-
|
|
175
|
-
run: |
|
|
176
|
-
rt net::await_url "https://pypi.org/pypi/$(uv version | tr ' ' '/')/json"
|
|
177
|
-
|
|
175
|
+
# An index serves what it has just accepted only after a delay, so the
|
|
176
|
+
# script retries the resolution rather than polling a different endpoint
|
|
177
|
+
# first: the JSON API answered for 0.2.0 while the simple index uv reads
|
|
178
|
+
# had not caught up, and the run went red on a publish that had worked.
|
|
178
179
|
- name: Verify it installs from PyPI
|
|
179
180
|
run: scripts/verify-publish.bash --prod
|
|
180
181
|
|
|
@@ -156,6 +156,11 @@ shorthands and whether the command changes the caller's directory. `gws`,
|
|
|
156
156
|
so, and `gwh` prints the table rather than a second list that can disagree
|
|
157
157
|
with it.
|
|
158
158
|
|
|
159
|
+
`version` is the one row that installs no console script, and `gwh` prints a
|
|
160
|
+
`—` in its first column to say so. A tool's version is asked through the
|
|
161
|
+
tool's name, so `gw version` and `gw v` are the two spellings and no parser
|
|
162
|
+
carries a `--version` flag.
|
|
163
|
+
|
|
159
164
|
`_canonical()` raises at import when two rows answer to one name. A dict
|
|
160
165
|
comprehension would keep the last one and leave a command reachable under a
|
|
161
166
|
name that runs a different one.
|
|
@@ -12,6 +12,29 @@ not in here, whatever it cost to build.
|
|
|
12
12
|
A release closes with a `### Choices` section when a decision in it is worth
|
|
13
13
|
the reader's time: what was chosen, and what the alternative failed to do.
|
|
14
14
|
|
|
15
|
+
## 0.2.1 - 2026-09-11
|
|
16
|
+
|
|
17
|
+
`--version` is gone from every command. `gw version`, or `gw v`, prints the
|
|
18
|
+
version this package installs; `gws --version` and the rest now fail as an
|
|
19
|
+
unrecognised argument, so a script reading the version back has one line to
|
|
20
|
+
change.
|
|
21
|
+
|
|
22
|
+
A name `gw` does not carry names the closest one that exists, instead of
|
|
23
|
+
printing a usage line and all twenty-two commands under an argparse error:
|
|
24
|
+
|
|
25
|
+
$ gw verison
|
|
26
|
+
gw: there is no command 'verison'
|
|
27
|
+
the closest is: gw version
|
|
28
|
+
gw help lists every command
|
|
29
|
+
|
|
30
|
+
### Choices
|
|
31
|
+
|
|
32
|
+
`version` installs no binary of its own, and `gwh` prints a `—` in its
|
|
33
|
+
column to say so. A tenth console script, for a question asked once, would
|
|
34
|
+
be a name nobody types. Keeping `--version` beside it was the alternative:
|
|
35
|
+
two spellings of one answer, on every command, each to be completed,
|
|
36
|
+
documented and tested.
|
|
37
|
+
|
|
15
38
|
## 0.2.0 - 2026-09-11
|
|
16
39
|
|
|
17
40
|
A squash-merged branch goes with its checkout, every removal names the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: git-worktrees
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
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
|
|
@@ -404,6 +404,7 @@ gwr [QUERY] gw remove (rm) remove one whose branch is finished
|
|
|
404
404
|
gwnb NAME gw new-branch (nb, new) branch off the head branch (alpha)
|
|
405
405
|
gwrot gw rotate (rot) the next branch in a series (alpha)
|
|
406
406
|
gwh gw help (h) every command and alias, this list
|
|
407
|
+
— gw version (v) the version this package installs
|
|
407
408
|
```
|
|
408
409
|
|
|
409
410
|
`gw help`, `gw h` and `gw` with no subcommand print the same thing. One table
|
|
@@ -412,9 +413,10 @@ cannot disagree, and it raises at import if two commands ever claim one name.
|
|
|
412
413
|
|
|
413
414
|
## Flags
|
|
414
415
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
416
|
+
`gw version`, or `gw v`, prints the version. No command takes a `--version`
|
|
417
|
+
flag. Every command but that one and `gwh`, which
|
|
418
|
+
print one thing each and have nothing to be quiet about, takes `--json`, `-q`,
|
|
419
|
+
`-v` and `--explain`. Beyond those:
|
|
418
420
|
|
|
419
421
|
| | |
|
|
420
422
|
| --- | --- |
|
|
@@ -379,6 +379,7 @@ gwr [QUERY] gw remove (rm) remove one whose branch is finished
|
|
|
379
379
|
gwnb NAME gw new-branch (nb, new) branch off the head branch (alpha)
|
|
380
380
|
gwrot gw rotate (rot) the next branch in a series (alpha)
|
|
381
381
|
gwh gw help (h) every command and alias, this list
|
|
382
|
+
— gw version (v) the version this package installs
|
|
382
383
|
```
|
|
383
384
|
|
|
384
385
|
`gw help`, `gw h` and `gw` with no subcommand print the same thing. One table
|
|
@@ -387,9 +388,10 @@ cannot disagree, and it raises at import if two commands ever claim one name.
|
|
|
387
388
|
|
|
388
389
|
## Flags
|
|
389
390
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
391
|
+
`gw version`, or `gw v`, prints the version. No command takes a `--version`
|
|
392
|
+
flag. Every command but that one and `gwh`, which
|
|
393
|
+
print one thing each and have nothing to be quiet about, takes `--json`, `-q`,
|
|
394
|
+
`-v` and `--explain`. Beyond those:
|
|
393
395
|
|
|
394
396
|
| | |
|
|
395
397
|
| --- | --- |
|
|
@@ -19,5 +19,4 @@ complete -c gw -n 'not __fish_use_subcommand' -s q -l quiet -d 'less output'
|
|
|
19
19
|
complete -c gw -n 'not __fish_use_subcommand' -s v -l verbose -d 'print every git command'
|
|
20
20
|
complete -c gw -n 'not __fish_use_subcommand' -l explain -d 'print every git command and exit'
|
|
21
21
|
|
|
22
|
-
complete -c gw -l version -d "show program's version number and exit"
|
|
23
22
|
complete -c gw -s h -l help -d 'show this help message and exit'
|
|
@@ -9,5 +9,4 @@ complete -c gwa -s q -l quiet -d 'the path alone'
|
|
|
9
9
|
complete -c gwa -s v -l verbose -d 'print every git command'
|
|
10
10
|
complete -c gwa -l explain -d 'print every git command and exit'
|
|
11
11
|
|
|
12
|
-
complete -c gwa -l version -d 'show the version and exit'
|
|
13
12
|
complete -c gwa -s h -l help -d 'show the help and exit'
|
|
@@ -11,5 +11,4 @@ complete -c gwl -s q -l quiet -d 'the path alone'
|
|
|
11
11
|
complete -c gwl -s v -l verbose -d 'print every git command'
|
|
12
12
|
complete -c gwl -l explain -d 'print every git command and exit'
|
|
13
13
|
|
|
14
|
-
complete -c gwl -l version -d 'show the version and exit'
|
|
15
14
|
complete -c gwl -s h -l help -d 'show the help and exit'
|
|
@@ -7,5 +7,4 @@ complete -c gwm -s q -l quiet -d 'the path alone'
|
|
|
7
7
|
complete -c gwm -s v -l verbose -d 'print every git command'
|
|
8
8
|
complete -c gwm -l explain -d 'print every git command and exit'
|
|
9
9
|
|
|
10
|
-
complete -c gwm -l version -d 'show the version and exit'
|
|
11
10
|
complete -c gwm -s h -l help -d 'show the help and exit'
|
|
@@ -9,5 +9,4 @@ complete -c gwnb -s q -l quiet -d 'say nothing on success'
|
|
|
9
9
|
complete -c gwnb -s v -l verbose -d 'print every git command'
|
|
10
10
|
complete -c gwnb -l explain -d 'print every git command and exit'
|
|
11
11
|
|
|
12
|
-
complete -c gwnb -l version -d 'show the version and exit'
|
|
13
12
|
complete -c gwnb -s h -l help -d 'show the help and exit'
|
|
@@ -13,5 +13,4 @@ complete -c gwp -s q -l quiet -d 'verdicts only'
|
|
|
13
13
|
complete -c gwp -s v -l verbose -d 'print every git command'
|
|
14
14
|
complete -c gwp -l explain -d 'print every git command and exit'
|
|
15
15
|
|
|
16
|
-
complete -c gwp -l version -d 'show the version and exit'
|
|
17
16
|
complete -c gwp -s h -l help -d 'show the help and exit'
|
|
@@ -14,5 +14,4 @@ complete -c gwr -s q -l quiet -d 'the path alone'
|
|
|
14
14
|
complete -c gwr -s v -l verbose -d 'print every git command'
|
|
15
15
|
complete -c gwr -l explain -d 'print every git command and exit'
|
|
16
16
|
|
|
17
|
-
complete -c gwr -l version -d 'show the version and exit'
|
|
18
17
|
complete -c gwr -s h -l help -d 'show the help and exit'
|
|
@@ -9,5 +9,4 @@ complete -c gwrot -s q -l quiet -d 'say nothing on success'
|
|
|
9
9
|
complete -c gwrot -s v -l verbose -d 'print every git command'
|
|
10
10
|
complete -c gwrot -l explain -d 'print every git command and exit'
|
|
11
11
|
|
|
12
|
-
complete -c gwrot -l version -d 'show the version and exit'
|
|
13
12
|
complete -c gwrot -s h -l help -d 'show the help and exit'
|
|
@@ -12,5 +12,4 @@ complete -c gws -s q -l quiet -d 'verdicts only'
|
|
|
12
12
|
complete -c gws -s v -l verbose -d 'print every git command'
|
|
13
13
|
complete -c gws -l explain -d 'print every git command and exit'
|
|
14
14
|
|
|
15
|
-
complete -c gws -l version -d 'show the version and exit'
|
|
16
15
|
complete -c gws -s h -l help -d 'show the help and exit'
|
|
@@ -9,7 +9,7 @@ authors = [
|
|
|
9
9
|
]
|
|
10
10
|
# Static, and the single source of truth for the version. Bump it in a pull
|
|
11
11
|
# request and run `uv lock`; the release tag only has to agree with it.
|
|
12
|
-
version = "0.2.
|
|
12
|
+
version = "0.2.1"
|
|
13
13
|
# The standard library answers every question this asks. A dependency here
|
|
14
14
|
# would be paid on every invocation, and the whole reason this is Python is
|
|
15
15
|
# that an invocation costs ~25 ms.
|
|
@@ -34,21 +34,49 @@ echo "Verifying $NAME==$VERSION from $WHICH..." >&2
|
|
|
34
34
|
TMP="$(mktemp -d)"
|
|
35
35
|
trap 'rm -rf "$TMP"' EXIT
|
|
36
36
|
|
|
37
|
-
#
|
|
37
|
+
# An index accepts an upload before it serves it, and the delay differs between the JSON
|
|
38
|
+
# API and the simple index resolved against here: 0.2.0 answered on the former while this
|
|
39
|
+
# still could not find it. So the wait is this resolution retried rather than a different
|
|
40
|
+
# endpoint polled, because this is the condition a green publish is claiming.
|
|
41
|
+
#
|
|
42
|
+
# Six attempts backing off from 15s is about 7.75 minutes, and it stops the moment the
|
|
43
|
+
# version resolves.
|
|
44
|
+
readonly ATTEMPTS=6
|
|
45
|
+
readonly BASE=15
|
|
46
|
+
|
|
47
|
+
# The version the installed command reports, compared against the one asked
|
|
38
48
|
# of the index. Running it and discarding the output would pass while every
|
|
39
49
|
# command on the machine printed a number from a previous release.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
50
|
+
attempt=0
|
|
51
|
+
delay="$BASE"
|
|
52
|
+
while :; do
|
|
53
|
+
attempt=$((attempt + 1))
|
|
54
|
+
|
|
55
|
+
if REPORTED="$(UV_CACHE_DIR="$TMP/cache" uv tool run \
|
|
56
|
+
--isolated \
|
|
57
|
+
--index "$INDEX" \
|
|
58
|
+
--index-strategy unsafe-best-match \
|
|
59
|
+
--from "$NAME==$VERSION" \
|
|
60
|
+
gw version 2>"$TMP/resolve.err")"; then
|
|
61
|
+
break
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
if [ "$attempt" -ge "$ATTEMPTS" ]; then
|
|
65
|
+
cat "$TMP/resolve.err" >&2
|
|
66
|
+
echo "$WHICH did not serve $NAME==$VERSION after $ATTEMPTS attempts." >&2
|
|
67
|
+
exit 1
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
echo "waiting ${delay}s for $WHICH to serve $NAME==$VERSION (attempt $attempt/$ATTEMPTS)..." >&2
|
|
71
|
+
sleep "$delay"
|
|
72
|
+
delay=$((delay * 2))
|
|
73
|
+
done
|
|
46
74
|
readonly REPORTED
|
|
47
75
|
|
|
48
76
|
[ "$REPORTED" = "$VERSION" ] || {
|
|
49
77
|
echo "Version mismatch after publishing to $WHICH." >&2
|
|
50
78
|
echo " asked the index for: $VERSION" >&2
|
|
51
|
-
echo "
|
|
79
|
+
echo " gw version says: $REPORTED" >&2
|
|
52
80
|
exit 1
|
|
53
81
|
}
|
|
54
82
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
|
+
import difflib
|
|
6
7
|
import json
|
|
7
8
|
import sys
|
|
8
9
|
from collections.abc import Callable
|
|
@@ -706,6 +707,19 @@ def run_remove(args: argparse.Namespace) -> int:
|
|
|
706
707
|
# --------------------------------------------------------------------------
|
|
707
708
|
|
|
708
709
|
|
|
710
|
+
def run_version(args: argparse.Namespace) -> int:
|
|
711
|
+
"""The version of the installed distribution."""
|
|
712
|
+
print(__version__)
|
|
713
|
+
return 0
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
def _listed(names: list[str]) -> str:
|
|
717
|
+
"""`a, b and c`, and `a` on its own when there is one."""
|
|
718
|
+
if len(names) == 1:
|
|
719
|
+
return names[0]
|
|
720
|
+
return f"{', '.join(names[:-1])} and {names[-1]}"
|
|
721
|
+
|
|
722
|
+
|
|
709
723
|
def run_help(args: argparse.Namespace) -> int:
|
|
710
724
|
"""Every name this package installs, and what each one answers."""
|
|
711
725
|
print(f"worktrees {__version__}: git worktree commands that refuse to lose work")
|
|
@@ -718,7 +732,9 @@ def run_help(args: argparse.Namespace) -> int:
|
|
|
718
732
|
spelled += " (" + ", ".join(c.aliases) + ")"
|
|
719
733
|
rows.append(
|
|
720
734
|
(
|
|
721
|
-
|
|
735
|
+
# A row installing no binary has nothing for this column:
|
|
736
|
+
# `gw version` is the only way to type it.
|
|
737
|
+
Cell(f"{c.binary} {c.usage}".strip() or "\u2014", BOLD),
|
|
722
738
|
Cell(spelled),
|
|
723
739
|
Cell(c.about),
|
|
724
740
|
)
|
|
@@ -728,7 +744,7 @@ def run_help(args: argparse.Namespace) -> int:
|
|
|
728
744
|
|
|
729
745
|
lands = [c.binary for c in _COMMANDS.values() if c.lands]
|
|
730
746
|
print(
|
|
731
|
-
f"{
|
|
747
|
+
f"{_listed(lands)} change the directory you are "
|
|
732
748
|
"standing in.\nA binary cannot do that, so each needs the shell function "
|
|
733
749
|
"of the same name\nfrom the plugin; the rest need nothing but $PATH."
|
|
734
750
|
)
|
|
@@ -736,8 +752,10 @@ def run_help(args: argparse.Namespace) -> int:
|
|
|
736
752
|
# Which commands take the common flags is the table's answer, not a second
|
|
737
753
|
# list here: a row with no flags takes none, and saying so in prose is how
|
|
738
754
|
# a help text comes to promise a flag the program refuses.
|
|
739
|
-
bare = [
|
|
740
|
-
|
|
755
|
+
bare = [
|
|
756
|
+
c.binary or f"gw {name}" for name, c in _COMMANDS.items() if c.flags is None
|
|
757
|
+
]
|
|
758
|
+
scope = f"every command but {_listed(bare)}" if bare else "every command"
|
|
741
759
|
print(
|
|
742
760
|
"gwnb and gwrot start branches rather than worktrees. Both are alpha "
|
|
743
761
|
f"and may go.\n\n--json, -q, -v and --explain work on {scope}, and\n"
|
|
@@ -785,7 +803,7 @@ class _Command:
|
|
|
785
803
|
run: Callable[[argparse.Namespace], int]
|
|
786
804
|
flags: Callable[[argparse.ArgumentParser], None] | None
|
|
787
805
|
about: str
|
|
788
|
-
binary: str
|
|
806
|
+
binary: str = "" # the console script, where the command installs one
|
|
789
807
|
usage: str = ""
|
|
790
808
|
aliases: tuple[str, ...] = ()
|
|
791
809
|
lands: bool = False # changes the caller's directory, so it needs a shim
|
|
@@ -864,6 +882,15 @@ _COMMANDS: dict[str, _Command] = {
|
|
|
864
882
|
"gwh",
|
|
865
883
|
aliases=("h",),
|
|
866
884
|
),
|
|
885
|
+
# The one row with no console script. Asking a tool its version is done
|
|
886
|
+
# through the tool's name, and a tenth binary for it would be a name
|
|
887
|
+
# nobody types.
|
|
888
|
+
"version": _Command(
|
|
889
|
+
run_version,
|
|
890
|
+
None,
|
|
891
|
+
"the version this package installs",
|
|
892
|
+
aliases=("v",),
|
|
893
|
+
),
|
|
867
894
|
}
|
|
868
895
|
|
|
869
896
|
|
|
@@ -902,9 +929,7 @@ _ROTATE_HELP = (
|
|
|
902
929
|
|
|
903
930
|
|
|
904
931
|
def _parser(prog: str, description: str) -> argparse.ArgumentParser:
|
|
905
|
-
|
|
906
|
-
p.add_argument("--version", action="version", version=__version__)
|
|
907
|
-
return p
|
|
932
|
+
return argparse.ArgumentParser(prog=prog, description=description)
|
|
908
933
|
|
|
909
934
|
|
|
910
935
|
def gws(argv: list[str] | None = None) -> int:
|
|
@@ -974,6 +999,23 @@ def gw(argv: list[str] | None = None) -> int:
|
|
|
974
999
|
return main(argv, prog="gw")
|
|
975
1000
|
|
|
976
1001
|
|
|
1002
|
+
def _unknown_command(prog: str, typed: str) -> int:
|
|
1003
|
+
"""A name no row answers to, and the nearest one that does.
|
|
1004
|
+
|
|
1005
|
+
argparse spells every command twice for this, once in a usage line and
|
|
1006
|
+
once in the choices, so the name worth reading arrives last. The pool is
|
|
1007
|
+
the same table `gwh` prints, plus `--help`, which people type as a
|
|
1008
|
+
command.
|
|
1009
|
+
"""
|
|
1010
|
+
_err(f"{prog}: there is no command {typed!r}")
|
|
1011
|
+
pool = [*_CANONICAL, "--help"]
|
|
1012
|
+
near = difflib.get_close_matches(typed, pool, n=1)
|
|
1013
|
+
if near:
|
|
1014
|
+
_err(f"the closest is: {prog} {near[0]}")
|
|
1015
|
+
_err(f"{prog} help lists every command")
|
|
1016
|
+
return 2
|
|
1017
|
+
|
|
1018
|
+
|
|
977
1019
|
def main(argv: list[str] | None = None, prog: str = "worktrees") -> int:
|
|
978
1020
|
"""The CLI a shim and an agent call."""
|
|
979
1021
|
p = _parser(prog, "Git worktree commands that refuse to lose work.")
|
|
@@ -992,11 +1034,12 @@ def main(argv: list[str] | None = None, prog: str = "worktrees") -> int:
|
|
|
992
1034
|
not in (
|
|
993
1035
|
"-h",
|
|
994
1036
|
"--help",
|
|
995
|
-
"--version",
|
|
996
1037
|
)
|
|
997
1038
|
):
|
|
998
1039
|
# `worktrees --explain` is not about one command; status answers it.
|
|
999
1040
|
args_in = ["status", *args_in]
|
|
1041
|
+
if args_in and not args_in[0].startswith("-") and args_in[0] not in _CANONICAL:
|
|
1042
|
+
return _unknown_command(prog, args_in[0])
|
|
1000
1043
|
return _dispatch(p, args_in, None)
|
|
1001
1044
|
|
|
1002
1045
|
|
|
@@ -326,6 +326,13 @@ def test_prune_prints_the_reason_rather_than_naming_the_command(world) -> None:
|
|
|
326
326
|
assert "gws" not in p.stdout
|
|
327
327
|
|
|
328
328
|
|
|
329
|
+
def test_version_is_a_command_and_not_a_flag(world) -> None:
|
|
330
|
+
for entry in ("gws", "gw", "main"):
|
|
331
|
+
p = run(world, entry, "--version")
|
|
332
|
+
assert p.returncode == 2
|
|
333
|
+
assert "unrecognized arguments: --version" in p.stderr
|
|
334
|
+
|
|
335
|
+
|
|
329
336
|
def test_dry_run_is_refused_by_name(world) -> None:
|
|
330
337
|
for entry in ("gws", "gwp"):
|
|
331
338
|
p = run(world, entry, "--dry-run")
|
|
@@ -397,6 +404,27 @@ def test_a_bare_flag_means_status(world) -> None:
|
|
|
397
404
|
assert "git worktree list --porcelain -z" in p.stdout
|
|
398
405
|
|
|
399
406
|
|
|
407
|
+
def test_an_unknown_command_names_the_closest_one(world) -> None:
|
|
408
|
+
"""argparse answers this with a usage line and twenty-two choices, and
|
|
409
|
+
the name worth reading arrives last."""
|
|
410
|
+
p = run(world, "gw", "verison")
|
|
411
|
+
assert p.returncode == 2
|
|
412
|
+
assert p.stderr.splitlines() == [
|
|
413
|
+
"gw: there is no command 'verison'",
|
|
414
|
+
"the closest is: gw version",
|
|
415
|
+
"gw help lists every command",
|
|
416
|
+
]
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def test_an_unknown_command_near_nothing_still_points_at_help(world) -> None:
|
|
420
|
+
p = run(world, "main", "xyzzy")
|
|
421
|
+
assert p.returncode == 2
|
|
422
|
+
assert p.stderr.splitlines() == [
|
|
423
|
+
"worktrees: there is no command 'xyzzy'",
|
|
424
|
+
"worktrees help lists every command",
|
|
425
|
+
]
|
|
426
|
+
|
|
427
|
+
|
|
400
428
|
# --------------------------------------------------------------------------
|
|
401
429
|
# shorthands, and the table they come from
|
|
402
430
|
# --------------------------------------------------------------------------
|
|
@@ -450,10 +478,12 @@ def test_every_command_is_installed_under_its_own_name() -> None:
|
|
|
450
478
|
|
|
451
479
|
root = Path(__file__).resolve().parents[1]
|
|
452
480
|
scripts = tomllib.loads((root / "pyproject.toml").read_text())["project"]["scripts"]
|
|
453
|
-
declared = {c.binary for c in cli._COMMANDS.values()}
|
|
481
|
+
declared = {c.binary for c in cli._COMMANDS.values() if c.binary}
|
|
454
482
|
assert declared <= set(scripts)
|
|
455
483
|
# The two that take a subcommand are the only ones with no row of their own.
|
|
456
484
|
assert set(scripts) - declared == {"gw", "worktrees"}
|
|
485
|
+
# and version is the only row that installs nothing.
|
|
486
|
+
assert [n for n, c in cli._COMMANDS.items() if not c.binary] == ["version"]
|
|
457
487
|
|
|
458
488
|
|
|
459
489
|
# --------------------------------------------------------------------------
|
|
@@ -36,7 +36,7 @@ SRC = str(ROOT / "src")
|
|
|
36
36
|
FISH = ROOT / "completions"
|
|
37
37
|
ZSH = ROOT / "zsh" / "plugins" / "worktrees" / "completions"
|
|
38
38
|
|
|
39
|
-
BINARIES = sorted(c.binary for c in _COMMANDS.values())
|
|
39
|
+
BINARIES = sorted(c.binary for c in _COMMANDS.values() if c.binary)
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
def _help(entry: str) -> str:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""The version is written once, and
|
|
1
|
+
"""The version is written once, and `gw version` reads that one."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -36,18 +36,18 @@ def test_version_matches_the_distribution() -> None:
|
|
|
36
36
|
assert __version__ == declared
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
def
|
|
40
|
-
"""Driven, because
|
|
39
|
+
def test_both_names_that_take_a_subcommand_report_it() -> None:
|
|
40
|
+
"""Driven, because the console script is what a person runs."""
|
|
41
41
|
declared = tomllib.loads((ROOT / "pyproject.toml").read_text())["project"][
|
|
42
42
|
"version"
|
|
43
43
|
]
|
|
44
|
-
for entry in ("
|
|
44
|
+
for entry in ("gw", "main"):
|
|
45
45
|
proc = subprocess.run(
|
|
46
46
|
[
|
|
47
47
|
sys.executable,
|
|
48
48
|
"-c",
|
|
49
49
|
f"from worktrees.cli import {entry}; raise SystemExit({entry}())",
|
|
50
|
-
"
|
|
50
|
+
"version",
|
|
51
51
|
],
|
|
52
52
|
capture_output=True,
|
|
53
53
|
text=True,
|
|
@@ -8,5 +8,4 @@ _arguments -s \
|
|
|
8
8
|
'(-q --quiet)'{-q,--quiet}'[the path alone]' \
|
|
9
9
|
'(-v --verbose)'{-v,--verbose}'[print every git command]' \
|
|
10
10
|
'--explain[print every git command and exit]' \
|
|
11
|
-
'--version[show the version and exit]' \
|
|
12
11
|
'(-h --help)'{-h,--help}'[show the help and exit]'
|
|
@@ -21,6 +21,5 @@ _arguments -s \
|
|
|
21
21
|
'(-q --quiet)'{-q,--quiet}'[the path alone]' \
|
|
22
22
|
'(-v --verbose)'{-v,--verbose}'[print every git command]' \
|
|
23
23
|
'--explain[print every git command and exit]' \
|
|
24
|
-
'--version[show the version and exit]' \
|
|
25
24
|
'(-h --help)'{-h,--help}'[show the help and exit]' \
|
|
26
25
|
'1:worktree:__worktrees_names'
|
|
@@ -7,5 +7,4 @@ _arguments -s \
|
|
|
7
7
|
'(-q --quiet)'{-q,--quiet}'[the path alone]' \
|
|
8
8
|
'(-v --verbose)'{-v,--verbose}'[print every git command]' \
|
|
9
9
|
'--explain[print every git command and exit]' \
|
|
10
|
-
'--version[show the version and exit]' \
|
|
11
10
|
'(-h --help)'{-h,--help}'[show the help and exit]'
|
|
@@ -8,5 +8,4 @@ _arguments -s \
|
|
|
8
8
|
'(-q --quiet)'{-q,--quiet}'[say nothing on success]' \
|
|
9
9
|
'(-v --verbose)'{-v,--verbose}'[print every git command]' \
|
|
10
10
|
'--explain[print every git command and exit]' \
|
|
11
|
-
'--version[show the version and exit]' \
|
|
12
11
|
'(-h --help)'{-h,--help}'[show the help and exit]'
|
|
@@ -12,5 +12,4 @@ _arguments -s \
|
|
|
12
12
|
'(-q --quiet)'{-q,--quiet}'[verdicts only]' \
|
|
13
13
|
'(-v --verbose)'{-v,--verbose}'[print every git command]' \
|
|
14
14
|
'--explain[print every git command and exit]' \
|
|
15
|
-
'--version[show the version and exit]' \
|
|
16
15
|
'(-h --help)'{-h,--help}'[show the help and exit]'
|
|
@@ -24,6 +24,5 @@ _arguments -s \
|
|
|
24
24
|
'(-q --quiet)'{-q,--quiet}'[the path alone]' \
|
|
25
25
|
'(-v --verbose)'{-v,--verbose}'[print every git command]' \
|
|
26
26
|
'--explain[print every git command and exit]' \
|
|
27
|
-
'--version[show the version and exit]' \
|
|
28
27
|
'(-h --help)'{-h,--help}'[show the help and exit]' \
|
|
29
28
|
'1:worktree:__gwr_targets'
|
|
@@ -8,5 +8,4 @@ _arguments -s \
|
|
|
8
8
|
'(-q --quiet)'{-q,--quiet}'[say nothing on success]' \
|
|
9
9
|
'(-v --verbose)'{-v,--verbose}'[print every git command]' \
|
|
10
10
|
'--explain[print every git command and exit]' \
|
|
11
|
-
'--version[show the version and exit]' \
|
|
12
11
|
'(-h --help)'{-h,--help}'[show the help and exit]'
|
|
@@ -11,5 +11,4 @@ _arguments -s \
|
|
|
11
11
|
'(-q --quiet)'{-q,--quiet}'[verdicts only]' \
|
|
12
12
|
'(-v --verbose)'{-v,--verbose}'[print every git command]' \
|
|
13
13
|
'--explain[print every git command and exit]' \
|
|
14
|
-
'--version[show the version and exit]' \
|
|
15
14
|
'(-h --help)'{-h,--help}'[show the help and exit]'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|