gitpp
Git Personal Parallel Manager — manage 100+ Git repos with one command.
Why gitpp?
When you maintain 100+ repos across multiple machines, the session start ritual gets old fast:
# Without gitpp — repeated for every repo
&&
&&
&&
# ... 97 more times
With gitpp — one command, all repos in parallel, with a live TUI:
That covers the basic case. gitpp also solves a subtler problem: commit author identity.
If you have personal OSS repos, work repos, and hobby side-projects on the same machine,
setting user.name/user.email in ~/.gitconfig means one identity wins and the others
get wrong attribution. gitpp takes the opposite approach — it sets git config --local
on each repo based on the YAML file in that directory tree, so identity follows location,
not a global config file.
Features
- Parallel clone/pull/push with configurable concurrency (
jobs, default 20) - Full-screen TUI (ratatui) with real-time per-repo progress bars and status icons
- Per-directory git config —
user.name,pull.rebase, and any other git config key, applied locally to every repo in the group - Push opt-in — push is disabled unless
comments.defaultis explicitly set; clone/pull work without it - AI-friendly summary — plain-text output after completion, paste directly to your AI assistant for diagnosis
- Interactive REPL mode with tab completion and history
- Quiet mode for scripts and CI (no TUI, summary to stdout)
- Single binary, zero runtime dependencies
What it does
Put a gitpp.yaml in your repos directory, then:
A full-screen TUI shows real-time progress for every repository:
List mode (default):
┌──────────────────────────────────────────────────────────────┐
│ gitpp j/k:move Enter:detail h/l:scroll q:quit │
└──────────────────────────────────────────────────────────────┘
┌─ Repositories [1-20/101] ────────────────────────────────────┐
│▸✓ freeza Done │
│ [████████████████████████████████████████] 100% │
│ ⚙ sss Pulling... │
│ [████████████████████░░░░░░░░░░░░░░░░░░░░] 50% │
│ ⏸ noun-gender Waiting... │
│ [░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0% │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ Total: 101 | Done: 50 | OK: 48 | Fail: 2 │
└──────────────────────────────────────────────────────────────┘
Press Enter on any repo to open the detail pane and see live git output:
Detail mode:
┌──────────────────────────────────────────────────────────────┐
│ gitpp j/k:move Enter:detail h/l:scroll q:quit │
└──────────────────────────────────────────────────────────────┘
┌─ Repositories [1-20/101] ──────┬─ sss ───────────────────────┐
│ ✓ freeza Done 100% │ remote: Enumerating objects: │
│▸⚙ sss Pull.. 50% │ 12, done. │
│ ⏸ noun-gender Wait.. 0% │ Receiving objects: 60% │
│ │ (7/12) 1.2 MiB │
└────────────────────────────────┴──────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ Total: 101 | Done: 50 | OK: 48 | Fail: 2 │
└──────────────────────────────────────────────────────────────┘
After all repos finish, a plain-text summary is printed to stdout — paste it directly to your AI assistant for diagnosis:
gitpp pull: 98/101 succeeded, 3 failed
error: Your local changes to the following files would be overwritten by merge:
src/main.rs
fatal: refusing to merge unrelated histories
Install
Or build from source:
Configuration
Create gitpp.yaml in the root of your repos directory:
config:
user.name: your-name
user.email: your-email@example.com
pull.rebase: "true"
comments:
default: sync.
jobs: 20
repos:
- enabled: true
remote: git@github.com:user/repo-a.git
branch: main
group: "projects"
- enabled: true
remote: git@github.com:user/repo-b.git
branch: main
group: "projects"
- enabled: false # skip this repo
remote: git@github.com:user/archived.git
branch: main
group: "archive"
| Field | Type | Required | Description |
|---|---|---|---|
config |
map | no | git config --local key-value pairs applied to every repo before and after each operation. Supports any valid git config key (user.name, pull.rebase, core.autocrlf, …). Removing a key from the YAML does not unset it from existing repos' .git/config — it only overwrites. |
comments.default |
string | no* | Commit message used by push. Push is disabled unless this is set to a non-empty string. Omit the comments section entirely if you only use clone/pull. |
jobs |
number | no | Max concurrent operations. Default: 20. Overridable with -j N on the CLI. |
repos[].enabled |
bool | yes | Set false to skip a repo without removing it from the file. |
repos[].remote |
string | yes | SSH or HTTPS remote URL. Repository name is extracted from the URL automatically (.git suffix stripped). |
repos[].branch |
string | yes | Branch passed to git clone -b. |
repos[].group |
string | yes | Subdirectory under the repo root where this repo is cloned (e.g., group: "2025" → ./2025/repo-name). |
Multiple identities on one machine
Create separate YAML files in separate directories, each with its own config: block:
~/repos/
personal/
gitpp.yaml # user.email: me@personal.dev
work/
gitpp.yaml # user.email: me@company.com
hobby/
gitpp.yaml # user.email: me@hobbyaccount.io
No global ~/.gitconfig user needed — missing global config is a feature, not a bug.
A repo without local config will refuse to commit, which is the correct fail-safe.
Usage
# One-shot mode
# Use a config file from another location
# Specify both config and repo root
# Quiet mode (no TUI — summary to stdout, progress to stderr)
# Interactive mode with tab completion
TUI Controls
| Key | Action |
|---|---|
j / k / ↑ / ↓ |
Navigate repos |
g / G |
Jump to top / bottom |
Enter |
Toggle detail pane |
h / l / ← / → |
Scroll detail pane (3 lines at a time) |
Esc |
Close detail pane (or exit browse mode) |
q |
Quit immediately |
When all repos finish, gitpp waits 2 seconds. Press any key to enter browse mode and inspect results; or do nothing and it exits automatically.
Clone duplicate detection
If the target directory already contains a .git:
| Situation | Result |
|---|---|
No .git |
Clone normally |
.git present, remote matches |
"Already cloned" (Success) — config still applied |
.git present, remote mismatch |
"Remote mismatch" (Failed) — shows expected vs actual URL |
License
MIT