Add a Clone tab that keeps local working copies of monitored repos - #1
Merged
Conversation
Pick a folder and every monitored repo gets a real git working copy in it. A repo that isn't there yet is cloned; one that already is gets fetched and fast-forwarded on whatever branch it happens to be checked out on. The update path is deliberately conservative — it only ever moves a branch pointer forward, and reports (rather than touches) anything else: - uncommitted changes to tracked files, so work in progress is never at risk (untracked files don't block: a fast-forward can't lose them, and treating build output as uncommitted work would wedge most real checkouts) - a detached HEAD, a purely local branch, or a branch that has diverged from its upstream, which is a merge/rebase decision only the user can make - a folder that isn't a working copy, has no remote, or whose origin points at some other repo entirely It follows the branch's actual tracking remote rather than assuming origin, so a fork whose main tracks a second remote is compared against the right ref. Updating spends no API calls at all — the remote comes from the working copy — and only a first clone needs the provider, for the clone URL. Credentials go through GitCli the same way the sync engine does (env var plus --config-env, never argv or .git/config), and are attached only when the remote is on the account's own host, since http.extraHeader applies to every request the invocation makes. The chosen folder and layout (<root>/owner/repo or a flat <root>/repo) persist in the document store, so they survive a restart on the desktop heads and ride along in a database backup. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M8jbrkMHb7wrUS5f2LH7UN
There was a problem hiding this comment.
Pull request overview
Adds a new Clone feature to GitHubShine that maintains real local git working copies for monitored repositories, persisting user-selected destination settings and surfacing per-repo outcomes in the UI.
Changes:
- Introduces
RepoCloneEngineand related models to clone missing repos and conservatively fast-forward existing working copies. - Adds Clone preferences persistence (
ClonePrefs) via DocumentDb + JSON source-gen wiring. - Adds a new
/clonepage and sidebar entry, plus small UI/CSS tweaks and README updates.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/GitHubShine/wwwroot/css/app.css | Allows the clone row “path” column to shrink/ellipsis correctly in flex layout. |
| src/GitHubShine/MauiProgram.cs | Registers ClonePrefs for DocumentDb table mapping. |
| src/GitHubShine/GlobalUsings.cs | Adds global GitHubShine.Clone namespace import for the new feature. |
| src/GitHubShine/GitHubShineJsonContext.cs | Adds ClonePrefs to source-generated JSON serialization context. |
| src/GitHubShine/Features/Clone/RepoCloneEngine.cs | Implements clone-or-fast-forward logic via git CLI with conservative safety checks. |
| src/GitHubShine/Features/Clone/CloneStore.cs | Persists clone settings and last-run timestamp in DocumentDb. |
| src/GitHubShine/Features/Clone/ClonePrefs.cs | Defines persisted clone settings (root directory, layout, last run time). |
| src/GitHubShine/Features/Clone/CloneOutcome.cs | Defines clone/update outcomes and UI-friendly summaries. |
| src/GitHubShine/Components/Pages/ClonePage.razor | Adds the Clone tab UI, selection, run loop, and progress/log rendering. |
| src/GitHubShine/Components/Layout/MainLayout.razor | Adds “Clone” navigation entry and icon in the sidebar. |
| README.md | Documents the new Clone tab and feature folder structure. |
Suppressed comments (1)
src/GitHubShine/Features/Clone/RepoCloneEngine.cs:193
- The code currently only verifies that
originpoints at the monitored repo, but then it may fetch from a different tracking remote. That can both (1) wrongly skip valid checkouts (fork withorigin=fork, tracking remote=upstream) and (2) fetch/fast-forward from an unrelated remote if the tracking remote is misconfigured. Validate the resolvedremoteUrl(the remote you are about to fetch) againstrepobefore fetching.
var remoteUrl = origin;
if (!string.Equals(remote, "origin", StringComparison.Ordinal))
{
var other = await this.TryGitAsync(path, ["remote", "get-url", remote], null, ct).ConfigureAwait(false);
remoteUrl = other.Success ? other.StdOut.Trim() : "";
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+146
to
+150
| // A folder holding some other repo (easily done under the flat layout, where two owners' | ||
| // repos of the same name land in one place) must not be fetched into. | ||
| if (!RemoteMatches(origin, repo)) | ||
| return Skip(CloneAction.SkippedDifferentRepo, path, null, progress, | ||
| $"Skipped — origin is {Redact(origin)}, not {repo.FullName}."); |
Comment on lines
+27
to
+37
| /// <summary>The checked-out branch has no matching branch on <c>origin</c>.</summary> | ||
| SkippedNoUpstream, | ||
| /// <summary>The folder exists but isn't a git working copy.</summary> | ||
| SkippedNotARepo, | ||
| /// <summary>It's a git working copy, but it has no <c>origin</c> remote to fetch from.</summary> | ||
| SkippedNoRemote, | ||
| /// <summary><c>origin</c> points at some other repo — almost certainly not the one asked for.</summary> | ||
| SkippedDifferentRepo |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pick a folder and every monitored repo gets a real git working copy in it.
A repo that isn't there yet is cloned; one that already is gets fetched and
fast-forwarded on whatever branch it happens to be checked out on.
The update path is deliberately conservative — it only ever moves a branch
pointer forward, and reports (rather than touches) anything else:
(untracked files don't block: a fast-forward can't lose them, and treating
build output as uncommitted work would wedge most real checkouts)
its upstream, which is a merge/rebase decision only the user can make
some other repo entirely
It follows the branch's actual tracking remote rather than assuming origin, so
a fork whose main tracks a second remote is compared against the right ref.
Updating spends no API calls at all — the remote comes from the working copy —
and only a first clone needs the provider, for the clone URL.
Credentials go through GitCli the same way the sync engine does (env var plus
--config-env, never argv or .git/config), and are attached only when the remote
is on the account's own host, since http.extraHeader applies to every request
the invocation makes.
The chosen folder and layout (/owner/repo or a flat /repo) persist
in the document store, so they survive a restart on the desktop heads and ride
along in a database backup.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01M8jbrkMHb7wrUS5f2LH7UN