Clone all your repositories and apply sweeping changes.
Clone all your repositories and apply sweeping changes.
pip install all-repos
All command line interfaces provided by all-repos
provide the following
options:
-h
/ --help
: show usage information-C CONFIG_FILENAME
/ --config-filename CONFIG_FILENAME
: use a non-defaultall-repos.json
can be changed with the environmentALL_REPOS_CONFIG_FILENAME
).--color {auto,always,never}
: use color in output (default auto
).all-repos-complete [options]
Add git clone
tab completion for all-repos repositories.
Requires jq to function.
Add to .bash_profile
:
eval "$(all-repos-complete -C ~/.../all-repos.json --bash)"
all-repos-clone [options]
Clone all the repositories into the output_dir
. If run again, this command
will update existing repositories.
Options:
-j JOBS
/ --jobs JOBS
: how many concurrent jobs will be used to complete8
).Sample invocations:
all-repos-clone
: clone the repositories specified in all-repos.json
all-repos-clone -C all-repos2.json
: clone using a non-default configall-repos-find-files [options] PATTERN
Similar to a distributed git ls-files | grep -P PATTERN
.
Arguments:
PATTERN
: the python regexOptions:
--repos-with-matches
: only print repositories with matches.Sample invocations:
all-repos-find-files setup.py
: find all setup.py
files.all-repos-find-files --repos setup.py
: find all repositories containingsetup.py
.all-repos-grep [options] [GIT_GREP_OPTIONS]
Similar to a distributed git grep ...
.
Options:
--repos-with-matches
: only print repositories with matches.GIT_GREP_OPTIONS
: additional arguments will be passed on to git grep
.git grep --help
for available options.Sample invocations:
all-repos-grep pre-commit -- 'requirements*.txt'
: find all repositoriespre-commit
listed in a requirements file.all-repos-grep -L six -- setup.py
: find setup.py files which do notsix
.all-repos-list-repos [options]
List all cloned repository names.
all-repos-manual [options]
Interactively apply a manual change across repos.
note: all-repos-manual
will always run in --interactive
autofixing mode.
note: all-repos-manual
requires the --repos
autofixer option.
Options:
all-repos-manual
is--branch-name BRANCH_NAME
: override the autofixer branch name (defaultall-repos-manual
).--commit-msg COMMIT_MSG
(required): set the autofixer commit message.all-repos-sed [options] EXPRESSION FILENAMES
Similar to a distributed
git ls-files -z -- FILENAMES | xargs -0 sed -i EXPRESSION
.
note: this assumes GNU sed. If you’re on macOS, install gnu-sed
with Homebrew:
brew install gnu-sed
# Add to .bashrc / .zshrc
export PATH="$(brew --prefix)/opt/gnu-sed/libexec/gnubin:$PATH"
Arguments:
EXPRESSION
: sed program. For example: s/hi/hello/g
.FILENAMES
: filenames glob (passed to git ls-files
).Options:
all-repos-sed
is-r
/ --regexp-extended
: use extended regular expressions in the script.man sed
for further details.--branch-name BRANCH_NAME
override the autofixer branch name (defaultall-repos-sed
).--commit-msg COMMIT_MSG
override the autofixer commit message. (defaultgit ls-files -z -- FILENAMES | xargs -0 sed -i ... EXPRESSION
).Sample invocations:
all-repos-sed 's/foo/bar/g' -- '*'
: replace foo
with bar
in all files.A configuration file looks roughly like this:
{
"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"
}
}
output_dir
: where repositories will be cloned to when all-repos-clone
issource
: the module import path to a source
, see below for builtinsource_settings
: the source-type-specific settings, the source module’spush
: the module import path to a push
, see below for builtin pushpush_settings
: the push-type-specific settings, the push module’sinclude
(default ""
): python regex for selecting repositories. Onlyexclude
(default "^$"
): python regex for excluding repositories.all_branches
(default false
): whether to clone all of the branches orall_repos.source.json_file
Clones all repositories listed in a file. The file must be formatted as
follows:
{
"example/repo1": "https://git.example.com/example/repo1",
"repo2": "https://git.example.com/repo2"
}
source_settings
filename
: file containing repositories one-per-line.output/
+--- repos.json
+--- repos_filtered.json
+--- {repo_key1}/
+--- {repo_key2}/
+--- {repo_key3}/
all_repos.source.github
Clones all repositories available to a user on github.
source_settings
api_key
: the api key which the user will log in as.
public_repo
, though you’llrepo
to access private repositories.api_key_env
: alternatively API key can also be passed via an environment variableusername
: the github username you will log in as.source_settings
collaborator
(default false
): whether to include repositories which areforks
(default false
): whether to include repositories which are forks.private
(default false
): whether to include private repositories.archived
(default: false
): whether to include archived repositories.base_url
(default: https://api.github.com
) is the base URL to the Githubhttps://{your_domain}/api/v3
).output/
+--- repos.json
+--- repos_filtered.json
+--- {username1}/
+--- {repo1}/
+--- {repo2}/
+--- {username2}/
+--- {repo3}/
all_repos.source.github_forks
Clones all repositories forked from a repository on github.
source_settings
api_key
: the api key which the user will log in as.
public_repo
.api_key_env
: alternatively API key can also be passed via an environment variablerepo
: the repo which has forkssource_settings
collaborator
(default true
): whether to include repositories which areforks
(default true
): whether to include repositories which are forks.private
(default false
): whether to include private repositories.archived
(default: false
): whether to include archived repositories.base_url
(default: https://api.github.com
) is the base URL to the Githubhttps://{your_domain}/api/v3
).See the directory structure for
all_repos.source.github
.
all_repos.source.github_org
Clones all repositories from an organization on github.
source_settings
api_key
: the api key which the user will log in as.
public_repo
, though you’llrepo
to access private repositories.api_key_env
: alternatively API key can also be passed via an environment variableorg
: the organization to clone fromsource_settings
collaborator
(default true
): whether to include repositories which areforks
(default false
): whether to include repositories which are forks.private
(default false
): whether to include private repositories.archived
(default: false
): whether to include archived repositories.base_url
(default: https://api.github.com
) is the base URL to the Githubhttps://{your_domain}/api/v3
).See the directory structure for
all_repos.source.github
.
all_repos.source.gitolite
Clones all repositories available to a user on a
gitolite host.
source_settings
username
: the user to SSH to the server as (usually git
)hostname
: the hostname of your gitolite server (e.g. git.mycompany.com
)The gitolite API is served over SSH. It is assumed that when all-repos-clone
is called, it’s possible to make SSH connections with the username and hostname
configured here in order to query that API.
source_settings
mirror_path
(default None
): an optional mirror to clone repositories from.
This is a Python format string, and can use the variable repo_name
.
This can be anything git understands, such as another remote server (e.g.
gitmirror.mycompany.com:{repo_name}
) or a local path (e.g.
/gitolite/git/{repo_name}.git
).
output/
+--- repos.json
+--- repos_filtered.json
+--- {repo_name1}.git/
+--- {repo_name2}.git/
+--- {repo_name3}.git/
all_repos.source.bitbucket
Clones all repositories available to a user on Bitbucket Cloud.
source_settings
username
: the Bitbucket username you will log in as.app_password
: the authentication method for the above user to login with
all_repos.source.bitbucket_server
Clones all repositories available to a user on Bitbucket Server.
source_settings
base_url
: the bitbucket server URL (eg bitbucket.domain.com
)username
: the Bitbucket username you will log in as.app_password
: the authentication method for the above user to login with
source_settings
project
(default None
): an optional project to restrict the search for repositories.output/
+--- repos.json
+--- repos_filtered.json
+--- {username1}/
+--- {repo1}/
+--- {repo2}/
+--- {username2}/
+--- {repo3}/
all_repos.source.gitlab_org
Clones all repositories from an organization on gitlab.
source_settings
api_key
: the api key which the user will log in as.
read_api
.api_key_env
: alternatively API key can also be passed via an environment variableorg
: the organization to clone fromsource_settings
base_url
: (default https://gitlab.com/api/v4
) the gitlab server URLarchived
(default: false
): whether to include archived repositories.output/
+--- repos.json
+--- repos_filtered.json
+--- {org}/
+--- {subpgroup1}/
+--- {subpgroup2}/
+--- {repo1}/
+--- {repo2}/
+--- {repo3}/
+--- {repo4}/
First create a module. This module must have the following api:
Settings
classThis class will receive keyword arguments for all values in the
source_settings
dictionary.
An easy way to implement the Settings
class is by using a namedtuple
:
Settings = collections.namedtuple('Settings', ('required_thing', 'optional'))
Settings.__new__.__defaults__ = ('optional default value',)
In this example, the required_thing
setting is a required setting
whereas optional
may be omitted (and will get a default value of
'optional default value'
).
def list_repos(settings: Settings) -> Dict[str, str]:
callableThis callable will be passed an instance of your Settings
class. It must
return a mapping from {repo_name: repository_url}
. The repo_name
is the
directory name inside the output_dir
.
all_repos.push.merge_to_master
Merges the branch directly to the default branch and pushes. The commands it
runs look roughly like this:
git checkout main
git pull
git merge --no-ff $BRANCH
git push origin HEAD
push_settings
fast_forward
(default: false
): if true
, perform a fast-forward--ff-only
). If false
, create a merge commit (--no-ff
).all_repos.push.github_pull_request
Pushes the branch to origin
and then creates a github pull request for the
branch.
push_settings
api_key
: the api key which the user will log in as.
public_repo
, though you’llrepo
to access private repositories.api_key_env
: alternatively API key can also be passed via an environment variableusername
: the github username you will log in as.push_settings
base_url
(default: https://api.github.com
) is the base URL to the Githubhttps://{your_domain}/api/v3
).draft
(default: false
) if true will open the pull request as a draft.fork
(default: false
): (if applicable) a fork will be created and pushedall_repos.push.bitbucket_server_pull_request
Pushes the branch to origin
and then creates a Bitbucket pull request for the branch.
push_settings
base_url
: the Bitbucket server URL (eg bitbucket.domain.com
)username
: the Bitbucket username you will log in as.app_password
: the authentication method for the above user to login with
all_repos.push.gitlab_pull_request
Pushes the branch to origin
and then creates a GitLab pull request for the branch.
push_settings
base_url
: the GitLab server URL (eg https://{gitlab.domain.com}/api/v4
)api_key
: the api key which the user will log in as.
write_repository
.api_key_env
: alternatively API key can also be passed via an environment variableall_repos.push.readonly
Does nothing.
push_settings
There are no configurable settings for readonly
.
First create a module. This module must have the following api:
Settings
classThis class will receive keyword arguments for all values in the push_settings
dictionary.
def push(settings: Settings, branch_name: str) -> None:
This callable will be passed an instance of your Settings
class. It should
deploy the branch. The function will be called with the root of the
repository as the cwd
.
An autofixer applies a change over all repositories.
all-repos
provides several api functions to write your autofixers with:
all_repos.autofix_lib.add_fixer_args
def add_fixer_args(parser):
Adds the autofixer cli options.
Options:
--dry-run
: show what would happen but do not push.-i
/ --interactive
: interactively approve / deny fixes.-j JOBS
/ --jobs JOBS
: how many concurrent jobs will be used to complete1
).--limit LIMIT
: maximum number of repos to process (default: unlimited).--author AUTHOR
: override commit author. This is passed directly togit commit
. An example: --author='Herp Derp <[email protected]>'
.--repos [REPOS [REPOS ...]]
: run against specific repositories instead.xargs autofixer ... --repos
. This can beall-repos
.all_repos.autofix_lib.from_cli
def from_cli(args, *, find_repos, msg, branch_name):
Parse cli arguments and produce autofix_lib
primitives. Returns
(repos, config, commit, autofix_settings)
. This is handled separately from
fix
to allow for fixers to adjust arguments.
find_repos
: callback taking Config
as a positional argument.msg
: commit message.branch_name
: identifier used to construct the branch name.all_repos.autofix_lib.fix
def fix(
repos, *,
apply_fix,
check_fix=_noop_check_fix,
config: Config,
commit: Commit,
autofix_settings: AutofixSettings,
):
Apply the fix.
apply_fix
: callback which will be called once per repository. The cwd
all_repos.autofix_lib.run
def run(*cmd, **kwargs):
Wrapper around subprocess.run
which prints the command it will run. Unlike
subprocess.run
, this defaults check=True
unless explicitly disabled.
The trivial autofixer is as follows:
import argparse
from all_repos import autofix_lib
def find_repos(config):
return []
def apply_fix():
pass
def main(argv=None):
parser = argparse.ArgumentParser()
autofix_lib.add_fixer_args(parser)
args = parser.parse_args(argv)
repos, config, commit, autofix_settings = autofix_lib.from_cli(
args, find_repos=find_repos, msg='msg', branch_name='branch-name',
)
autofix_lib.fix(
repos, apply_fix=apply_fix, config=config, commit=commit,
autofix_settings=autofix_settings,
)
if __name__ == '__main__':
raise SystemExit(main())
You can find some more involved examples in all_repos/autofix:
all_repos.autofix.azure_pipelines_autoupdate
: upgrade pinned azureall_repos.autofix.pre_commit_autoupdate
: runs pre-commit autoupdate
.all_repos.autofix.pre_commit_autopep8_migrate
: migrates autopep8-wrapper
all_repos.autofix.pre_commit_cache_dir
: updates the cache directoryall_repos.autofix.pre_commit_flake8_migrate
: migrates flake8
fromall_repos.autofix.pre_commit_migrate_config
: runspre-commit migrate-config
.all_repos.autofix.setup_py_upgrade
: runs setup-py-upgrade and thensetup.py
to setup.cfg
.