Skip to content

Commit 13d34b9

Browse files
Address PR review: prune short-circuit, dry-run sync, fetch-mainline guard
- applyRepoFetchPrune: skip git config read when CLI sets prune; warn and fall back on ReadRainFetchPrune errors - Reject --fetch-mainline with full-sync triggers; add test - runDryRun: show fetch --prune resolution for --sync path too - Fix Upsert for-loop indentation (gofmt) - UAT scenario 7 echo matches prune-opt-in default - CLAUDE/README: prune precedence and --fetch-mainline note - Replace misleading frozen label (fetch --all) Co-authored-by: Ben Schellenberger <bschellenberger2600@users.noreply.github.com>
1 parent ab54194 commit 13d34b9

5 files changed

Lines changed: 37 additions & 9 deletions

File tree

CLAUDE.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,16 @@
22

33
## Project Overview
44

5-
`git-rain` is a standalone Go CLI for pulling remote state back down — the reverse of `git-fire`. Operating modes, lightest to heaviest: **(1)** default `git fetch --all` per repo (`--prune` opt-in via `--prune`/`--no-prune`, `global.fetch_prune`, registry `fetch_prune`, or `git config --local rain.fetchprune`); **(2)** `--fetch-mainline`; **(3)** `--sync` + `branch_mode`; **(4)** `--risky` on the sync path. Extracted from the `git-fire` codebase and promoted to a first-class tool.
5+
`git-rain` is a standalone Go CLI for pulling remote state back down — the reverse of `git-fire`.
6+
7+
Operating modes, lightest to heaviest:
8+
9+
1. **Default**`git fetch --all` per repo. `--prune` is opt-in. Precedence per repo: CLI (`--prune` / `--no-prune`) → local `git config rain.fetchprune` → registry `fetch_prune` → global `fetch_prune`.
10+
2. **`--fetch-mainline`** — targeted fetches for mainline branches only (incompatible with `--sync` / full-sync triggers; CLI returns an error if combined).
11+
3. **`--sync` + `branch_mode`** — hydrate local branches from remotes.
12+
4. **`--risky`** — on the sync path only; allows hard reset to upstream after backup refs.
13+
14+
Extracted from the `git-fire` codebase and promoted to a first-class tool.
615

716
Module: `github.com/git-rain/git-rain`
817
Go version: 1.24.2

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ git-rain → fetch all remotes by default, or hydrate locals with --sync
2020

2121
`git-rain` discovers git repositories under your scan path (and known registry entries). **Default:** `git fetch --all` per repo (no `--prune` unless you opt in) so **remote-tracking refs** update without moving local branches. **Lighter fetch:** `--fetch-mainline`. **Local updates:** `--sync` with `--branch-mode` / config. **Destructive realignment:** `--risky` on the sync path only.
2222

23-
> **Warning: `--prune` is opt-in.** Passing `--prune` on `git fetch` 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 the next fetch brings them back if the branch reappears. Enable pruning only when you intend it: **`--prune`** / **`--no-prune`** for this run, **`global.fetch_prune`** in config, **`fetch_prune`** on a registry entry, or per-repo **`git config --local --bool rain.fetchprune true`**. Precedence for a given repo is: **CLI****repo `rain.fetchprune`****registry `fetch_prune`****global `fetch_prune`**.
23+
> **Warning: `--prune` is opt-in.** Passing `--prune` on `git fetch` 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 the next fetch brings them back if the branch reappears. Enable pruning only when you intend it, in this order of override: **`--prune`** / **`--no-prune`** for this run, per-repo **`git config --local --bool rain.fetchprune`**, **`fetch_prune`** on a registry entry, or **`global.fetch_prune`** in config. Effective precedence for each repo is: **CLI****`rain.fetchprune`****registry `fetch_prune`****global `fetch_prune`**.
2424
2525
Invocation note: `git-rain` and `git rain` are equivalent when `git-rain` is on your PATH.
2626

@@ -223,7 +223,7 @@ git-rain --init
223223
| `--dry-run` | Show what would run without making changes |
224224
| `--rain` | Interactive TUI repo picker before running (like `git-fire --fire`) |
225225
| `--sync` | Update local branches from remotes (after `git fetch --all`; default run does not sync locals) |
226-
| `--fetch-mainline` | Mainline-only remote `git fetch` per remote instead of default `git fetch --all` |
226+
| `--fetch-mainline` | Mainline-only remote `git fetch` per remote instead of default `git fetch --all` (not with `--sync` or other full-sync triggers) |
227227
| `--branch-mode` | With `--sync`: `mainline`, `checked-out`, `all-local`, or `all-branches` (overrides config for this run) |
228228
| `--prune` | Pass `--prune` on fetch for this run (highest precedence; cannot combine with `--no-prune`) |
229229
| `--no-prune` | Never pass `--prune` on fetch for this run (overrides `--prune`, config, registry, and `rain.fetchprune`) |

cmd/prune_resolve_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,17 @@ func TestApplyRepoFetchPrune_NoPruneOverridesGitTrue(t *testing.T) {
168168
}
169169
}
170170

171+
func TestRunRain_FetchMainlineWithSyncErrors(t *testing.T) {
172+
tmpHome := t.TempDir()
173+
setTestUserDirs(t, tmpHome)
174+
resetFlags()
175+
rainFetchMainline = true
176+
rainSync = true
177+
if err := runRain(rootCmd, []string{}); err == nil {
178+
t.Fatal("expected error when --fetch-mainline and --sync both set")
179+
}
180+
}
181+
171182
func TestRunRain_PruneAndNoPruneTogetherErrors(t *testing.T) {
172183
tmpHome := t.TempDir()
173184
setTestUserDirs(t, tmpHome)

cmd/root.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ func runRain(_ *cobra.Command, _ []string) error {
149149
}
150150

151151
fullSync := computeFullSync(rainSync, rainRisky, cfg.Global.RiskyMode, rainBranchMode, cfg.Global.BranchMode)
152+
if fullSync && rainFetchMainline {
153+
return fmt.Errorf("--fetch-mainline only applies to fetch-only runs; remove --sync, --risky, non-mainline --branch-mode, or risky_mode in config")
154+
}
152155
if fullSync && rainOpts.BranchMode == git.BranchSyncAllBranches {
153156
fmt.Println("⚠️ all-branches mode: remote branches will be created locally — this can produce many local branches")
154157
}
@@ -266,7 +269,7 @@ func runRain(_ *cobra.Command, _ []string) error {
266269

267270
if !fullSync && !rainFetchMainline {
268271
if fetchErr := fetchOnly(repo.Path, repoOpts); fetchErr != nil {
269-
fmt.Printf(" ❄ (all branches): %s\n\n",
272+
fmt.Printf(" ❄ (fetch --all): %s\n\n",
270273
safety.SanitizeText(fetchFailureMessage(fetchErr.Error())))
271274
totalFrozen++
272275
continue
@@ -614,7 +617,7 @@ func runRainOnRepos(reg *registry.Registry, repos []git.Repository, opts git.Rai
614617

615618
if !fullSync && !rainFetchMainline {
616619
if fetchErr := fetchOnly(repo.Path, repoOpts); fetchErr != nil {
617-
fmt.Printf(" ❄ (all branches): %s\n\n",
620+
fmt.Printf(" ❄ (fetch --all): %s\n\n",
618621
safety.SanitizeText(fetchFailureMessage(fetchErr.Error())))
619622
totalFrozen++
620623
continue
@@ -684,6 +687,12 @@ func runRainOnRepos(reg *registry.Registry, repos []git.Repository, opts git.Rai
684687

685688
func applyRepoFetchPrune(repoPath string, base git.RainOptions, absPath string, reg *registry.Registry) (git.RainOptions, error) {
686689
opt := base
690+
cliSet := rainPrune || rainNoPrune
691+
cliVal := rainPrune && !rainNoPrune
692+
if cliSet {
693+
opt.FetchPrune = git.ResolveFetchPrune(cliSet, cliVal, false, false, nil, base.FetchPrune)
694+
return opt, nil
695+
}
687696
var regPrune *bool
688697
if reg != nil {
689698
if e := reg.FindByPath(absPath); e != nil {
@@ -692,10 +701,9 @@ func applyRepoFetchPrune(repoPath string, base git.RainOptions, absPath string,
692701
}
693702
gitSet, gitVal, err := git.ReadRainFetchPrune(repoPath)
694703
if err != nil {
695-
return opt, err
704+
fmt.Fprintf(os.Stderr, "warning: reading %s in %s: %v\n", git.RainFetchPruneConfigKey, repoPath, err)
705+
gitSet, gitVal = false, false
696706
}
697-
cliSet := rainPrune || rainNoPrune
698-
cliVal := rainPrune && !rainNoPrune
699707
opt.FetchPrune = git.ResolveFetchPrune(cliSet, cliVal, gitSet, gitVal, regPrune, base.FetchPrune)
700708
return opt, nil
701709
}

scripts/uat.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ echo "── scenario 6: risky mode resets local-ahead branch"
269269

270270
# ── SCENARIO 7: default full fetch (no local branch moves) ─────────────────
271271
echo
272-
echo "── scenario 7: default git fetch --all --prune"
272+
echo "── scenario 7: default git fetch --all (prune opt-in)"
273273
{
274274
scenario_begin s7
275275
d="$SCENARIO_D"

0 commit comments

Comments
 (0)