multi-gitter: run one script across many repositories and open the pull requests for you
Update multiple repositories in with one command
At a glance
- What is it?
- multi-gitter is a Go CLI that clones a set of repositories, runs your script inside each one, and turns any diff into a pull request. It is a fit for org-wide edits you can express as a script, and a poor fit for one-off changes in a single repo.
- Who is it for?
- Adopt multi-gitter if you regularly apply the same scripted edit to many repositories and want the changes to arrive as reviewable pull requests rather than pushes to default branches. Do not adopt it for a single repository, for edits you cannot express as a script, or for platforms where you need the API push path, since the run options state that api-push is GitHub only.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The org-wide edit problem multi-gitter targets
If you maintain more than a handful of repositories, some changes are not interesting enough to deserve a branch each. A PR template that needs to match, a linter rule that needs a config line, a dependency version that has to move in twenty go.mod files. Doing that by hand means twenty clones, twenty commits and twenty pull requests, and the work is mostly waiting.
multi-gitter compresses that into one invocation. The README describes the model directly: it runs a script or program in the context of multiple repositories, and if any changes are made, it creates a pull request that reviewers can merge manually or that multi-gitter can merge automatically once CI pipelines have completed successfully. The audience is whoever owns a fleet of repositories on GitHub, GitLab, Gitea, Bitbucket or Gerrit, and is comfortable writing the change as a shell script, a Node script, a Python file or a Go program. The README's framing is that any language works, because multi-gitter does not interpret your script. It just runs it.
The examples the README lists are the honest scope: syncing a file such as a PR template, programmatic refactoring, updating a dependency, fixing linting issues, search and replace. Every one of those is a change you can describe as a deterministic script. If the edit needs judgement per repository, multi-gitter will still run, but you will be reviewing the output repository by repository, which is the work you were trying to avoid.
How the run command clones, executes and opens pull requests
The unit of work is the run subcommand. You give it a script, a selector for which repositories to touch, a commit message and a branch name. The selectors visible in the README are -O for an organisation, -U for a user, and -R for individual repositories, repeated per repository. There is also a code-search option, documented as GitHub only, which selects repositories by code search results; repeated results from a given repository are ignored and forks are not included by default unless you pass fork:true.
From the repository layout and the run options, the flow is: multi-gitter clones the matched repositories into a temporary directory (clone-dir lets you override the default OS temp directory), runs your script with the repository as the working directory, and inspects the result. If the working tree is unchanged, nothing happens for that repository. If it changed, the tool commits to the branch you named and opens a pull request, with assignees and reviewers taken from the corresponding run options.
Two details in the options shape the behaviour more than they look. The concurrent option defaults to 1, so runs are serial unless you raise it; that is a deliberate default for a tool that clones repositories and hits a platform API. And branch-already-exists handling is explicit: the options describe skip, which leaves the existing branch alone and creates no new pull request, and replace, which replaces the existing content. Neither is a merge, so re-running a job over repositories that already have the branch is a decision you have to make rather than a default you can ignore.
The Go module list confirms the platform coverage is implemented per provider rather than through one abstraction: go-github, the GitLab client, the Gitea SDK, two Bitbucket libraries and go-gerrit are all direct dependencies. That is why feature parity is uneven, and the README says so where it matters.
Installing multi-gitter and running a first dry run
Homebrew is the shortest path on macOS and Linux. The README gives the cask form:
brew install --cask lindell/multi-gitter/multi-gitterThere is also a manual route through the release page, and a script that installs the latest version:
curl -s https://raw.githubusercontent.com/lindell/multi-gitter/master/install.sh | shInstalling from source with go install github.com/lindell/multi-gitter@latest is documented but the README calls it not recommended for most cases.
Before anything runs you need a token that can list repositories and create pull requests. It goes in GITHUB_TOKEN, GITLAB_TOKEN or GITEA_TOKEN, or you pass it with --token. For GitHub the README asks for repo permissions on a classic personal access token; for GitLab it asks for the api permission; for Gitea you generate a token under Settings, Applications, Manage Access Tokens.
Now write a script and make it executable, as the README insists:
chmod +x ./my-script.shRun it against one organisation with a dry run first. The README pairs --dry-run with debug logging so the changes that would have been made are printed:
multi-gitter run ./script.sh --dry-run --log-level=debug -O my-org -m "Commit message" -B branch-nameWhat you should see is per-repository output showing the clone, the script execution and the diff it would have committed. Nothing is pushed and no pull request is created. When that output looks right, drop --dry-run and the same command commits to branch-name and opens the pull requests.
If your change is written in an interpreted language, the path has to be absolute, because the script runs inside each cloned repository rather than where you invoked the command. The README recommends $PWD for this:
multi-gitter run "python $PWD/run.py" -O my-org -m "Commit message" -B branch-nameThe examples directory in the repository has general, Go and Node variants if you want a starting point that is closer to a real refactor than a hello-world script.
Configuration precedence and what the config file does not do
Every run option can be set by flag, by config file, or by a mix of both. You point at a file with --config=./path/to/config.yaml, and the flag can be repeated: later files override earlier ones. Separately, multi-gitter reads ~/.multi-gitter/config as a static config. The documented priority is flags first, then the config file you named, then the static config file.
That layering is useful for keeping the noisy parts (base-url for a self-hosted instance, auth-type, clone-dir, concurrent) in ~/.multi-gitter/config while the per-job parts (commit message, branch name, repository selector) stay on the command line. It is also a source of confusion: a value you set on the command line silently wins over the file you just edited, and the static config applies even when you did not pass --config at all. If a run is behaving differently from your YAML, the first thing to check is whether the same key is set somewhere higher in that priority order.
The config file is not a workflow engine. There is no notion of ordering between repositories, no dependency graph, and no retry policy described in the run options. The concurrent value is a number, not a scheduler. For a fleet-wide edit that must not half-apply, that means the coordination lives in your script and in how you review the pull requests, not in multi-gitter.
Where multi-gitter is the wrong tool
The clearest limitation is the one the options file states outright: api-push, which pushes changes through the API instead of git and produces verified commits, is only supported for GitHub. It is also described as slower and not suited for changes to large files. On GitLab, Gitea, Bitbucket or Gerrit you are on the git path, and the verified-commit property is not available to you through this flag.
Code search as a repository selector is likewise GitHub only, so the selective, query-driven targeting that makes large fleets tractable is not portable across providers.
The second limitation is structural. multi-gitter runs your script; it does not understand it. If the script fails halfway through a repository, or succeeds but produces a change you did not intend, multi-gitter has no model of what correct looks like. The dry run is the only preview, and it is textual. For a change with real blast radius, such as rewriting build files across an organisation, the review burden lands entirely on the pull requests, and twenty pull requests is not meaningfully cheaper to review than twenty commits.
Third, the branch-already-exists behaviour is a choice between skip and replace, and replace overwrites. Re-running a job after amending your script, over repositories where the branch already exists, is not a no-op. If your workflow assumes idempotent re-runs, verify which of the two modes you have configured before the second run, not after.
Finally, if you have one repository, or three that you touch twice a year, the setup cost of a token, a config file and a script exceeds the cost of doing the edit by hand. That is not a criticism of the tool; it is the boundary of the problem it solves.
Alternatives and how their approach differs
The closest comparison is a general-purpose repository automation tool such as Renovate or a similar bot that runs on a schedule and proposes changes. The difference is where the logic lives. Those tools own the change: you configure rules and they decide what to edit, then open pull requests. multi-gitter owns nothing about the change. It handles the fan-out (clone, run, commit, open pull request) and leaves the edit entirely to your script. That makes multi-gitter more general, because any scriptable edit fits, and less safe, because nothing validates the edit except your dry run and the reviewers.
A second alternative is a CI job in each repository that performs the same edit on a schedule. This avoids a central token with organisation-wide pull request rights, which is a genuine security difference: multi-gitter needs a credential that can list repositories and create pull requests across the whole target set. The trade-off is coordination. A per-repository CI job applies the change whenever that repository's pipeline runs, so the fleet drifts, and you have no single place to see whether the change landed everywhere. multi-gitter gives you one command and one branch name, and the pull requests are the audit trail.
A third option is doing it by hand with a shell loop over git clone, which is what multi-gitter replaces. The loop is trivial to write and trivial to get wrong: it does not handle the platform API, it does not open pull requests, and it does not give you a dry run that prints the diffs.
Maintenance, licence and upgrade cost
The repository is not archived, and the last push was on 2026-09-06. The most recent release listed is v0.63.1 from 2026-05-10, preceded by v0.63.0 in March 2026 and v0.62.0 in February 2026. Releases are versioned below 1.0, which is worth reading as a statement about API and flag stability rather than as a maturity verdict.
Upgrade cost is mostly about the config surface. Because options can come from flags, a named config file and ~/.multi-gitter/config, a change to a default or a renamed key can surface as a behaviour change in a job you did not touch, and the priority order means the effective value may not be the one in the file you are reading. Pinning a version and reading the changelog before moving is the cheap insurance here.
The licence is Apache-2.0, a permissive licence that permits commercial use and modification and includes an explicit patent grant. It also requires that you preserve the licence and notices when redistributing. That is a summary of the licence's general shape, not legal advice; if you are redistributing multi-gitter inside a product, have counsel read the actual LICENSE file in the repository.
One operational cost that the README does not address: the token. multi-gitter needs a credential that can list repositories and create pull requests on the target platform, and for GitHub that is a classic token with repo permissions. That is a broad grant by design, and the README does not document a narrower scoping or a rollback path for changes already pushed.
Editorial conclusion
Adopt multi-gitter if you regularly apply the same scripted edit to many repositories and want the changes to arrive as reviewable pull requests rather than pushes to default branches. Do not adopt it for a single repository, for edits you cannot express as a script, or for platforms where you need the API push path, since the run options state that api-push is GitHub only. Before your first live run, verify two things: that your token can list repositories and create pull requests on the target platform, and that the script behaves the same when the working directory is a fresh clone rather than the checkout you developed it in. Run it once with --dry-run --log-level=debug against the real org and read the diff output before you drop --dry-run.
Frequently asked questions
What is multi-gitter?
It is a command line tool that makes changes in multiple repositories at once by running a script or program in the context of each repository. If a run produces changes, multi-gitter creates a pull request that can be merged manually or automatically once CI pipelines complete.
How do I install multi-gitter?
On macOS or Linux the README gives a Homebrew cask, brew install --cask lindell/multi-gitter/multi-gitter. You can also download a binary from the release page, run the install script at https://raw.githubusercontent.com/lindell/multi-gitter/master/install.sh, or build from source with go install, which the README does not recommend for most cases.
Which token does multi-gitter need?
It needs a token allowed to list repositories and create pull requests, set in GITHUB_TOKEN, GITLAB_TOKEN or GITEA_TOKEN, or passed with the --token flag. For GitHub the README asks for repo permissions on a classic personal access token, and for GitLab it asks for the api permission.
Can I test a multi-gitter run without creating commits?
Yes. The --dry-run flag tests without making modifications, and the README suggests combining it with --log-level=debug so the changes that would have been made are also printed.
Does multi-gitter work with GitLab or Gitea, not just GitHub?
It supports GitHub, GitLab, Gitea, Bitbucket and Gerrit, and base-url is the option for GitHub Enterprise, a self-hosted GitLab instance, Gitea, Bitbucket or Gerrit. Two features are GitHub only: the api-push option that pushes through the API instead of git, and code-search for selecting repositories.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/lindell-multi-gitter)