Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions docs/EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 23 additions & 0 deletions docs/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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:
Expand Down
83 changes: 50 additions & 33 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -106,44 +107,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()
}

// Report excluded-but-present
for _, entry := range scanResult.ExcludedButPresent {
printer.ExcludedButPresent(entry.Name)
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)
}
}

// Print summary
Expand Down
Loading