{"id":50907954,"url":"https://github.com/schmir/repoactive","last_synced_at":"2026-06-16T07:02:07.839Z","repository":{"id":363664336,"uuid":"1264178327","full_name":"schmir/repoactive","owner":"schmir","description":"Script-driven code changes with automated merge requests","archived":false,"fork":false,"pushed_at":"2026-06-09T22:07:02.000Z","size":80,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-09T22:12:51.302Z","etag":null,"topics":["automation","cli-tool","git","github","gitlab","jujutsu"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/schmir.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-09T16:26:47.000Z","updated_at":"2026-06-09T21:54:03.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/schmir/repoactive","commit_stats":null,"previous_names":["schmir/repoactive"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/schmir/repoactive","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/schmir%2Frepoactive","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/schmir%2Frepoactive/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/schmir%2Frepoactive/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/schmir%2Frepoactive/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/schmir","download_url":"https://codeload.github.com/schmir/repoactive/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/schmir%2Frepoactive/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34393305,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-16T02:00:06.860Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["automation","cli-tool","git","github","gitlab","jujutsu"],"created_at":"2026-06-16T07:02:04.635Z","updated_at":"2026-06-16T07:02:07.827Z","avatar_url":"https://github.com/schmir.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# repoactive - Script-driven code changes with automated merge requests\n\n\u003e **Warning:** This project is in an early stage of development. Use at your\n\u003e own risk.\n\n`repoactive` runs your scripts against a git repository and optionally keeps\nthe corresponding merge requests up to date. You write the scripts that\nproduce the code changes; `repoactive` handles the rest - branches, commits,\nand (with `--create-prs`) the full MR lifecycle.\n\n## How it works\n\nYou configure one or more **jobs**, each with a script (any shell command or\nexecutable) that modifies the repository's working tree. `repoactive` runs\neach script, captures the resulting diff, and records the change locally.\nWith `--create-prs` it also:\n\n- opens a new merge request if one does not already exist for that job, or\n- updates the existing merge request branch if the diff has changed.\n\nBranches and MR descriptions are managed automatically - the only code you\nneed to write is the script that produces the change.\n\n```\n[your script] → diff → repoactive → branch\n                                       ↓ (with --push or --create-prs)\n                                    git push → merge request\n                                                    ↑ (with --create-prs)\n                                            (create or update)\n```\n\n1. `repoactive` creates a new commit on top of the base branch or on top of\n   other repoactive managed branches.\n2. It runs the job's script against the working tree.\n3. If the script produced a diff, it records the change. With `--push` or\n   `--create-prs`, it pushes the branch; with `--create-prs`, it also\n   creates or updates the merge request.\n4. If the script produced no diff, the branch is reset to the base. With\n   `--push` or `--create-prs`, the reset branch is pushed without opening or\n   updating an MR.\n\n## Use cases\n\n- Keeping generated files (API clients, protobuf bindings, lock files) in\n  sync with their sources\n- Applying organization-wide refactors or policy changes across many\n  repositories\n- Automating any periodic code transformation that should go through a\n  review process\n\n## Configuration\n\n`repoactive` is configured via `.repoactive.toml` in the repository root (or\npassed via `--config`). See [Config file locations](#config-file-locations)\nfor how the defaults are discovered and how to split the config across\nseveral files.\n\nEvery key in `[job-defaults]` supplies the default for the matching per-job\nkey; any job may override it by setting the same key in its `[[job]]` block.\n\n```toml\n[job-defaults]\n# Prefix prepended to job.name to form the branch name\nbranch_prefix = \"repoactive/\"\n# Prefix prepended to every MR/PR title (set to \"\" to disable)\nmr_title_prefix = \"[repoactive] \"\n# Prefix prepended to every commit title (set to \"\" to disable)\ncommit_title_prefix = \"[repoactive] \"\n# Labels applied to every MR/PR unless overridden per job\nlabels = [\"repoactive\"]\n# Optional: default target branch for jobs that do not set their own\n# (default: repo default branch)\nbase_branch = \"main\"\n# Optional: default cooldown_period applied to jobs that do not set their own\n# (default: none). See \"Throttling jobs with cooldown_period\" below.\ncooldown_period = \"7d\"\n\n[[job]]\n# Unique identifier - branch name is always \u003cbranch_prefix\u003e\u003cname\u003e\nname = \"regenerate-api-client\"\n# Script run in the repo working directory; non-zero exit = failure\ncommand = \"python scripts/regen_api.py\"\n# MR/PR title\ntitle = \"api: regenerate API client\"\n# Optional: MR description\ndescription = \"Automated regeneration of the API client from the OpenAPI spec.\"\n# Optional: extra labels (merged with job-defaults.labels)\nlabels = [\"automated\", \"api\"]\n# Optional: target branch (default: repo default branch)\nbase_branch = \"main\"\n# Optional: open the MR/PR as a draft (default: false)\ndraft = false\n# Optional: create an MR/PR for this job (default: true). Set to false to\n# push the branch without opening an MR/PR.\ncreate_mr = true\n# Optional: append the job's command and its output to the commit message\n# (default: true). Set to false to keep the commit message clean.\noutput_in_commit = true\n# Optional: skip this job on \"run all\" invocations (default: false). See\n# \"Disabling jobs\" below.\ndisabled = false\n# Optional: override branch_prefix/mr_title_prefix/commit_title_prefix from\n# job-defaults for this job only.\nmr_title_prefix = \"[api] \"\n# Optional: minimum time between landed changes for this job. If a commit\n# from this job landed on the base branch within this window, the job is\n# skipped. Format: \u003cnumber\u003e\u003cunit\u003e, unit one of s, m, h, d, w (e.g. \"7d\").\ncooldown_period = \"7d\"\n\n[[job]]\nname = \"sync-license-headers\"\ncommand = \"./scripts/add_license_headers.sh\"\ntitle = \"sync license headers\"\n\n[[job]]\nname = \"integration-tests-update\"\ncommand = \"./scripts/update_integration_tests.py\"\ntitle = \"tests: update integration tests\"\n# Optional: run this job on top of the merged output of the listed jobs\ndepends_on = [\"regenerate-api-client\", \"sync-license-headers\"]\n```\n\nFor public GitHub.com or GitLab.com repositories no platform declaration is\nneeded — `repoactive` detects the remote URL automatically. To use a\nself-hosted instance, add a `[[platform]]` section:\n\n```toml\n[[platform]]\n# Base URL of the platform instance\nurl = \"https://gitlab.example.com\"\n# Name of the env var holding the API token\ntoken_env = \"GITLAB_TOKEN\"\n# type must be either \"github\" or \"gitlab\"\ntype = \"gitlab\"\n```\n\nThe branch for each job is always `branch_prefix + job.name`, where\n`branch_prefix` is the job's own value if set, otherwise\n`job-defaults.branch_prefix`. Secrets are kept out of the config file by\nreferencing environment variable names rather than inline values.\n\nWhen `depends_on` is set, `repoactive` starts the job's script from a\nworking tree that has all listed dependency branches merged together, rather\nthan from the plain base branch. The resulting MR branch will therefore\ninclude both the dependency jobs and the new job on top. Links to the parent\nMRs are automatically added to the MR description.\n\n### Config file locations\n\nWhen no `--config`/`-c` option is given, `repoactive` looks for\nconfiguration inside the `--repo` directory (the current directory by\ndefault):\n\n- the `.repoactive.d/` directory, if present, contributes every `*.toml`\n  file it contains, merged in sorted filename order;\n- the `.repoactive.toml` file, if present, is merged last so it overrides\n  the directory.\n\nIf neither exists, `repoactive` exits with an error. Splitting configuration\nacross `.repoactive.d/*.toml` is handy for dropping in per-job files without\ntouching a single large config.\n\n`--config`/`-c` overrides this discovery. It may point at a file or at a\ndirectory of `*.toml` files, and may be repeated to merge several sources;\nlater sources win. Explicit paths are resolved relative to the current\ndirectory, not `--repo`.\n\n## Disabling jobs\n\nSet `disabled = true` on a `[[job]]` to keep it in the config but leave it\nout of normal runs. The flag only affects \"run all\" invocations\n(`repoactive run` with no job names):\n\n- On `repoactive run`, disabled jobs are skipped. Any job that `depends_on`\n  a disabled job is skipped too, since its dependency would not be produced\n  (`==\u003e [name] disabled (dependency disabled)`).\n- Naming a job explicitly overrides the flag: `repoactive run my-job` runs\n  `my-job` even when it is disabled. This makes `disabled` a way to keep a\n  job available for on-demand runs while excluding it from the default\n  schedule.\n\n## Throttling jobs with `cooldown_period`\n\nEvery commit `repoactive` creates carries a `Repoactive-Job: \u003cname\u003e` trailer\nidentifying the job that produced it. When a job sets `cooldown_period`,\n`repoactive` looks at the base branch for a commit with that job's trailer\nand a committer date inside the window before running. If one is found the\njob is on cooldown and is skipped for this run (dependents proceed as if it\nproduced no changes); otherwise the job runs normally. This keeps recurring\njobs - for example a dependency upgrade - from landing more often than the\nconfigured interval.\n\nThe signal is the trailer on the base branch, so the cooldown only starts\nonce a change has _landed_. An open, unmerged MR does not trigger it (the\nexisting MR keeps being updated as usual). Because the check relies on the\ncommit trailer reaching the base branch, MRs for throttled jobs must be\nmerged with a merge commit or rebase - a **squash merge discards the commit\nmessage** and with it the trailer, so the cooldown would never trigger.\n\n## Usage\n\n```bash\n# Print the installed version and exit\nrepoactive --version\n```\n\n```\nrepoactive run [OPTIONS] [JOBS]...\n```\n\nRun all configured jobs (or a named subset - dependencies are\nauto-included):\n\n```bash\n# Apply all jobs locally (no push, no MR creation)\nrepoactive run\n\n# Apply specific jobs locally\nrepoactive run regenerate-api-client sync-license-headers\n\n# Push branches to the remote without creating MRs\nrepoactive run --push\n\n# Push branches and create or update merge requests\nrepoactive run --create-prs\n\n# Enable debug logging\nrepoactive run --debug\n```\n\n| Option          | Short | Description                                                                                                                  |\n| --------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------- |\n| `--config PATH` | `-c`  | Config file or directory of `*.toml` files; repeat to merge. Default: `.repoactive.d/` and `.repoactive.toml` under `--repo` |\n| `--repo PATH`   | `-r`  | jj repository path (default: `.`)                                                                                            |\n| `--push`        |       | Push branches to the remote repository                                                                                       |\n| `--create-prs`  |       | Push branches and create or update pull requests                                                                             |\n| `--debug`       | `-d`  | Enable debug logging                                                                                                         |\n\n## Inspecting repoactive commits\n\n```\nrepoactive recent-commits [OPTIONS] [JOBS]...\n```\n\nList commits produced by repoactive, filtered by a time window and\noptionally by job name or merge status:\n\n```bash\n# Show all repoactive commits from the last 2 weeks (default window)\nrepoactive recent-commits --repo /path/to/repo\n\n# Narrow to a specific window\nrepoactive recent-commits --within 30d --repo /path/to/repo\n\n# Filter by one or more job names\nrepoactive recent-commits --within 7d uv-lock-upgrade prek-autoupdate\n\n# Only commits that have landed in trunk\nrepoactive recent-commits --merged\n\n# Only commits still on open branches\nrepoactive recent-commits --unmerged\n```\n\n| Option        | Short | Description                                            |\n| ------------- | ----- | ------------------------------------------------------ |\n| `--within`    |       | How far back to look (default: `2w`; e.g. `7d`, `24h`) |\n| `--repo PATH` | `-r`  | jj repository path (default: `.`)                      |\n| `--merged`    |       | Only commits that are ancestors of trunk               |\n| `--unmerged`  |       | Only commits not yet in trunk                          |\n\n### jj revset aliases\n\nTo query repoactive commits directly in jj, add these aliases to your\nrepository config (`jj config set --repo`) or your global config\n(`jj config set --user`):\n\n```toml\n[revset-aliases]\n'repoactive()' = 'description(regex:\"(?m)^Repoactive-Job: \")'\n'repoactive_merged()' = 'repoactive() \u0026 ::trunk()'\n'repoactive_unmerged()' = 'repoactive() \u0026 ~(::trunk())'\n```\n\nThen use them directly in jj:\n\n```bash\njj log -r 'repoactive()'\njj log -r 'repoactive_unmerged()'\njj log -r 'repoactive() \u0026 committer_date(after:\"2025-01-01\")'\njj log -r 'repoactive() \u0026 description(regex:\"(?m)^Repoactive-Job: uv-lock-upgrade$\")'\n```\n\n## Validating configuration\n\n```\nrepoactive validate-config [OPTIONS]\n```\n\nCheck that a config file is syntactically and semantically valid without\nrunning any jobs:\n\n```bash\n# Validate the discovered defaults (.repoactive.d/ and .repoactive.toml)\nrepoactive validate-config\n\n# Validate a specific config file or directory\nrepoactive validate-config --config myconfig.toml\n\n# Validate a merged config (same merging rules as `run`)\nrepoactive validate-config --config base.toml --config override.toml\n```\n\nOn success the command prints `Config OK: N job(s) defined.` and exits with\ncode 0. On failure it prints the validation error to stderr and exits with\ncode 1.\n\nValidation checks include unknown keys, missing required fields, invalid\n`depends_on` references, and circular job dependencies.\n\n| Option          | Short | Description                                                                                                                  |\n| --------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------- |\n| `--config PATH` | `-c`  | Config file or directory of `*.toml` files; repeat to merge. Default: `.repoactive.d/` and `.repoactive.toml` under `--repo` |\n| `--repo PATH`   | `-r`  | jj repository path (default: `.`)                                                                                            |\n\n## Requirements\n\n- Python 3.11 or later\n- [jj (Jujutsu)](https://github.com/jj-vcs/jj) - `repoactive` uses jj to\n  manage branches and commits in the target repository\n- A GitLab or GitHub API token exposed via the environment variable named in\n  `platform.token_env`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fschmir%2Frepoactive","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fschmir%2Frepoactive","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fschmir%2Frepoactive/lists"}