From 9fc9ac667ff027dd651877aab26aa820fbba7a9f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 00:29:07 +0000 Subject: [PATCH 1/3] Initial plan From c3ccf63397d826c82f945d8b8eed75f75b887513 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 00:33:21 +0000 Subject: [PATCH 2/3] Add --clone flag for clone-only mode Implements --clone flag that skips processing existing repos (fetch, dirty check, checkout, pull) and directory auditing. Only clones missing repositories for faster execution when that's all that's needed. Does not implement --pull as it would not meaningfully reduce runtime since it still requires the heaviest operations (per-repo git fetch, status check, checkout, and pull). Updates USAGE.md and EXAMPLES.md documentation. Co-authored-by: JaredHatfield <208119+JaredHatfield@users.noreply.github.com> --- docs/EXAMPLES.md | 24 +++++++++++++ docs/USAGE.md | 23 ++++++++++++ main.go | 93 +++++++++++++++++++++++++++++++----------------- 3 files changed, 107 insertions(+), 33 deletions(-) diff --git a/docs/EXAMPLES.md b/docs/EXAMPLES.md index be2190e..f122317 100644 --- a/docs/EXAMPLES.md +++ b/docs/EXAMPLES.md @@ -178,6 +178,30 @@ Summary: total: 8 | cloned: 1 | updated: 1 | dirty: 1 | branch-drift: 1 | unknown: 1 | excluded-but-present: 1 | errors: 0 ``` +### Clone-Only Mode + +Using `--clone` to quickly clone only missing repositories without processing existing ones: + +``` +$ ghorgsync --clone + repo new-service [cloned] + repo new-library [cloned] + +Summary: + total: 10 | cloned: 2 | updated: 0 | dirty: 0 | branch-drift: 0 | unknown: 0 | excluded-but-present: 0 | errors: 0 +``` + +When all repositories are already cloned locally, `--clone` finishes quickly with no output: + +``` +$ ghorgsync --clone + +Summary: + total: 10 | cloned: 0 | updated: 0 | dirty: 0 | branch-drift: 0 | unknown: 0 | excluded-but-present: 0 | errors: 0 +``` + +This mode skips all per-repository processing (fetch, dirty check, checkout, pull) and directory auditing (collisions, unknown folders, excluded-but-present), making it significantly faster when you only need to pull down new repositories. + ## Troubleshooting ### Missing Dotfile diff --git a/docs/USAGE.md b/docs/USAGE.md index b51c810..9e141b9 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -60,6 +60,11 @@ ghorgsync [flags] | `--version` | Print version and exit | | `--verbose` | Enable verbose output (show per-repository processing detail) | | `--no-color` | Disable color output | +| `--clone` | Clone-only mode: only clone missing repositories (see [Clone-Only Mode](#clone-only-mode)) | + +### Mode Flags + +The `--clone` flag is a mode flag that changes the sync behavior. Mode flags are mutually exclusive; if multiple mode flags are provided, the command exits with an error. ## Runtime Behavior @@ -83,6 +88,24 @@ When invoked, **ghorgsync** performs the following steps: During the repository clone/process phase, **ghorgsync** shows a live progress bar on TTY output to indicate completion across managed repositories. +### Clone-Only Mode + +When invoked with `--clone`, **ghorgsync** runs a streamlined workflow focused exclusively on cloning missing repositories: + +1. **Load configuration** and **resolve authentication** (same as default mode). +2. **Fetch the organization repository list** and **filter repositories** (same as default mode). +3. **Scan the local directory** to identify which included repositories are missing. +4. **Clone missing repositories** only. +5. **Print a summary line** with counts. + +**What is skipped** compared to the default workflow: + +- Processing existing repositories (fetch, dirty check, checkout, pull) is entirely skipped. +- Collisions, unknown folders, and excluded-but-present findings are not reported. +- The progress bar only covers missing repositories to clone. + +This mode is useful when you know a new repository has been added to the organization and you want to quickly pull it down without waiting for every existing repository to be fetched and checked. + ### Per-Repository Processing For each included repository that exists locally: diff --git a/main.go b/main.go index 4636216..208aa5a 100644 --- a/main.go +++ b/main.go @@ -33,6 +33,7 @@ func main() { versionFlag := flag.Bool("version", false, "Print version and exit") verboseFlag := flag.Bool("verbose", false, "Enable verbose output") noColorFlag := flag.Bool("no-color", false, "Disable color output") + cloneOnlyFlag := flag.Bool("clone", false, "Only clone missing repositories (skip processing existing repos)") flag.Parse() if *versionFlag { @@ -40,6 +41,16 @@ func main() { os.Exit(0) } + // Validate mutually exclusive mode flags + modeCount := 0 + if *cloneOnlyFlag { + modeCount++ + } + if modeCount > 1 { + fmt.Fprintln(os.Stderr, "error: --clone cannot be combined with other mode flags") + os.Exit(1) + } + useColor := !*noColorFlag && output.ShouldColor() printer := output.NewPrinter(useColor, *verboseFlag) @@ -106,44 +117,60 @@ func main() { // Summary counters var summary model.Summary summary.TotalRepos = len(included) - summary.UnknownFolders = len(scanResult.Unknown) - summary.ExcludedButPresent = len(scanResult.ExcludedButPresent) - summary.Errors = len(scanResult.Collisions) - - repoWorkTotal := len(scanResult.ManagedMissing) + len(scanResult.ManagedFound) - printer.StartRepoProgress(repoWorkTotal) - - // Clone missing repos - for _, name := range scanResult.ManagedMissing { - repo := repoMap[name] - result := eng.CloneRepo(repo) - handleResult(printer, result, &summary) - printer.AdvanceRepoProgress() - } - // Process existing repos - for _, name := range scanResult.ManagedFound { - repo := repoMap[name] - result := eng.ProcessRepo(repo) - handleResult(printer, result, &summary) - printer.AdvanceRepoProgress() - } + if *cloneOnlyFlag { + // Clone-only mode: only clone missing repos, skip everything else + printer.StartRepoProgress(len(scanResult.ManagedMissing)) - printer.FinishRepoProgress() + for _, name := range scanResult.ManagedMissing { + repo := repoMap[name] + result := eng.CloneRepo(repo) + handleResult(printer, result, &summary) + printer.AdvanceRepoProgress() + } - // Report collisions - for _, entry := range scanResult.Collisions { - printer.Collision(entry.Name, entry.Detail) - } + printer.FinishRepoProgress() + } else { + // Default mode: full sync + summary.UnknownFolders = len(scanResult.Unknown) + summary.ExcludedButPresent = len(scanResult.ExcludedButPresent) + summary.Errors = len(scanResult.Collisions) + + repoWorkTotal := len(scanResult.ManagedMissing) + len(scanResult.ManagedFound) + printer.StartRepoProgress(repoWorkTotal) + + // Clone missing repos + for _, name := range scanResult.ManagedMissing { + repo := repoMap[name] + result := eng.CloneRepo(repo) + handleResult(printer, result, &summary) + printer.AdvanceRepoProgress() + } - // Report unknown folders - for _, entry := range scanResult.Unknown { - printer.UnknownFolder(entry.Name) - } + // Process existing repos + for _, name := range scanResult.ManagedFound { + repo := repoMap[name] + result := eng.ProcessRepo(repo) + handleResult(printer, result, &summary) + printer.AdvanceRepoProgress() + } + + printer.FinishRepoProgress() + + // Report collisions + for _, entry := range scanResult.Collisions { + printer.Collision(entry.Name, entry.Detail) + } + + // Report unknown folders + for _, entry := range scanResult.Unknown { + printer.UnknownFolder(entry.Name) + } - // Report excluded-but-present - for _, entry := range scanResult.ExcludedButPresent { - printer.ExcludedButPresent(entry.Name) + // Report excluded-but-present + for _, entry := range scanResult.ExcludedButPresent { + printer.ExcludedButPresent(entry.Name) + } } // Print summary From 68eebab36dacd611692a72b203d3603c433e1495 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 00:34:11 +0000 Subject: [PATCH 3/3] Remove unreachable mutual exclusivity validation for single flag Co-authored-by: JaredHatfield <208119+JaredHatfield@users.noreply.github.com> --- main.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/main.go b/main.go index 208aa5a..107be6b 100644 --- a/main.go +++ b/main.go @@ -41,16 +41,6 @@ func main() { os.Exit(0) } - // Validate mutually exclusive mode flags - modeCount := 0 - if *cloneOnlyFlag { - modeCount++ - } - if modeCount > 1 { - fmt.Fprintln(os.Stderr, "error: --clone cannot be combined with other mode flags") - os.Exit(1) - } - useColor := !*noColorFlag && output.ShouldColor() printer := output.NewPrinter(useColor, *verboseFlag)