madgit

Run one git command in every repository under a directory.

Pull, status and clone across dozens of repos at once.

Git toolsCLIRust, clapv0.6.0, MIT

$ cargo install madgit

The problem

I keep a lot of repositories side by side in one directory. Pulling, checking status or switching to the main branch means walking into each of them in turn. madgit runs the command in every repository under a directory instead. The blog post covers how this wrapper grew out of custom git commands and a lightning talk at Cebula Camp 2025.

madgit --dir ~/projects status
madgit pull
madgit --dir ~/projects exec -- git log --oneline -5

The usual alternatives are a shell loop over */ or a multi-repository tool like myrepos. madgit adds what those leave to you: cloning every repository of a forge owner, remembering where each checkout came from, and mirrors.

Any top-level command madgit does not know is forwarded as git <subcommand> to every repository, so madgit stash or madgit rev-parse --show-toplevel just work. status colours each header: red needs sync, yellow has changes, cyan is clean.

What it does

CommandDetails
clonefrom a list file, or every repository of a GitHub, GitLab or Forgejo owner (self-hosted instances too)
reposprint repository URLs for an owner on any of the three forges
forge-infoforge, owner and repo per checkout, recorded at clone time or inferred from remotes
checkout-mainmain, falling back to master
no-gitsubdirectories that are not repositories
mirror add/sync/list/removeone-directional mirrors between existing repositories
mirror migratemove repositories between providers, optionally with issues, comments, PRs, releases, wikis, labels and milestones

Mirrors

The source is always authoritative. Sync runs in a throwaway bare workspace: branches and tags are fetched from the source and pushed to the mirror, and forge-managed refs like refs/pull/* are skipped. Without --force only fast-forwards are pushed and divergence is an error; --force rewrites the mirror to match, deletions included. --dry-run lists the refs that would change.

The config stores names and URLs only. URLs with embedded credentials are rejected, and git transport uses whatever ssh-agent or credential helper you already have. GITHUB_TOKEN, GITLAB_TOKEN and FORGEJO_TOKEN are used only for API discovery and migration.

madgit mirror add git@gitlab.com:wykwit/madgit.git git@github.com:wykwit/madgit.git
madgit mirror sync wykwit/madgit --dry-run
madgit mirror migrate --dry-run --from-gitlab mygroup --to-forgejo codeberg.org/myuser

A mirror is not a backup. Pushes are not atomic, concurrent syncs are not coordinated, and with --force a history rewrite on the source propagates to the mirror.