gate-cache 0.2.0__tar.gz → 0.3.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/PKG-INFO +261 -0
- gate_cache-0.3.0/README.md +225 -0
- {gate_cache-0.2.0 → gate_cache-0.3.0}/pyproject.toml +2 -1
- {gate_cache-0.2.0 → gate_cache-0.3.0}/pyproject.toml.orig +2 -1
- {gate_cache-0.2.0 → gate_cache-0.3.0}/src/gate_cache/__init__.py +1 -1
- {gate_cache-0.2.0 → gate_cache-0.3.0}/src/gate_cache/cli.py +21 -11
- gate_cache-0.3.0/src/gate_cache/gate.py +349 -0
- {gate_cache-0.2.0 → gate_cache-0.3.0}/src/gate_cache/markers.py +85 -13
- {gate_cache-0.2.0 → gate_cache-0.3.0}/src/gate_cache/snapshot.py +48 -10
- gate_cache-0.2.0/PKG-INFO +0 -234
- gate_cache-0.2.0/README.md +0 -198
- gate_cache-0.2.0/src/gate_cache/gate.py +0 -222
- {gate_cache-0.2.0 → gate_cache-0.3.0}/LICENSE +0 -0
- {gate_cache-0.2.0 → gate_cache-0.3.0}/src/gate_cache/console.py +0 -0
- {gate_cache-0.2.0 → gate_cache-0.3.0}/src/gate_cache/runner.py +0 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: gate-cache
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Skip lint and test gates that already passed on the exact same git tree.
|
|
5
|
+
Author: Nate Landau
|
|
6
|
+
Author-email: Nate Landau <github@natenate.org>
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2026 Nate Landau
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
29
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
30
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
31
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
32
|
+
Requires-Dist: cyclopts>=5.2.0,<6.0.0
|
|
33
|
+
Requires-Dist: nclutils>=3.4.4,<4.0.0
|
|
34
|
+
Requires-Python: >=3.11
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# gate-cache
|
|
38
|
+
|
|
39
|
+
Skip a command that already passed on the same code.
|
|
40
|
+
|
|
41
|
+
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
|
+
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 tracked files. 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
|
+
```console
|
|
46
|
+
$ gate-cache --name pytest -- uv run pytest
|
|
47
|
+
============================= 412 passed in 94.20s =============================
|
|
48
|
+
|
|
49
|
+
$ gate-cache --name pytest -- uv run pytest
|
|
50
|
+
gate-cache: pytest already passed on tree aaff74984ccc, skipping
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
A change to a tracked file 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
|
+
## Install
|
|
56
|
+
|
|
57
|
+
gate-cache requires Python 3.11 or later, on macOS or Linux.
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
uv tool install gate-cache
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The prek and pre-commit hooks install gate-cache themselves, so the hooks work without this step.
|
|
64
|
+
|
|
65
|
+
## Run a command through gate-cache
|
|
66
|
+
|
|
67
|
+
The name goes after `--name`, and the command goes after `--`:
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
gate-cache --name pytest -- uv run pytest
|
|
71
|
+
gate-cache --name ruff -- uv run ruff check .
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Each name has its own markers. A command run under one name never skips because of a marker for another name.
|
|
75
|
+
|
|
76
|
+
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
|
+
|
|
78
|
+
When the working copy has uncommitted edits or files that git does not track, the content on disk is not a commit-ready snapshot. gate-cache then runs the command and writes no marker.
|
|
79
|
+
|
|
80
|
+
## Run it as a hook
|
|
81
|
+
|
|
82
|
+
gate-cache works with [prek](https://github.com/j178/prek) and with [pre-commit](https://pre-commit.com) 3.2 or later. Each `gate-cache` hook takes the same arguments as the command line:
|
|
83
|
+
|
|
84
|
+
```yaml
|
|
85
|
+
repos:
|
|
86
|
+
- repo: https://github.com/natelandau/gate-cache
|
|
87
|
+
rev: v0.1.0
|
|
88
|
+
hooks:
|
|
89
|
+
- id: gate-cache
|
|
90
|
+
name: pytest
|
|
91
|
+
args: [--name, pytest, --, uv, run, pytest]
|
|
92
|
+
files: ^(src/|tests/)
|
|
93
|
+
- id: gate-cache
|
|
94
|
+
name: ruff
|
|
95
|
+
args: [--name, ruff, --, uv, run, ruff, check, .]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The hook runs at commit time and at push time. At commit time, gate-cache matches the content that you commit.
|
|
99
|
+
|
|
100
|
+
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
|
+
|
|
102
|
+
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.
|
|
103
|
+
|
|
104
|
+
## Examples
|
|
105
|
+
|
|
106
|
+
These examples show gate-cache inside the task runner of a project. When a recipe holds the wrapped command, everything that runs the recipe uses the same name and the same markers.
|
|
107
|
+
|
|
108
|
+
### justfile
|
|
109
|
+
|
|
110
|
+
```just
|
|
111
|
+
test:
|
|
112
|
+
gate-cache --name test -- uv run pytest
|
|
113
|
+
|
|
114
|
+
lint:
|
|
115
|
+
gate-cache --name lint -- uv run ruff check .
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
After `just test` passes, the next `just test` on the same content skips pytest.
|
|
119
|
+
|
|
120
|
+
### Recipes with arguments
|
|
121
|
+
|
|
122
|
+
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. Giving each set of arguments its own name, or caching only the bare form, keeps the two apart.
|
|
123
|
+
|
|
124
|
+
This recipe caches `just test` and runs `just test -k unit` without the cache:
|
|
125
|
+
|
|
126
|
+
```just
|
|
127
|
+
test *args:
|
|
128
|
+
#!/usr/bin/env bash
|
|
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 }}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Makefile
|
|
137
|
+
|
|
138
|
+
```make
|
|
139
|
+
test:
|
|
140
|
+
gate-cache --name test -- uv run pytest
|
|
141
|
+
|
|
142
|
+
lint:
|
|
143
|
+
gate-cache --name lint -- uv run ruff check .
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### A hook that calls the recipe
|
|
147
|
+
|
|
148
|
+
A local hook can run the recipe instead of the gate-cache hook repository:
|
|
149
|
+
|
|
150
|
+
```yaml
|
|
151
|
+
repos:
|
|
152
|
+
- repo: local
|
|
153
|
+
hooks:
|
|
154
|
+
- id: test
|
|
155
|
+
name: test
|
|
156
|
+
entry: just test
|
|
157
|
+
language: system
|
|
158
|
+
pass_filenames: false
|
|
159
|
+
stages: [pre-commit, pre-push]
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
With this hook, a commit, a push, an agent, and your terminal all run `just test` and share its markers. The recipe runs in the project environment, so `uv run` prints no `VIRTUAL_ENV` warning. This form needs `gate-cache` and `just` on the `PATH`.
|
|
163
|
+
|
|
164
|
+
### CI
|
|
165
|
+
|
|
166
|
+
gate-cache turns off caching when the `CI` variable is on. A variable is on when its value is not empty, `0`, or `false`. Case and spaces around the value do not matter. Many CI systems set `CI=true` in every job, for example GitHub Actions and GitLab CI/CD. Jenkins does not set `CI`.
|
|
167
|
+
|
|
168
|
+
When caching is off, gate-cache runs the command each time and writes no marker.
|
|
169
|
+
|
|
170
|
+
`GATE_CACHE_DISABLE` overrides `CI`. To keep caching on when `CI` is on, set `GATE_CACHE_DISABLE` to `0` or `false`:
|
|
171
|
+
|
|
172
|
+
```sh
|
|
173
|
+
GATE_CACHE_DISABLE=0 just test
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
To turn off caching when `CI` is not set, set `GATE_CACHE_DISABLE` to `1`:
|
|
177
|
+
|
|
178
|
+
```sh
|
|
179
|
+
GATE_CACHE_DISABLE=1 just test
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## See what already passed
|
|
183
|
+
|
|
184
|
+
`gate-cache status` lists the names that passed on the current content, and the names that ran on it without a pass:
|
|
185
|
+
|
|
186
|
+
```console
|
|
187
|
+
$ gate-cache status
|
|
188
|
+
tree: aaff74984cccd156a469afa7d9ab10e4777beb24
|
|
189
|
+
passed: pytest
|
|
190
|
+
missed: ruff (exit code 1)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
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
|
+
|
|
195
|
+
With `--name`, it reports on one name:
|
|
196
|
+
|
|
197
|
+
```console
|
|
198
|
+
$ gate-cache status --name ruff
|
|
199
|
+
name: ruff
|
|
200
|
+
tree: aaff74984cccd156a469afa7d9ab10e4777beb24
|
|
201
|
+
marker: /path/to/repo/.git/gate-cache/7d94ca2f57ec0a77f60d328fb405d339fb0c2a645991b97c042ac54403104e9e
|
|
202
|
+
result: miss
|
|
203
|
+
last run: exit code 1
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The `last run` line shows only when the name ran on this content without a pass.
|
|
207
|
+
|
|
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 because of uncommitted edits, the `tree` line gives the reason.
|
|
209
|
+
|
|
210
|
+
With `--json`, `status` writes one JSON object to stdout instead of text. The exit codes do not change:
|
|
211
|
+
|
|
212
|
+
```console
|
|
213
|
+
$ gate-cache status --json
|
|
214
|
+
{
|
|
215
|
+
"enabled": true,
|
|
216
|
+
"tree": "aaff74984cccd156a469afa7d9ab10e4777beb24",
|
|
217
|
+
"reason": null,
|
|
218
|
+
"passed": ["pytest"],
|
|
219
|
+
"missed": [
|
|
220
|
+
{"name": "ruff", "exit_code": 1, "reason": "exit code 1", "timestamp": "2026-10-12T12:31:55Z"}
|
|
221
|
+
]
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`reason` tells why gate-cache cannot match the content, and is `null` when it can. When caching is off, `enabled` is `false` and `reason` names the variable. With `--name`, the object has the `name`, `marker`, `result`, and `last_run` keys in place of `passed` and `missed`.
|
|
226
|
+
|
|
227
|
+
## Start fresh
|
|
228
|
+
|
|
229
|
+
A marker records only the content of the tracked files. 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.
|
|
230
|
+
|
|
231
|
+
`gate-cache clear` deletes every marker for the repository, and the records of missed runs:
|
|
232
|
+
|
|
233
|
+
```console
|
|
234
|
+
$ gate-cache clear
|
|
235
|
+
gate-cache: removed 3 markers
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
`GATE_CACHE_DISABLE=1` runs every command without the cache, for one command or for a whole session:
|
|
239
|
+
|
|
240
|
+
```sh
|
|
241
|
+
GATE_CACHE_DISABLE=1 git push
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Settings
|
|
245
|
+
|
|
246
|
+
Three environment variables control gate-cache.
|
|
247
|
+
|
|
248
|
+
| Variable | Effect | Default |
|
|
249
|
+
| ------------------------- | ------------------------------------------------------------------------------ | ------- |
|
|
250
|
+
| `GATE_CACHE_DISABLE` | If on, turns off caching. If `0` or `false`, keeps caching on when `CI` is on. | not set |
|
|
251
|
+
| `CI` | If on, turns off caching. A value in `GATE_CACHE_DISABLE` overrides it. | not set |
|
|
252
|
+
| `GATE_CACHE_MAX_AGE_DAYS` | gate-cache deletes markers older than this number of days. | `30` |
|
|
253
|
+
|
|
254
|
+
A variable is on when its value is not empty, `0`, or `false`. Case and spaces around the value do not matter.
|
|
255
|
+
|
|
256
|
+
Each marker is one small file in the `.git` directory of the repository, so all worktrees of the repository share the markers.
|
|
257
|
+
|
|
258
|
+
## Learn more
|
|
259
|
+
|
|
260
|
+
- [How gate-cache works](docs/how-it-works.md) describes the exact rules for a skip, what a marker covers, and the marker format for tools that read markers directly.
|
|
261
|
+
- `gate-cache --help` gives the full command reference.
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# gate-cache
|
|
2
|
+
|
|
3
|
+
Skip a command that already passed on the same code.
|
|
4
|
+
|
|
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
|
+
|
|
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 tracked files. 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
|
+
|
|
9
|
+
```console
|
|
10
|
+
$ gate-cache --name pytest -- uv run pytest
|
|
11
|
+
============================= 412 passed in 94.20s =============================
|
|
12
|
+
|
|
13
|
+
$ gate-cache --name pytest -- uv run pytest
|
|
14
|
+
gate-cache: pytest already passed on tree aaff74984ccc, skipping
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
A change to a tracked file 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
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
gate-cache requires Python 3.11 or later, on macOS or Linux.
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
uv tool install gate-cache
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The prek and pre-commit hooks install gate-cache themselves, so the hooks work without this step.
|
|
28
|
+
|
|
29
|
+
## Run a command through gate-cache
|
|
30
|
+
|
|
31
|
+
The name goes after `--name`, and the command goes after `--`:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
gate-cache --name pytest -- uv run pytest
|
|
35
|
+
gate-cache --name ruff -- uv run ruff check .
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Each name has its own markers. A command run under one name never skips because of a marker for another name.
|
|
39
|
+
|
|
40
|
+
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
|
+
|
|
42
|
+
When the working copy has uncommitted edits or files that git does not track, the content on disk is not a commit-ready snapshot. gate-cache then runs the command and writes no marker.
|
|
43
|
+
|
|
44
|
+
## Run it as a hook
|
|
45
|
+
|
|
46
|
+
gate-cache works with [prek](https://github.com/j178/prek) and with [pre-commit](https://pre-commit.com) 3.2 or later. Each `gate-cache` hook takes the same arguments as the command line:
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
repos:
|
|
50
|
+
- repo: https://github.com/natelandau/gate-cache
|
|
51
|
+
rev: v0.1.0
|
|
52
|
+
hooks:
|
|
53
|
+
- id: gate-cache
|
|
54
|
+
name: pytest
|
|
55
|
+
args: [--name, pytest, --, uv, run, pytest]
|
|
56
|
+
files: ^(src/|tests/)
|
|
57
|
+
- id: gate-cache
|
|
58
|
+
name: ruff
|
|
59
|
+
args: [--name, ruff, --, uv, run, ruff, check, .]
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The hook runs at commit time and at push time. At commit time, gate-cache matches the content that you commit.
|
|
63
|
+
|
|
64
|
+
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
|
+
|
|
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.
|
|
67
|
+
|
|
68
|
+
## Examples
|
|
69
|
+
|
|
70
|
+
These examples show gate-cache inside the task runner of a project. When a recipe holds the wrapped command, everything that runs the recipe uses the same name and the same markers.
|
|
71
|
+
|
|
72
|
+
### justfile
|
|
73
|
+
|
|
74
|
+
```just
|
|
75
|
+
test:
|
|
76
|
+
gate-cache --name test -- uv run pytest
|
|
77
|
+
|
|
78
|
+
lint:
|
|
79
|
+
gate-cache --name lint -- uv run ruff check .
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
After `just test` passes, the next `just test` on the same content skips pytest.
|
|
83
|
+
|
|
84
|
+
### Recipes with arguments
|
|
85
|
+
|
|
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. Giving each set of arguments its own name, or caching only the bare form, keeps the two apart.
|
|
87
|
+
|
|
88
|
+
This recipe caches `just test` and runs `just test -k unit` without the cache:
|
|
89
|
+
|
|
90
|
+
```just
|
|
91
|
+
test *args:
|
|
92
|
+
#!/usr/bin/env bash
|
|
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
|
+
```
|
|
99
|
+
|
|
100
|
+
### Makefile
|
|
101
|
+
|
|
102
|
+
```make
|
|
103
|
+
test:
|
|
104
|
+
gate-cache --name test -- uv run pytest
|
|
105
|
+
|
|
106
|
+
lint:
|
|
107
|
+
gate-cache --name lint -- uv run ruff check .
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### A hook that calls the recipe
|
|
111
|
+
|
|
112
|
+
A local hook can run the recipe instead of the gate-cache hook repository:
|
|
113
|
+
|
|
114
|
+
```yaml
|
|
115
|
+
repos:
|
|
116
|
+
- repo: local
|
|
117
|
+
hooks:
|
|
118
|
+
- id: test
|
|
119
|
+
name: test
|
|
120
|
+
entry: just test
|
|
121
|
+
language: system
|
|
122
|
+
pass_filenames: false
|
|
123
|
+
stages: [pre-commit, pre-push]
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
With this hook, a commit, a push, an agent, and your terminal all run `just test` and share its markers. The recipe runs in the project environment, so `uv run` prints no `VIRTUAL_ENV` warning. This form needs `gate-cache` and `just` on the `PATH`.
|
|
127
|
+
|
|
128
|
+
### CI
|
|
129
|
+
|
|
130
|
+
gate-cache turns off caching when the `CI` variable is on. A variable is on when its value is not empty, `0`, or `false`. Case and spaces around the value do not matter. Many CI systems set `CI=true` in every job, for example GitHub Actions and GitLab CI/CD. Jenkins does not set `CI`.
|
|
131
|
+
|
|
132
|
+
When caching is off, gate-cache runs the command each time and writes no marker.
|
|
133
|
+
|
|
134
|
+
`GATE_CACHE_DISABLE` overrides `CI`. To keep caching on when `CI` is on, set `GATE_CACHE_DISABLE` to `0` or `false`:
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
GATE_CACHE_DISABLE=0 just test
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
To turn off caching when `CI` is not set, set `GATE_CACHE_DISABLE` to `1`:
|
|
141
|
+
|
|
142
|
+
```sh
|
|
143
|
+
GATE_CACHE_DISABLE=1 just test
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## See what already passed
|
|
147
|
+
|
|
148
|
+
`gate-cache status` lists the names that passed on the current content, and the names that ran on it without a pass:
|
|
149
|
+
|
|
150
|
+
```console
|
|
151
|
+
$ gate-cache status
|
|
152
|
+
tree: aaff74984cccd156a469afa7d9ab10e4777beb24
|
|
153
|
+
passed: pytest
|
|
154
|
+
missed: ruff (exit code 1)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
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
|
+
|
|
159
|
+
With `--name`, it reports on one name:
|
|
160
|
+
|
|
161
|
+
```console
|
|
162
|
+
$ gate-cache status --name ruff
|
|
163
|
+
name: ruff
|
|
164
|
+
tree: aaff74984cccd156a469afa7d9ab10e4777beb24
|
|
165
|
+
marker: /path/to/repo/.git/gate-cache/7d94ca2f57ec0a77f60d328fb405d339fb0c2a645991b97c042ac54403104e9e
|
|
166
|
+
result: miss
|
|
167
|
+
last run: exit code 1
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The `last run` line shows only when the name ran on this content without a pass.
|
|
171
|
+
|
|
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 because of uncommitted edits, the `tree` line gives the reason.
|
|
173
|
+
|
|
174
|
+
With `--json`, `status` writes one JSON object to stdout instead of text. The exit codes do not change:
|
|
175
|
+
|
|
176
|
+
```console
|
|
177
|
+
$ gate-cache status --json
|
|
178
|
+
{
|
|
179
|
+
"enabled": true,
|
|
180
|
+
"tree": "aaff74984cccd156a469afa7d9ab10e4777beb24",
|
|
181
|
+
"reason": null,
|
|
182
|
+
"passed": ["pytest"],
|
|
183
|
+
"missed": [
|
|
184
|
+
{"name": "ruff", "exit_code": 1, "reason": "exit code 1", "timestamp": "2026-10-12T12:31:55Z"}
|
|
185
|
+
]
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`reason` tells why gate-cache cannot match the content, and is `null` when it can. When caching is off, `enabled` is `false` and `reason` names the variable. With `--name`, the object has the `name`, `marker`, `result`, and `last_run` keys in place of `passed` and `missed`.
|
|
190
|
+
|
|
191
|
+
## Start fresh
|
|
192
|
+
|
|
193
|
+
A marker records only the content of the tracked files. 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.
|
|
194
|
+
|
|
195
|
+
`gate-cache clear` deletes every marker for the repository, and the records of missed runs:
|
|
196
|
+
|
|
197
|
+
```console
|
|
198
|
+
$ gate-cache clear
|
|
199
|
+
gate-cache: removed 3 markers
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`GATE_CACHE_DISABLE=1` runs every command without the cache, for one command or for a whole session:
|
|
203
|
+
|
|
204
|
+
```sh
|
|
205
|
+
GATE_CACHE_DISABLE=1 git push
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Settings
|
|
209
|
+
|
|
210
|
+
Three environment variables control gate-cache.
|
|
211
|
+
|
|
212
|
+
| Variable | Effect | Default |
|
|
213
|
+
| ------------------------- | ------------------------------------------------------------------------------ | ------- |
|
|
214
|
+
| `GATE_CACHE_DISABLE` | If on, turns off caching. If `0` or `false`, keeps caching on when `CI` is on. | not set |
|
|
215
|
+
| `CI` | If on, turns off caching. A value in `GATE_CACHE_DISABLE` overrides it. | not set |
|
|
216
|
+
| `GATE_CACHE_MAX_AGE_DAYS` | gate-cache deletes markers older than this number of days. | `30` |
|
|
217
|
+
|
|
218
|
+
A variable is on when its value is not empty, `0`, or `false`. Case and spaces around the value do not matter.
|
|
219
|
+
|
|
220
|
+
Each marker is one small file in the `.git` directory of the repository, so all worktrees of the repository share the markers.
|
|
221
|
+
|
|
222
|
+
## Learn more
|
|
223
|
+
|
|
224
|
+
- [How gate-cache works](docs/how-it-works.md) describes the exact rules for a skip, what a marker covers, and the marker format for tools that read markers directly.
|
|
225
|
+
- `gate-cache --help` gives the full command reference.
|
|
@@ -13,7 +13,7 @@ description = "Skip lint and test gates that already passed on the exact same gi
|
|
|
13
13
|
name = "gate-cache"
|
|
14
14
|
readme = "README.md"
|
|
15
15
|
requires-python = ">=3.11"
|
|
16
|
-
version = "0.
|
|
16
|
+
version = "0.3.0"
|
|
17
17
|
|
|
18
18
|
[[project.authors]]
|
|
19
19
|
name = "Nate Landau"
|
|
@@ -36,6 +36,7 @@ module-name = "gate_cache"
|
|
|
36
36
|
annotated_tag = true
|
|
37
37
|
bump_message = "bump(release): v$current_version → v$new_version"
|
|
38
38
|
changelog_merge_prerelease = true
|
|
39
|
+
major_version_zero = true
|
|
39
40
|
tag_format = "v$version"
|
|
40
41
|
update_changelog_on_bump = true
|
|
41
42
|
version_files = ["src/gate_cache/__init__.py:__version__"]
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
name = "gate-cache"
|
|
13
13
|
readme = "README.md"
|
|
14
14
|
requires-python = ">=3.11"
|
|
15
|
-
version = "0.
|
|
15
|
+
version = "0.3.0"
|
|
16
16
|
|
|
17
17
|
[project.scripts]
|
|
18
18
|
gate-cache = "gate_cache.cli:main"
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
annotated_tag = true
|
|
52
52
|
bump_message = "bump(release): v$current_version → v$new_version"
|
|
53
53
|
changelog_merge_prerelease = true
|
|
54
|
+
major_version_zero = true
|
|
54
55
|
tag_format = "v$version"
|
|
55
56
|
update_changelog_on_bump = true
|
|
56
57
|
version_files = ["src/gate_cache/__init__.py:__version__"]
|
|
@@ -30,10 +30,13 @@ Exit codes:
|
|
|
30
30
|
run the command's own exit code, or 0 when a recorded pass lets it skip.
|
|
31
31
|
Usage errors exit 2.
|
|
32
32
|
status 0 when the named gate, or any gate without --name, passed on the
|
|
33
|
-
current tree. 1 otherwise.
|
|
33
|
+
current tree. 1 otherwise. --json prints one JSON object instead.
|
|
34
34
|
clear 0 on success, 1 on failure.
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
GATE_CACHE_DISABLE and CI are on when the value is not empty, 0, or false (any
|
|
37
|
+
case). If GATE_CACHE_DISABLE is on, the command always runs without the cache.
|
|
38
|
+
If it is 0 or false, caching stays on. If it is empty or not set, CI decides the
|
|
39
|
+
same way.
|
|
37
40
|
Set GATE_CACHE_MAX_AGE_DAYS to a positive whole number to change how many days a
|
|
38
41
|
recorded pass lasts (default 30).
|
|
39
42
|
|
|
@@ -51,7 +54,7 @@ app = App(
|
|
|
51
54
|
help_format="plaintext",
|
|
52
55
|
usage=(
|
|
53
56
|
"Usage: gate-cache --name NAME -- COMMAND [ARGS...]\n"
|
|
54
|
-
" gate-cache status [--name NAME]\n"
|
|
57
|
+
" gate-cache status [--name NAME] [--json]\n"
|
|
55
58
|
" gate-cache clear"
|
|
56
59
|
),
|
|
57
60
|
result_action="return_int_as_exit_code_else_zero",
|
|
@@ -71,8 +74,8 @@ def run(
|
|
|
71
74
|
|
|
72
75
|
On a skip, prints one line to stderr and exits 0 without running anything. Otherwise
|
|
73
76
|
runs the command and exits with its exit code. The command runs without caching
|
|
74
|
-
(and nothing is recorded) when GATE_CACHE_DISABLE
|
|
75
|
-
changes or untracked files, or this is not a git repository. A pass is recorded only
|
|
77
|
+
(and nothing is recorded) when GATE_CACHE_DISABLE or CI turns caching off, the tree
|
|
78
|
+
has unstaged changes or untracked files, or this is not a git repository. A pass is recorded only
|
|
76
79
|
when the command exits 0 and the run leaves the tree unchanged.
|
|
77
80
|
|
|
78
81
|
Parameters
|
|
@@ -89,21 +92,28 @@ def run(
|
|
|
89
92
|
|
|
90
93
|
|
|
91
94
|
@app.command
|
|
92
|
-
def status(
|
|
95
|
+
def status(
|
|
96
|
+
*,
|
|
97
|
+
name: str | None = None,
|
|
98
|
+
as_json: Annotated[bool, Parameter(name="--json", negative="")] = False,
|
|
99
|
+
) -> int:
|
|
93
100
|
"""Report whether gates would skip, without running anything.
|
|
94
101
|
|
|
95
102
|
With --name, print the tree hash (or why it is unknown), the marker path,
|
|
96
|
-
|
|
103
|
+
hit or miss, and how the last run on this tree failed when it did. Exit 0 on a
|
|
104
|
+
hit, 1 otherwise.
|
|
97
105
|
|
|
98
|
-
Without --name, list
|
|
99
|
-
Exit 0 when at least one gate
|
|
106
|
+
Without --name, list the gates that passed on the current tree and the gates
|
|
107
|
+
that ran on it without passing. Exit 0 when at least one gate passed, 1 otherwise.
|
|
100
108
|
|
|
101
109
|
Parameters
|
|
102
110
|
----------
|
|
103
111
|
name
|
|
104
|
-
Gate name to look up. Omit it to list every gate that
|
|
112
|
+
Gate name to look up. Omit it to list every gate that ran on this tree.
|
|
113
|
+
as_json
|
|
114
|
+
Print one JSON object to stdout instead of text. Exit codes are unchanged.
|
|
105
115
|
"""
|
|
106
|
-
return gate_status(name)
|
|
116
|
+
return gate_status(name, as_json=as_json)
|
|
107
117
|
|
|
108
118
|
|
|
109
119
|
@app.command
|