CLI tool
asottile/all-repos avatar
asottile/all-repos

all-repos: sweeping changes across every repository you own

Clone all your repositories and apply sweeping changes.

638 stars98 forksPythonMIT

At a glance

What is it?
all-repos is a Python CLI that clones all of your GitHub repositories into one directory and then applies the same change to all of them, with distributed grep, file finding, and an autofixer mode that opens a branch and a pull request per repository. It is MIT licensed and installed with pip.
Who is it for?
all-repos fits a maintainer with dozens of small repositories who needs to rename a dependency, change a CI setting or update a licence header everywhere at once, since doing that by hand is the kind of work that never gets done. It is the wrong tool for a single repository, and the wrong tool if you want a tool that understands your code, because the edits are textual by design.
Can I use it commercially?
Yes. MIT 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 last received commits 10 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem of forty repositories with the same problem

all-repos is described in four words: clone all your repositories and apply sweeping changes. The project is aimed at a specific and increasingly common situation, a maintainer with a large number of small repositories, a personal utility library, a set of plugins, and a group of libraries that all share a configuration file, a dependency pin, a licence header or a CI definition. Any change to that shared thing becomes forty pull requests, and a change nobody has time to make is a change nobody makes. The topics on the repository make the same point, listing git, monolith and refactoring, so this is a project about breaking a monolith apart and keeping the pieces consistent. The tool is a Python CLI, MIT licensed, installed from pip, and the README documents every subcommand in detail rather than leaving the interface to be discovered.

Installing and the three options every subcommand takes

Installation is a single pip command:

bash
pip install all-repos

Every command line interface provided by the tool shares the same three options, which is a small design decision that pays off across the subcommands. The `-h` or `--help` flag shows usage information. The `-C CONFIG_FILENAME` or `--config-filename CONFIG_FILENAME` option selects a non-default config file, and the default `all-repos.json` can itself be changed through the environment variable `ALL_REPOS_CONFIG_FILENAME`. And `--color {auto,always,never}` controls colour in the output, defaulting to `auto`. That last one is worth noting in a tool that prints a lot of output, and the config filename override is what makes it possible to keep a personal config and a work config side by side without rewriting files.

Reading first, writing second

Two of the subcommands are pure reads, and they are the ones to run before anything else. `all-repos-find-files` behaves like a distributed `git ls-files | grep -P PATTERN`, where PATTERN is a Python regular expression, and it has a `--repos-with-matches` option that prints only the repositories containing a match rather than the matching paths. The documented examples are `all-repos-find-files setup.py` to find every `setup.py`, and `all-repos-find-files --repos setup.py` to list the repositories that contain one. `all-repos-grep` does the same for content, behaving like a distributed `git grep`, with extra arguments passed through to `git grep` itself, so the full power of that tool is available. The documented examples are `all-repos-grep pre-commit -- 'requirements*.txt'` to find repositories listing pre-commit in a requirements file, and `all-repos-grep -L six -- setup.py` to find setup.py files that do not contain six. The `-L` flag is a good example of why passing arguments through is worth the small complexity.

The autofixer model is where the risk lives

The write path is built on what the documentation calls autofixer options, and `all-repos-sed` is the clearest example. It behaves like a distributed `git ls-files -z -- FILENAMES | xargs -0 sed -i EXPRESSION`, so the expression is a sed program and the filenames are a glob passed to `git ls-files`. The documented invocation is `all-repos-sed 's/foo/bar/g' -- '*'` to replace foo with bar in all files, and it supports `-r` or `--regexp-extended` for extended regular expressions. The autofixer framing is what makes this safe enough to consider: it is not a blind sed across a directory tree, it is a git-aware edit that works on tracked files, it works on a branch rather than on the default branch, and it can be pointed at a specific subset of repositories. There is also `all-repos-manual`, which interactively applies a manual change across repos, and which the documentation notes will always run in interactive autofixing mode and requires the `--repos` autofixer option, with `--branch-name` and a required `--commit-msg`.

Configuration, and the filters that decide what gets touched

The config file is a JSON document, and the shape is worth reading because the two regexes are the part everyone should set:

json
{
    "output_dir": "output",
    "source": "all_repos.source.github",
    "source_settings": {
        "api_key": "...",
        "username": "asottile"
    },
    "push": "all_repos.push.github_pull_request",
    "push_settings": {
        "api_key": "...",
        "username": "asottile"
    }
}

The `output_dir` is where repositories are cloned, `source` and `push` are module import paths, and the two settings blocks are passed to those modules. Then there are the filters. The `include` option defaults to an empty string and is a Python regex for selecting repositories, so only matching names are included, and the `exclude` option defaults to `^$`, a regex that matches nothing, so by default nothing is excluded. That default is the one to think about, because a regex that excludes nothing means a rewrite lands in every repository the source module can see. The `all_branches` option, defaulting to false, decides whether all branches are cloned or just the default upstream branch. The `source` and `push` entries are also the extension point, since the documentation describes builtin modules and directions for writing your own.

Where the repositories come from and where the changes go

Two source modules are documented. The `all_repos.source.json_file` module clones all repositories listed in a file, where the file maps a repository key to a URL, and it requires a `filename` setting for the file containing the repositories one per line. The `all_repos.source.github` module clones all repositories available to a user on GitHub, and it requires an `api_key` and a `username`. The API key can also be passed through the `api_key_env` setting, which is the friendlier route for a token that you would rather not have in a file, and the minimum scope needed to function is `public_repo`, with `repo` required for private repositories. Optional settings on the GitHub source include `collaborator`, defaulting to false, for repositories you can contribute to but do not own, `forks`, also defaulting to false, and `private`. On the push side, the configuration names `all_repos.push.github_pull_request` as the module that opens pull requests, which is where the whole workflow ends up.

The details that will bite you

Three small things in the documentation are worth knowing before you start. The first is the tab completion command, `all-repos-complete`, which adds `git clone` completion for all-repos repositories, requires jq to function, and is wired into your shell with a line in `.bash_profile`:

bash
eval "$(all-repos-complete -C ~/.../all-repos.json --bash)"

The second is the `all-repos-clone` behaviour and its concurrency setting. It clones all repositories into the `output_dir`, and running it again updates the existing repositories rather than failing, and the `-j JOBS` or `--jobs JOBS` option sets how many concurrent jobs run the operation, with 0 or -1 matching the number of CPUs and a default of 8. The third is the sed dependency: `all-repos-sed` assumes GNU sed, and on macOS the documentation tells you to install `gnu-sed` with Homebrew and put its directory on your PATH:

bash
brew install gnu-sed

# Add to .bashrc / .zshrc
export PATH="$(brew --prefix)/opt/gnu-sed/libexec/gnubin:$PATH"

That is a BSD sed versus GNU sed difference that produces silently different results on macOS if you skip it, which is exactly the kind of detail a sweeping change tool cannot recover from. There is also `all-repos-list-repos`, which simply lists the names of the cloned repositories.

Where this is the wrong tool

The limits are clear from the design. The first is scale of intent. The edits are textual, so a sweeping change works for anything that is a literal string in a file, and does not work for anything that needs to understand the code, and it will happily apply a change that is wrong for one repository in forty. The `include` and `exclude` regexes are the mitigation, which means the tool's correctness depends on you having thought about which repositories are in scope. The second is the credential surface. The configuration calls for an API key for the source and an API key for the push, and for the GitHub source the scope can be as broad as `repo` for private repositories, so the config file holds a token that can write to everything you own. The `api_key_env` setting exists for that reason. The third is the macOS and Windows asymmetry, where the sed dependency and the shell completion snippet both assume a Unix environment, and the tool has no documented Windows support. The last is maturity in the good sense: the repository has a build status badge and a pre-commit.ci badge, a `testing/` directory, a `tox.ini` and a `setup.py` that is a thin shim over `setup.cfg`, which is a well maintained small project, but one that publishes no releases, so you install whatever is on PyPI at the time.

Editorial conclusion

all-repos fits a maintainer with dozens of small repositories who needs to rename a dependency, change a CI setting or update a licence header everywhere at once, since doing that by hand is the kind of work that never gets done. It is the wrong tool for a single repository, and the wrong tool if you want a tool that understands your code, because the edits are textual by design. Before you use the autofixer, run the read-only commands first, `all-repos-find-files` and `all-repos-grep`, to see the blast radius, and check the include and exclude regexes in your config so a rewrite does not land in repositories you did not mean to touch. The last push was on 2026-10-13, the repository publishes no GitHub releases, and the licence is MIT.

Frequently asked questions

How do I install and configure all-repos?

Install it with `pip install all-repos`, then create an `all-repos.json` config file with an `output_dir`, a `source` module path and a `push` module path. A different config file can be selected per invocation with `-C CONFIG_FILENAME`, and the default filename can be changed with the `ALL_REPOS_CONFIG_FILENAME` environment variable.

How do I search across all my repositories with all-repos?

Use `all-repos-find-files` for paths and `all-repos-grep` for content, both of which take a Python regular expression. The `--repos-with-matches` option prints only the repositories that matched, and additional arguments to `all-repos-grep` are passed on to `git grep`.

Which repositories does all-repos modify?

The `include` option is a Python regex for selecting repository names and defaults to an empty string, so everything is included by default, while `exclude` defaults to `^$` and so excludes nothing. Setting both is the practical way to limit a sweeping change to a subset of your repositories.

Does all-repos-sed work on macOS?

Only with GNU sed installed. The documentation notes that the command assumes GNU sed and tells macOS users to install `gnu-sed` with Homebrew and add its libexec directory to PATH, because the BSD sed that ships with the system gives different results.

Official sources

  1. asottile/all-repos on GitHub
  2. Issues
  3. License: MIT
  4. README
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/asottile-all-repos.svg)](https://hysenlabs.com/projects/asottile-all-repos)

Was this useful?