Usage
Commands
| Command | Description |
|---|---|
<provider> login | Authenticate with one provider |
<provider> sync | Sync repositories from one provider |
login [provider] | Authenticate (all detected providers if none specified) |
sync --all | Sync every provider whose CLI or credentials are available |
status | Show dirty, ahead, behind and diverged repos |
init | Create the default config file |
update | Check for a new release and self-update |
completions <shell> | Print a shell completion script |
version | Print the version |
Providers are github, gitlab, forgejo (aliases gitea, gogs), bitbucket, radicle, sourcehut (alias srht), onedev and any (alias git).
login
Authenticate with one or all providers:
repos-manager login # All detected providers
repos-manager login gitlab # GitLab only
repos-manager github login # GitHub only sync
Sync repositories from a provider:
repos-manager github sync # Sync GitHub repos
repos-manager sync --all # All providers
repos-manager sync --all --parallel 8 # 8 concurrent jobs
repos-manager github sync --filter 'Dxsk/*' # Only Dxsk's repos
repos-manager sync --all --ignore 'Dxsk/old-*' # Everything but these
repos-manager sync --all --prune # Remove deleted remote repos
repos-manager sync --all --dry-run # Preview without changes
repos-manager sync --all --plan # Recon summary only, clone nothing
repos-manager sync --all --write-ignore # Write .repos.yml to pick from
repos-manager sync --all --yes # No confirmation, whatever the size For each repository:
- missing locally: it is cloned;
- present and clean:
git fetch --all --prune, then a fast-forward of the remote default branch if it is checked out. Another branch or a detached HEAD is only fetched, so a sync never moves the branch you work on; - present with uncommitted changes: it is skipped and reported as
dirty.
sync --all skips providers it cannot use, that is those with neither their CLI installed nor saved credentials, and an error on one host never stops the others. --host targets a single provider and is rejected with sync --all: use repos-manager <provider> sync --host <host> instead.
--prune removes local repos under the host directory that are no longer listed by the remote. It never deletes:
- a repo with uncommitted or untracked changes, unpushed commits or a stash;
- a repo an
ignorelist catches, or one noonlylist allows (see Filtering); - anything outside the base directory.
When more than half of a host's local repos would go, the listing is treated as incomplete and prune stops: check with --dry-run, then add --force.
Before it clones
Every host is listed first, and a sync says what it is about to do before downloading anything:
github.com 42 in scope, 39 cloned, 3 new (1.2 GB)
biggest new: org/a 586 MB, org/b 586 MB
gitlab.corp 18 in scope, 0 cloned, 18 new (4.7 GB, 2 unknown)
biggest new: team/monorepo 3.1 GB, team/assets 900 MB
with --prune: 2 local repos would be deleted
21 clones, 39 updates, about 5.9 GB to download, 2 of unknown size.
Proceed? [Y/n] in scope is what the filters kept for that host, cloned what is already on disk, new what would be cloned. The volume in brackets is that of the new repositories only, since an update transfers very little.
The figures come from the forge APIs, which report the size of the repository on the server, not the size of the transfer: that is why the total says "about". A size a provider does not report is counted as unknown and never estimated, so a host whose new repositories all have unknown sizes shows ? instead of a figure.
The summary and the question only appear when the run is worth a word: by default 5 new clones or more, or 1 GB or more to download, counted across the whole run rather than per host. A daily sync where nothing is new prints exactly what it has always printed.
The question is asked once per run, sync --all included, and only when stdin and stdout are both terminals. A cron job, a CI step or a pipe gets the summary and carries straight on, so nothing can ever wait for an answer that will not come. --yes and --dry-run print the summary without asking. Answering n exits with a success code, clones nothing, and prints how to narrow the scope (--filter, --ignore, <base_dir>/.repos.yml).
A host whose listing came back short already has --prune disabled for it, and the summary says so on that host's line, before the question rather than after the run.
See the Configuration page, Sync confirmation, for confirm_sync and its thresholds.
Planning a big sync
Two flags make that summary a workflow of its own. Neither clones, updates or prunes anything, and both exit with a success code.
# 1. Reconnaissance: print the summary, then stop
repos-manager sync --all --plan
# 2. Write a file holding every repo in scope, all commented out
repos-manager sync --all --write-ignore
# 3. Uncomment what you do not want in <base_dir>/.repos.yml, then
repos-manager sync --all --plan always prints: under the thresholds, under -q, and when there is nothing to do, since a summary of zeros answers the question you asked. It never prompts. It is not --dry-run: --dry-run walks the list and says "would clone / would update" for every repository, --plan prints the aggregate summary and nothing else.
--write-ignore writes <base_dir>/.repos.yml and stops, printing the path it wrote. Every repository is commented out, so the file as generated changes nothing about what gets synced: uncommenting a line is how you drop a repository. It refuses to overwrite an existing .repos.yml without --force, and --force carries the rules already in the file over into the new one, so a rewrite can only ever narrow the scope. See Filtering for the generated file in detail.
status
Show dirty, ahead, behind, and diverged repos:
repos-manager status Output:
github.com/Dxsk/git-identity-manager dirty
github.com/Dxsk/mtd ahead (+2)
gitlab.com/work/api behind (-3)
Total: 42 repos - 39 clean, 1 dirty, 1 ahead, 1 behind, 0 diverged While the scan is running, a single-line progress indicator shows the current repo being inspected on stderr ([N] host/owner/repo). It is suppressed when stderr is not a TTY or when --quiet is set.
status walks every .git directory under base_dir, pruning heavy vendored directories (node_modules, .venv, venv, __pycache__, target, vendor, dist, build, .next, .cache) and, on Linux, every network or FUSE mount point under base_dir (cloud drives, NFS, SMB, sshfs). Use --verbose to see which mounts were skipped, and see the Configuration page, Network-mount scanning, to opt back in.
init
Create a default config file at ~/.config/repos-manager/config.json (does nothing if it already exists):
repos-manager init update
Check for a newer release and replace the binary in place:
repos-manager update # asks for confirmation
repos-manager update --yes # no prompt The release archive for your platform is downloaded and verified against the release SHA256SUMS before the binary is swapped.
In addition to this manual command, login, sync and status print a one-line yellow banner when a newer release is available. The banner reads a cache refreshed at most once a day in the background, so it never slows down the command in progress. See the Configuration page, Update banner, for the opt-outs.
completions
Print a completion script for bash, zsh, fish, powershell or elvish:
repos-manager completions zsh > ~/.local/share/zsh/site-functions/_repos-manager See Getting Started for every shell.
help
Every command has its own help page:
repos-manager --help # General help
repos-manager github --help # GitHub commands
repos-manager github sync --help # Sync flags Flags
Sync flags (<provider> sync and sync --all):
| Flag | Description | Default |
|---|---|---|
--filter <pattern> | Only sync matching repos, repeatable | None |
--ignore <pattern> | Skip matching repos, repeatable | None |
--base-dir <path> | Base directory | ~/Documents |
--https | Use HTTPS instead of SSH | SSH |
--prune | Remove local repos not on remote | Off |
--force | With --prune, allow removing most local repos | Off |
--dry-run | Preview without making changes | Off |
--yes, -y | Do not ask before cloning | Off |
--host <host> | Custom host for self-hosted (provider sync only) | Configured hosts |
--parallel <n> | Concurrent sync jobs | 4 |
--verbose, -v | Show debug output | Off |
--quiet, -q | Suppress info/success messages (errors still shown) | Off |
status accepts --base-dir, --verbose and --quiet. update accepts --yes / -y too. Both --flag value and --flag=value forms work.