james.maesandClaude Fable 5.1 3f05d541d2 fix(build): enable ReadyToRun and trimming only when publishing (#79)
## What

The first rehearsal of the shared release workflow (`v1.0.1-beta.2`, run
38053540124) failed in the Release build with NETSDK1094: `gclo.csproj`
enabled `PublishReadyToRun` and `PublishTrimmed` for every non-Debug
configuration, so a plain `dotnet build -c Release` needed the crossgen
pack, which only a restore made with that property downloads. The shared
workflow restores once (no configuration) and then builds Release, and a
clean runner has no cached pack.

Both properties are now publish-only (`'$(_IsPublishing)' == 'true'`),
so the publish step's own restore fetches the pack and a Release build
needs nothing extra. The published app is unchanged: ReadyToRun still
applies at publish, and the shared workflow still passes
`-p:PublishTrimmed=false` as before.

Closes #78 (part of #62; blocks #67)

## How it was tested

- With `NUGET_PACKAGES` pointed at an empty directory, the shared
workflow's exact sequence: `dotnet restore gclo.slnx -p:Platform=x64
--locked-mode`, `dotnet build gclo.slnx -c Release -p:Platform=x64
-warnaserror --no-restore`, `dotnet test gclo.Cli.Tests -c Release
--no-build`, then the app and CLI publishes with
`-p:RestoreLockedMode=true`. Results in the PR comments.
- Next rehearsal: `v1.0.1-beta.3` (the beta.2 tag is spent; no release
was created).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: James Maes <james@kof22.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-10 08:06:45 -05:00

gclo — Git Clone Large Organizations

Clone an entire GitHub organization. Keep it up to date. In parallel.

OpenSSF Scorecard License: MIT Platform: Windows 11 .NET 10

Tier Product (see the KofTwentyTwo standards scope)
Standards Conforms to KofTwentyTwo standards main@855cf44 (pre-1.0 draft); conformance tracked in #62
Latest release Releases

gclo (Git Clone Large Organizations) mirrors every repository of a GitHub organization or user account into a local folder in one pass: it clones the repositories that are missing and fast-forwards the ones that already exist, with live per-repo progress and bounded parallelism. It ships as a Windows 11 desktop app (WinUI 3) and a scriptable CLI built on the same engine.

gclo mid-sync: overall progress bar, an active-repo strip, and per-repository status with a Cloning row at 15%

Download & install

All builds are published on the GitHub Releases page.

Desktop app (GUI) — download and run gclo-stable-Setup.exe. It installs per-user (no admin rights needed) and updates itself in-app via Help > Check for updates. A portable build (gclo-stable-Portable.zip) is also attached to each release; portable builds do not self-update.

CLI — download gclo-cli-win-x64-<version>.zip and unzip it anywhere. It is a self-contained single gclo.exe — no .NET runtime required.

Plain version tags (v1.2.3) are stable releases; pre-release tags (v1.2.3-beta.1) publish to the dev channel, and an installed app only ever updates within its own channel.

Upgrading from a beta? Channels don't cross: a dev/beta install never auto-updates to a stable release (its Check for updates only looks at the dev channel). To move to stable, install gclo-stable-Setup.exe once — it takes over the existing install and self-updates within the stable channel from then on.

Package managers (CLI):

# Scoop: this repository is its own bucket
scoop bucket add gclo https://github.com/KofTwentyTwo/gclo
scoop install gclo

# Chocolatey (once the package has cleared moderation; see #7)
choco install gclo

winget support for the desktop app is tracked in #7. Every official channel (GitHub Releases, the in-app updater, Scoop, Chocolatey, winget) is served over HTTPS.

Verifying a release

Every release asset is listed in SHA256SUMS, carries a build-provenance attestation, and ships with a CycloneDX SBOM (gclo-<version>.cdx.json for the app, gclo-cli-<version>.cdx.json for the CLI). Releases are built by the KofTwentyTwo standards' shared release workflow, so the provenance names that workflow as the builder:

# 1. Integrity: the file matches the published checksum
(Get-FileHash .\gclo-stable-Setup.exe -Algorithm SHA256).Hash   # compare with the line in SHA256SUMS
sha256sum --check --ignore-missing SHA256SUMS                   # Linux / macOS

# 2. Origin: built from this repository's tagged commit by the shared release workflow
gh attestation verify .\gclo-stable-Setup.exe --repo KofTwentyTwo/gclo --signer-repo KofTwentyTwo/standards

# 3. Dependencies: the SBOM is attested too
gh attestation verify .\gclo-cli-win-x64-<version>.zip --repo KofTwentyTwo/gclo --signer-repo KofTwentyTwo/standards --predicate-type https://cyclonedx.org/bom

The binaries are not yet Authenticode-signed (standards exception EX-0002, #57), so step 2 is the origin check until Get-AuthenticodeSignature can be.

Getting a token

gclo authenticates to GitHub with a personal access token — it is used for both the API (listing repositories) and the git transport (cloning and fetching). Create one under GitHub Settings > Developer settings > Personal access tokens.

Token type What you need
Classic PAT repo scope (read access to the org's repositories); optionally read:org so the organization dropdown / gclo orgs can list your orgs
Fine-grained PAT Repository read access for the organization

Token storage. In Quick Sync, the token is held in memory for the session only and never written anywhere. A saved account's token and the optional default token (Settings) are kept in the Windows Credential Manager (per-user, DPAPI-protected) — never in settings.json, accounts.json, or any log. See SECURITY.md.

Using the GUI

On launch, gclo shows a brief branded splash, then the Quick Sync connect card:

gclo startup splash    Quick Sync connect card: personal access token, organization dropdown, target folder with subfolder toggle and path preview, and Load repositories

  1. Paste your token first. After a moment, the Organization or account dropdown fills with everything the token can sync — your personal account is listed first, then your organizations alphabetically. If the token cannot list organizations (a fine-grained PAT, or a classic PAT without read:org), the status line says so; the dropdown is editable, so just type the name.

  2. Pick the organization or account from the dropdown.

  3. Choose a target folder — type a path or click Browse.... Tick Create a <org> subfolder to keep several organizations under one root; the live preview line underneath always shows exactly where repositories will land (e.g. C:\src\acme\my-repo).

  4. Set parallelism if you like (1–64, default 8): how many git operations run at once.

  5. Press Load repos. The table fills with every repository of the organization — name, status, default branch, and an Archived marker — all selected. Click a column header to sort by it (click again to flip); use the funnel on any column to filter by name, status, branch, or archived state.

    Loaded repository table: 59 repositories all selected, with sortable, filterable Name / Status / Branch / Archived columns and a Sync 59 repos button

  6. Uncheck anything you don't want. The header checkbox selects or clears every row at once.

  7. Press Sync selected. Each row moves through its states live — Queued, Cloning (with a per-row progress bar and transfer percentage), Pulling, Done, Failed, or Canceled — while the overall progress bar and completed/total counter track the run, which ends in a summary (Finished: 3 cloned, 41 updated, 1 failed, 0 canceled of 45.).

    Finished sync: every row Done, with the summary 58 cloned, 0 updated, 1 failed, 0 canceled of 59

  8. Cancel at any time: in-flight repos stop cleanly, unstarted repos are marked Canceled, and the summary still appears. After a run, Retry failed re-runs exactly the failed rows, and Open folder opens the target folder in Explorer.

When something goes wrong: failures are isolated per repository — one broken repo never stops the rest, and a failed row shows its error message inline. Failed clones are cleaned up, and a clone whose checkout never finished (a crash, a kill, a cleanup that could not delete the folder) is flagged inside its .git, so a partial working tree is never mistaken for an up-to-date repo on the next run — gclo completes the checkout instead. Repositories with Windows-impossible paths are the deliberate exception: gclo checks every path in the incoming tree against Windows file-system rules before checkout — invalid characters, reserved device names like CON, trailing spaces or dots, names that differ only by case — and when it finds any, the repo is marked Failed with a per-path list of reasons, but the fully fetched .git is kept (nothing was ever checked out). Recovery happens in place, with no re-download: click the row's Resolve… link to rename each offending path (safe names are pre-suggested) or skip it, and gclo materializes the working tree right there; the CLI does the same automatically with --sanitize-paths. When WSL is installed with git in its default distribution, the same dialog offers Clone in WSL instead: the repository is cloned unchanged into ~/gclo/<org>/<repo> inside WSL (reachable from Explorer as \wsl.localhost\<distro>\home\...), the token travels only through the environment, and the Windows copy is left as it is. Long paths beyond the classic 260-character limit are handled automatically (core.longpaths).

Settings (File > Settings…) let you set a default GitHub token (stored in the Windows Credential Manager), the default target folder (with a folder picker), the default parallelism, and the theme (System, Light, or Dark); an Advanced section toggles the startup splash and its duration.

Activity log: View > Activity log… shows the tail of today's log and links to the logs folder. Logs live under %LOCALAPPDATA%\gclo\logs, one file per day; they record run parameters and per-repo failures and never contain your token (see SECURITY.md).

Updates: Help > Check for updates… downloads and applies the latest release for your channel. Self-update is only available in installed builds (not the portable zip or a local debug build).

Saved accounts

Quick Sync is ad-hoc — nothing is saved. For organizations you sync regularly, Add account stores a reusable profile: a name, the organization, target folder, and parallelism, with the token kept in the Windows Credential Manager (never in a file). Each account gets its own entry in the navigation pane and its own workspace; Sync all runs every saved account in turn, and the pane shows each account's live status.

Add account wizard, step 1 of 4: account name and optional description

Using the CLI

The CLI drives the same engine from scripts and scheduled jobs. There is deliberately no --token <value> option — command-line arguments leak to other processes — so the token comes from the GITHUB_TOKEN environment variable by default, or from --token-env, --token-file, or --token-stdin.

PowerShell:

# List everything the token can sync (your account first, then orgs)
$env:GITHUB_TOKEN = (Get-Secret -Name GitHubPat -AsPlainText)
gclo orgs

# Mirror an organization, 16 git operations at a time
gclo sync --org contoso --target C:\src\contoso --parallel 16

# Preview, then sync a subset (globs on the repository name; archived ones skipped)
gclo repos --org contoso --include "platform-*" --skip-archived
gclo sync --org contoso --target C:\src\contoso --include "platform-*" --skip-archived --dry-run

# Save the connection as an account (token in Windows Credential Manager) and reuse it
gh auth token | gclo accounts add --name work --org contoso --target C:\src\contoso --token-stdin
gclo sync --account work

# Pipe the token from a secret store — it never touches a command line or disk
gh auth token | gclo sync --org contoso --target C:\src\contoso --token-stdin

# Machine-readable result
$result = gclo sync --org contoso --target C:\src\contoso --json | ConvertFrom-Json
if ($result.failed -gt 0) { $result.failures | ForEach-Object { "$($_.repo): $($_.error)" } }

bash:

# List orgs, then mirror one
export GITHUB_TOKEN="$(pass show github/pat)"
gclo orgs
gclo sync --org contoso --target ~/src/contoso

# Pipe the token from a secret store
op read "op://Private/GitHub PAT/token" | gclo sync --org contoso --target ~/src/contoso --token-stdin

# Machine-readable result with jq
gclo sync --org contoso --target ~/src/contoso --json |
  jq -r '.failures[] | "\(.repo): \(.error)"'

--org accepts an organization or a user account login. --json suppresses progress output and prints a single JSON summary line; combined with the exit code it makes gclo easy to wire into cron/scheduled tasks:

Exit code Meaning
0 Everything succeeded.
1 The run completed, but some repositories failed or the run was canceled (Ctrl+C).
2 Fatal: bad arguments, missing/empty/rejected token, or organization not found.

Full reference — all options, output streams, Ctrl+C behavior, and more examples: docs/CLI.md.

How it works

The engine lists every repository through the GitHub REST API (Octokit, paginated 100 per page), then runs the set through Parallel.ForEachAsync bounded by the parallelism setting. Git operations use LibGit2Sharp: missing repos are cloned with object-transfer progress; existing repos get a fetch from origin followed by a fast-forward-only merge — if a local branch has diverged from its upstream, the repo is reported Failed rather than auto-merged, so gclo never manufactures merge commits. Archived repositories are included and sync normally. Every repo ends in exactly one terminal state — Done, Failed (with the error), or Canceled — and per-repo exceptions are contained so the rest of the run continues. The PAT is the only credential; on the git side it is sent as HTTPS basic credentials with username x-access-token.

Build from source

Requirements: Windows 11 (or Windows 10 1809+), the .NET 10 SDK, and — for the desktop app — Visual Studio 2026 with the Windows App SDK / WinUI workload.

git clone https://github.com/KofTwentyTwo/gclo.git
cd gclo

# Desktop app (packaged build)
dotnet build gclo.slnx -p:Platform=x64

# Desktop app, unpackaged — a plain exe under gclo\bin\x64\Debug\, no MSIX deployment
dotnet build gclo.slnx -p:Platform=x64 -p:WindowsPackageType=None

# CLI only (plain net10.0 — also builds on Linux/macOS)
dotnet run --project gclo.Cli -- sync --org contoso --target C:\src\contoso

# Tests
dotnet test

To F5-debug the packaged app in Visual Studio, open gclo.slnx, set gclo as the startup project, select the x64 platform, and make sure Windows Developer Mode is enabled (Settings > System > For developers) — packaged deployment requires it.

Project layout

Project Description
gclo WinUI 3 packaged desktop app — XAML, dialogs, pickers, and update plumbing only; no business logic
gclo.Cli Cross-platform command-line head over the engine (docs/CLI.md)
gclo.Engine Core sync engine: OrgSyncEngine, Octokit-based listers, LibGit2Sharp git client, Windows path validation
gclo.ViewModels UI-framework-free view models and settings persistence, shared presentation logic
gclo.Engine.Tests xunit tests: engine (with fakes), real-git integration suite, and view-model tests
gclo.Cli.Tests xunit tests for the CLI, driving injectable command cores over fakes
gclo.UiTests FlaUI/UIA end-to-end tests that drive the real gclo.exe

Line coverage is held at 100% for gclo.Engine, gclo.ViewModels, and gclo (the CLI), enforced in CI.

Support

Only the latest release line receives fixes: the latest MINOR of the latest MAJOR gets all fixes, and when a new MAJOR ships the previous MAJOR's last MINOR gets security fixes for 6 months. Details, and how to report a vulnerability privately, are in SECURITY.md.

Contributing

Contributions are welcome. Branch from main (<type>/<description>), commit with Conventional Commit messages signed and signed off (git commit -S -s), and open a pull request against main; it is squash-merged once every required check is green. See CONTRIBUTING.md for prerequisites, build and test commands, the checks that gate a merge, and the code style.

License

gclo is licensed under the MIT License. Copyright (c) 2026 James Maes.

gclo builds on the following open-source projects:

Dependency License
Octokit MIT
LibGit2Sharp MIT
libgit2 (native library used by LibGit2Sharp) GPLv2 with linking exception
CommunityToolkit.Mvvm MIT
Windows App SDK MIT
Velopack (self-update) MIT
S
Description
gclo — Git Clone Large Organizations. Windows 11 desktop app + CLI that clone and update every repository in a GitHub org, in parallel.
https://github.com/KofTwentyTwo/gclo/releases/latest Readme MIT
6.7 MiB
0 Stars 2 Watchers 0 Forks
gclo 1.1.0
Latest
2026-10-11 12:22:42 +00:00
Languages
C# 98.7%
PowerShell 1.3%