git-rain is a command-line tool designed to streamline Git repository management by discovering local repositories and synchronizing them from their remotes in one command. This tool serves as the counterpart to git-fire, focusing on bringing updates into your local environment rather than pushing changes out.
Key Features:
Repository Discovery: Automatically identifies Git repositories under a specified scan path or known registry entries.
Modes of Operation: Offers multiple modes including default fetch-all, targeted mainline fetching (--fetch-mainline), and full branch hydration (--sync).
Configuration Flexibility: Supports customization through CLI flags, local Git config, registry entries, and a global user configuration file.
Interactive TUI: Provides an interactive text-based interface for selecting repositories and choosing between fetch or sync operations.
Audience & Benefit:
Ideal for developers and DevOps teams managing multiple Git repositories. git-rain enhances efficiency by automating repository discovery and synchronization, reducing manual effort and potential errors in managing remote updates. Its configurable modes and safety measures ensure controlled data management, making it a reliable tool for streamlining workflow processes.
This description adheres to the specified structure, focusing on clarity and benefit without unnecessary technical jargon, ensuring it is both informative and accessible.
--sync — hydrate locals; scope from --branch-mode or config.
Destructive realignment
--risky or config risky_mode on the full branch-hydration path (same machinery as --sync). That path also runs without --sync when you pass --risky, set risky_mode, use a non-mainline branch_mode in config, or pass any --branch-mode value on the CLI (even mainline). Hard-reset to upstream only after backup refs.
> Warning: --prune is opt-in. On git fetch, --prune deletes stale remote-tracking branch refs (for example refs/remotes/origin/old-feature after that branch was removed on the server). That is usually what you want for a tidy clone, but it removes those ref names locally until a later fetch brings them back if the branch reappears. Turn pruning on only when you mean to.
# preview first — lists repos and whether each would get fetch-only vs branch hydration, without running git
# (still does a filesystem scan; the flag name is a little ironic — "dry" rain that still kicks up dust)
git-rain --dry-run
# default: scan repos, then git fetch --all per repo (no --prune unless configured or --prune)
git-rain
# lighter: mainline remote-tracking refs only
git-rain --fetch-mainline
# full local branch sync (same safety/risky rules as before)
git-rain --sync
# interactive TUI: pick repos, then default fetch or --sync behavior
git-rain --rain
First-party install script (same idea as git-fire/scripts/install.sh): downloads the matching .tar.gz from Releases, verifies checksums.txt, and installs to $INSTALL_DIR (default ~/.local/bin).
The main URL below always runs the installer script from the latest commit on that branch, while the binary itself comes from the latest GitHub release (or from VERSION if you set it). That is convenient for copy-paste installs, but it means the script can drift ahead of any given release. For a fully pinned install, use the release tag in the URL (as in each release’s notes) and set VERSION to the same tag.
For repeated automation against the GitHub API (resolving latest), set GITHUB_TOKEN or GH_TOKEN so authenticated rate limits apply. VERSION may be a bare semver (0.9.1); the installer tries the v-prefixed release tag first, then the exact string you passed.
If your shell does not already include ~/.local/bin on PATH, add it (the installer prints a reminder). Example for bash (skips the line if .local/bin is already mentioned in ~/.bashrc):
if ! grep -qF '.local/bin' ~/.bashrc 2>/dev/null; then
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
fi
source ~/.bashrc
Windows is not supported by this script — use WinGet or download a .zip from Releases.
Then add $env:USERPROFILE\bin to your user PATH if not already present.
Verify install
git-rain --version
which git-rain
Build from source
git clone https://github.com/git-fire/git-rain.git
cd git-rain
make build # produces ./git-rain
make install # installs to ~/.local/bin/git-rain
Requires Go 1.24.2+.
How It Works
Scan — walks your configured scan path and includes known registry repos (--no-scan limits to registry only)
Default: fetch all remotes — for each repo, git fetch --all plus optional --prune (see warning above), and optional --tags from config or --tags. Updates remote-tracking refs under refs/remotes/ only; local branch refs are not created or moved (fast path: checkout origin/ when you need a branch).
--fetch-mainline — targeted git fetch for mainline branches (and mainline_patterns) only, with the same opt-in --prune rules as the default fetch.
--sync — hydrates local branches: git fetch --all (same prune/tags rules), then updates eligible locals toward upstream. Scope comes from --branch-mode or config branch_mode: mainline, checked-out, all-local, or all-branches (creates local tracking branches for remotes you do not have yet — can be many branches).
--risky — does not change fetch behavior by itself; on the full branch-hydration path (see --sync above — entered by --sync, --risky, config risky_mode, a non-mainline branch_mode, or any --branch-mode flag on the CLI) it allows hard reset to upstream after creating git-rain-backup-* refs when you would otherwise skip local-only commits.
Report — one summary line per repo on the default full fetch; per-branch lines on --fetch-mainline and --sync.
Key Features
One-command workflow — default full remote fetch, optional --fetch-mainline, then --sync (+ branch_mode) for locals, --risky when you accept destructive realignment
Safety-first defaults — never rewrites local-only commits; dirty worktrees are skipped, not clobbered
Risky mode — opt-in destructive realignment: creates a git-rain-backup-* ref, then hard-resets to upstream
Non-checked-out branches — updated directly without touching the worktree
Interactive TUI (--rain) — streaming repo picker, then the same default fetch, --fetch-mainline, or --sync behavior
Registry — discovered repos persist across runs; mark repos ignored to skip them permanently
Dry run — preview all repos that would be fetched or synced without making any changes
--fetch-mainline — mainline-only remote-tracking ref refresh instead of the default full git fetch --all
Core Commands
# dry run — preview repos, no changes (still scans disk unless you add --no-scan)
git-rain --dry-run
# default run — scan repos, git fetch --all per repo
git-rain
# this run: prune stale remote-tracking refs for every repo
git-rain --prune
# mainline-only remote-tracking ref refresh (lighter than default)
git-rain --fetch-mainline
# full local branch sync after scan
git-rain --sync
# interactive TUI before default fetch or sync
git-rain --rain
# sync only known registry repos, skip filesystem scan
git-rain --no-scan
# scan a specific path
git-rain --path ~/projects
# risky full sync — realign local-only commits after creating backup branches
git-rain --sync --risky
# generate example config file
git-rain --init
Flags
Flag
Description
--dry-run
No git fetch / branch updates — still scans disk to list repos unless --no-scan (then only registry-known paths are considered). The name is weather-themed irony: no “wet” git work, but not a no-op.
--rain
Interactive TUI repo picker before running
--sync
Update local branches from remotes (after git fetch --all; default run does not sync locals)
--fetch-mainline
Mainline-only remote git fetch per remote instead of default git fetch --all (not with --sync or other full-sync triggers)
--branch-mode
On the full branch-hydration path (same triggers as --sync — see table above): mainline, checked-out, all-local, or all-branches (overrides config branch_mode for this run)
--prune
Pass --prune on fetch for this run (highest precedence; cannot combine with --no-prune)
--no-prune
Never pass --prune on fetch for this run (overrides --prune, config, registry, and rain.fetchprune)
Picking rain_animation_mode = "garden" swaps the rain background for a
slow-paced lifecycle: seeds drift down with the rain, plants progress through
sprout, bud, and bloom, then wither and scatter 2–3 new seeds nearby. When the
visible plants cover roughly 80% of the rain strip, the storm clears, the sun
comes out, and the surviving flowers stay forever.
The defaults aim for a calm pace, but a few advanced TOML keys are available
under [ui] for tweaking. They are intentionally not surfaced in the
in-app settings TUI — leave any unset (or set to 0) to keep the default.
[ui]
rain_animation_mode = "garden"
# garden_seed_rate = 0.055 # fraction of new sky drops that fall as seeds (0..1)
# garden_growth_pace = 1.0 # multiplier on stage moisture thresholds (>1 = slower)
# garden_bloom_duration_base = 60 # min frames a flower lingers in full bloom
# garden_bloom_duration_jitter = 40 # extra random frames added to bloom lifetime
# garden_wither_duration = 28 # frames a withered plant lingers before re-seeding
# garden_offspring_min = 2 # minimum seeds a dying plant scatters
# garden_offspring_max = 3 # maximum seeds a dying plant scatters
# garden_offspring_spread = 3 # X-jitter half-width around the parent column
garden_growth_pace is the most useful single dial: set it to 2.0 to roughly
halve growth speed, or 0.5 to roughly double it. The other knobs trade
visual density (more or fewer seeds, longer or shorter blooms) for clarity.
Snow mode and rain panel size
rain_animation_mode = "snow" uses the same animation strip for a winter scene:
falling snowflakes, snow that keeps piling on the ground, a small log cabin
with chimney smoke and lit windows, occasional evergreen trees that pick up
frost, and a snowman that grows in stages (two spheres, then face, pipe, and
top hat).
rain_panel_size controls how many terminal rows the animation canvas uses:
compact (5), comfortable (8, default), or tall (11). The TUI clamps the
height automatically so the bordered panel still fits short terminals.
Registry (repos.toml) — Writes use a cross-process lock file (repos.toml.lock), atomic replace, and stale-lock detection (owner PID). If a process dies mid-run you may still see a leftover lock: the CLI prompts to remove it when safe, or you can use --force-unlock-registry in scripts. This is the same class of “stale lock / don’t corrupt the database” problem as other multi-repo tools; treat lock removal like any other forced unlock — only when you are sure no other git-rain is running.
User config (config.toml) — Writes use config.toml.lock with a bounded wait (so the --rain settings UI does not hang forever if another process holds the lock), then an atomic replace (PID-scoped temp file + rename). If the lock cannot be acquired in time, the TUI shows a save error and keeps in-memory settings. Avoid hand-editing config.toml while a session is saving; you might leave an orphan *.tmp after a crash — safe to delete if present.
Interactive TUI
git-rain --rain opens an interactive picker. Repositories stream in as the filesystem scan finds them — no waiting for the full scan to complete before you can start picking. After you confirm, the tool runs the default full fetch (git fetch --all, prune opt-in) unless you passed --fetch-mainline, or full branch hydration is implied by --sync, --risky, risky_mode in config, a non-mainline branch_mode, or any --branch-mode on the CLI. Quitting (q or ctrl+c) cancels the in-progress scan (in-flight git subprocesses are aborted via the scan context); ctrl+c outside raw TTY mode is treated like cancel.
Status strip and optional log panel are driven from structured session events. Confirming selection still exits TUI and runs fetch/sync work in normal CLI output.
Key bindings:
Key
Action
space
Toggle repo selection
a
Select all / deselect all
enter
Confirm selection and begin fetch or sync
q / ctrl+c
Abort picker
c / Esc
Back from settings (ignored list uses Esc / i / b)
↑ / ↓
Navigate
Shift+L
Toggle in-TUI log panel
e
Export visible TUI log buffer to ~/.cache/git-rain/exports/
Safe Mode vs Risky Mode
Situation
Safe mode (default)
Risky mode (--risky)
Branch is fast-forwardable
✓ Updated
✓ Updated
Branch has local-only commits
⊘ Skipped
⚠ Backed up + reset
Checked-out branch, dirty worktree
⊘ Skipped
⊘ Skipped
No upstream tracked
⊘ Skipped
⊘ Skipped
In risky mode, a backup ref named like git-rain-backup--- is created before any hard reset so local work is always recoverable.
Registry
Discovered repositories are stored in ~/.config/git-rain/repos.toml. Each entry tracks path, name, status, and last-seen time. Optional per-repo fetch_prune (boolean) opts that repository into --prune on fetch when set; it is overridden by that repo’s local git config rain.fetchprune, and both are overridden by --prune / --no-prune on the CLI for that run.
Repo statuses:
active — present on disk and eligible for sync
missing — was discovered previously but the directory is gone
ignored — permanently excluded from sync
The registry uses a file lock to prevent concurrent git-rain instances from corrupting it. If a previous run exited uncleanly, git-rain detects the stale lock and prompts to remove it (or pass --force-unlock-registry for non-interactive use).
Security Notes
git-rain shells out to the system git binary and inherits your existing git credentials (SSH agent, credential helper, etc.). No credentials are stored or transmitted by git-rain itself.
Secret detection: git-rain sanitizes error messages and log output to avoid echoing paths or git output that might contain tokens. This is a best-effort measure — keep secrets out of repo paths and remote URLs.
Contributing
Contributions are welcome. Tests use git-testkit for building git repository fixtures in integration-style tests. Prefer table-driven tests and real git invocations over mocks.
make test-race # run all tests with race detector
make lint # go vet