gate-cache 0.4.0__tar.gz → 0.5.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: gate-cache
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Skip lint and test gates that already passed on the exact same git tree.
5
5
  Author: Nate Landau
6
6
  Author-email: Nate Landau <github@natenate.org>
@@ -41,14 +41,14 @@ Skip a command that already passed on the same code.
41
41
 
42
42
  A lint or test command often runs many times on code that did not change. It runs when you finish the work, in a pre-commit hook, after a rebase that changed nothing, and in a pre-push hook. When an agent works in the repository, it runs the same command again. If the command takes minutes, the repeat runs take most of the time between finished code and a pushed branch.
43
43
 
44
- gate-cache remembers which commands passed on which code. It wraps a command and gives it a name. When the command passes, gate-cache writes a marker for that name and for the exact content of the files in the working copy that git does not ignore. The next time the same name runs on the same content, gate-cache finds the marker, skips the command, and exits 0 at once.
44
+ gate-cache remembers which commands passed on which code. It wraps a command, and the command line is the name of the gate. When the command passes, gate-cache writes a marker for that name and for the exact content of the files in the working copy that git does not ignore. The next time the same name runs on the same content, gate-cache finds the marker, skips the command, and exits 0 at once.
45
45
 
46
46
  ```console
47
- $ gate-cache --name pytest -- uv run pytest
47
+ $ gate-cache -- uv run pytest
48
48
  ============================= 412 passed in 94.20s =============================
49
49
 
50
- $ gate-cache --name pytest -- uv run pytest
51
- gate-cache: pytest already passed on tree aaff74984ccc, skipping
50
+ $ gate-cache -- uv run pytest
51
+ gate-cache: uv run pytest already passed on tree aaff74984ccc, skipping
52
52
  ```
53
53
 
54
54
  A change to any file that git does not ignore changes the content, so no marker matches and the command runs again. A CI runner that starts from a fresh clone has no markers, so it runs every command.
@@ -65,14 +65,22 @@ The prek and pre-commit hooks install gate-cache themselves, so the hooks work w
65
65
 
66
66
  ## Run a command through gate-cache
67
67
 
68
- The name goes after `--name`, and the command goes after `--`:
68
+ The command goes after `--`:
69
+
70
+ ```sh
71
+ gate-cache -- uv run pytest
72
+ gate-cache -- uv run ruff check .
73
+ ```
74
+
75
+ The command and its arguments are the name of the gate. Each name has its own markers, so a pass of `uv run pytest -k unit` never skips `uv run pytest`.
76
+
77
+ To choose the name yourself, put `--name` before `--`:
69
78
 
70
79
  ```sh
71
80
  gate-cache --name pytest -- uv run pytest
72
- gate-cache --name ruff -- uv run ruff check .
73
81
  ```
74
82
 
75
- Each name has its own markers. A command run under one name never skips because of a marker for another name.
83
+ With `--name`, the name alone identifies the gate, and the arguments of the command are not part of it. Give a name when two different command lines do the same check and must share markers, or when a gate depends on [inputs that git does not track](#inputs-that-git-does-not-track).
76
84
 
77
85
  gate-cache exits with the exit code of the command, or with 0 when it skips the command. It writes a marker only when the command passes, so a command that failed runs again the next time.
78
86
 
@@ -89,18 +97,18 @@ repos:
89
97
  hooks:
90
98
  - id: gate-cache
91
99
  name: pytest
92
- args: [--name, pytest, --, uv, run, pytest]
100
+ args: [--, uv, run, pytest]
93
101
  files: ^(src/|tests/)
94
102
  - id: gate-cache
95
103
  name: ruff
96
- args: [--name, ruff, --, uv, run, ruff, check, .]
104
+ args: [--, uv, run, ruff, check, .]
97
105
  ```
98
106
 
99
107
  The hook runs at commit time and at push time. At commit time, gate-cache matches the content that you commit.
100
108
 
101
109
  The hook runs in its own virtual environment, and the wrapped command inherits that environment. A command started with `uv run`, or a project command such as `just test`, runs in the project environment. `uv run` prints a warning that `VIRTUAL_ENV` does not match the project. The warning has no effect on the run.
102
110
 
103
- Hooks, agents, and your terminal all read and write the same markers. When one of them runs `gate-cache --name pytest -- uv run pytest` and it passes, the others skip that name on the same content.
111
+ Hooks, agents, and your terminal all read and write the same markers. When one of them runs `gate-cache -- uv run pytest` and it passes, the others skip `uv run pytest` on the same content.
104
112
 
105
113
  ## Examples
106
114
 
@@ -110,24 +118,26 @@ These examples show gate-cache inside the task runner of a project. When a recip
110
118
 
111
119
  ```just
112
120
  test:
113
- gate-cache --name test -- uv run pytest
121
+ gate-cache -- uv run pytest
114
122
 
115
123
  lint:
116
- gate-cache --name lint -- uv run ruff check .
124
+ gate-cache -- uv run ruff check .
117
125
  ```
118
126
 
119
127
  After `just test` passes, the next `just test` on the same content skips pytest.
120
128
 
121
129
  ### Recipes with arguments
122
130
 
123
- gate-cache identifies a command by its name alone, not by its arguments. If `just test -k unit` and `just test` ran under the same name, a pass of the unit tests would also skip the full suite. To keep them apart, put the arguments in the name:
131
+ A recipe that passes its arguments to the command needs no `--name`. Each set of arguments gives a different command line, so each set has its own markers:
124
132
 
125
133
  ```just
126
134
  test *args:
127
- gate-cache --name "{{ trim('test ' + args) }}" -- uv run pytest {{ args }}
135
+ gate-cache -- uv run pytest {{ args }}
128
136
  ```
129
137
 
130
- `just test` runs under the name `test`, and `just test -k unit` runs under the name `test -k unit`. Each set of arguments has its own markers. `trim` removes the trailing space when there are no arguments, so `gate-cache status --name test` finds the bare form.
138
+ `just test` runs under the name `uv run pytest`, and `just test -k unit` runs under the name `uv run pytest -k unit`. A pass of the unit tests never skips the full suite.
139
+
140
+ If the recipe gives `--name`, the arguments are not part of the name. Put them in the name, or a pass of `just test -k unit` also skips `just test`.
131
141
 
132
142
  ### Inputs that git does not track
133
143
 
@@ -143,7 +153,7 @@ integration:
143
153
  gate-cache --name "integration-$(git hash-object .env)" -- uv run pytest tests/integration
144
154
  ```
145
155
 
146
- `git hash-object` prints a hash of the file, so the name does not hold the secrets in `.env`. Do not put a secret value in a name, because the marker and `gate-cache status` record the name as plain text.
156
+ `git hash-object` prints a hash of the file, so the name does not hold the secrets in `.env`. Do not put a secret value in a name, because the marker and `gate-cache status` record the name as plain text. Without `--name`, the command line is the name, so give `--name` to a command that takes a secret as an argument.
147
157
 
148
158
  The shell of the recipe expands `$(...)` before gate-cache starts. A hook `entry` or `args` list does not go through a shell, so put the command in a recipe and call the recipe from the hook. To look up the gate with `gate-cache status --name`, give the expanded name, such as `test-uv0.13.0`.
149
159
 
@@ -151,10 +161,10 @@ The shell of the recipe expands `$(...)` before gate-cache starts. A hook `entry
151
161
 
152
162
  ```make
153
163
  test:
154
- gate-cache --name test -- uv run pytest
164
+ gate-cache -- uv run pytest
155
165
 
156
166
  lint:
157
- gate-cache --name lint -- uv run ruff check .
167
+ gate-cache -- uv run ruff check .
158
168
  ```
159
169
 
160
170
  ### A hook that calls the recipe
@@ -200,17 +210,17 @@ GATE_CACHE_DISABLE=1 just test
200
210
  ```console
201
211
  $ gate-cache status
202
212
  tree: aaff74984cccd156a469afa7d9ab10e4777beb24
203
- passed: pytest
204
- missed: ruff (exit code 1)
213
+ passed: uv run pytest
214
+ missed: uv run ruff check . (exit code 1)
205
215
  ```
206
216
 
207
217
  A name is missed when its command failed, or when it exited 0 but changed the files. A later pass of the same name removes it from the `missed` line.
208
218
 
209
- With `--name`, it reports on one name:
219
+ With `--name`, it reports on one name. For a gate that ran without `--name`, give the command line as the name:
210
220
 
211
221
  ```console
212
- $ gate-cache status --name ruff
213
- name: ruff
222
+ $ gate-cache status --name "uv run ruff check ."
223
+ name: uv run ruff check .
214
224
  tree: aaff74984cccd156a469afa7d9ab10e4777beb24
215
225
  marker: /path/to/repo/.git/gate-cache/7d94ca2f57ec0a77f60d328fb405d339fb0c2a645991b97c042ac54403104e9e
216
226
  result: miss
@@ -229,9 +239,9 @@ $ gate-cache status --json
229
239
  "enabled": true,
230
240
  "tree": "aaff74984cccd156a469afa7d9ab10e4777beb24",
231
241
  "reason": null,
232
- "passed": ["pytest"],
242
+ "passed": ["uv run pytest"],
233
243
  "missed": [
234
- {"name": "ruff", "exit_code": 1, "reason": "exit code 1", "timestamp": "2026-10-12T12:31:55Z"}
244
+ {"name": "uv run ruff check .", "exit_code": 1, "reason": "exit code 1", "timestamp": "2026-10-12T12:31:55Z"}
235
245
  ]
236
246
  }
237
247
  ```
@@ -4,14 +4,14 @@ Skip a command that already passed on the same code.
4
4
 
5
5
  A lint or test command often runs many times on code that did not change. It runs when you finish the work, in a pre-commit hook, after a rebase that changed nothing, and in a pre-push hook. When an agent works in the repository, it runs the same command again. If the command takes minutes, the repeat runs take most of the time between finished code and a pushed branch.
6
6
 
7
- gate-cache remembers which commands passed on which code. It wraps a command and gives it a name. When the command passes, gate-cache writes a marker for that name and for the exact content of the files in the working copy that git does not ignore. The next time the same name runs on the same content, gate-cache finds the marker, skips the command, and exits 0 at once.
7
+ gate-cache remembers which commands passed on which code. It wraps a command, and the command line is the name of the gate. When the command passes, gate-cache writes a marker for that name and for the exact content of the files in the working copy that git does not ignore. The next time the same name runs on the same content, gate-cache finds the marker, skips the command, and exits 0 at once.
8
8
 
9
9
  ```console
10
- $ gate-cache --name pytest -- uv run pytest
10
+ $ gate-cache -- uv run pytest
11
11
  ============================= 412 passed in 94.20s =============================
12
12
 
13
- $ gate-cache --name pytest -- uv run pytest
14
- gate-cache: pytest already passed on tree aaff74984ccc, skipping
13
+ $ gate-cache -- uv run pytest
14
+ gate-cache: uv run pytest already passed on tree aaff74984ccc, skipping
15
15
  ```
16
16
 
17
17
  A change to any file that git does not ignore changes the content, so no marker matches and the command runs again. A CI runner that starts from a fresh clone has no markers, so it runs every command.
@@ -28,14 +28,22 @@ The prek and pre-commit hooks install gate-cache themselves, so the hooks work w
28
28
 
29
29
  ## Run a command through gate-cache
30
30
 
31
- The name goes after `--name`, and the command goes after `--`:
31
+ The command goes after `--`:
32
+
33
+ ```sh
34
+ gate-cache -- uv run pytest
35
+ gate-cache -- uv run ruff check .
36
+ ```
37
+
38
+ The command and its arguments are the name of the gate. Each name has its own markers, so a pass of `uv run pytest -k unit` never skips `uv run pytest`.
39
+
40
+ To choose the name yourself, put `--name` before `--`:
32
41
 
33
42
  ```sh
34
43
  gate-cache --name pytest -- uv run pytest
35
- gate-cache --name ruff -- uv run ruff check .
36
44
  ```
37
45
 
38
- Each name has its own markers. A command run under one name never skips because of a marker for another name.
46
+ With `--name`, the name alone identifies the gate, and the arguments of the command are not part of it. Give a name when two different command lines do the same check and must share markers, or when a gate depends on [inputs that git does not track](#inputs-that-git-does-not-track).
39
47
 
40
48
  gate-cache exits with the exit code of the command, or with 0 when it skips the command. It writes a marker only when the command passes, so a command that failed runs again the next time.
41
49
 
@@ -52,18 +60,18 @@ repos:
52
60
  hooks:
53
61
  - id: gate-cache
54
62
  name: pytest
55
- args: [--name, pytest, --, uv, run, pytest]
63
+ args: [--, uv, run, pytest]
56
64
  files: ^(src/|tests/)
57
65
  - id: gate-cache
58
66
  name: ruff
59
- args: [--name, ruff, --, uv, run, ruff, check, .]
67
+ args: [--, uv, run, ruff, check, .]
60
68
  ```
61
69
 
62
70
  The hook runs at commit time and at push time. At commit time, gate-cache matches the content that you commit.
63
71
 
64
72
  The hook runs in its own virtual environment, and the wrapped command inherits that environment. A command started with `uv run`, or a project command such as `just test`, runs in the project environment. `uv run` prints a warning that `VIRTUAL_ENV` does not match the project. The warning has no effect on the run.
65
73
 
66
- Hooks, agents, and your terminal all read and write the same markers. When one of them runs `gate-cache --name pytest -- uv run pytest` and it passes, the others skip that name on the same content.
74
+ Hooks, agents, and your terminal all read and write the same markers. When one of them runs `gate-cache -- uv run pytest` and it passes, the others skip `uv run pytest` on the same content.
67
75
 
68
76
  ## Examples
69
77
 
@@ -73,24 +81,26 @@ These examples show gate-cache inside the task runner of a project. When a recip
73
81
 
74
82
  ```just
75
83
  test:
76
- gate-cache --name test -- uv run pytest
84
+ gate-cache -- uv run pytest
77
85
 
78
86
  lint:
79
- gate-cache --name lint -- uv run ruff check .
87
+ gate-cache -- uv run ruff check .
80
88
  ```
81
89
 
82
90
  After `just test` passes, the next `just test` on the same content skips pytest.
83
91
 
84
92
  ### Recipes with arguments
85
93
 
86
- gate-cache identifies a command by its name alone, not by its arguments. If `just test -k unit` and `just test` ran under the same name, a pass of the unit tests would also skip the full suite. To keep them apart, put the arguments in the name:
94
+ A recipe that passes its arguments to the command needs no `--name`. Each set of arguments gives a different command line, so each set has its own markers:
87
95
 
88
96
  ```just
89
97
  test *args:
90
- gate-cache --name "{{ trim('test ' + args) }}" -- uv run pytest {{ args }}
98
+ gate-cache -- uv run pytest {{ args }}
91
99
  ```
92
100
 
93
- `just test` runs under the name `test`, and `just test -k unit` runs under the name `test -k unit`. Each set of arguments has its own markers. `trim` removes the trailing space when there are no arguments, so `gate-cache status --name test` finds the bare form.
101
+ `just test` runs under the name `uv run pytest`, and `just test -k unit` runs under the name `uv run pytest -k unit`. A pass of the unit tests never skips the full suite.
102
+
103
+ If the recipe gives `--name`, the arguments are not part of the name. Put them in the name, or a pass of `just test -k unit` also skips `just test`.
94
104
 
95
105
  ### Inputs that git does not track
96
106
 
@@ -106,7 +116,7 @@ integration:
106
116
  gate-cache --name "integration-$(git hash-object .env)" -- uv run pytest tests/integration
107
117
  ```
108
118
 
109
- `git hash-object` prints a hash of the file, so the name does not hold the secrets in `.env`. Do not put a secret value in a name, because the marker and `gate-cache status` record the name as plain text.
119
+ `git hash-object` prints a hash of the file, so the name does not hold the secrets in `.env`. Do not put a secret value in a name, because the marker and `gate-cache status` record the name as plain text. Without `--name`, the command line is the name, so give `--name` to a command that takes a secret as an argument.
110
120
 
111
121
  The shell of the recipe expands `$(...)` before gate-cache starts. A hook `entry` or `args` list does not go through a shell, so put the command in a recipe and call the recipe from the hook. To look up the gate with `gate-cache status --name`, give the expanded name, such as `test-uv0.13.0`.
112
122
 
@@ -114,10 +124,10 @@ The shell of the recipe expands `$(...)` before gate-cache starts. A hook `entry
114
124
 
115
125
  ```make
116
126
  test:
117
- gate-cache --name test -- uv run pytest
127
+ gate-cache -- uv run pytest
118
128
 
119
129
  lint:
120
- gate-cache --name lint -- uv run ruff check .
130
+ gate-cache -- uv run ruff check .
121
131
  ```
122
132
 
123
133
  ### A hook that calls the recipe
@@ -163,17 +173,17 @@ GATE_CACHE_DISABLE=1 just test
163
173
  ```console
164
174
  $ gate-cache status
165
175
  tree: aaff74984cccd156a469afa7d9ab10e4777beb24
166
- passed: pytest
167
- missed: ruff (exit code 1)
176
+ passed: uv run pytest
177
+ missed: uv run ruff check . (exit code 1)
168
178
  ```
169
179
 
170
180
  A name is missed when its command failed, or when it exited 0 but changed the files. A later pass of the same name removes it from the `missed` line.
171
181
 
172
- With `--name`, it reports on one name:
182
+ With `--name`, it reports on one name. For a gate that ran without `--name`, give the command line as the name:
173
183
 
174
184
  ```console
175
- $ gate-cache status --name ruff
176
- name: ruff
185
+ $ gate-cache status --name "uv run ruff check ."
186
+ name: uv run ruff check .
177
187
  tree: aaff74984cccd156a469afa7d9ab10e4777beb24
178
188
  marker: /path/to/repo/.git/gate-cache/7d94ca2f57ec0a77f60d328fb405d339fb0c2a645991b97c042ac54403104e9e
179
189
  result: miss
@@ -192,9 +202,9 @@ $ gate-cache status --json
192
202
  "enabled": true,
193
203
  "tree": "aaff74984cccd156a469afa7d9ab10e4777beb24",
194
204
  "reason": null,
195
- "passed": ["pytest"],
205
+ "passed": ["uv run pytest"],
196
206
  "missed": [
197
- {"name": "ruff", "exit_code": 1, "reason": "exit code 1", "timestamp": "2026-10-12T12:31:55Z"}
207
+ {"name": "uv run ruff check .", "exit_code": 1, "reason": "exit code 1", "timestamp": "2026-10-12T12:31:55Z"}
198
208
  ]
199
209
  }
200
210
  ```
@@ -14,7 +14,7 @@ description = "Skip lint and test gates that already passed on the exact same gi
14
14
  name = "gate-cache"
15
15
  readme = "README.md"
16
16
  requires-python = ">=3.11"
17
- version = "0.4.0"
17
+ version = "0.5.0"
18
18
 
19
19
  [[project.authors]]
20
20
  name = "Nate Landau"
@@ -16,7 +16,7 @@
16
16
  name = "gate-cache"
17
17
  readme = "README.md"
18
18
  requires-python = ">=3.11"
19
- version = "0.4.0"
19
+ version = "0.5.0"
20
20
 
21
21
  [project.scripts]
22
22
  gate-cache = "gate_cache.cli:main"
@@ -1,3 +1,3 @@
1
1
  """Skip lint and test gates that already passed on the exact same git tree."""
2
2
 
3
- __version__ = "0.4.0"
3
+ __version__ = "0.5.0"
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import shlex
5
6
  import sys
6
7
  from typing import Annotated
7
8
 
@@ -23,8 +24,11 @@ runs the command, records a pass keyed on the git tree of the working copy
23
24
  tree is identical. A pass on uncommitted work therefore skips when the same
24
25
  content is committed.
25
26
 
26
- Both --name and -- are required, and only --name may come before --. Everything
27
- after -- is the command, verbatim.
27
+ -- is required, and only --name may come before it. Everything after -- is the
28
+ command, verbatim. Without --name, the gate name is the command itself, so each
29
+ set of arguments has its own records. Give --name to share records between
30
+ commands that check the same thing, or to key on inputs that the tree does not
31
+ cover.
28
32
 
29
33
  Exit codes:
30
34
  run the command's own exit code, or 0 when a recorded pass lets it skip.
@@ -40,15 +44,18 @@ same way.
40
44
  Set GATE_CACHE_MAX_AGE_DAYS to a positive whole number to change how many days a
41
45
  recorded pass lasts (default 30).
42
46
 
43
- Only the name and the file content are keyed. To also key on recipe arguments,
44
- an ignored file, an environment variable, or a tool version, put its value in
47
+ Only the name and the file content are keyed. The working directory is not, so
48
+ a command whose meaning depends on it needs a distinct --name. To also key on an
49
+ ignored file, an environment variable, or a tool version, put its value in
45
50
  --name. A changed value gives a new name, so the command runs again. Never put a
46
- secret in a name, because markers and status store it as plain text. Never wrap
47
- checks that depend on the commit message or the branch name.
51
+ secret in a name, because markers and status store it as plain text. Without
52
+ --name the command line is the name, so give --name to a command that takes a
53
+ secret argument. Never wrap checks that depend on the commit message or the
54
+ branch name.
48
55
 
49
56
  Typical use:
57
+ gate-cache -- uv run pytest -x
50
58
  gate-cache --name pytest -- uv run pytest -x
51
- gate-cache --name "pytest -k unit" -- uv run pytest -k unit
52
59
  gate-cache --name "test-$(git hash-object .env)" -- uv run pytest
53
60
  """
54
61
 
@@ -58,7 +65,7 @@ app = App(
58
65
  help=_HELP,
59
66
  help_format="plaintext",
60
67
  usage=(
61
- "Usage: gate-cache --name NAME -- COMMAND [ARGS...]\n"
68
+ "Usage: gate-cache [--name NAME] -- COMMAND [ARGS...]\n"
62
69
  " gate-cache status [--name NAME] [--json]\n"
63
70
  " gate-cache clear"
64
71
  ),
@@ -73,7 +80,7 @@ def run(
73
80
  ],
74
81
  /,
75
82
  *,
76
- name: str,
83
+ name: str | None = None,
77
84
  ) -> int:
78
85
  """Run COMMAND unless it already passed on the current git tree.
79
86
 
@@ -89,12 +96,15 @@ def run(
89
96
  The command and its arguments, given after `--`. Example: -- uv run pytest -x
90
97
  name
91
98
  Gate name. Records match only when the name and the tree both match.
92
- Include arguments or other inputs that the tree does not cover.
99
+ Defaults to the command and its arguments, quoted as a shell would.
93
100
  """
94
101
  if not command:
95
102
  say("no command given after --")
96
103
  return _USAGE_ERROR
97
- return run_gate(name, command)
104
+ if name == "":
105
+ say("--name needs a value, e.g. gate-cache --name pytest -- uv run pytest")
106
+ return _USAGE_ERROR
107
+ return run_gate(shlex.join(command) if name is None else name, command)
98
108
 
99
109
 
100
110
  @app.command
@@ -153,7 +163,7 @@ def _usage_error(tokens: list[str]) -> str | None:
153
163
  index += 1
154
164
  else:
155
165
  break
156
- return "put -- before the command, e.g. gate-cache --name pytest -- uv run pytest"
166
+ return "put -- before the command, e.g. gate-cache -- uv run pytest"
157
167
 
158
168
 
159
169
  def main() -> None:
File without changes