gate-cache 0.3.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.
- {gate_cache-0.3.0 → gate_cache-0.5.0}/PKG-INFO +60 -36
- {gate_cache-0.3.0 → gate_cache-0.5.0}/README.md +58 -35
- {gate_cache-0.3.0 → gate_cache-0.5.0}/pyproject.toml +2 -1
- {gate_cache-0.3.0 → gate_cache-0.5.0}/pyproject.toml.orig +6 -2
- {gate_cache-0.3.0 → gate_cache-0.5.0}/src/gate_cache/__init__.py +1 -1
- {gate_cache-0.3.0 → gate_cache-0.5.0}/src/gate_cache/cli.py +31 -15
- {gate_cache-0.3.0 → gate_cache-0.5.0}/src/gate_cache/gate.py +23 -21
- {gate_cache-0.3.0 → gate_cache-0.5.0}/src/gate_cache/markers.py +42 -10
- gate_cache-0.5.0/src/gate_cache/scratch.py +67 -0
- gate_cache-0.5.0/src/gate_cache/snapshot.py +366 -0
- gate_cache-0.3.0/src/gate_cache/snapshot.py +0 -148
- {gate_cache-0.3.0 → gate_cache-0.5.0}/LICENSE +0 -0
- {gate_cache-0.3.0 → gate_cache-0.5.0}/src/gate_cache/console.py +0 -0
- {gate_cache-0.3.0 → gate_cache-0.5.0}/src/gate_cache/runner.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: gate-cache
|
|
3
|
-
Version: 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>
|
|
@@ -31,6 +31,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
31
31
|
Classifier: Programming Language :: Python :: 3.14
|
|
32
32
|
Requires-Dist: cyclopts>=5.2.0,<6.0.0
|
|
33
33
|
Requires-Dist: nclutils>=3.4.4,<4.0.0
|
|
34
|
+
Requires-Dist: platformdirs>=4.13.0,<5.0.0
|
|
34
35
|
Requires-Python: >=3.11
|
|
35
36
|
Description-Content-Type: text/markdown
|
|
36
37
|
|
|
@@ -40,17 +41,17 @@ Skip a command that already passed on the same code.
|
|
|
40
41
|
|
|
41
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.
|
|
42
43
|
|
|
43
|
-
gate-cache remembers which commands passed on which code. It wraps a command and
|
|
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.
|
|
44
45
|
|
|
45
46
|
```console
|
|
46
|
-
$ gate-cache --
|
|
47
|
+
$ gate-cache -- uv run pytest
|
|
47
48
|
============================= 412 passed in 94.20s =============================
|
|
48
49
|
|
|
49
|
-
$ gate-cache --
|
|
50
|
-
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
|
|
51
52
|
```
|
|
52
53
|
|
|
53
|
-
A change to
|
|
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.
|
|
54
55
|
|
|
55
56
|
## Install
|
|
56
57
|
|
|
@@ -64,18 +65,26 @@ The prek and pre-commit hooks install gate-cache themselves, so the hooks work w
|
|
|
64
65
|
|
|
65
66
|
## Run a command through gate-cache
|
|
66
67
|
|
|
67
|
-
The
|
|
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 `--`:
|
|
68
78
|
|
|
69
79
|
```sh
|
|
70
80
|
gate-cache --name pytest -- uv run pytest
|
|
71
|
-
gate-cache --name ruff -- uv run ruff check .
|
|
72
81
|
```
|
|
73
82
|
|
|
74
|
-
|
|
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).
|
|
75
84
|
|
|
76
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.
|
|
77
86
|
|
|
78
|
-
|
|
87
|
+
The tree includes uncommitted edits and untracked files that git does not ignore. A gate that passes on uncommitted work therefore skips in the commit hook when you commit the same content. gate-cache computes the tree from a private copy of the index, so it never stages files or changes your index.
|
|
79
88
|
|
|
80
89
|
## Run it as a hook
|
|
81
90
|
|
|
@@ -88,18 +97,18 @@ repos:
|
|
|
88
97
|
hooks:
|
|
89
98
|
- id: gate-cache
|
|
90
99
|
name: pytest
|
|
91
|
-
args: [
|
|
100
|
+
args: [--, uv, run, pytest]
|
|
92
101
|
files: ^(src/|tests/)
|
|
93
102
|
- id: gate-cache
|
|
94
103
|
name: ruff
|
|
95
|
-
args: [
|
|
104
|
+
args: [--, uv, run, ruff, check, .]
|
|
96
105
|
```
|
|
97
106
|
|
|
98
107
|
The hook runs at commit time and at push time. At commit time, gate-cache matches the content that you commit.
|
|
99
108
|
|
|
100
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.
|
|
101
110
|
|
|
102
|
-
Hooks, agents, and your terminal all read and write the same markers. When one of them runs `gate-cache --
|
|
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.
|
|
103
112
|
|
|
104
113
|
## Examples
|
|
105
114
|
|
|
@@ -109,38 +118,53 @@ These examples show gate-cache inside the task runner of a project. When a recip
|
|
|
109
118
|
|
|
110
119
|
```just
|
|
111
120
|
test:
|
|
112
|
-
gate-cache --
|
|
121
|
+
gate-cache -- uv run pytest
|
|
113
122
|
|
|
114
123
|
lint:
|
|
115
|
-
gate-cache --
|
|
124
|
+
gate-cache -- uv run ruff check .
|
|
116
125
|
```
|
|
117
126
|
|
|
118
127
|
After `just test` passes, the next `just test` on the same content skips pytest.
|
|
119
128
|
|
|
120
129
|
### Recipes with arguments
|
|
121
130
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
This recipe caches `just test` and runs `just test -k unit` without the cache:
|
|
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:
|
|
125
132
|
|
|
126
133
|
```just
|
|
127
134
|
test *args:
|
|
128
|
-
|
|
129
|
-
set -euo pipefail
|
|
130
|
-
if [[ -z "{{ args }}" ]]; then
|
|
131
|
-
exec gate-cache --name test -- uv run pytest
|
|
132
|
-
fi
|
|
133
|
-
exec uv run pytest {{ args }}
|
|
135
|
+
gate-cache -- uv run pytest {{ args }}
|
|
134
136
|
```
|
|
135
137
|
|
|
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`.
|
|
141
|
+
|
|
142
|
+
### Inputs that git does not track
|
|
143
|
+
|
|
144
|
+
A marker does not cover ignored files, environment variables, or tool versions. To make a gate depend on one of these inputs, put its value in the name. When the value changes, the name changes, so no marker matches and the command runs again:
|
|
145
|
+
|
|
146
|
+
```just
|
|
147
|
+
# Run again when the uv version changes
|
|
148
|
+
test:
|
|
149
|
+
gate-cache --name "test-uv$(uv --version | cut -d' ' -f2)" -- uv run pytest
|
|
150
|
+
|
|
151
|
+
# Run again when the ignored .env file changes
|
|
152
|
+
integration:
|
|
153
|
+
gate-cache --name "integration-$(git hash-object .env)" -- uv run pytest tests/integration
|
|
154
|
+
```
|
|
155
|
+
|
|
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.
|
|
157
|
+
|
|
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`.
|
|
159
|
+
|
|
136
160
|
### Makefile
|
|
137
161
|
|
|
138
162
|
```make
|
|
139
163
|
test:
|
|
140
|
-
gate-cache --
|
|
164
|
+
gate-cache -- uv run pytest
|
|
141
165
|
|
|
142
166
|
lint:
|
|
143
|
-
gate-cache --
|
|
167
|
+
gate-cache -- uv run ruff check .
|
|
144
168
|
```
|
|
145
169
|
|
|
146
170
|
### A hook that calls the recipe
|
|
@@ -186,17 +210,17 @@ GATE_CACHE_DISABLE=1 just test
|
|
|
186
210
|
```console
|
|
187
211
|
$ gate-cache status
|
|
188
212
|
tree: aaff74984cccd156a469afa7d9ab10e4777beb24
|
|
189
|
-
passed: pytest
|
|
190
|
-
missed: ruff (exit code 1)
|
|
213
|
+
passed: uv run pytest
|
|
214
|
+
missed: uv run ruff check . (exit code 1)
|
|
191
215
|
```
|
|
192
216
|
|
|
193
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.
|
|
194
218
|
|
|
195
|
-
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:
|
|
196
220
|
|
|
197
221
|
```console
|
|
198
|
-
$ gate-cache status --name ruff
|
|
199
|
-
name: ruff
|
|
222
|
+
$ gate-cache status --name "uv run ruff check ."
|
|
223
|
+
name: uv run ruff check .
|
|
200
224
|
tree: aaff74984cccd156a469afa7d9ab10e4777beb24
|
|
201
225
|
marker: /path/to/repo/.git/gate-cache/7d94ca2f57ec0a77f60d328fb405d339fb0c2a645991b97c042ac54403104e9e
|
|
202
226
|
result: miss
|
|
@@ -205,7 +229,7 @@ $ gate-cache status --name ruff
|
|
|
205
229
|
|
|
206
230
|
The `last run` line shows only when the name ran on this content without a pass.
|
|
207
231
|
|
|
208
|
-
`status` runs nothing. With `--name`, it exits 0 when a marker matches and 1 when none does. Without `--name`, it exits 0 when at least one name passed. When gate-cache cannot match the content, for example
|
|
232
|
+
`status` runs nothing. With `--name`, it exits 0 when a marker matches and 1 when none does. Without `--name`, it exits 0 when at least one name passed. When gate-cache cannot match the content, for example during a merge conflict, the `tree` line gives the reason.
|
|
209
233
|
|
|
210
234
|
With `--json`, `status` writes one JSON object to stdout instead of text. The exit codes do not change:
|
|
211
235
|
|
|
@@ -215,18 +239,18 @@ $ gate-cache status --json
|
|
|
215
239
|
"enabled": true,
|
|
216
240
|
"tree": "aaff74984cccd156a469afa7d9ab10e4777beb24",
|
|
217
241
|
"reason": null,
|
|
218
|
-
"passed": ["pytest"],
|
|
242
|
+
"passed": ["uv run pytest"],
|
|
219
243
|
"missed": [
|
|
220
|
-
{"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"}
|
|
221
245
|
]
|
|
222
246
|
}
|
|
223
247
|
```
|
|
224
248
|
|
|
225
|
-
`reason` tells why gate-cache cannot match the content, and is `null` when it can. When caching is off, `enabled` is `false
|
|
249
|
+
`reason` tells why gate-cache cannot match the content, and is `null` when it can. When caching is off, `enabled` is `false`, `reason` names the variable, and the other keys are empty or `null`. With `--name`, the object has the `name`, `marker`, `result`, and `last_run` keys in place of `passed` and `missed`.
|
|
226
250
|
|
|
227
251
|
## Start fresh
|
|
228
252
|
|
|
229
|
-
A marker records only the content of the
|
|
253
|
+
A marker records only the content of the files that git does not ignore. A new tool version, a changed environment variable, or a change to a service does not change that content, so the old markers still match. To make a gate run again when one of these inputs changes, put the input in the name, as [Inputs that git does not track](#inputs-that-git-does-not-track) shows.
|
|
230
254
|
|
|
231
255
|
`gate-cache clear` deletes every marker for the repository, and the records of missed runs:
|
|
232
256
|
|
|
@@ -4,17 +4,17 @@ 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
|
|
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 --
|
|
10
|
+
$ gate-cache -- uv run pytest
|
|
11
11
|
============================= 412 passed in 94.20s =============================
|
|
12
12
|
|
|
13
|
-
$ gate-cache --
|
|
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
|
-
A change to
|
|
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.
|
|
18
18
|
|
|
19
19
|
## Install
|
|
20
20
|
|
|
@@ -28,18 +28,26 @@ 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
|
|
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
|
-
|
|
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
|
|
|
42
|
-
|
|
50
|
+
The tree includes uncommitted edits and untracked files that git does not ignore. A gate that passes on uncommitted work therefore skips in the commit hook when you commit the same content. gate-cache computes the tree from a private copy of the index, so it never stages files or changes your index.
|
|
43
51
|
|
|
44
52
|
## Run it as a hook
|
|
45
53
|
|
|
@@ -52,18 +60,18 @@ repos:
|
|
|
52
60
|
hooks:
|
|
53
61
|
- id: gate-cache
|
|
54
62
|
name: pytest
|
|
55
|
-
args: [
|
|
63
|
+
args: [--, uv, run, pytest]
|
|
56
64
|
files: ^(src/|tests/)
|
|
57
65
|
- id: gate-cache
|
|
58
66
|
name: ruff
|
|
59
|
-
args: [
|
|
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 --
|
|
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,38 +81,53 @@ 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 --
|
|
84
|
+
gate-cache -- uv run pytest
|
|
77
85
|
|
|
78
86
|
lint:
|
|
79
|
-
gate-cache --
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
This recipe caches `just test` and runs `just test -k unit` without the cache:
|
|
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:
|
|
89
95
|
|
|
90
96
|
```just
|
|
91
97
|
test *args:
|
|
92
|
-
|
|
93
|
-
set -euo pipefail
|
|
94
|
-
if [[ -z "{{ args }}" ]]; then
|
|
95
|
-
exec gate-cache --name test -- uv run pytest
|
|
96
|
-
fi
|
|
97
|
-
exec uv run pytest {{ args }}
|
|
98
|
+
gate-cache -- uv run pytest {{ args }}
|
|
98
99
|
```
|
|
99
100
|
|
|
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`.
|
|
104
|
+
|
|
105
|
+
### Inputs that git does not track
|
|
106
|
+
|
|
107
|
+
A marker does not cover ignored files, environment variables, or tool versions. To make a gate depend on one of these inputs, put its value in the name. When the value changes, the name changes, so no marker matches and the command runs again:
|
|
108
|
+
|
|
109
|
+
```just
|
|
110
|
+
# Run again when the uv version changes
|
|
111
|
+
test:
|
|
112
|
+
gate-cache --name "test-uv$(uv --version | cut -d' ' -f2)" -- uv run pytest
|
|
113
|
+
|
|
114
|
+
# Run again when the ignored .env file changes
|
|
115
|
+
integration:
|
|
116
|
+
gate-cache --name "integration-$(git hash-object .env)" -- uv run pytest tests/integration
|
|
117
|
+
```
|
|
118
|
+
|
|
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.
|
|
120
|
+
|
|
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`.
|
|
122
|
+
|
|
100
123
|
### Makefile
|
|
101
124
|
|
|
102
125
|
```make
|
|
103
126
|
test:
|
|
104
|
-
gate-cache --
|
|
127
|
+
gate-cache -- uv run pytest
|
|
105
128
|
|
|
106
129
|
lint:
|
|
107
|
-
gate-cache --
|
|
130
|
+
gate-cache -- uv run ruff check .
|
|
108
131
|
```
|
|
109
132
|
|
|
110
133
|
### A hook that calls the recipe
|
|
@@ -150,17 +173,17 @@ GATE_CACHE_DISABLE=1 just test
|
|
|
150
173
|
```console
|
|
151
174
|
$ gate-cache status
|
|
152
175
|
tree: aaff74984cccd156a469afa7d9ab10e4777beb24
|
|
153
|
-
passed: pytest
|
|
154
|
-
missed: ruff (exit code 1)
|
|
176
|
+
passed: uv run pytest
|
|
177
|
+
missed: uv run ruff check . (exit code 1)
|
|
155
178
|
```
|
|
156
179
|
|
|
157
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.
|
|
158
181
|
|
|
159
|
-
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:
|
|
160
183
|
|
|
161
184
|
```console
|
|
162
|
-
$ gate-cache status --name ruff
|
|
163
|
-
name: ruff
|
|
185
|
+
$ gate-cache status --name "uv run ruff check ."
|
|
186
|
+
name: uv run ruff check .
|
|
164
187
|
tree: aaff74984cccd156a469afa7d9ab10e4777beb24
|
|
165
188
|
marker: /path/to/repo/.git/gate-cache/7d94ca2f57ec0a77f60d328fb405d339fb0c2a645991b97c042ac54403104e9e
|
|
166
189
|
result: miss
|
|
@@ -169,7 +192,7 @@ $ gate-cache status --name ruff
|
|
|
169
192
|
|
|
170
193
|
The `last run` line shows only when the name ran on this content without a pass.
|
|
171
194
|
|
|
172
|
-
`status` runs nothing. With `--name`, it exits 0 when a marker matches and 1 when none does. Without `--name`, it exits 0 when at least one name passed. When gate-cache cannot match the content, for example
|
|
195
|
+
`status` runs nothing. With `--name`, it exits 0 when a marker matches and 1 when none does. Without `--name`, it exits 0 when at least one name passed. When gate-cache cannot match the content, for example during a merge conflict, the `tree` line gives the reason.
|
|
173
196
|
|
|
174
197
|
With `--json`, `status` writes one JSON object to stdout instead of text. The exit codes do not change:
|
|
175
198
|
|
|
@@ -179,18 +202,18 @@ $ gate-cache status --json
|
|
|
179
202
|
"enabled": true,
|
|
180
203
|
"tree": "aaff74984cccd156a469afa7d9ab10e4777beb24",
|
|
181
204
|
"reason": null,
|
|
182
|
-
"passed": ["pytest"],
|
|
205
|
+
"passed": ["uv run pytest"],
|
|
183
206
|
"missed": [
|
|
184
|
-
{"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"}
|
|
185
208
|
]
|
|
186
209
|
}
|
|
187
210
|
```
|
|
188
211
|
|
|
189
|
-
`reason` tells why gate-cache cannot match the content, and is `null` when it can. When caching is off, `enabled` is `false
|
|
212
|
+
`reason` tells why gate-cache cannot match the content, and is `null` when it can. When caching is off, `enabled` is `false`, `reason` names the variable, and the other keys are empty or `null`. With `--name`, the object has the `name`, `marker`, `result`, and `last_run` keys in place of `passed` and `missed`.
|
|
190
213
|
|
|
191
214
|
## Start fresh
|
|
192
215
|
|
|
193
|
-
A marker records only the content of the
|
|
216
|
+
A marker records only the content of the files that git does not ignore. A new tool version, a changed environment variable, or a change to a service does not change that content, so the old markers still match. To make a gate run again when one of these inputs changes, put the input in the name, as [Inputs that git does not track](#inputs-that-git-does-not-track) shows.
|
|
194
217
|
|
|
195
218
|
`gate-cache clear` deletes every marker for the repository, and the records of missed runs:
|
|
196
219
|
|
|
@@ -8,12 +8,13 @@ classifiers = [
|
|
|
8
8
|
dependencies = [
|
|
9
9
|
"cyclopts>=5.2.0,<6.0.0",
|
|
10
10
|
"nclutils>=3.4.4,<4.0.0",
|
|
11
|
+
"platformdirs>=4.13.0,<5.0.0",
|
|
11
12
|
]
|
|
12
13
|
description = "Skip lint and test gates that already passed on the exact same git tree."
|
|
13
14
|
name = "gate-cache"
|
|
14
15
|
readme = "README.md"
|
|
15
16
|
requires-python = ">=3.11"
|
|
16
|
-
version = "0.
|
|
17
|
+
version = "0.5.0"
|
|
17
18
|
|
|
18
19
|
[[project.authors]]
|
|
19
20
|
name = "Nate Landau"
|
|
@@ -6,13 +6,17 @@
|
|
|
6
6
|
"Programming Language :: Python :: 3.13",
|
|
7
7
|
"Programming Language :: Python :: 3.14",
|
|
8
8
|
]
|
|
9
|
-
dependencies = [
|
|
9
|
+
dependencies = [
|
|
10
|
+
"cyclopts>=5.2.0,<6.0.0",
|
|
11
|
+
"nclutils>=3.4.4,<4.0.0",
|
|
12
|
+
"platformdirs>=4.13.0,<5.0.0",
|
|
13
|
+
]
|
|
10
14
|
description = "Skip lint and test gates that already passed on the exact same git tree."
|
|
11
15
|
license = { file = "LICENSE" }
|
|
12
16
|
name = "gate-cache"
|
|
13
17
|
readme = "README.md"
|
|
14
18
|
requires-python = ">=3.11"
|
|
15
|
-
version = "0.
|
|
19
|
+
version = "0.5.0"
|
|
16
20
|
|
|
17
21
|
[project.scripts]
|
|
18
22
|
gate-cache = "gate_cache.cli:main"
|
|
@@ -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
|
|
|
@@ -17,14 +18,17 @@ _SUBCOMMANDS = {"status", "clear"}
|
|
|
17
18
|
_INFO_FLAGS = {"-h", "--help", "--version"}
|
|
18
19
|
|
|
19
20
|
_HELP = """\
|
|
20
|
-
Skip a lint or test command that already passed on the same git tree. gate-cache
|
|
21
|
-
the command, records a pass keyed on the git tree of the
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
is
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
-- is the command,
|
|
21
|
+
Skip a lint or test command that already passed on the same git tree. gate-cache
|
|
22
|
+
runs the command, records a pass keyed on the git tree of the working copy
|
|
23
|
+
(tracked and untracked, non-ignored files), and skips the command next time the
|
|
24
|
+
tree is identical. A pass on uncommitted work therefore skips when the same
|
|
25
|
+
content is committed.
|
|
26
|
+
|
|
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,11 +44,19 @@ 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 file content
|
|
44
|
-
|
|
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
|
|
50
|
+
--name. A changed value gives a new name, so the command runs again. Never put a
|
|
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.
|
|
45
55
|
|
|
46
56
|
Typical use:
|
|
57
|
+
gate-cache -- uv run pytest -x
|
|
47
58
|
gate-cache --name pytest -- uv run pytest -x
|
|
59
|
+
gate-cache --name "test-$(git hash-object .env)" -- uv run pytest
|
|
48
60
|
"""
|
|
49
61
|
|
|
50
62
|
app = App(
|
|
@@ -53,7 +65,7 @@ app = App(
|
|
|
53
65
|
help=_HELP,
|
|
54
66
|
help_format="plaintext",
|
|
55
67
|
usage=(
|
|
56
|
-
"Usage: gate-cache --name NAME -- COMMAND [ARGS...]\n"
|
|
68
|
+
"Usage: gate-cache [--name NAME] -- COMMAND [ARGS...]\n"
|
|
57
69
|
" gate-cache status [--name NAME] [--json]\n"
|
|
58
70
|
" gate-cache clear"
|
|
59
71
|
),
|
|
@@ -68,14 +80,14 @@ def run(
|
|
|
68
80
|
],
|
|
69
81
|
/,
|
|
70
82
|
*,
|
|
71
|
-
name: str,
|
|
83
|
+
name: str | None = None,
|
|
72
84
|
) -> int:
|
|
73
85
|
"""Run COMMAND unless it already passed on the current git tree.
|
|
74
86
|
|
|
75
87
|
On a skip, prints one line to stderr and exits 0 without running anything. Otherwise
|
|
76
88
|
runs the command and exits with its exit code. The command runs without caching
|
|
77
89
|
(and nothing is recorded) when GATE_CACHE_DISABLE or CI turns caching off, the tree
|
|
78
|
-
|
|
90
|
+
is unknown (for example an unmerged index), or this is not a git repository. A pass is recorded only
|
|
79
91
|
when the command exits 0 and the run leaves the tree unchanged.
|
|
80
92
|
|
|
81
93
|
Parameters
|
|
@@ -84,11 +96,15 @@ def run(
|
|
|
84
96
|
The command and its arguments, given after `--`. Example: -- uv run pytest -x
|
|
85
97
|
name
|
|
86
98
|
Gate name. Records match only when the name and the tree both match.
|
|
99
|
+
Defaults to the command and its arguments, quoted as a shell would.
|
|
87
100
|
"""
|
|
88
101
|
if not command:
|
|
89
102
|
say("no command given after --")
|
|
90
103
|
return _USAGE_ERROR
|
|
91
|
-
|
|
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)
|
|
92
108
|
|
|
93
109
|
|
|
94
110
|
@app.command
|
|
@@ -147,7 +163,7 @@ def _usage_error(tokens: list[str]) -> str | None:
|
|
|
147
163
|
index += 1
|
|
148
164
|
else:
|
|
149
165
|
break
|
|
150
|
-
return "put -- before the command, e.g. gate-cache --
|
|
166
|
+
return "put -- before the command, e.g. gate-cache -- uv run pytest"
|
|
151
167
|
|
|
152
168
|
|
|
153
169
|
def main() -> None:
|