git-worktrees 0.1.0__tar.gz → 0.2.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.1.0 → git_worktrees-0.2.0}/.github/workflows/publish.yml +2 -1
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/AGENTS.md +62 -11
- git_worktrees-0.2.0/CHANGELOG.md +172 -0
- git_worktrees-0.2.0/PKG-INFO +485 -0
- git_worktrees-0.2.0/README.md +460 -0
- git_worktrees-0.2.0/completions/gw.fish +23 -0
- git_worktrees-0.2.0/completions/gwa.fish +13 -0
- git_worktrees-0.2.0/completions/gwh.fish +6 -0
- git_worktrees-0.2.0/completions/gwl.fish +15 -0
- git_worktrees-0.2.0/completions/gwm.fish +11 -0
- git_worktrees-0.2.0/completions/gwnb.fish +13 -0
- git_worktrees-0.2.0/completions/gwp.fish +17 -0
- git_worktrees-0.2.0/completions/gwr.fish +18 -0
- git_worktrees-0.2.0/completions/gwrot.fish +13 -0
- git_worktrees-0.2.0/completions/gws.fish +16 -0
- git_worktrees-0.2.0/conf.d/worktrees.fish +3 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/docs/DEVELOPMENT.md +9 -7
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/pyproject.toml +3 -2
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/src/worktrees/cli.py +376 -87
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/src/worktrees/git.py +22 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/src/worktrees/pick.py +6 -1
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/src/worktrees/prune.py +20 -15
- git_worktrees-0.2.0/src/worktrees/render.py +88 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/src/worktrees/repo.py +13 -2
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/src/worktrees/verdicts.py +12 -2
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/src/worktrees/worktree.py +36 -7
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/tests/test_cli.py +230 -9
- git_worktrees-0.2.0/tests/test_completions.py +189 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/tests/test_gates.py +62 -1
- git_worktrees-0.2.0/tests/test_render.py +75 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/tests/test_worktree.py +139 -2
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/uv.lock +1 -1
- git_worktrees-0.2.0/zsh/plugins/worktrees/completions/_gw +54 -0
- git_worktrees-0.2.0/zsh/plugins/worktrees/completions/_gwa +12 -0
- git_worktrees-0.2.0/zsh/plugins/worktrees/completions/_gwh +7 -0
- git_worktrees-0.2.0/zsh/plugins/worktrees/completions/_gwl +26 -0
- git_worktrees-0.2.0/zsh/plugins/worktrees/completions/_gwm +11 -0
- git_worktrees-0.2.0/zsh/plugins/worktrees/completions/_gwnb +12 -0
- git_worktrees-0.2.0/zsh/plugins/worktrees/completions/_gwp +16 -0
- git_worktrees-0.2.0/zsh/plugins/worktrees/completions/_gwr +29 -0
- git_worktrees-0.2.0/zsh/plugins/worktrees/completions/_gwrot +12 -0
- git_worktrees-0.2.0/zsh/plugins/worktrees/completions/_gws +15 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/zsh/plugins/worktrees/worktrees.plugin.zsh +1 -1
- git_worktrees-0.1.0/CHANGELOG.md +0 -70
- git_worktrees-0.1.0/PKG-INFO +0 -335
- git_worktrees-0.1.0/README.md +0 -310
- git_worktrees-0.1.0/conf.d/worktrees.fish +0 -2
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/.agents/skills/release/SKILL.md +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/.claude/settings.json +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/.github/workflows/tests.yml +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/.gitignore +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/.pre-commit-config.yaml +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/.python-version +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/LICENSE +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/functions/gwa.fish +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/functions/gwl.fish +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/functions/gwm.fish +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/functions/gwr.fish +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/renovate.json +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/scripts/verify-publish.bash +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/src/worktrees/__init__.py +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/src/worktrees/forge.py +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/src/worktrees/layout.py +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/src/worktrees/merged.py +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/src/worktrees/new_branch.py +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/src/worktrees/rotate.py +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/tests/conftest.py +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/tests/test_forge.py +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/tests/test_new_branch.py +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/tests/test_rotate.py +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/tests/test_shims.py +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/tests/test_spec.py +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/tests/test_version.py +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/zsh/plugins/worktrees/functions/gwa +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/zsh/plugins/worktrees/functions/gwl +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/zsh/plugins/worktrees/functions/gwm +0 -0
- {git_worktrees-0.1.0 → git_worktrees-0.2.0}/zsh/plugins/worktrees/functions/gwr +0 -0
|
@@ -14,7 +14,8 @@ name: Publish
|
|
|
14
14
|
# or replaced, so if the rehearsal fails there is nothing to pin against and
|
|
15
15
|
# the run stops there.
|
|
16
16
|
#
|
|
17
|
-
# No API token is stored anywhere. See
|
|
17
|
+
# No API token is stored anywhere. See docs/DEVELOPMENT.md, "Publishing a
|
|
18
|
+
# version".
|
|
18
19
|
|
|
19
20
|
on:
|
|
20
21
|
push:
|
|
@@ -23,7 +23,7 @@ call `command <name>` and `cd` to what that prints.**
|
|
|
23
23
|
|
|
24
24
|
| | changes directory | ships as |
|
|
25
25
|
| --- | --- | --- |
|
|
26
|
-
| `gws`, `gwp`, `gwnb`, `gwrot` | no | a console script, and nothing else |
|
|
26
|
+
| `gws`, `gwp`, `gwnb`, `gwrot`, `gwh` | no | a console script, and nothing else |
|
|
27
27
|
| a command that lands you somewhere | yes | a console script, plus a function of the same name |
|
|
28
28
|
|
|
29
29
|
A binary cannot `cd` its caller. That is the only thing shell code is here for,
|
|
@@ -85,9 +85,16 @@ Nothing installs this from a checkout on the user's disk. It is consumed as a
|
|
|
85
85
|
third-party plugin and a third-party tool, so anything that assumes a local
|
|
86
86
|
clone at a known path is wrong here.
|
|
87
87
|
|
|
88
|
-
A completion asks the CLI for its candidates rather than deriving them
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
A completion asks the CLI for its candidates rather than deriving them, and
|
|
89
|
+
`--complete` is how: `gw --complete` prints every subcommand and shorthand,
|
|
90
|
+
`gwl --complete` prints every worktree, both as name and description separated
|
|
91
|
+
by a tab, which fish reads directly and zsh splits for `_describe`. Hidden from
|
|
92
|
+
`--help`, because a person has `gwh` and `--list` for the same answers.
|
|
93
|
+
|
|
94
|
+
No shell file holds a candidate or a rank. What each file does carry is its own
|
|
95
|
+
command's flags, and `tests/test_completions.py` diffs those against `--help`
|
|
96
|
+
in both directions for both shells: a renamed flag fails there rather than
|
|
97
|
+
going quiet in a completion nobody is watching.
|
|
91
98
|
|
|
92
99
|
## The picker is Python's, and there is no fzf
|
|
93
100
|
|
|
@@ -101,9 +108,9 @@ number of linked worktrees in one is four. A fuzzy finder is the wrong
|
|
|
101
108
|
instrument at that size, and a numbered prompt reads faster:
|
|
102
109
|
|
|
103
110
|
```
|
|
104
|
-
1 fix-parser
|
|
105
|
-
2 add-tests
|
|
106
|
-
|
|
111
|
+
1 fix-parser /home/you/git/.worktrees/fix-parser/repo
|
|
112
|
+
2 add-tests /home/you/git/.worktrees/add-tests/repo
|
|
113
|
+
which? [1-2, or blank to cancel]
|
|
107
114
|
```
|
|
108
115
|
|
|
109
116
|
Dropping `fzf` is less code, not more. It removes the spawn, the tty rules
|
|
@@ -123,12 +130,57 @@ tightly they cluster. About twenty lines, `difflib` not required.
|
|
|
123
130
|
Two rules, both the shape `gwp`'s prompt already has:
|
|
124
131
|
|
|
125
132
|
- One match takes it outright, with no prompt at all.
|
|
126
|
-
- No terminal, no prompt. Refuse at exit
|
|
133
|
+
- No terminal, no prompt. Refuse at exit 3 and name the flag that answers
|
|
127
134
|
without one, rather than blocking on something nothing can drive.
|
|
128
135
|
|
|
136
|
+
Two more about what is in the list at all:
|
|
137
|
+
|
|
138
|
+
- The main checkout is a destination. It is the one that is always there and
|
|
139
|
+
the one a finished branch leaves you in, so leaving it out leaves out the
|
|
140
|
+
only answer that is always right. `main` finds it whatever branch it stands
|
|
141
|
+
on, because its label carries the word, once.
|
|
142
|
+
- The worktree you are standing in is listed and never offered. Picking it is
|
|
143
|
+
the one answer that takes you nowhere, and a picker whose single candidate
|
|
144
|
+
is where you already are prints nothing at all, which reads as a broken
|
|
145
|
+
command.
|
|
146
|
+
|
|
129
147
|
`--json` and `--list` answer the same question without any of this, and an
|
|
130
148
|
agent uses those.
|
|
131
149
|
|
|
150
|
+
## One row per command, under every name it answers to
|
|
151
|
+
|
|
152
|
+
`_COMMANDS` in `cli.py` is the table. A row carries the function, the flags,
|
|
153
|
+
the one-line description, the console script, the argument shape, the
|
|
154
|
+
shorthands and whether the command changes the caller's directory. `gws`,
|
|
155
|
+
`gw status`, `gw st` and `gw s` reach the same function because that row says
|
|
156
|
+
so, and `gwh` prints the table rather than a second list that can disagree
|
|
157
|
+
with it.
|
|
158
|
+
|
|
159
|
+
`_canonical()` raises at import when two rows answer to one name. A dict
|
|
160
|
+
comprehension would keep the last one and leave a command reachable under a
|
|
161
|
+
name that runs a different one.
|
|
162
|
+
|
|
163
|
+
A shorthand is for typing, so it is short and it is not clever: the first
|
|
164
|
+
letters of the word, or the two-letter name the same job has in git and in a
|
|
165
|
+
shell. `rm` and `mv` mean what they mean everywhere else.
|
|
166
|
+
|
|
167
|
+
## Colour is a property of the stream, not of the program
|
|
168
|
+
|
|
169
|
+
`render.py` holds both halves of it: the codes, and a table whose columns line
|
|
170
|
+
up. `render.setup()` runs once per invocation and decides per stream.
|
|
171
|
+
|
|
172
|
+
Padding runs on the text and painting after it. The other order counts an
|
|
173
|
+
escape sequence as width, and every column under a coloured cell sits crooked
|
|
174
|
+
by exactly the length of the code.
|
|
175
|
+
|
|
176
|
+
stdout carries the path `cd $(gwa x)` reads and the JSON `jq` parses, so a
|
|
177
|
+
redirect, a pipe, a non-empty `NO_COLOR`, or `TERM=dumb` mean the bytes go out
|
|
178
|
+
as they would have without the module. `NO_COLOR=` is not a request to turn it
|
|
179
|
+
off: that is the no-color.org rule, and it is why `supported` tests the value
|
|
180
|
+
and not the key. `--json` is never painted at all:
|
|
181
|
+
the colour is applied at the call site that formats a table, and the JSON
|
|
182
|
+
paths do not pass through one.
|
|
183
|
+
|
|
132
184
|
## Every git command is one spec
|
|
133
185
|
|
|
134
186
|
The spec is the command as you would type it, with `$name` where a value goes.
|
|
@@ -186,9 +238,8 @@ no call site can assemble its way past it. `reset --hard`, a forced `checkout`
|
|
|
186
238
|
or `switch`, `clean -f`, a bare `push --force`, `worktree remove --force` and
|
|
187
239
|
`branch -D` are refused absolutely. There is no flag, and `--yes` least of all.
|
|
188
240
|
|
|
189
|
-
`verdicts.py` declares no mutating command.
|
|
190
|
-
|
|
191
|
-
asserts it.
|
|
241
|
+
`verdicts.py` declares no mutating command. A read-only command may not call
|
|
242
|
+
one: a test reads the verbose log and asserts it.
|
|
192
243
|
|
|
193
244
|
## Content cannot settle a stacked branch
|
|
194
245
|
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Newest first. Each entry says what changed for somebody running these
|
|
4
|
+
commands, and the choices behind it. Dates are ISO 8601, versions follow
|
|
5
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html), and the shape
|
|
6
|
+
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
7
|
+
|
|
8
|
+
An entry earns its place by being observable. If running a command produces
|
|
9
|
+
no different result, no different output and no different exit code, it is
|
|
10
|
+
not in here, whatever it cost to build.
|
|
11
|
+
|
|
12
|
+
A release closes with a `### Choices` section when a decision in it is worth
|
|
13
|
+
the reader's time: what was chosen, and what the alternative failed to do.
|
|
14
|
+
|
|
15
|
+
## 0.2.0 - 2026-09-11
|
|
16
|
+
|
|
17
|
+
A squash-merged branch goes with its checkout, every removal names the
|
|
18
|
+
ignored files it deletes, `gwl` finds every worktree and `gwp` says why it
|
|
19
|
+
removed nothing. Every command is also reachable by a short name, output takes
|
|
20
|
+
colour, and both shells complete every command.
|
|
21
|
+
|
|
22
|
+
A branch the probe proved squash-merged is deleted with its worktree. It used
|
|
23
|
+
to survive: the verdict came from comparing content, and the delete then went
|
|
24
|
+
through `git branch -d`, which reads history and refuses exactly that case, so
|
|
25
|
+
`gwp` removed the checkout and left the branch behind with git's refusal
|
|
26
|
+
printed under it. The delete is now `git update-ref -d <ref> <sha>` against the
|
|
27
|
+
sha the verdict was formed on, so a commit landing between the verdict and the
|
|
28
|
+
removal fails it rather than going with it. `git branch -D` stays refused, and
|
|
29
|
+
so does `update-ref -d` with no sha, which is the same thing spelled longer.
|
|
30
|
+
|
|
31
|
+
Every removal names the ignored files it is about to delete, not just the ones
|
|
32
|
+
under `--delete-ignored`. `gwr --force` used to print a worktree, remove it,
|
|
33
|
+
and say nothing about the `.env` inside it; `git worktree remove` takes the
|
|
34
|
+
whole directory whatever flag got it there, so `--delete-ignored` decides
|
|
35
|
+
consent and never decides what is deleted.
|
|
36
|
+
|
|
37
|
+
`gwr` no longer calls a finished branch unfinished. A worktree holding ignored
|
|
38
|
+
files was refused with `is not finished: squash-merged, but holds 6 ignored
|
|
39
|
+
path(s)`, contradicting itself in one line, and the flag it offered was
|
|
40
|
+
`--force`, which keeps a branch whose work already landed and deletes those
|
|
41
|
+
files anyway. It now says `is finished` and names `--delete-ignored` alone.
|
|
42
|
+
|
|
43
|
+
`gwl` offers the main checkout, and never the worktree you are standing in.
|
|
44
|
+
In a repository with one linked worktree, standing in it, `gwl` printed
|
|
45
|
+
nothing and exited 0: the main checkout was filtered out of the candidates,
|
|
46
|
+
leaving one, which the picker took outright and handed back the path you were
|
|
47
|
+
already at. `gwl main` now finds the main checkout whatever branch it stands
|
|
48
|
+
on, two worktrees and no query go to the other one, and asking for the one
|
|
49
|
+
you are in says `already in <branch>` and stays put. `--list` shows every
|
|
50
|
+
worktree and marks that one. A repository with no linked worktrees says so
|
|
51
|
+
and names `gwa`, where it used to answer as though nothing were there.
|
|
52
|
+
|
|
53
|
+
`gws --json` carries `ignored` and `sha` on every verdict. The count of
|
|
54
|
+
ignored paths reads as a number rather than as a phrase inside `why`, and the
|
|
55
|
+
sha is the commit the verdict was formed against, which is the value a caller
|
|
56
|
+
would otherwise have to go and read for itself.
|
|
57
|
+
|
|
58
|
+
`gwp` with nothing removable prints the verdict table. It used to print
|
|
59
|
+
`nothing to remove; gws says why`, so the reason each worktree stayed cost a
|
|
60
|
+
second command to read.
|
|
61
|
+
|
|
62
|
+
`gwh` is the help, so nothing needs `gw --help` typed out. It joins `gwa`,
|
|
63
|
+
`gwl`, `gwm`, `gwr`, `gws`, `gwp`, `gwnb`, `gwrot` and `gw` on `$PATH`, and
|
|
64
|
+
`gw` still takes the same set as subcommands. `gw` with no subcommand prints
|
|
65
|
+
that table too, where it used to print argparse's usage. `gw` has its own
|
|
66
|
+
entry point now, so a usage error from it names `gw` rather than `worktrees`.
|
|
67
|
+
|
|
68
|
+
Every subcommand answers to a shorthand as well as its name: `s` and `st` for
|
|
69
|
+
`status`, `p` for `prune`, `a` for `add`, `l` and `ls` for `list`, `m` and
|
|
70
|
+
`mv` for `move`, `rm` for `remove`, `nb` and `new` for `new-branch`, `rot` for
|
|
71
|
+
`rotate`, `h` for `help`. One table in the CLI carries every name, so what
|
|
72
|
+
`gwh` prints, what the completions offer and what `gw` accepts cannot
|
|
73
|
+
disagree, and it refuses to import if two commands ever claim one name.
|
|
74
|
+
|
|
75
|
+
Output is coloured, and `NO_COLOR` turns it off. The check reads the value
|
|
76
|
+
rather than the key, which is the no-color.org rule: `NO_COLOR=` unsets the
|
|
77
|
+
request instead of making it. A redirect or a pipe turns colour off as well,
|
|
78
|
+
because `cd $(gwa x)` and `jq` read that output and neither wants escapes.
|
|
79
|
+
|
|
80
|
+
Completion covers every command in fish and in zsh, ten files per shell. The
|
|
81
|
+
candidates come from the CLI and never from a shell file: `gw --complete`
|
|
82
|
+
prints every subcommand and shorthand, `gwl --complete` and `gwr --complete`
|
|
83
|
+
print worktrees, each as a name and a description separated by a tab, which
|
|
84
|
+
fish reads directly and zsh splits for `_describe`. A command added to the
|
|
85
|
+
table needs no edit in either shell.
|
|
86
|
+
|
|
87
|
+
`gwl` and `gwr` do not offer the same set. `gwr` cannot remove the main
|
|
88
|
+
checkout, so offering it would complete to "no worktree matches"; for `gwl`
|
|
89
|
+
it is the one destination always there.
|
|
90
|
+
|
|
91
|
+
`gwa` says when it leaves something behind. It creates the directories before
|
|
92
|
+
git is asked, so a refusal used to strand an empty one silently.
|
|
93
|
+
|
|
94
|
+
The worktree picker prints destinations in full rather than abbreviating them
|
|
95
|
+
to `~/git/...`. The abbreviation was not what got printed and not what you
|
|
96
|
+
could paste.
|
|
97
|
+
|
|
98
|
+
### Choices
|
|
99
|
+
|
|
100
|
+
A branch is deleted by `update-ref -d <ref> <sha>` rather than by `branch -D`.
|
|
101
|
+
Both force the delete; only one names what it expects to find, which turns a
|
|
102
|
+
concurrent commit into a failure instead of a loss. The guard reads that last
|
|
103
|
+
argument rather than counting the arguments: git takes `""` as "no old value"
|
|
104
|
+
and deletes the branch at exit 0, so a rule checking the shape would have
|
|
105
|
+
passed the one spelling that matters. `branch -d` was the
|
|
106
|
+
original choice and it inverts on the case this tool exists for: it reads
|
|
107
|
+
history, a squash merge leaves none, and deferring to it meant the content
|
|
108
|
+
probe bought the checkout and never the branch.
|
|
109
|
+
|
|
110
|
+
Completion flags live in the shell files, because that is the half that rots
|
|
111
|
+
without failing: rename a flag and nothing breaks, the candidate just stops
|
|
112
|
+
being offered. `test_completions.py` diffs both shells against `--help` in
|
|
113
|
+
both directions rather than trusting either copy, and drives every candidate
|
|
114
|
+
`gwr` offers back through `gwr` rather than comparing against a second copy
|
|
115
|
+
of the filter.
|
|
116
|
+
|
|
117
|
+
## 0.1.0 - 2026-09-11
|
|
118
|
+
|
|
119
|
+
The first release. Eight commands for git worktrees, four of which answer a
|
|
120
|
+
question and print it, and four of which land you somewhere.
|
|
121
|
+
|
|
122
|
+
`gws` says which of a repository's worktrees are finished and why, in three
|
|
123
|
+
verdicts. `remove` was proved finished, `keep` has a reason not to be, and
|
|
124
|
+
`unknown` could not tell. It removes nothing and has no flag that could.
|
|
125
|
+
|
|
126
|
+
`gwp` removes the ones `gws` marks `remove`, under the same flags, printing
|
|
127
|
+
what goes and the command that puts it back before asking. `-y` skips the
|
|
128
|
+
question, and a run whose stdin is not a terminal refuses rather than
|
|
129
|
+
blocking.
|
|
130
|
+
|
|
131
|
+
Two failures in git are why those two exist. A squash merge makes
|
|
132
|
+
`git branch -d` report "not fully merged" and point at `-D`, which deletes
|
|
133
|
+
anything; the probe replays the branch's tree as one commit on the merge
|
|
134
|
+
base and asks `git cherry` whether that patch is already upstream. And
|
|
135
|
+
`git worktree remove` deletes `.env` and `node_modules/` at exit 0 with no
|
|
136
|
+
`--force` and no word about it, so ignored paths are counted and the
|
|
137
|
+
worktree is kept until you pass `--delete-ignored`.
|
|
138
|
+
|
|
139
|
+
Where content cannot tell, the forge is asked. A branch merged as part of a
|
|
140
|
+
stack is that case: its changes reach the head branch across several
|
|
141
|
+
squashes, so a diff cannot separate it from a branch with work left. A
|
|
142
|
+
merged pull request settles it, cross-checked against the commits an
|
|
143
|
+
upstream has not got. `--no-forge` decides from git alone.
|
|
144
|
+
|
|
145
|
+
`gwa NAME` creates a worktree at `<PARENT>/.worktrees/<NAME>/<REPO>` and
|
|
146
|
+
prints where it is. `gwl [QUERY]` picks one of the existing ones, taking a
|
|
147
|
+
single match outright and asking when there are several. `gwm NEW` renames
|
|
148
|
+
this worktree's branch and moves the checkout to match. `gwr` removes one
|
|
149
|
+
whose branch is finished, and the branch with it.
|
|
150
|
+
|
|
151
|
+
Those four change your shell's directory, which a binary cannot do, so each
|
|
152
|
+
ships a shell function of the same name for fish and for zsh. The function
|
|
153
|
+
reads the path the binary printed and does the `cd`, and carries nothing
|
|
154
|
+
else.
|
|
155
|
+
|
|
156
|
+
`gwnb NAME` starts a branch off the head branch as the remote has it after a
|
|
157
|
+
fetch. `gwrot` starts the next branch in a series, named
|
|
158
|
+
`<stem>-YYYY-MM-DD_NNN` at the first number free today. **Both are alpha and
|
|
159
|
+
may go**: they start branches rather than worktrees, `origin new-branch` and
|
|
160
|
+
`origin rotate` already do that, and only one of the two sets survives.
|
|
161
|
+
|
|
162
|
+
Six git commands are refused wherever they appear and whatever flags are
|
|
163
|
+
passed: `reset --hard`, a forced `checkout` or `switch`, `clean -f`, a bare
|
|
164
|
+
`push --force`, `worktree remove --force`, and `branch -D`. A branch is
|
|
165
|
+
deleted on git's own proof of merge or not at all.
|
|
166
|
+
|
|
167
|
+
Every command takes `--json`, so an agent reads verdicts as data rather than
|
|
168
|
+
parsing a table. Data goes to stdout and every diagnostic to stderr, the
|
|
169
|
+
prompts included, so `--json` parses in every mode. `--version` reports the
|
|
170
|
+
version of the installed distribution.
|
|
171
|
+
|
|
172
|
+
Python 3.11 or newer, and no dependencies.
|