repoactive 0.2.8.dev2__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. {repoactive-0.2.8.dev2 → repoactive-0.3.1}/PKG-INFO +418 -152
  2. {repoactive-0.2.8.dev2 → repoactive-0.3.1}/README.md +417 -151
  3. {repoactive-0.2.8.dev2 → repoactive-0.3.1}/pyproject.toml +1 -1
  4. repoactive-0.3.1/src/repoactive/boxquote.py +51 -0
  5. {repoactive-0.2.8.dev2 → repoactive-0.3.1}/src/repoactive/cli.py +1 -1
  6. {repoactive-0.2.8.dev2 → repoactive-0.3.1}/src/repoactive/config.py +151 -5
  7. {repoactive-0.2.8.dev2 → repoactive-0.3.1}/src/repoactive/jj.py +12 -1
  8. {repoactive-0.2.8.dev2 → repoactive-0.3.1}/src/repoactive/runner.py +170 -23
  9. repoactive-0.2.8.dev2/src/repoactive/boxquote.py +0 -32
  10. {repoactive-0.2.8.dev2 → repoactive-0.3.1}/.gitignore +0 -0
  11. {repoactive-0.2.8.dev2 → repoactive-0.3.1}/LICENSE +0 -0
  12. {repoactive-0.2.8.dev2 → repoactive-0.3.1}/src/repoactive/__init__.py +0 -0
  13. {repoactive-0.2.8.dev2 → repoactive-0.3.1}/src/repoactive/constants.py +0 -0
  14. {repoactive-0.2.8.dev2 → repoactive-0.3.1}/src/repoactive/graph.py +0 -0
  15. {repoactive-0.2.8.dev2 → repoactive-0.3.1}/src/repoactive/jobtree.py +0 -0
  16. {repoactive-0.2.8.dev2 → repoactive-0.3.1}/src/repoactive/lock.py +0 -0
  17. {repoactive-0.2.8.dev2 → repoactive-0.3.1}/src/repoactive/platforms/__init__.py +0 -0
  18. {repoactive-0.2.8.dev2 → repoactive-0.3.1}/src/repoactive/platforms/base.py +0 -0
  19. {repoactive-0.2.8.dev2 → repoactive-0.3.1}/src/repoactive/platforms/github.py +0 -0
  20. {repoactive-0.2.8.dev2 → repoactive-0.3.1}/src/repoactive/platforms/gitlab.py +0 -0
  21. {repoactive-0.2.8.dev2 → repoactive-0.3.1}/src/repoactive/progress.py +0 -0
  22. {repoactive-0.2.8.dev2 → repoactive-0.3.1}/src/repoactive/selection.py +0 -0
  23. {repoactive-0.2.8.dev2 → repoactive-0.3.1}/src/repoactive/settings.py +0 -0
  24. {repoactive-0.2.8.dev2 → repoactive-0.3.1}/src/repoactive/trailers.py +0 -0
  25. {repoactive-0.2.8.dev2 → repoactive-0.3.1}/src/repoactive/ui.py +0 -0
  26. {repoactive-0.2.8.dev2 → repoactive-0.3.1}/src/repoactive/updates.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: repoactive
3
- Version: 0.2.8.dev2
3
+ Version: 0.3.1
4
4
  Summary: Script-driven code changes with automated merge requests
5
5
  License-Expression: GPL-3.0-or-later
6
6
  License-File: LICENSE
@@ -27,27 +27,37 @@ Description-Content-Type: text/markdown
27
27
 
28
28
  # repoactive - Script-driven code changes with automated merge requests
29
29
 
30
+ [![PyPI](https://img.shields.io/pypi/v/repoactive.svg)](https://pypi.org/project/repoactive/)
31
+ [![CI](https://github.com/schmir/repoactive/actions/workflows/ci.yml/badge.svg)](https://github.com/schmir/repoactive/actions/workflows/ci.yml)
32
+ [![Python versions](https://img.shields.io/pypi/pyversions/repoactive.svg)](https://pypi.org/project/repoactive/)
33
+ [![License](https://img.shields.io/pypi/l/repoactive.svg)](LICENSE)
34
+
30
35
  > **Warning:** This project is in an early stage of development. Use at your
31
36
  > own risk.
32
37
 
33
38
  `repoactive` runs your scripts against a git repository and optionally keeps
34
39
  the corresponding merge requests up to date. You write the scripts that
35
40
  produce the code changes; `repoactive` handles the rest - branches, commits,
36
- and (with `--mode publish`) the full MR lifecycle.
41
+ and (with `--mode publish`) the full MR lifecycle. This document says merge
42
+ request (MR) throughout; on GitHub, read that as pull request (PR).
37
43
 
38
44
  ## Contents
39
45
 
40
46
  - [Use cases](#use-cases)
47
+ - [How it compares](#how-it-compares)
41
48
  - [Installation](#installation)
42
49
  - [Quick start](#quick-start)
43
50
  - [How it works](#how-it-works)
51
+ - [What a run produces](#what-a-run-produces)
44
52
  - [Keeping the local clone current](#keeping-the-local-clone-current)
45
- - [Usage](#usage)
53
+ - [Undoing a run](#undoing-a-run)
54
+ - [Commands](#commands)
55
+ - [`repoactive run`](#repoactive-run)
56
+ - [`repoactive recent-commits`](#repoactive-recent-commits)
57
+ - [`repoactive validate-config`](#repoactive-validate-config)
58
+ - [`repoactive info jobs`](#repoactive-info-jobs)
59
+ - [`repoactive info tags`](#repoactive-info-tags)
46
60
  - [Environment variables](#environment-variables)
47
- - [Inspecting repoactive commits](#inspecting-repoactive-commits)
48
- - [Validating configuration](#validating-configuration)
49
- - [Listing jobs](#listing-jobs)
50
- - [Listing tags](#listing-tags)
51
61
  - [Configuration](#configuration)
52
62
  - [`[job-defaults]`](#job-defaults)
53
63
  - [`[job.<name>]`](#jobname)
@@ -55,20 +65,23 @@ and (with `--mode publish`) the full MR lifecycle.
55
65
  - [Example](#example)
56
66
  - [`[platform.<name>]`](#platformname)
57
67
  - [Config file locations](#config-file-locations)
68
+ - [Variables passed to job commands](#variables-passed-to-job-commands)
69
+ - [Passing secrets to commands with `secret_env`](#passing-secrets-to-commands-with-secret_env)
58
70
  - [Overriding values on the command line](#overriding-values-on-the-command-line)
59
- - [Selecting jobs with tags](#selecting-jobs-with-tags)
60
- - [Keeping unmerged branches current](#keeping-unmerged-branches-current)
61
- - [Disabling jobs](#disabling-jobs)
62
- - [Running a job on a schedule](#running-a-job-on-a-schedule)
63
- - [One run at a time per repository](#one-run-at-a-time-per-repository)
64
- - [Gating jobs with `run_only_if_changed`](#gating-jobs-with-run_only_if_changed)
65
- - [Throttling jobs with `cooldown_period`](#throttling-jobs-with-cooldown_period)
66
- - [Throttling a job when a superset lands with `cooldown_on`](#throttling-a-job-when-a-superset-lands-with-cooldown_on)
67
- - [Limiting job runtime with `timeout`](#limiting-job-runtime-with-timeout)
68
- - [Generating jobs dynamically](#generating-jobs-dynamically)
71
+ - [Run control](#run-control)
72
+ - [Selecting jobs with tags](#selecting-jobs-with-tags)
73
+ - [Disabling jobs](#disabling-jobs)
74
+ - [Running a job on a schedule](#running-a-job-on-a-schedule)
75
+ - [Gating jobs with `run_only_if_changed`](#gating-jobs-with-run_only_if_changed)
76
+ - [Throttling jobs with `cooldown_period`](#throttling-jobs-with-cooldown_period)
77
+ - [Throttling a job when a superset lands with `cooldown_on`](#throttling-a-job-when-a-superset-lands-with-cooldown_on)
78
+ - [Limiting job runtime with `timeout`](#limiting-job-runtime-with-timeout)
79
+ - [Generating jobs dynamically](#generating-jobs-dynamically)
69
80
  - [Requirements](#requirements)
70
81
  - [Appendix](#appendix)
71
82
  - [jj revset aliases](#jj-revset-aliases)
83
+ - [Changelog](#changelog)
84
+ - [License](#license)
72
85
 
73
86
  ## Use cases
74
87
 
@@ -79,6 +92,23 @@ and (with `--mode publish`) the full MR lifecycle.
79
92
  - Automating any periodic code transformation that should go through a
80
93
  review process
81
94
 
95
+ ### How it compares
96
+
97
+ [Renovate](https://docs.renovatebot.com/) and
98
+ [Dependabot](https://docs.github.com/en/code-security/dependabot) are built
99
+ around dependency updates; repoactive runs whatever script you write, so the
100
+ change can be anything the repository generates - a lock-file upgrade is
101
+ just the simplest case.
102
+ [multi-gitter](https://github.com/lindell/multi-gitter) also runs your
103
+ script and opens the MR, but as a one-shot bulk operation across
104
+ repositories; repoactive is built for re-running against one repository: an
105
+ unchanged diff leaves the MR untouched, a changed one updates it, branches
106
+ are rebased onto the moving base branch, cooldowns keep a landed change from
107
+ immediately reopening, and jobs can stack their MRs on one another. And it
108
+ is a plain CLI working against your local clone - no hosted service and
109
+ nothing to install into the repository - that talks to GitHub and GitLab,
110
+ including self-hosted instances.
111
+
82
112
  ## Installation
83
113
 
84
114
  `repoactive` is published on [PyPI](https://pypi.org/project/repoactive/).
@@ -123,7 +153,7 @@ needs a GitHub or GitLab API token in the environment. See
123
153
  4. **Run the job locally.** In the default `local` mode nothing is pushed
124
154
  and no MR is created - repoactive just records the diff your script
125
155
  produced on the branch `repoactive/uv-lock-upgrade` and prints a
126
- `jj op restore` command to undo the run:
156
+ `jj op restore` command to [undo the run](#undoing-a-run):
127
157
 
128
158
  ```bash
129
159
  repoactive run
@@ -156,14 +186,6 @@ With `--mode publish` it also:
156
186
  Branches and MR descriptions are managed automatically - the only code you
157
187
  need to write is the script that produces the change.
158
188
 
159
- ```
160
- [your script] → diff → repoactive → branch
161
- ↓ (with --mode push or --mode publish)
162
- git push → merge request
163
- ↑ (with --mode publish)
164
- (create or update)
165
- ```
166
-
167
189
  1. `repoactive` creates a new commit on top of the base branch or on top of
168
190
  other repoactive managed branches.
169
191
  2. It runs the job's script against the working tree.
@@ -188,6 +210,46 @@ need to write is the script that produces the change.
188
210
  > `.gitignore` up to date so build artifacts, caches, and other stray files
189
211
  > your script produces do not end up in the diff.
190
212
 
213
+ ### What a run produces
214
+
215
+ For the quick-start job above, a `--mode publish` run pushes the branch
216
+ `repoactive/uv-lock-upgrade` and opens an MR titled
217
+ `[repoactive] build: upgrade dependencies`. Its description holds the job's
218
+ `description` (when set), the command and its captured output in a code
219
+ block, and - for [stacked jobs](#stacking-mrs) - links to the parent MRs
220
+ ([a real example](https://github.com/schmir/repoactive/pull/7)):
221
+
222
+ ```
223
+ $ uv lock --upgrade
224
+ Using CPython 3.14.1
225
+ Resolved 51 packages in 4.19s
226
+ Updated annotated-types v0.7.0 -> v0.8.0
227
+ Updated nox v2026.4.10 -> v2026.7.11
228
+ Updated ruff v0.15.21 -> v0.16.0
229
+ Updated ty v0.0.58 -> v0.0.63
230
+ ```
231
+
232
+ The commit carries the same output (unless `output_in_commit` is disabled)
233
+ plus the `Repoactive-Job` trailer that
234
+ [`recent-commits`](#repoactive-recent-commits), the
235
+ [cooldown check](#throttling-jobs-with-cooldown_period), and the
236
+ [unmerged-branch refresh](#keeping-unmerged-branches-current) all key on:
237
+
238
+ ```
239
+ [repoactive] build: upgrade dependencies
240
+
241
+ ,----[ uv lock --upgrade ]
242
+ | Using CPython 3.14.1
243
+ | Resolved 51 packages in 4.19s
244
+ | Updated annotated-types v0.7.0 -> v0.8.0
245
+ | Updated nox v2026.4.10 -> v2026.7.11
246
+ | Updated ruff v0.15.21 -> v0.16.0
247
+ | Updated ty v0.0.58 -> v0.0.63
248
+ `----
249
+
250
+ Repoactive-Job: uv-lock-upgrade
251
+ ```
252
+
191
253
  ### Keeping the local clone current
192
254
 
193
255
  `repoactive` works entirely from the **local** repository view and never
@@ -204,13 +266,39 @@ because the commit that would trigger it has not reached the local base
204
266
  branch. See
205
267
  [ADR 0005](docs/adr/0005-local-repository-is-the-source-of-truth.md).
206
268
 
207
- ## Usage
269
+ ### Undoing a run
270
+
271
+ Every `run` captures the jj operation id beforehand and prints a
272
+ `jj op restore <id>` command at the end of the run (last, since a run can
273
+ produce a lot of output). Run it to roll the local repository - commits,
274
+ bookmarks and colocated git refs - back to the state it was in before the
275
+ run. It only undoes local changes: a branch already pushed or an MR already
276
+ created by a `--mode push`/`--mode publish` run is not affected, as the hint
277
+ panel itself points out.
278
+
279
+ ## Commands
208
280
 
209
281
  ```bash
210
282
  # Print the installed version and exit
211
283
  repoactive --version
212
284
  ```
213
285
 
286
+ Every command accepts `--repo`/`-r` and `--debug`/`-d`. The commands that
287
+ read configuration - `run`, `validate-config`, `info jobs`, and
288
+ `info tags` - also accept the same `--config`/`-c` and `--set`/`-s` options,
289
+ described in the [`repoactive run`](#repoactive-run) table below.
290
+ `recent-commits` works from the repository alone and reads no configuration.
291
+
292
+ All commands exit with these status codes:
293
+
294
+ | Code | Meaning |
295
+ | ---- | ------------------------------------------------------------------------------------------ |
296
+ | 0 | Success |
297
+ | 1 | Failure (invalid configuration, a failed job, ...) |
298
+ | 2 | `run` only: another run holds the [per-repository lock](#one-run-at-a-time-per-repository) |
299
+
300
+ ### `repoactive run`
301
+
214
302
  ```
215
303
  repoactive run [OPTIONS] [JOBS]...
216
304
  ```
@@ -247,64 +335,7 @@ repoactive run --debug
247
335
  | `--tag TAG` | `-t` | Run jobs carrying any of these tags (repeatable). With no tags/jobs the default run targets the `enabled` tag |
248
336
  | `--debug` | `-d` | Enable debug logging |
249
337
 
250
- ### Environment variables
251
-
252
- Besides the command-line options, repoactive reads a few `REPOACTIVE_*`
253
- environment variables that tune how it presents itself in the current
254
- environment (as opposed to the [configuration](#configuration), which
255
- describes the jobs to run). They are handled uniformly: all of them go
256
- through a single settings model and are validated together at startup, so a
257
- misconfigured variable fails immediately with a one-line error naming it
258
- instead of surfacing mid-run or being silently ignored. Values are
259
- case-insensitive.
260
-
261
- #### `REPOACTIVE_UI`
262
-
263
- `interactive` (default) or `noninteractive`.
264
-
265
- Every `run` captures the jj operation id beforehand and prints a
266
- `jj op restore <id>` command at the end of the run (last, since a run can
267
- produce a lot of output). Run it to roll the local repository - commits,
268
- bookmarks and colocated git refs - back to the state it was in before the
269
- run. It only undoes local changes: a branch already pushed or an MR already
270
- created by a `--mode push`/`--mode publish` run is not affected, as the hint
271
- panel itself points out.
272
-
273
- `noninteractive` suppresses these "how to undo" hint panels. Set it where
274
- nobody is at the keyboard, say an unattended CI job. This is an explicit
275
- switch rather than CI auto-detection, because a CI container someone has
276
- logged in to _is_ interactive.
277
-
278
- #### `REPOACTIVE_LOG_LEVEL`
279
-
280
- `debug`, `info`, `warning`, `error`, or `critical`; unset by default
281
- (logging off).
282
-
283
- Enables logging at that level without passing `--debug` on every invocation;
284
- an explicit `--debug` takes precedence.
285
-
286
- #### `REPOACTIVE_LOG_HANDLER`
287
-
288
- `rich` or `plain`; defaults to `rich`, or `plain` when `REPOACTIVE_UI` is
289
- `noninteractive`.
290
-
291
- How log records are rendered: a colorized column layout via
292
- [rich](https://github.com/Textualize/rich), or the stdlib's plain stream
293
- handler (e.g. when the output is collected by a log aggregator).
294
-
295
- #### `REPOACTIVE_PROGRESS_LINES`
296
-
297
- An integer (default: `8`).
298
-
299
- While a job's command runs in an interactive terminal, repoactive shows a
300
- live, scrolling block of its most recent output lines. The block stays on
301
- screen once the command finishes, with the job's status line printed below
302
- it. `REPOACTIVE_PROGRESS_LINES` sets how many lines that live tail shows;
303
- `0` or less disables the live block entirely. When output is not a terminal
304
- (piped or in CI) the block is disabled and the command's output is left
305
- untouched.
306
-
307
- ## Inspecting repoactive commits
338
+ ### `repoactive recent-commits`
308
339
 
309
340
  ```
310
341
  repoactive recent-commits [OPTIONS] [JOBS]...
@@ -337,7 +368,7 @@ repoactive recent-commits --status unmerged
337
368
  | `--status [all\|merged\|unmerged]` | `-s` | Filter by merge status into trunk (default: `all`) |
338
369
  | `--debug` | `-d` | Enable debug logging |
339
370
 
340
- ## Validating configuration
371
+ ### `repoactive validate-config`
341
372
 
342
373
  ```
343
374
  repoactive validate-config [OPTIONS]
@@ -364,10 +395,7 @@ code 1.
364
395
  Validation checks include unknown keys, missing required fields, invalid
365
396
  `depends_on` references, and circular job dependencies.
366
397
 
367
- The command accepts the same `--config`, `--set`, `--repo`, and `--debug`
368
- options as [`repoactive run`](#usage).
369
-
370
- ## Listing jobs
398
+ ### `repoactive info jobs`
371
399
 
372
400
  ```
373
401
  repoactive info jobs [OPTIONS]
@@ -391,10 +419,7 @@ build Build the project enabled
391
419
  └── deploy Deploy to staging nightly, risky
392
420
  ```
393
421
 
394
- The command accepts the same `--config`, `--set`, `--repo`, and `--debug`
395
- options as [`repoactive run`](#usage).
396
-
397
- ## Listing tags
422
+ ### `repoactive info tags`
398
423
 
399
424
  ```
400
425
  repoactive info tags [OPTIONS]
@@ -426,8 +451,54 @@ nightly:
426
451
  benchmark Run nightly benchmarks nightly
427
452
  ```
428
453
 
429
- The command accepts the same `--config`, `--set`, `--repo`, and `--debug`
430
- options as [`repoactive run`](#usage).
454
+ ### Environment variables
455
+
456
+ Besides the command-line options, repoactive reads a few `REPOACTIVE_*`
457
+ environment variables that tune how it presents itself in the current
458
+ environment (as opposed to the [configuration](#configuration), which
459
+ describes the jobs to run). They are handled uniformly: all of them go
460
+ through a single settings model and are validated together at startup, so a
461
+ misconfigured variable fails immediately with a one-line error naming it
462
+ instead of surfacing mid-run or being silently ignored. Values are
463
+ case-insensitive.
464
+
465
+ #### `REPOACTIVE_UI`
466
+
467
+ `interactive` (default) or `noninteractive`.
468
+
469
+ `noninteractive` suppresses the ["how to undo" hint panels](#undoing-a-run)
470
+ printed after each run. Set it where nobody is at the keyboard, say an
471
+ unattended CI job. This is an explicit switch rather than CI auto-detection,
472
+ because a CI container someone has logged in to _is_ interactive.
473
+
474
+ #### `REPOACTIVE_LOG_LEVEL`
475
+
476
+ `debug`, `info`, `warning`, `error`, or `critical`; unset by default
477
+ (logging off).
478
+
479
+ Enables logging at that level without passing `--debug` on every invocation;
480
+ an explicit `--debug` takes precedence.
481
+
482
+ #### `REPOACTIVE_LOG_HANDLER`
483
+
484
+ `rich` or `plain`; defaults to `rich`, or `plain` when `REPOACTIVE_UI` is
485
+ `noninteractive`.
486
+
487
+ How log records are rendered: a colorized column layout via
488
+ [rich](https://github.com/Textualize/rich), or the stdlib's plain stream
489
+ handler (e.g. when the output is collected by a log aggregator).
490
+
491
+ #### `REPOACTIVE_PROGRESS_LINES`
492
+
493
+ An integer (default: `8`).
494
+
495
+ While a job's command runs in an interactive terminal, repoactive shows a
496
+ live, scrolling block of its most recent output lines. The block stays on
497
+ screen once the command finishes, with the job's status line printed below
498
+ it. `REPOACTIVE_PROGRESS_LINES` sets how many lines that live tail shows;
499
+ `0` or less disables the live block entirely. When output is not a terminal
500
+ (piped or in CI) the block is disabled and the command's output is left
501
+ untouched.
431
502
 
432
503
  ## Configuration
433
504
 
@@ -450,14 +521,13 @@ block.
450
521
 
451
522
  ### `[job-defaults]`
452
523
 
453
- **MR/PR options:**
524
+ **MR options:**
454
525
 
455
- - **`labels`** (default: `[]`) - Labels applied to every MR/PR. Per-job
526
+ - **`labels`** (default: `[]`) - Labels applied to every MR. Per-job
456
527
  `labels` are merged with, not replaced by, this list.
457
528
  - **`auto_merge`** (default: `false`) - When `true`, enable auto-merge on
458
- every MR/PR so it merges automatically once its pipeline passes. On
459
- GitHub, the repository must have "Allow auto-merge" enabled in its
460
- settings.
529
+ every MR so it merges automatically once its pipeline passes. On GitHub,
530
+ the repository must have "Allow auto-merge" enabled in its settings.
461
531
  - **`required_approvals`** (default: none) - Minimum number of approvals
462
532
  required before the MR can be merged. **GitLab only** - this sets the
463
533
  per-MR approval requirement. GitHub has no per-PR equivalent (required
@@ -467,13 +537,17 @@ block.
467
537
  **Branch and commit options:**
468
538
 
469
539
  - **`branch_prefix`** (default: `"repoactive/"`) - Prefix prepended to the
470
- job name to form the branch name.
540
+ job name to form the branch name. Set to `""` to use the job name alone.
541
+ With an empty prefix the branch name is exactly the job name, so make sure
542
+ no job is named after your base branch (e.g. a job named `main`) - the two
543
+ branches would otherwise collide.
471
544
  - **`mr_title_prefix`** (default: `"[repoactive] "`) - Prefix prepended to
472
- every MR/PR title. Set to `""` to disable.
545
+ every MR title. Set to `""` to disable.
473
546
  - **`commit_title_prefix`** (default: `"[repoactive] "`) - Prefix prepended
474
547
  to every commit title. Set to `""` to disable.
475
548
  - **`base_branch`** (default: repo default) - Target branch for all jobs
476
- that do not set their own.
549
+ that do not set their own. May also be a jj revset expression such as
550
+ `trunk()`, `root()`, or a user-defined revset alias.
477
551
 
478
552
  **Run control:**
479
553
 
@@ -484,6 +558,18 @@ block.
484
558
  - **`timeout`** (default: `"2m"`) - Maximum runtime for a job's command.
485
559
  Same format as `cooldown_period`. See
486
560
  [Limiting job runtime with `timeout`](#limiting-job-runtime-with-timeout).
561
+ - **`shell`** (default: `/bin/sh`) - Interpreter used to run job commands. A
562
+ bare name is resolved on `PATH` (e.g. `"bash"`); an absolute path also
563
+ works. The command runs as `<shell> -c <command>`. Arguments are not
564
+ allowed - the value must be a single binary.
565
+
566
+ **Secrets:**
567
+
568
+ - **`secret_env`** (default: `[]`) - Names of environment variables to mark
569
+ as secrets config-wide. Each marked name is stripped from every job
570
+ command's environment, but a job still reads one only by listing it in its
571
+ own `secret_env`; `job-defaults` marks names, it does not grant them. See
572
+ [Passing secrets to commands with `secret_env`](#passing-secrets-to-commands-with-secret_env).
487
573
 
488
574
  ### `[job.<name>]`
489
575
 
@@ -495,34 +581,35 @@ underscores.
495
581
 
496
582
  - **`command`** - Shell command (or executable path) run in the repository
497
583
  working directory. A non-zero exit is a failure.
498
- - **`title`** - MR/PR title (also the commit subject, after
584
+ - **`title`** - MR title (also the commit subject, after
499
585
  `commit_title_prefix`).
500
586
 
501
- **MR/PR options:**
587
+ **MR options:**
502
588
 
503
- - **`description`** - Body text of the MR/PR.
504
- - **`labels`** (default: `[]`) - Extra labels for this job's MR/PR, merged
505
- with `job-defaults.labels`.
506
- - **`draft`** (default: `false`) - Open the MR/PR as a draft. On GitHub,
507
- draft state cannot be changed after creation.
508
- - **`create_mr`** (default: `true`) - Whether to create an MR/PR: `true`
589
+ - **`description`** - Body text of the MR.
590
+ - **`labels`** (default: `[]`) - Extra labels for this job's MR, merged with
591
+ `job-defaults.labels`.
592
+ - **`draft`** (default: `false`) - Open the MR as a draft. On GitHub, draft
593
+ state cannot be changed after creation.
594
+ - **`create_mr`** (default: `true`) - Whether to create an MR: `true`
509
595
  (always), `false` (push the branch but skip the MR), or
510
596
  `"unless-superseded"` (skip when a dependent's MR from the same run
511
597
  already contains this job's changes - see [Stacking MRs](#stacking-mrs)).
512
598
  - **`auto_merge`** (default: inherited from `job-defaults`) - When `true`,
513
- enable auto-merge on this job's MR/PR.
599
+ enable auto-merge on this job's MR.
514
600
  - **`required_approvals`** (default: inherited from `job-defaults`) -
515
601
  Minimum number of approvals required before this job's MR can be merged.
516
602
  GitLab only; see `job-defaults.required_approvals`.
517
603
 
518
604
  **Branch and commit options:**
519
605
 
520
- - **`base_branch`** (default: inherited) - Target branch for this job's
521
- MR/PR.
606
+ - **`base_branch`** (default: inherited) - Target branch for this job's MR.
607
+ May also be a jj revset expression such as `trunk()`, `root()`, or a
608
+ user-defined revset alias.
522
609
  - **`branch_prefix`** (default: inherited) - Override the branch-name prefix
523
610
  for this job only.
524
- - **`mr_title_prefix`** (default: inherited) - Override the MR/PR title
525
- prefix for this job only.
611
+ - **`mr_title_prefix`** (default: inherited) - Override the MR title prefix
612
+ for this job only.
526
613
  - **`commit_title_prefix`** (default: inherited) - Override the commit title
527
614
  prefix for this job only.
528
615
  - **`output_in_commit`** (default: `true`) - Append the job's command and
@@ -553,9 +640,20 @@ underscores.
553
640
  - **`timeout`** (default: inherited) - Maximum runtime for this job's
554
641
  command. Set to `"0s"` to disable the timeout entirely. See
555
642
  [Limiting job runtime with `timeout`](#limiting-job-runtime-with-timeout).
643
+ - **`shell`** (default: inherited) - Interpreter used to run this job's
644
+ command, overriding `job-defaults.shell`. See
645
+ [`job-defaults.shell`](#job-defaults).
556
646
  - **`emits_jobs`** (default: `false`) - Generator job: the command writes
557
- `*.toml` job fragments into `$REPOACTIVE_JOBS_DIR` instead of producing a
558
- diff. See [Generating jobs dynamically](#generating-jobs-dynamically).
647
+ `*.toml` job fragments into `$RA_JOBS_DIR` instead of producing a diff.
648
+ See [Generating jobs dynamically](#generating-jobs-dynamically).
649
+
650
+ **Secrets:**
651
+
652
+ - **`secret_env`** (default: `[]`) - Names of environment variables holding
653
+ secrets this job's command may read. A marked name is stripped from every
654
+ command's environment; only a job that lists it here has it injected back
655
+ for its own command. See
656
+ [Passing secrets to commands with `secret_env`](#passing-secrets-to-commands-with-secret_env).
559
657
 
560
658
  ### Stacking MRs
561
659
 
@@ -569,14 +667,13 @@ Because a dependent's MR already contains its dependencies' changes, a
569
667
  dependency chain normally opens one MR per job that produced a diff. Setting
570
668
  `create_mr = "unless-superseded"` on the earlier jobs collapses that: such a
571
669
  job skips its MR when a dependent job produced an MR in the same run, so the
572
- chain yields a single MR on the topmost job that actually changed something
573
-
574
- - falling back to the job below it when the jobs above came up empty. The
575
- branch is still pushed either way. Only the current run counts: a
576
- dependent that is empty, failed, on cooldown, or not selected does not
577
- suppress anything. See
578
- [ADR 0009](docs/adr/0009-unless-superseded-mr-creation.md) for details and
579
- limitations.
670
+ chain yields a single MR on the topmost job that actually changed
671
+ something - falling back to the job below it when the jobs above came up
672
+ empty. The branch is still pushed either way. Only the current run counts: a
673
+ dependent that is empty, failed, on cooldown, or not selected does not
674
+ suppress anything. See
675
+ [ADR 0009](docs/adr/0009-unless-superseded-mr-creation.md) for details and
676
+ limitations.
580
677
 
581
678
  ### Example
582
679
 
@@ -658,6 +755,165 @@ directory of `*.toml` files, and may be repeated to merge several sources;
658
755
  later sources win. Explicit paths are resolved relative to the current
659
756
  directory, not `--repo`.
660
757
 
758
+ ### Variables passed to job commands
759
+
760
+ `repoactive` injects a few `RA_`-prefixed environment variables into every
761
+ job command's environment. These are distinct from the `REPOACTIVE_`
762
+ variables [above](#environment-variables), which configure `repoactive`
763
+ itself.
764
+
765
+ | Variable | Value | Set when |
766
+ | ---------------------- | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
767
+ | `RA_JOB_NAME` | The name of the job the command belongs to. | Always. |
768
+ | `RA_WORKSPACE_DIR` | The temporary jj workspace created for the job (also the command's working directory). | Always. |
769
+ | `RA_JOB_BRANCH` | The bookmark/branch repoactive uses for the job's output. | Always. |
770
+ | `RA_JOB_BASE_BRANCH` | The branch the job's MR targets (`base_branch`, or `trunk()` by default). | Always. |
771
+ | `RA_CONFIG_SOURCE_DIR` | The directory of the config file that defined the command. | The command came from a config file (not a `--set` override or a built-in default). |
772
+ | `RA_JOBS_DIR` | The directory a generator writes its `*.toml` job fragments into. | The command is a [generator](#generating-jobs-dynamically) (`emits_jobs`). |
773
+
774
+ #### `RA_JOB_NAME`
775
+
776
+ `RA_JOB_NAME` holds the name of the job the command belongs to. A command
777
+ shared by several jobs (for instance one set in `[job-defaults]` or emitted
778
+ by a generator) can read it to tell which job is running:
779
+
780
+ ```toml
781
+ [job.lint]
782
+ command = "./run-check.sh $RA_JOB_NAME"
783
+ title = "run the shared check script for this job"
784
+ ```
785
+
786
+ #### `RA_WORKSPACE_DIR`
787
+
788
+ A job's command runs in a temporary jj workspace, which is also its working
789
+ directory. `RA_WORKSPACE_DIR` names that directory explicitly, so a command
790
+ that changes directory can still find its way back:
791
+
792
+ ```toml
793
+ [job.build]
794
+ command = "cd subdir && make && cp result $RA_WORKSPACE_DIR/subdir/"
795
+ title = "build in a subdirectory"
796
+ ```
797
+
798
+ #### `RA_JOB_BRANCH`
799
+
800
+ `RA_JOB_BRANCH` holds the bookmark (git branch) repoactive uses for the
801
+ job's output, e.g. `repoactive/uv-lock-upgrade`. The command runs on a fresh
802
+ commit while that bookmark still points at the **previous** run's commit, so
803
+ a command can inspect what it produced last time - for example to build on
804
+ it or diff against it:
805
+
806
+ ```toml
807
+ [job.notes]
808
+ command = "jj diff --from $RA_JOB_BRANCH --to @ > /tmp/delta || true; ./make-notes.sh"
809
+ title = "regenerate release notes from what changed since last run"
810
+ ```
811
+
812
+ The bookmark may not exist yet: a job's first run, a run that produced no
813
+ diff, and a generator all leave it unset on the remote, so a command that
814
+ reads it should tolerate a missing ref.
815
+
816
+ #### `RA_JOB_BASE_BRANCH`
817
+
818
+ `RA_JOB_BASE_BRANCH` holds the branch the job's merge request targets - its
819
+ `base_branch`, or `trunk()` when that is unset. A command can use it to look
820
+ at only what changed relative to the target, for instance to lint or
821
+ reformat just the affected files:
822
+
823
+ ```toml
824
+ [job.format]
825
+ command = "treefmt $(git diff --name-only $RA_JOB_BASE_BRANCH)"
826
+ title = "format files changed since the base branch"
827
+ ```
828
+
829
+ This is the branch you _configured_ as the base. When a job stacks on
830
+ another via `depends_on`, its commit actually sits on top of that other
831
+ job's output, so `RA_JOB_BASE_BRANCH` names the ultimate target of the
832
+ stack, not the immediate parent.
833
+
834
+ #### `RA_CONFIG_SOURCE_DIR`
835
+
836
+ Because the command runs in that throwaway workspace rather than in the
837
+ directory its config lives in, a relative path cannot reach a helper file
838
+ kept beside the config. `RA_CONFIG_SOURCE_DIR` bridges that: it holds the
839
+ directory of the config file that defined the job's command:
840
+
841
+ - a job defined in `.repoactive.toml` gets the directory holding it (the
842
+ repo directory, by default);
843
+ - a job defined in `.repoactive.d/foo.toml` gets the `.repoactive.d`
844
+ directory.
845
+
846
+ ```toml
847
+ [job.fixup]
848
+ command = "$RA_CONFIG_SOURCE_DIR/fixup.sh"
849
+ title = "run the fixup script kept beside the config"
850
+ ```
851
+
852
+ The value is the config file's real location on disk, so it is the same
853
+ whether the file was discovered automatically or named with `-c`. A command
854
+ whose value came from a `--set` override has no config file, so
855
+ `RA_CONFIG_SOURCE_DIR` is unset for it.
856
+
857
+ ### Passing secrets to commands with `secret_env`
858
+
859
+ A job command inherits `repoactive`'s environment, so a secret exported
860
+ before the run (an LLM key, a registry token) reaches it. Left unmanaged
861
+ that secret reaches **every** job, whether it needs it or not. `secret_env`
862
+ scopes secrets to the jobs that ask for them.
863
+
864
+ The field separates two things:
865
+
866
+ - **Marking** - naming a variable in _any_ `secret_env` (a job's or
867
+ `[job-defaults]`') marks it a secret and **removes it from every job
868
+ command's environment**.
869
+ - **Granting** - a job reads a marked secret back only by listing it in
870
+ **its own** `secret_env`. `[job-defaults].secret_env` marks names but
871
+ grants to no job.
872
+
873
+ So a secret is present only in the jobs that name it, never ambient:
874
+
875
+ ```toml
876
+ [job-defaults]
877
+ secret_env = ["OPENAI_API_KEY"]
878
+
879
+ [job.rewrite-docs]
880
+ command = "./llm-rewrite.sh"
881
+ title = "docs: rewrite with the LLM"
882
+ secret_env = ["OPENAI_API_KEY"]
883
+
884
+ [job.format]
885
+ command = "treefmt"
886
+ title = "format the tree"
887
+ ```
888
+
889
+ Here `OPENAI_API_KEY` is marked in `[job-defaults]`, so `format` (which does
890
+ not grant it) runs without it in its environment; only `rewrite-docs`, which
891
+ lists it in its own `secret_env`, can read it.
892
+
893
+ A job that grants a secret which is **unset** in `repoactive`'s environment
894
+ fails at its start with a clear error, rather than letting the command fail
895
+ obscurely later. Secret **values** must never be written in config - a
896
+ `.repoactive.toml` is checked into the repo; `secret_env` names variables,
897
+ your environment (or your CI's secret store) supplies the values. The `RA_`
898
+ and `REPOACTIVE_` prefixes are reserved and rejected.
899
+
900
+ A [generated job](#generating-jobs-dynamically) may only grant a secret that
901
+ the static config already marks; an emitted `secret_env` naming a variable
902
+ no static job or `[job-defaults]` marked is rejected. Mark such a secret up
903
+ front in `[job-defaults].secret_env` (or on the generator), and the emitted
904
+ jobs grant it.
905
+
906
+ A platform's `token_env` is normally stripped from every command
907
+ ([the platform token](#platformname) is kept out of jobs). A job that
908
+ genuinely needs it can opt back in by naming that variable in its own
909
+ `secret_env`; only that job then receives it.
910
+
911
+ > **Note:** `secret_env` scopes _which jobs_ can read a secret. It does not
912
+ > yet redact secret values from a command's captured output, so a command
913
+ > that echoes a secret it was granted can still write it into the commit
914
+ > message. Avoid printing granted secrets. See
915
+ > [ADR 0017](docs/adr/0017-secret-env-redaction.md).
916
+
661
917
  ### Overriding values on the command line
662
918
 
663
919
  `--set NAME=VALUE`/`-s` tweaks individual config values without editing a
@@ -693,7 +949,13 @@ job across several flags - put a new job's required fields in one expression
693
949
  unknown key or malformed value is reported with the offending `--set`
694
950
  argument named.
695
951
 
696
- ## Selecting jobs with tags
952
+ ## Run control
953
+
954
+ The sections below cover the run-control options from the
955
+ [configuration reference](#jobname) in detail: which jobs a run selects, how
956
+ to schedule and throttle them, and how to generate jobs dynamically.
957
+
958
+ ### Selecting jobs with tags
697
959
 
698
960
  Which jobs a `repoactive run` touches is decided by **tags**. Every job has
699
961
  a set of tags, with a smart default:
@@ -728,8 +990,8 @@ mistyped one.
728
990
  Because assigning a tag removes the implicit `enabled` tag, **tags are
729
991
  load-bearing, not free-form labels**: tagging a job takes it out of the bare
730
992
  `repoactive run`. If you want a job to stay in the default run _and_ belong
731
- to a group, list both: `tags = ["enabled", "weekly"]`. (For MR/PR labels,
732
- use `labels` - a separate concept.)
993
+ to a group, list both: `tags = ["enabled", "weekly"]`. (For MR labels, use
994
+ `labels` - a separate concept.)
733
995
 
734
996
  Tag selection is _explicit_ selection, so - like naming a job - it ignores
735
997
  the `enabled`/`disabled` defaults and force-includes dependencies. The bare
@@ -737,7 +999,7 @@ the `enabled`/`disabled` defaults and force-includes dependencies. The bare
737
999
  itself selected is dropped
738
1000
  (`==> [name] skipped (dependency not in default run)`).
739
1001
 
740
- ### Keeping unmerged branches current
1002
+ #### Keeping unmerged branches current
741
1003
 
742
1004
  The bare `repoactive run` additionally refreshes **any job that currently
743
1005
  has an unmerged branch**, regardless of its tags. A branch is "unmerged"
@@ -756,7 +1018,7 @@ don't have to wait for the next weekly run to resolve a conflict with
756
1018
  disabled job's unmerged branch is refreshed too: it was most likely created
757
1019
  by an explicit run, and letting it drift out of date helps no one.)
758
1020
 
759
- ## Disabling jobs
1021
+ ### Disabling jobs
760
1022
 
761
1023
  Set `disabled = true` on a `[job.<name>]` to keep it in the config but leave
762
1024
  it out of normal runs; it is exactly sugar for `tags = ["disabled"]` (so the
@@ -777,7 +1039,7 @@ disabled = true
777
1039
  `repoactive run --tag disabled`, which runs everything currently turned
778
1040
  off.
779
1041
 
780
- ## Running a job on a schedule
1042
+ ### Running a job on a schedule
781
1043
 
782
1044
  `repoactive` is not a daemon and has no built-in scheduler - the cadence of
783
1045
  a job is whatever cadence you invoke it with. To run a job on a fixed
@@ -805,7 +1067,7 @@ inferring a schedule from `repoactive`'s own history: real cron is stateful
805
1067
  and excludes the other days, whereas `repoactive` only ever sees what has
806
1068
  _landed_ (see `cooldown_period` below).
807
1069
 
808
- ### One run at a time per repository
1070
+ #### One run at a time per repository
809
1071
 
810
1072
  A `repoactive run` takes an exclusive per-repository lock for its duration,
811
1073
  so two runs against the same repository never interleave (and corrupt each
@@ -823,7 +1085,7 @@ The lock is an advisory `flock` on `.jj/repoactive.lock`; the OS releases it
823
1085
  automatically if a run is killed, so a crashed run never leaves the
824
1086
  repository locked.
825
1087
 
826
- ## Gating jobs with `run_only_if_changed`
1088
+ ### Gating jobs with `run_only_if_changed`
827
1089
 
828
1090
  `run_only_if_changed` lets a job declare that it should only run when
829
1091
  specific upstream jobs actually produced a diff. If none of the named jobs
@@ -831,12 +1093,12 @@ changed anything in the current run, the job is skipped.
831
1093
 
832
1094
  ```toml
833
1095
  [job.prek-autoupdate]
834
- command = "./scripts/prek.sh autoupdate"
1096
+ command = "uvx prek autoupdate"
835
1097
  title = "ci: update pre-commit hooks"
836
1098
  tags = ["weekly"]
837
1099
 
838
1100
  [job.prek-run-all]
839
- command = "sh -c './scripts/prek.sh run -a; true'"
1101
+ command = "sh -c 'uvx prek run -a; true'"
840
1102
  title = "ci: apply pre-commit fixes"
841
1103
  depends_on = ["prek-autoupdate"]
842
1104
  run_only_if_changed = ["prek-autoupdate"]
@@ -862,7 +1124,7 @@ Key behaviour:
862
1124
  order can be listed; in practice most usages name a direct dependency, as
863
1125
  in the example above.
864
1126
 
865
- ## Throttling jobs with `cooldown_period`
1127
+ ### Throttling jobs with `cooldown_period`
866
1128
 
867
1129
  Every commit `repoactive` creates carries a `Repoactive-Job: <name>` trailer
868
1130
  identifying the job that produced it. When a job sets `cooldown_period`,
@@ -885,7 +1147,7 @@ runs: `repoactive` does not fetch, so a clone that has not pulled the merge
885
1147
  will not see the cooldown and will re-run the job. See
886
1148
  [Keeping the local clone current](#keeping-the-local-clone-current).
887
1149
 
888
- ## Throttling a job when a superset lands with `cooldown_on`
1150
+ ### Throttling a job when a superset lands with `cooldown_on`
889
1151
 
890
1152
  Some jobs are strict supersets of others. `uv lock --upgrade` refreshes
891
1153
  every dependency group, so its diff already contains everything
@@ -925,7 +1187,7 @@ has an open, unmerged branch is always refreshed regardless of cooldown (see
925
1187
  its branch is rebased and, if the change is now redundant, self-closes on
926
1188
  the next run.
927
1189
 
928
- ## Limiting job runtime with `timeout`
1190
+ ### Limiting job runtime with `timeout`
929
1191
 
930
1192
  A job's `command` can hang or run away. Setting `timeout` caps how long the
931
1193
  command may run; when the limit is reached `repoactive` kills the command's
@@ -955,15 +1217,15 @@ title = "chore: fix lint"
955
1217
  timeout = "0s" # opt out of the timeout entirely
956
1218
  ```
957
1219
 
958
- ## Generating jobs dynamically
1220
+ ### Generating jobs dynamically
959
1221
 
960
1222
  Sometimes the useful set of jobs depends on the repository's contents - one
961
1223
  job per package in a monorepo, one per entry in a manifest - and you don't
962
1224
  want to hand-maintain them. A **generator** is an ordinary `[job.<name>]`
963
1225
  with `emits_jobs = true`. Instead of producing a diff, its command writes
964
1226
  one or more `*.toml` job fragments into the directory named by the
965
- `REPOACTIVE_JOBS_DIR` environment variable, and the jobs it emits run in the
966
- same invocation.
1227
+ `RA_JOBS_DIR` environment variable, and the jobs it emits run in the same
1228
+ invocation.
967
1229
 
968
1230
  ```toml
969
1231
  [job.per-package]
@@ -978,7 +1240,7 @@ cooldown_period = "7d"
978
1240
  The command writes fragments using the normal `[job.<name>]` syntax, e.g.
979
1241
 
980
1242
  ```toml
981
- # $REPOACTIVE_JOBS_DIR/pkg-a.toml
1243
+ # $RA_JOBS_DIR/pkg-a.toml
982
1244
  [job.upgrade-pkg-a]
983
1245
  command = "uv lock --upgrade --package pkg-a"
984
1246
  title = "build: upgrade pkg-a"
@@ -986,7 +1248,7 @@ title = "build: upgrade pkg-a"
986
1248
 
987
1249
  Fragments may only contain `[job.<name>]` tables; anything else (e.g.
988
1250
  `[job-defaults]`) fails the generator. To set defaults for the emitted jobs,
989
- set them on the generator itself — they are inherited (see below).
1251
+ set them on the generator itself - they are inherited (see below).
990
1252
 
991
1253
  Key points:
992
1254
 
@@ -1033,13 +1295,8 @@ design.
1033
1295
  - [jj (Jujutsu)](https://github.com/jj-vcs/jj) - `repoactive` uses jj to
1034
1296
  manage branches and commits in the target repository
1035
1297
  - A configured jj user name and email - jj records them as the commit
1036
- author. Set them once with:
1037
-
1038
- ```bash
1039
- jj config set --user user.name "My Name"
1040
- jj config set --user user.email "me@example.com"
1041
- ```
1042
-
1298
+ author. Set them once with the `jj config set` commands shown in
1299
+ [Quick start](#quick-start) step 1.
1043
1300
  - A GitLab or GitHub API token exposed via the environment variable named in
1044
1301
  `platform.token_env` (default: `GITHUB_TOKEN` for GitHub.com,
1045
1302
  `GITLAB_TOKEN` for GitLab.com)
@@ -1067,3 +1324,12 @@ jj log -r 'repoactive_unmerged()'
1067
1324
  jj log -r 'repoactive() & committer_date(after:"2025-01-01")'
1068
1325
  jj log -r 'repoactive() & description(regex:"(?m)^Repoactive-Job: uv-lock-upgrade$")'
1069
1326
  ```
1327
+
1328
+ ## Changelog
1329
+
1330
+ User-facing changes are recorded in [CHANGELOG.md](CHANGELOG.md).
1331
+
1332
+ ## License
1333
+
1334
+ `repoactive` is licensed under the
1335
+ [GNU General Public License v3.0 or later](LICENSE).