From 7f1c15bc436645b86922c0c3504b48f1041d0df6 Mon Sep 17 00:00:00 2001 From: James Maes Date: Wed, 11 Nov 2026 01:33:53 -0600 Subject: [PATCH 1/3] feat(app): the Add account wizard asks where the repositories are, and can add an account on a Gitea server Step 3 of Gitea support (#201): the desktop app adds, loads and syncs an account on a Gitea server. - Step 1 of the wizard asks, for a new account, where the repositories are: GitHub (the default) or a Gitea server and its address. The address is checked by the rule the command line uses and shown as it was normalized, which is the address the token goes to. - The wizard holds the lister of one place at a time. The shell gives it a lister per place, step 1 takes the one of the chosen place, and step 2 has no other to send the token to. A wizard that was given one lister (an edit, a Quick Sync connection saved as an account) offers no choice and says where the account is. - What was entered for one place is not carried to another: when step 1 is left with another place than before, the typed token, the organization and the organization list are emptied. The default token is a GitHub token and is neither offered nor preselected for a Gitea server. - Texts that named GitHub follow the place: the token box, the page a token is made on, the note of step 2, and the connect card of an account's page. - The offline UI fixture answers for a Gitea server with an organization of its own, and the UI suite walks the wizard for one. The wizard's listers are wrapped by the fixture like a workspace's. - README, ADR 0010, threat model T16. Closes #206 Co-Authored-By: Claude Fable 5.2 Signed-off-by: James Maes --- README.md | 6 +- docs/adr/0010-forges-beside-github.md | 19 + docs/security/threat-model.md | 5 +- gclo.Engine.Tests/AccountWizardPlaceTests.cs | 553 +++++++++++++++++++ gclo.UiTests/SmokeTests.cs | 49 +- gclo.ViewModels/AccountWizardViewModel.cs | 241 +++++++- gclo.ViewModels/ForgeTexts.cs | 46 ++ gclo.ViewModels/WorkspaceViewModel.cs | 19 + gclo/AboutDialog.xaml | 2 +- gclo/AccountWizardDialog.xaml | 65 ++- gclo/AccountWizardDialog.xaml.cs | 18 +- gclo/ConnectionFormControl.xaml | 3 +- gclo/ConnectionFormControl.xaml.cs | 7 + gclo/MainWindow.xaml.cs | 49 +- gclo/StatusFormat.cs | 6 + gclo/UiTestFixture.cs | 51 +- gclo/WorkspacePage.xaml | 7 +- 17 files changed, 1079 insertions(+), 67 deletions(-) create mode 100644 gclo.Engine.Tests/AccountWizardPlaceTests.cs create mode 100644 gclo.ViewModels/ForgeTexts.cs diff --git a/README.md b/README.md index 7021b05..6a34e07 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ | **Standards** | Conforms to [KofTwentyTwo standards](https://github.com/KofTwentyTwo/standards) `main@855cf44` (pre-1.0 draft); conformance tracked in [#62](https://git.kof22.com/KofTwentyTwo/gclo/issues/62) | | **Latest release** | [Releases](https://git.kof22.com/KofTwentyTwo/gclo/releases/latest) | -**gclo** (**G**it **C**lone **L**arge **O**rganizations) 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** (**G**it **C**lone **L**arge **O**rganizations) mirrors every repository of a GitHub organization or user account, or of one on a Gitea server, 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% @@ -124,7 +124,7 @@ On launch, gclo shows a brief branded splash, then the **Quick Sync** connect ca ### 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. A status bar above the workspace is there for the whole pass, whichever workspace is on screen: it says which account is syncing, which of how many, and how far along it is, takes you to that account, and holds the result when the pass is over. The pane shows each account's live status as well. **Cancel sync all** stops the pass after the account that is syncing, and the result says how many accounts were not started. An account that could not be synced (it has no token, or its repositories could not be loaded) is marked in the pane and counted as needing attention, so a pass in which it was left out does not read as a success. An account's token is changed with **Edit account…** on the account's page (**Edit…** in the pane's menu); the token box on an account's page takes no input. Sync all syncs the repositories each account's page lists: **Refresh** on that page picks up repositories created since it was loaded. +Quick Sync is ad-hoc — nothing is saved. For organizations you sync regularly, **Add account** stores a reusable profile: a name, where the repositories are, the organization, target folder, and parallelism, with the token kept in the Windows Credential Manager (never in a file). The first step asks where the repositories are: **GitHub**, or **a Gitea server** and its address (HTTPS only; a Forgejo server works the same way). The token of the next step is checked with that place and sent nowhere else, and an account stays on the place it was added for: to move one, delete it and add it again. Quick Sync connects to GitHub. Each account gets its own entry in the navigation pane and its own workspace; **Sync all** runs every saved account in turn. A status bar above the workspace is there for the whole pass, whichever workspace is on screen: it says which account is syncing, which of how many, and how far along it is, takes you to that account, and holds the result when the pass is over. The pane shows each account's live status as well. **Cancel sync all** stops the pass after the account that is syncing, and the result says how many accounts were not started. An account that could not be synced (it has no token, or its repositories could not be loaded) is marked in the pane and counted as needing attention, so a pass in which it was left out does not read as a success. An account's token is changed with **Edit account…** on the account's page (**Edit…** in the pane's menu); the token box on an account's page takes no input. Sync all syncs the repositories each account's page lists: **Refresh** on that page picks up repositories created since it was loaded.

Add account wizard, step 1 of 4: account name and optional description @@ -199,7 +199,7 @@ Get-Secret -Name GiteaToken -AsPlainText | gclo accounts add --name platform --o gclo sync --account platform ``` -An account's forge and server are fixed once it is added (its token was saved for that place); the app's wizard adds GitHub accounts, a Gitea account is added from the command line for now. +An account's forge and server are fixed once it is added (its token was saved for that place). The app adds such an account as well: **Add account** asks where the repositories are on its first step. Full reference — all options, output streams, Ctrl+C behavior, and more examples: [docs/CLI.md](docs/CLI.md). diff --git a/docs/adr/0010-forges-beside-github.md b/docs/adr/0010-forges-beside-github.md index 011e3ae..1fe986c 100644 --- a/docs/adr/0010-forges-beside-github.md +++ b/docs/adr/0010-forges-beside-github.md @@ -122,6 +122,25 @@ were the two lister implementations and the failure type they throw. forge or server cannot be used (a file edited by hand) gets listers that ask no server anything and fail with the reason. +### The desktop app's wizard (#206) + +- **The place is asked first, on step 1, for a new account only.** GitHub is the + default; a Gitea server needs its address, which is checked by the same rule as + on the command line (`ForgeEndpoint.TryCreate`) and shown afterwards as it was + normalized: that is the address the token goes to. An edit shows where the + account is and that it stays there. A Quick Sync connection saved as an account + is a GitHub account: Quick Sync itself connects to GitHub only. +- **The wizard has the lister of one place at a time.** The shell gives it a + lister per place; step 1 takes the lister of the place that was chosen, and step + 2 has no other to send the token to. A wizard that was given one lister (an + edit, a saved Quick Sync connection) offers no choice. +- **What was entered for one place is not carried to another.** When step 1 is + left with another place than before, the typed token, the organization and the + list of organizations are emptied, and the default token (a GitHub token) is + neither offered nor preselected for a Gitea server. +- **Texts follow the place.** The page a token is made on is the one of the + chosen place (`ForgeTexts`); step 2 names the place the token is checked with. + ## Alternatives considered - **A Gitea client package.** One more dependency to track and to trust with the diff --git a/docs/security/threat-model.md b/docs/security/threat-model.md index 61727f2..182ac30 100644 --- a/docs/security/threat-model.md +++ b/docs/security/threat-model.md @@ -27,7 +27,7 @@ about the user's accounts or repositories. It sends nothing anywhere else. | gclo app / CLI | `gclo.exe` (WinUI 3) and the CLI `gclo.exe`, same engine | Trusted; runs as the user | | GitHub REST API | Lists organizations and repositories | Untrusted input (responses are parsed), authenticated by the PAT | | Git remotes (`github.com`) | Clone and fetch over HTTPS via libgit2 | Untrusted input (repository content, including file paths) | -| Gitea server at an address the user names | Lists organizations and repositories through its REST API (ADR 0010). Used by accounts and the command line (#204); the desktop app's wizard cannot add such an account yet (#201) | Untrusted beyond being the place the user chose to send the account's token to: its answers are parsed, bounded, and never decide where the token goes | +| Gitea server at an address the user names | Lists organizations and repositories through its REST API (ADR 0010). Used by accounts and the command line (#204), and by the desktop app's Add account wizard (#206) | Untrusted beyond being the place the user chose to send the account's token to: its answers are parsed, bounded, and never decide where the token goes | | Windows Credential Manager | Stores saved-account and default tokens | Trusted store; boundary is the Windows user account | | Local data directory | `%LOCALAPPDATA%\KofTwentyTwo\gclo` (or `GCLO_DATA_DIR`): settings, accounts, logs. Deliberately outside the Velopack install root `%LOCALAPPDATA%\gclo`, which Setup.exe deletes on reinstall (#91) | Trusted; same user | | Target folder | Where repositories are written; may already contain repositories | Partly untrusted (pre-existing content, `.git` config) | @@ -100,7 +100,7 @@ flowchart LR | T12 | Repository | Repudiation | Unattributed change reaches `main` | Low | High | Signed, signed-off commits; squash-only through gated pull requests; no bypass actors | Mitigated | | T14 | PAT handling | Information disclosure | The token is offered to a host other than the one the repository was listed on: an existing folder whose `origin` points elsewhere, or a server named by the repository's own configuration during the WSL clone (for example an LFS endpoint) | Low | Critical | The git credential callback answers only a remote whose URL is HTTPS on the listed URL's host and port, and refuses every other remote with a clear error (a redirect that the listed host itself issues is followed by libgit2; that host already holds the token); a folder whose origin names another repository fails before any request; the WSL credential helper is scoped to the listed host (`credential.https://.helper`) and an existing WSL destination is pulled only when its stored origin is the listed URL (#139) | Mitigated | | T15 | Logs and terminal | Spoofing / Tampering | Text from a repository or a server (a file path may contain a newline, an escape sequence, or a character that reorders or hides text) forges an entry in the activity log or drives the terminal the CLI prints to | Low | Medium | `DisplayText` shows control, separator and format characters (direction marks and overrides, invisible characters, tag characters) and unpaired surrogates as visible `\uXXXX` text: every log message is one line, and an exception's lines are indented under their entry so none can start like one. The CLI writes every line through one place, `Terminal`, which also shows any character the console's code page cannot represent in that form, because a legacy code page turns some printable characters into control bytes; a test fails if any other CLI file writes to the console. The JSON modes rely on the JSON encoder (#143) | Mitigated for the log and the CLI. Not covered: the desktop app's own surfaces (row error text, the path-recovery dialog), where a newline is harmless but a direction override in a path the user is asked to approve is not; and text that merely looks like something else (look-alike letters, an escape written out literally) | -| T16 | Forge server (Gitea) | Information disclosure / Spoofing | The address of a server is typed by a user. A wrong address, a server that is hostile or misconfigured, or something between gclo and the server gets the account's token sent elsewhere, or answers in a way that sends clones (and the token with them) to another host, or makes gclo read without end | Low | Critical | `GiteaServerUrl`: HTTPS only, no credentials or query in the address, no setting for `http://`. Certificates are validated by the platform and that cannot be switched off. Every request goes to the configured address with the token in a header; redirects are not followed; the address of a next page is never taken from an answer; the client keeps no cookies. An address that is refused is not repeated in a message (it may hold a password or a token), and neither is a token. Clone addresses are built from the configured address, the owner and the name, and the server's `clone_url` is not read, so the git client's rule (the token only to the host of the clone address, T14) binds the token to the account's server. Answers are bounded in size, lists in pages and items, requests in time (reading the answer included). A server's own text reaches a message only as the reason of a refusal, on one line and cut to a length (T15). Accounts (#204): the forge and server of an account cannot be changed after it is added, so a saved token is not sent to an address somebody edited; `gclo sync --account` takes no other place from the command line; the default token (a GitHub token) is never handed to an account on another forge, and each forge has its own default token variable; accounts that are not on GitHub are kept in a file of their own, which a gclo from before forges neither reads nor rewrites, so that version cannot turn one into a GitHub account by writing back what it knows; the token of such an account is found only by that account at the address it was saved for (T13); an account whose stored forge or server cannot be used, or whose forge this version does not know, asks no server anything. | Mitigated. The server the user names sees the token: that is the purpose. A machine that trusts an intercepting certificate authority is outside this (as for GitHub) | +| T16 | Forge server (Gitea) | Information disclosure / Spoofing | The address of a server is typed by a user. A wrong address, a server that is hostile or misconfigured, or something between gclo and the server gets the account's token sent elsewhere, or answers in a way that sends clones (and the token with them) to another host, or makes gclo read without end | Low | Critical | `GiteaServerUrl`: HTTPS only, no credentials or query in the address, no setting for `http://`. Certificates are validated by the platform and that cannot be switched off. Every request goes to the configured address with the token in a header; redirects are not followed; the address of a next page is never taken from an answer; the client keeps no cookies. An address that is refused is not repeated in a message (it may hold a password or a token), and neither is a token. Clone addresses are built from the configured address, the owner and the name, and the server's `clone_url` is not read, so the git client's rule (the token only to the host of the clone address, T14) binds the token to the account's server. Answers are bounded in size, lists in pages and items, requests in time (reading the answer included). A server's own text reaches a message only as the reason of a refusal, on one line and cut to a length (T15). Accounts (#204): the forge and server of an account cannot be changed after it is added, so a saved token is not sent to an address somebody edited; `gclo sync --account` takes no other place from the command line; the default token (a GitHub token) is never handed to an account on another forge, and each forge has its own default token variable; accounts that are not on GitHub are kept in a file of their own, which a gclo from before forges neither reads nor rewrites, so that version cannot turn one into a GitHub account by writing back what it knows; the token of such an account is found only by that account at the address it was saved for (T13); an account whose stored forge or server cannot be used, or whose forge this version does not know, asks no server anything. The app's wizard (#206): the address is checked by the same rule and shown as it was normalized before a token is asked for; the wizard holds the lister of the chosen place only; a token typed for one place is emptied when another place is chosen, and the default token is not offered for, or sent to, a Gitea server. | Mitigated. The server the user names sees the token: that is the purpose. A machine that trusts an intercepting certificate authority is outside this (as for GitHub) | | T13 | CLI / data directory | Tampering | A malicious `accounts.json`, `accounts-forges.json` or `settings.json` redirects targets, or names another place for an account | Low | Medium | Files live under the user profile; sanitized on load; target paths resolved and contained. The token of an account on a Gitea server is stored under a name made from the account's id, forge and server address as the file states them, so an account whose address was changed in the file finds no token for the new address, and a record that names another account's id finds none of that account's. What remains for someone who can write these files: a record made to look like a GitHub account can name the entry of a Gitea token (the name can be computed from the file), and that token is then offered to github.com, not to a host of their choosing. The app resolves a token for the place a workspace was opened for, so a file that is changed while the app runs does not bring the token of one place to the listers of another | Accepted (same-user boundary: whoever can write these files can read the tokens) | ## 7. Accepted risks @@ -134,3 +134,4 @@ flowchart LR | 2026-11-10 | v1.1.1 | James Maes (@KofTwentyTwo) | T4: a proxy named in a folder's own git configuration, and its own `http.followRedirects`, are refused like a URL rewrite, so the folder does not choose the server a fetch is made through or sent on to (the token was not exposed by the proxy: TLS is end to end). T3: a repository whose name is git's own folder name, in any spelling Windows opens as that folder, fails its row in the sync on every platform, not only on Windows, and is refused by the WSL clone (#184). | | 2026-11-11 | v1.2.0 (in work) | James Maes (@KofTwentyTwo) | T16 added with the Gitea listers of the engine (ADR 0010, #202): a server at an address the user names is a new component and a new place a token is sent to. The rules that keep the token on that server are stated there. Accounts, the CLI and the app do not use a Gitea server yet. | | 2026-11-11 | v1.2.0 (in work) | James Maes (@KofTwentyTwo) | T16 extended for accounts and the command line (#204): where an account lives is fixed when it is added; tokens do not cross forges by default (the default token, the environment variables); accounts on another forge are in `accounts-forges.json`, which an older gclo does not rewrite; their token is bound to the account and its server address. T13 restated for it. | +| 2026-11-11 | v1.2.0 (in work) | James Maes (@KofTwentyTwo) | T16 extended for the desktop app's Add account wizard (#206): the place is chosen before the token is typed, the token is checked with that place only, and nothing typed for one place is carried to another. | diff --git a/gclo.Engine.Tests/AccountWizardPlaceTests.cs b/gclo.Engine.Tests/AccountWizardPlaceTests.cs new file mode 100644 index 0000000..18dea4f --- /dev/null +++ b/gclo.Engine.Tests/AccountWizardPlaceTests.cs @@ -0,0 +1,553 @@ +/* + * Copyright (c) 2026 James Maes (KofTwentyTwo) + * SPDX-License-Identifier: MIT + */ + +using System.Collections.Concurrent; +using gclo.ViewModels; + + +namespace gclo.Engine.Tests; + + +///

+/// Where a new account lives, as the wizard's first step asks it (#206): GitHub or a +/// Gitea server and its address. The rule these tests are built around is that a +/// token goes to one place only, the one that was chosen, and that what was entered +/// for one place is not carried to another. Each lister here records the tokens it +/// was given under the place it is the lister of. +/// +public sealed class AccountWizardPlaceTests : IDisposable +{ + private const string Server = "https://git.example.test/"; + + private readonly string _root = + Path.Combine(Path.GetTempPath(), "gclo-tests", Guid.NewGuid().ToString("N")); + + private readonly InMemoryVault _vault = new(); + + private readonly AccountsStore _store; + + private readonly AppSettings _defaults = new() + { + DefaultTargetFolder = @"C:\repos-default", + DefaultMaxConcurrency = 6, + }; + + /// Every token a lister was given, with the name of the place the lister is for. + private readonly ConcurrentQueue<(string Place, string Token)> _sent = new(); + + + + public AccountWizardPlaceTests() + { + _store = new AccountsStore(_vault, _root); + } + + + + public void Dispose() => GitTestHelpers.TryDeleteDirectory(_root); + + + + /// The lister of a place: it records what it is given and lists one organization named after the place. + private FakeOrganizationLister ListerOf(ForgeEndpoint place) => new() + { + Handler = (token, _) => + { + _sent.Enqueue((place.DisplayName, token)); + return Task.FromResult>([$"org-at-{place.DisplayName}"]); + }, + }; + + + + private AccountWizardViewModel ChoosingWizard(string? defaultToken = null) + => new(_store, ListerOf, _defaults, defaultToken: () => defaultToken); + + + + private static Account GiteaAccount(string? server = Server) => new() + { + Id = Guid.NewGuid(), + Name = "Forge", + Organization = "platform", + TargetRoot = @"C:\repos", + Forge = ForgeKind.Gitea, + Server = server, + }; + + + + private static async Task AdvanceToStepAsync(AccountWizardViewModel wizard, int step) + { + while(wizard.Step < step) + { + int before = wizard.Step; + Assert.True(await wizard.TryAdvanceAsync(), $"expected to advance past step {before}"); + } + Assert.Equal(step, wizard.Step); + } + + + + // ---------------------------------------------------------------- the choice + + [Fact] + public void AWizardThatMayChoose_StartsOnGitHub_AndAsksForNoServer() + { + AccountWizardViewModel wizard = ChoosingWizard(); + + Assert.True(wizard.CanChoosePlace); + Assert.False(wizard.IsOnGitea); + Assert.Equal(0, wizard.PlaceIndex); + Assert.False(wizard.AsksForServer); + Assert.Equal("", wizard.Server); + Assert.Equal("", wizard.ServerError); + Assert.Equal("", wizard.PlaceNote); // step 1 asks, so there is nothing to tell + Assert.Equal("GitHub", wizard.PlaceName); + Assert.Equal("Personal access token", wizard.TokenHeader); + Assert.Equal("ghp_…", wizard.TokenPlaceholder); + Assert.Equal("Stored in Windows Credential Manager, never in a file; needs repository read access", wizard.TokenAdvice); + Assert.Equal(new Uri("https://github.com/settings/tokens"), wizard.TokenPage); + Assert.Equal("Next checks the token with GitHub and lists the organizations it can see.", wizard.TokenStepNote); + + // Seeded from the defaults like any new account. + Assert.Equal(@"C:\repos-default", wizard.TargetRoot); + Assert.Equal(6, wizard.MaxConcurrency); + Assert.False(wizard.IsEditing); + } + + + + [Fact] + public void PlaceIndex_IsTheChoiceAsARadioGroupReadsIt_AndNoSelectionChangesNothing() + { + AccountWizardViewModel wizard = ChoosingWizard(); + var changed = new List(); + wizard.PropertyChanged += (_, e) => changed.Add(e.PropertyName); + + wizard.PlaceIndex = 1; + + Assert.True(wizard.IsOnGitea); + Assert.True(wizard.AsksForServer); + Assert.Equal(1, wizard.PlaceIndex); + Assert.Contains(nameof(AccountWizardViewModel.PlaceIndex), changed, StringComparer.Ordinal); + Assert.Contains(nameof(AccountWizardViewModel.AsksForServer), changed, StringComparer.Ordinal); + + wizard.PlaceIndex = -1; // a radio group that has no selection for a moment + + Assert.True(wizard.IsOnGitea); + + wizard.PlaceIndex = 0; + + Assert.False(wizard.IsOnGitea); + Assert.False(wizard.AsksForServer); + } + + + + // ---------------------------------------------------------------- where the token goes + + [Fact] + public async Task AnAccountOnAGiteaServer_HasItsTokenCheckedThere_AndIsSavedThere() + { + AccountWizardViewModel wizard = ChoosingWizard(); + wizard.Name = "Forge"; + wizard.IsOnGitea = true; + wizard.Server = " git.example.test "; // as a person types it + + await AdvanceToStepAsync(wizard, 2); + + // The address is shown as it was normalized: it is the one the token goes to. + Assert.Equal(Server, wizard.Server); + Assert.Equal("", wizard.ServerError); + Assert.Equal("git.example.test", wizard.PlaceName); + Assert.Equal("Access token", wizard.TokenHeader); + Assert.Equal("", wizard.TokenPlaceholder); + Assert.Equal( + "Stored in Windows Credential Manager, never in a file; needs read access to repositories, organizations and the user", + wizard.TokenAdvice); + Assert.Equal(new Uri("https://git.example.test/user/settings/applications"), wizard.TokenPage); + Assert.Equal("Next checks the token with git.example.test and lists the organizations it can see.", wizard.TokenStepNote); + Assert.Empty(_sent); // nothing was sent anywhere to get here + + wizard.Token = "gitea_token"; + await AdvanceToStepAsync(wizard, 3); + + Assert.Equal([("git.example.test", "gitea_token")], _sent); + Assert.Equal(["org-at-git.example.test"], wizard.Organizations); + + wizard.Organization = "platform"; + await AdvanceToStepAsync(wizard, 4); + await wizard.SaveAsync(); + + Account saved = Assert.Single(new AccountsStore(_vault, _root).GetAll()); + Assert.Equal(ForgeKind.Gitea, saved.Forge); + Assert.Equal(Server, saved.Server); + Assert.Equal(TokenSource.Own, saved.TokenSource); + Assert.Equal("gitea_token", _vault.TryRetrieve(saved.TokenEntryId)); + Assert.Null(_vault.TryRetrieve(saved.Id)); // not where a version that knows only GitHub looks + } + + + + [Fact] + public async Task AnAccountLeftOnGitHub_IsAGitHubAccount_AsBefore() + { + AccountWizardViewModel wizard = ChoosingWizard(); + wizard.Name = "Work"; + wizard.Server = "git.example.test"; // typed, then GitHub chosen after all: not used + wizard.Token = "ghp_token"; + await AdvanceToStepAsync(wizard, 3); + wizard.Organization = "acme"; + await AdvanceToStepAsync(wizard, 4); + await wizard.SaveAsync(); + + Assert.Equal([("GitHub", "ghp_token")], _sent); + Account saved = Assert.Single(new AccountsStore(_vault, _root).GetAll()); + Assert.Equal(ForgeKind.GitHub, saved.Forge); + Assert.Null(saved.Server); + Assert.Equal("ghp_token", _vault.TryRetrieve(saved.Id)); + } + + + + [Theory] + [InlineData("", "Enter the address of the server, like https://git.example.com.")] + [InlineData(" ", "Enter the address of the server, like https://git.example.com.")] + [InlineData("http://git.example.test", "The server address cannot be used: it has to begin with https://.")] + [InlineData( + "https://someone:s3cret-token@git.example.test", + "The server address cannot be used: it contains a user name or a password; the token is entered separately.")] + [InlineData("https:/git.example.test", "The server address cannot be used: it is not an address (a server's address looks like https://git.example.com).")] + public async Task AServerAddressThatCannotBeUsed_KeepsTheWizardOnStepOne_AndNothingIsSent(string typed, string said) + { + AccountWizardViewModel wizard = ChoosingWizard(); + wizard.Name = "Forge"; + wizard.IsOnGitea = true; + wizard.Server = typed; + wizard.Token = "gitea_token"; + + Assert.False(await wizard.TryAdvanceAsync()); + + Assert.Equal(1, wizard.Step); + Assert.Equal(said, wizard.ServerError); + Assert.DoesNotContain("s3cret", wizard.ServerError, StringComparison.Ordinal); // a refused address is not repeated + Assert.Equal("", wizard.NameError); + Assert.Equal(typed, wizard.Server); // left as it was typed, to be corrected + Assert.Equal("GitHub", wizard.PlaceName); // no place was taken + Assert.Empty(_sent); + + // Corrected, the wizard goes on and the message goes away. + wizard.Server = "git.example.test"; + + Assert.True(await wizard.TryAdvanceAsync()); + Assert.Equal(2, wizard.Step); + Assert.Equal("", wizard.ServerError); + } + + + + [Fact] + public async Task TheMessageAboutTheServer_GoesAway_WhenTheOtherPlaceIsChosen() + { + AccountWizardViewModel wizard = ChoosingWizard(); + wizard.Name = "Work"; + wizard.IsOnGitea = true; + Assert.False(await wizard.TryAdvanceAsync()); + Assert.Equal("Enter the address of the server, like https://git.example.com.", wizard.ServerError); + + wizard.IsOnGitea = false; + + Assert.Equal("", wizard.ServerError); + Assert.True(await wizard.TryAdvanceAsync()); + } + + + + [Fact] + public async Task ABlankName_IsSaidFirst_AndTheServerIsNotLookedAt() + { + AccountWizardViewModel wizard = ChoosingWizard(); + wizard.IsOnGitea = true; + wizard.Server = "http://git.example.test"; + + Assert.False(await wizard.TryAdvanceAsync()); + + Assert.Equal("Enter a name for this account.", wizard.NameError); + Assert.Equal("", wizard.ServerError); + } + + + + // ---------------------------------------------------------------- nothing is carried to another place + + [Fact] + public async Task ATokenTypedForOnePlace_IsNotKept_WhenAnotherPlaceIsChosen() + { + AccountWizardViewModel wizard = ChoosingWizard(); + wizard.Name = "Work"; + wizard.Token = "ghp_typed_for_github"; + await AdvanceToStepAsync(wizard, 3); + wizard.Organization = "org-at-GitHub"; + var changed = new List(); + wizard.PropertyChanged += (_, e) => changed.Add(e.PropertyName); + + // Back to step 1, and the account is to be on a Gitea server after all. + wizard.GoBack(); + wizard.GoBack(); + wizard.IsOnGitea = true; + wizard.Server = Server; + await AdvanceToStepAsync(wizard, 2); + + Assert.Equal("", wizard.Token); + Assert.Contains(nameof(AccountWizardViewModel.Token), changed, StringComparer.Ordinal); // the dialog empties its box on this + Assert.Equal("", wizard.Organization); // an organization of the other place + Assert.Empty(wizard.Organizations); + + // With nothing typed for the new place, nothing is sent to it. + Assert.False(await wizard.TryAdvanceAsync()); + Assert.Equal("Enter a token to continue.", wizard.TokenError); + Assert.Equal([("GitHub", "ghp_typed_for_github")], _sent); + + // The same holds from one server to another. + wizard.Token = "token_for_the_first_server"; + await AdvanceToStepAsync(wizard, 3); + wizard.GoBack(); + wizard.GoBack(); + wizard.Server = "https://other.example.test"; + await AdvanceToStepAsync(wizard, 2); + + Assert.Equal("", wizard.Token); + Assert.Equal("other.example.test", wizard.PlaceName); + Assert.False(await wizard.TryAdvanceAsync()); + Assert.Equal( + [("GitHub", "ghp_typed_for_github"), ("git.example.test", "token_for_the_first_server")], + _sent); + } + + + + [Fact] + public async Task GoingBackWithoutChoosingAnotherPlace_KeepsWhatWasEntered() + { + AccountWizardViewModel wizard = ChoosingWizard(); + wizard.Name = "Forge"; + wizard.IsOnGitea = true; + wizard.Server = "git.example.test"; + await AdvanceToStepAsync(wizard, 2); + wizard.Token = "gitea_token"; + await AdvanceToStepAsync(wizard, 3); + wizard.Organization = "platform"; + + wizard.GoBack(); + wizard.GoBack(); + wizard.Name = "Forge, renamed"; + wizard.Server = "https://GIT.example.test"; // the same server, spelled another way + await AdvanceToStepAsync(wizard, 2); + + Assert.Equal("gitea_token", wizard.Token); + Assert.Equal("platform", wizard.Organization); + Assert.Equal(Server, wizard.Server); + } + + + + // ---------------------------------------------------------------- the default token is a GitHub token + + [Fact] + public async Task TheDefaultToken_IsOnOfferForGitHub_AndNeverSentToAGiteaServer() + { + AccountWizardViewModel wizard = ChoosingWizard(defaultToken: "ghp_default"); + Assert.True(wizard.OffersTokenChoice); + Assert.True(wizard.UseDefaultToken); + var changed = new List(); + wizard.PropertyChanged += (_, e) => changed.Add(e.PropertyName); + + wizard.Name = "Forge"; + wizard.IsOnGitea = true; + wizard.Server = Server; + await AdvanceToStepAsync(wizard, 2); + + Assert.False(wizard.OffersTokenChoice); + Assert.False(wizard.UseDefaultToken); + Assert.Contains(nameof(AccountWizardViewModel.OffersTokenChoice), changed, StringComparer.Ordinal); + + Assert.False(await wizard.TryAdvanceAsync()); // no token of its own was typed + Assert.Equal("Enter a token to continue.", wizard.TokenError); + Assert.Empty(_sent); + + // The choice is not shown here. Set all the same, it is refused: nothing is sent. + wizard.TokenChoiceIndex = 0; + Assert.False(await wizard.TryAdvanceAsync()); + Assert.Equal("The default token is a GitHub token. This account needs a token of its own.", wizard.TokenError); + Assert.Empty(_sent); + + // Back on GitHub, it is on offer again, and preselected as for any new account. + wizard.GoBack(); + wizard.IsOnGitea = false; + await AdvanceToStepAsync(wizard, 3); + + Assert.True(wizard.OffersTokenChoice); + Assert.True(wizard.UseDefaultToken); + Assert.Equal("", wizard.TokenError); + Assert.Equal([("GitHub", "ghp_default")], _sent); + } + + + + // ---------------------------------------------------------------- when there is no choice + + [Fact] + public async Task AWizardThatWasGivenOneLister_OffersNoChoice_AndItsAccountIsOnGitHub() + { + var onlyLister = new FakeOrganizationLister(); + var wizard = new AccountWizardViewModel(_store, onlyLister, _defaults); + + Assert.False(wizard.CanChoosePlace); + Assert.Equal("This account is on GitHub.", wizard.PlaceNote); + + // Whatever is set on it, it asks for no server and stays where its lister is. + wizard.IsOnGitea = true; + wizard.Server = Server; + Assert.False(wizard.AsksForServer); + + wizard.Name = "Work"; + wizard.Token = "ghp_token"; + await AdvanceToStepAsync(wizard, 3); + wizard.Organization = "acme"; + await AdvanceToStepAsync(wizard, 4); + await wizard.SaveAsync(); + + Assert.Equal(1, onlyLister.Calls); + Account saved = Assert.Single(new AccountsStore(_vault, _root).GetAll()); + Assert.Equal(ForgeKind.GitHub, saved.Forge); + Assert.Null(saved.Server); + } + + + + [Fact] + public void AQuickSyncConnectionSavedAsAnAccount_IsOnGitHub_AndOffersNoChoice() + { + var seed = new AccountWizardSeed("ghp_token", "acme", @"C:\repos", CreateOrgSubfolder: false, MaxConcurrency: 4); + + var wizard = new AccountWizardViewModel(_store, new FakeOrganizationLister(), seed); + + Assert.False(wizard.CanChoosePlace); + Assert.Equal("This account is on GitHub.", wizard.PlaceNote); + Assert.Equal(new Uri("https://github.com/settings/tokens"), wizard.TokenPage); + } + + + + [Fact] + public void EditingAnAccountOnAGiteaServer_SaysWhereItIs_AndThatItStaysThere() + { + Account account = GiteaAccount(); + + var wizard = new AccountWizardViewModel(_store, new FakeOrganizationLister(), _defaults, account, () => "gitea_token"); + + Assert.False(wizard.CanChoosePlace); + Assert.False(wizard.AsksForServer); + Assert.Equal(Server, wizard.Server); + Assert.Equal( + "This account is on git.example.test. That cannot be changed: its token was saved for that place.", + wizard.PlaceNote); + Assert.Equal("git.example.test", wizard.PlaceName); + Assert.Equal("Access token", wizard.TokenHeader); + Assert.Equal("Leave empty to keep the stored token", wizard.TokenPlaceholder); + Assert.Equal(new Uri("https://git.example.test/user/settings/applications"), wizard.TokenPage); + } + + + + [Fact] + public void EditingAGitHubAccount_SaysItIsOnGitHub() + { + Account account = GiteaAccount(server: null) with { Forge = ForgeKind.GitHub, TokenSource = TokenSource.Default }; + + var wizard = new AccountWizardViewModel(_store, new FakeOrganizationLister(), _defaults, account, () => null); + + Assert.Equal("", wizard.Server); + Assert.Equal("This account is on GitHub. That cannot be changed: its token was saved for that place.", wizard.PlaceNote); + Assert.Equal("ghp_…", wizard.TokenPlaceholder); // it has no stored token of its own to keep + } + + + + [Fact] + public void EditingAnAccountWhoseStoredAddressCannotBeUsed_SaysSo_AndPointsAtNoPage() + { + Account account = GiteaAccount(server: "http://git.example.test"); // as a file edited by hand could say + + var wizard = new AccountWizardViewModel(_store, new FakeOrganizationLister(), _defaults, account, () => "gitea_token"); + + Assert.Equal( + "This account is on Gitea, at an address that cannot be used. Delete the account and add it again.", + wizard.PlaceNote); + Assert.Equal("Gitea, at an address that cannot be used", wizard.PlaceName); + Assert.Null(wizard.TokenPage); + Assert.Equal("Access token", wizard.TokenHeader); + } + + + + [Fact] + public void EditingAnAccountOnAForgeThisVersionDoesNotKnow_SaysWhichVersionToUse() + { + Account account = GiteaAccount() with { ForgeName = "GitLab" }; // as a later version of gclo could have added it + + var wizard = new AccountWizardViewModel(_store, new FakeOrganizationLister(), _defaults, account, () => "a_token"); + + Assert.Equal( + "This account is on a forge this version of gclo does not know. Use the version of gclo that added it.", + wizard.PlaceNote); + Assert.Null(wizard.TokenPage); + Assert.False(wizard.OffersTokenChoice); + } + + + + // ---------------------------------------------------------------- arguments + + [Fact] + public void AWizardThatMayChoose_NeedsItsListersAndItsDefaults() + { + Assert.Throws( + () => new AccountWizardViewModel(_store, (Func)null!, _defaults)); + Assert.Throws(() => new AccountWizardViewModel(_store, ListerOf, null!)); + } + + + + // ---------------------------------------------------------------- an account's page + + [Fact] + public void AWorkspace_NamesThePlaceItConnectsTo() + { + using var quickSync = new WorkspaceViewModel(new FakeRepositoryLister(), new FakeGitClient(), new FakeOrganizationLister()); + using var onGitea = new WorkspaceViewModel( + new FakeRepositoryLister(), new FakeGitClient(), new FakeOrganizationLister(), account: GiteaAccount(), tokenVault: _vault); + using var onGitHub = new WorkspaceViewModel( + new FakeRepositoryLister(), new FakeGitClient(), new FakeOrganizationLister(), + account: GiteaAccount(server: null) with { Forge = ForgeKind.GitHub }, tokenVault: _vault); + using var unusable = new WorkspaceViewModel( + new FakeRepositoryLister(), new FakeGitClient(), new FakeOrganizationLister(), + account: GiteaAccount(server: "http://git.example.test"), tokenVault: _vault); + + Assert.Equal("Connect to GitHub", quickSync.ConnectTitle); + Assert.Equal(new Uri("https://github.com/settings/tokens"), quickSync.TokenPage); + + Assert.Equal("Connect to git.example.test", onGitea.ConnectTitle); + Assert.Equal(new Uri("https://git.example.test/user/settings/applications"), onGitea.TokenPage); + + Assert.Equal("Connect to GitHub", onGitHub.ConnectTitle); + Assert.Equal(new Uri("https://github.com/settings/tokens"), onGitHub.TokenPage); + + // What is stored cannot be used: the address is not repeated, and there is no page. + Assert.Equal("Connect to Gitea, at an address that cannot be used", unusable.ConnectTitle); + Assert.Null(unusable.TokenPage); + } +} diff --git a/gclo.UiTests/SmokeTests.cs b/gclo.UiTests/SmokeTests.cs index f85f2a6..cd8c8cf 100644 --- a/gclo.UiTests/SmokeTests.cs +++ b/gclo.UiTests/SmokeTests.cs @@ -241,7 +241,54 @@ public sealed partial class SmokeTests : UiTestBase - // (9) Resizing below the minimum window size is intentionally not covered: + // (9) A new account on a Gitea server (#206). Step 1 asks for the server's + // address only once that place is chosen; step 2 names the server the token + // is checked with; and step 3 lists what that server listed, which here is + // the fixture's organization for a Gitea server: the fixture token is + // answered offline for the place the wizard asked, so nothing leaves the + // machine. The wizard is canceled on step 3, so nothing is saved either. + [Fact] + public void AddAccountWizard_OnAGiteaServer_ChecksTheTokenWithThatServer() + { + Session.InvokeNavItem("NavAddAccount"); + try + { + Session.EnterText(Session.WaitForElement("WizardNameBox"), "Forge"); + Assert.Null(Session.FindInApp(cf => cf.ByAutomationId("WizardServerBox"))); // GitHub is the default: no address is asked + + Session.WaitForElement("WizardPlaceGitea").AsRadioButton().IsChecked = true; + Session.EnterText(Session.WaitForElement("WizardServerBox"), "git.example.test"); + Session.ClickButton("Next"); + + Session.WaitForText("Step 2 of 4: Access token"); + Session.WaitForText("Next checks the token with git.example.test and lists the organizations it can see."); + Session.EnterText(Session.WaitForElement("WizardTokenBox"), "gclo-uitest-fixture-token"); + Session.ClickButton("Next"); + + Session.WaitForText("Step 3 of 4: Organization", TimeSpan.FromSeconds(20)); + ComboBox orgBox = Session.WaitForElement("WizardOrgBox").AsComboBox(); + Session.WaitFor( + () => + { + orgBox.Expand(); + return orgBox.Items.FirstOrDefault(i => string.Equals(i.Name, "fixture-gitea-org", StringComparison.Ordinal)); + }, + "the fixture's Gitea organization in the organization list"); + orgBox.Collapse(); + Assert.False(Session.App.HasExited); + + Session.ClickButton("Cancel"); + Session.WaitForElementGone("WizardOrgBox"); + } + finally + { + Session.TryClickButton("Cancel"); // no-op when the happy path closed it + } + } + + + + // (10) Resizing below the minimum window size is intentionally not covered: // driving OS-level resize through UIA transforms is flaky across DPI // scales, and the minimum is already enforced by OverlappedPresenter. diff --git a/gclo.ViewModels/AccountWizardViewModel.cs b/gclo.ViewModels/AccountWizardViewModel.cs index 2937f62..775275a 100644 --- a/gclo.ViewModels/AccountWizardViewModel.cs +++ b/gclo.ViewModels/AccountWizardViewModel.cs @@ -12,17 +12,37 @@ namespace gclo.ViewModels; /// -/// Drives the four-step add/edit account wizard: identity (name + description), token -/// (validated by listing the organizations it can see), organization, and destination. -/// gates each step; persists the -/// result through the , writing the token to the vault only -/// when the user actually changed it (or always, for a new account). +/// Drives the four-step add/edit account wizard: identity (name + description, and +/// for a new account the place it lives: GitHub or a Gitea server), token (validated +/// by listing the organizations it can see at that place), organization, and +/// destination. gates each step; +/// persists the result through the , writing the token to +/// the vault only when the user actually changed it (or always, for a new account). /// +/// +/// A token is sent to one place only: the one the account is on. A wizard that was +/// given one lister is for the place of that lister (the account being edited, or +/// GitHub) and offers no choice; one that was given a lister per place lets step 1 +/// choose, and forgets the token when the place changes after it was typed. +/// public sealed partial class AccountWizardViewModel : ObservableObject { private readonly AccountsStore _store; - private readonly IOrganizationLister _orgLister; + /// + /// The lister of the place the account is on: the one place a token of this + /// wizard is sent to. Replaced when step 1 chooses another place. + /// + private IOrganizationLister _orgLister; + + /// The lister of a place, when step 1 may choose the place; null when the place is fixed. + private readonly Func? _listerOf; + + /// The place of a new account, as step 1 last accepted it. + private ForgeEndpoint _chosen = ForgeEndpoint.GitHub; + + /// The place of the account being edited; null for a new account, and when what is stored cannot be used. + private readonly ForgeEndpoint? _existingPlace; private readonly IActivityLog _log; @@ -61,7 +81,7 @@ public sealed partial class AccountWizardViewModel : ObservableObject Func? storedToken = null, IActivityLog? log = null, Func? defaultToken = null) - : this(store, orgLister, existing, storedToken, defaultToken, log) + : this(store, orgLister, listerOf: null, existing, storedToken, defaultToken, log) { ArgumentNullException.ThrowIfNull(defaults); @@ -82,11 +102,33 @@ public sealed partial class AccountWizardViewModel : ObservableObject TargetRoot = existing.TargetRoot; CreateOrgSubfolder = existing.CreateOrgSubfolder; MaxConcurrency = existing.MaxConcurrency; + Server = existing.Server ?? ""; } } + /// + /// A wizard for a NEW account whose place is chosen on step 1: GitHub, or a Gitea + /// server and its address. gives the organization + /// lister of a place; the token of step 2 goes to the lister of the chosen place + /// and to no other. + /// + public AccountWizardViewModel( + AccountsStore store, + Func listerOf, + AppSettings defaults, + IActivityLog? log = null, + Func? defaultToken = null) + : this(store, ListerOfGitHub(listerOf), listerOf, existing: null, storedToken: null, defaultToken, log) + { + ArgumentNullException.ThrowIfNull(defaults); + TargetRoot = defaults.DefaultTargetFolder; + MaxConcurrency = defaults.DefaultMaxConcurrency; + } + + + /// /// A wizard for a NEW account seeded from a working Quick Sync connection /// (): token, organization, folder, subfolder preference, @@ -97,7 +139,7 @@ public sealed partial class AccountWizardViewModel : ObservableObject public AccountWizardViewModel( AccountsStore store, IOrganizationLister orgLister, AccountWizardSeed seed, IActivityLog? log = null, Func? defaultToken = null) - : this(store, orgLister, existing: null, storedToken: null, defaultToken, log) + : this(store, orgLister, listerOf: null, existing: null, storedToken: null, defaultToken, log) { ArgumentNullException.ThrowIfNull(seed); Name = ""; @@ -116,16 +158,27 @@ public sealed partial class AccountWizardViewModel : ObservableObject + /// A new account starts on GitHub; the lister of that place is the one the wizard begins with. + private static IOrganizationLister ListerOfGitHub(Func listerOf) + { + ArgumentNullException.ThrowIfNull(listerOf); + return listerOf(ForgeEndpoint.GitHub); + } + + + private AccountWizardViewModel( - AccountsStore store, IOrganizationLister orgLister, Account? existing, Func? storedToken, - Func? defaultToken, IActivityLog? log) + AccountsStore store, IOrganizationLister orgLister, Func? listerOf, + Account? existing, Func? storedToken, Func? defaultToken, IActivityLog? log) { ArgumentNullException.ThrowIfNull(store); ArgumentNullException.ThrowIfNull(orgLister); _store = store; _orgLister = orgLister; + _listerOf = listerOf; _log = log ?? new NullActivityLog(); _existing = existing; + _existingPlace = existing is not null && existing.TryGetEndpoint(out ForgeEndpoint? place, out _) ? place : null; _storedToken = existing is null ? null : storedToken; _defaultToken = defaultToken; // Decided once: the choice is offered only when a default token exists at @@ -135,6 +188,8 @@ public sealed partial class AccountWizardViewModel : ObservableObject Step = 1; NameError = ""; + Server = ""; + ServerError = ""; TokenError = ""; OrganizationError = ""; TargetError = ""; @@ -162,13 +217,93 @@ public sealed partial class AccountWizardViewModel : ObservableObject public partial string Description { get; set; } /// - /// GitHub personal access token as typed; validated when leaving step 2. For an - /// edit it starts empty and stays empty unless the user types a replacement — - /// the stored token is never loaded into it. + /// The token as typed; validated when leaving step 2. For an edit it starts + /// empty and stays empty unless the user types a replacement — the stored token + /// is never loaded into it. Emptied when step 1 changes the place: a token is + /// typed for one place. /// [ObservableProperty] public partial string Token { get; set; } + /// + /// Whether step 1 asks where the account lives. Only for a new account in a + /// wizard that was given a lister per place; an account being edited stays where + /// it is, and a Quick Sync connection saved as an account is a GitHub connection. + /// + public bool CanChoosePlace => _listerOf is not null; + + /// Step 1's choice: false is GitHub, true is a Gitea server whose address is . + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(PlaceIndex))] + [NotifyPropertyChangedFor(nameof(AsksForServer))] + public partial bool IsOnGitea { get; set; } + + /// + /// as the selected index of a two-item radio group: + /// 0 = GitHub, 1 = a Gitea server. Any other index (a radio group reports -1 + /// while it has no selection) changes nothing. + /// + public int PlaceIndex + { + get => IsOnGitea ? 1 : 0; + set + { + if(value is 0 or 1) + { + IsOnGitea = value == 1; + } + } + } + + /// Whether step 1 shows the box for the server's address. + public bool AsksForServer => CanChoosePlace && IsOnGitea; + + /// + /// The address of the Gitea server as typed; after step 1 accepted it, as it was + /// normalized, which is the address the token is sent to. For an edit, the + /// account's stored address. + /// + [ObservableProperty] + public partial string Server { get; set; } + + /// Step 1's message about the server address; empty when it is acceptable. + [ObservableProperty] + public partial string ServerError { get; set; } + + /// + /// How the place the token goes to is named: "GitHub", or the server's host. For + /// an account whose stored place cannot be used, what says. + /// + public string PlaceName => _existing is null ? _chosen.DisplayName : _existing.DescribePlace(); + + /// + /// Said on step 1 when the place cannot be chosen: where the account is, and for + /// an edit why that stays. Empty when step 1 asks. + /// + public string PlaceNote + => CanChoosePlace ? "" + : _existing is null ? $"This account is on {PlaceName}." + : Place is null ? $"This account is on {PlaceName}. {_existing.AdviceWhenUnusable("Delete")}" + : $"This account is on {PlaceName}. That cannot be changed: its token was saved for that place."; + + /// The header of step 2's token box: what the forge calls a token. + public string TokenHeader => ForgeTexts.TokenName(PlaceKind); + + /// What step 2's empty token box says. + public string TokenPlaceholder + => _existing is { UsesDefaultToken: false } ? "Leave empty to keep the stored token" + : PlaceKind == ForgeKind.GitHub ? "ghp_…" + : ""; + + /// The tooltip of step 2's token box: where the token is kept and what it needs. + public string TokenAdvice => $"Stored in Windows Credential Manager, never in a file; {ForgeTexts.TokenNeeds(PlaceKind)}"; + + /// The page a token is made on; null when the account's place is not known. + public Uri? TokenPage => ForgeTexts.TokenPage(Place); + + /// Step 2's note on what Next does, naming the place the token is sent to. + public string TokenStepNote => $"Next checks the token with {PlaceName} and lists the organizations it can see."; + /// Organization (or user) to sync; picked from or typed. [ObservableProperty] public partial string Organization { get; set; } @@ -220,7 +355,7 @@ public sealed partial class AccountWizardViewModel : ObservableObject /// token, and such an account has a token of its own. /// public bool OffersTokenChoice - => (_existing?.Forge ?? ForgeKind.GitHub) == ForgeKind.GitHub && (HasDefaultToken || _existing?.UsesDefaultToken == true); + => PlaceKind == ForgeKind.GitHub && (HasDefaultToken || _existing?.UsesDefaultToken == true); /// /// as the selected index of a two-item radio group: @@ -300,6 +435,20 @@ public sealed partial class AccountWizardViewModel : ObservableObject + /// The kind of forge the account is on: the stored one for an edit, the chosen one for a new account. + private ForgeKind PlaceKind => _existing?.Forge ?? _chosen.Kind; + + + + /// The place the account is on; null for an edit whose stored forge and server cannot be used. + private ForgeEndpoint? Place => _existing is null ? _chosen : _existingPlace; + + + + partial void OnIsOnGiteaChanged(bool value) => ServerError = ""; + + + partial void OnMaxConcurrencyChanged(int value) { int clamped = Math.Clamp(value, AppSettings.MinConcurrency, AppSettings.MaxConcurrency); @@ -337,6 +486,10 @@ public sealed partial class AccountWizardViewModel : ObservableObject return false; } NameError = ""; + if(_listerOf is not null && !TryChoosePlace(_listerOf)) + { + return false; + } Step = 2; return true; } @@ -347,7 +500,7 @@ public sealed partial class AccountWizardViewModel : ObservableObject // An edit with the box left empty validates the STORED token, fetched // here and used for this one call only. string candidate; - if(UseDefaultToken && (_existing?.Forge ?? ForgeKind.GitHub) != ForgeKind.GitHub) + if(UseDefaultToken && PlaceKind != ForgeKind.GitHub) { /////////////////////////////////////////////////////////////////// // The choice is not shown for such an account. Whatever set it, // @@ -450,6 +603,60 @@ public sealed partial class AccountWizardViewModel : ObservableObject + /// + /// Step 1, for a new account: takes the place that was chosen, or says in + /// why the server address cannot be used. The address + /// is checked by the rule the command line uses (), + /// and shown afterwards as it was normalized. + /// + private bool TryChoosePlace(Func listerOf) + { + if(IsOnGitea && string.IsNullOrWhiteSpace(Server)) + { + ServerError = "Enter the address of the server, like https://git.example.com."; + return false; + } + + if(!ForgeEndpoint.TryCreate(IsOnGitea ? ForgeKind.Gitea : ForgeKind.GitHub, IsOnGitea ? Server : null, out ForgeEndpoint? place, out string? reason)) + { + ServerError = char.ToUpperInvariant(reason[0]) + reason[1..] + "."; + return false; + } + + ServerError = ""; + if(!place.Equals(_chosen)) + { + ////////////////////////////////////////////////////////////////////////////// + // Another place than the one the wizard was on. What was entered for the // + // other place is not carried over: a token typed for one server is not // + // sent to another, and an organization of one is not one of the other. The // + // default token is a GitHub token, so it is not the choice anywhere else. // + ////////////////////////////////////////////////////////////////////////////// + _chosen = place; + _orgLister = listerOf(place); + Token = ""; + TokenError = ""; + Organizations.Clear(); + Organization = ""; + UseDefaultToken = place.Kind == ForgeKind.GitHub && HasDefaultToken; + OnPropertyChanged(nameof(PlaceName)); + OnPropertyChanged(nameof(OffersTokenChoice)); + OnPropertyChanged(nameof(TokenHeader)); + OnPropertyChanged(nameof(TokenPlaceholder)); + OnPropertyChanged(nameof(TokenAdvice)); + OnPropertyChanged(nameof(TokenPage)); + OnPropertyChanged(nameof(TokenStepNote)); + } + + if(place.Server is not null) + { + Server = place.Server.AbsoluteUri; + } + return true; + } + + + /// Returns to the previous step; a no-op on the first step. public void GoBack() { @@ -494,8 +701,8 @@ public sealed partial class AccountWizardViewModel : ObservableObject TokenSource = UseDefaultToken ? TokenSource.Default : TokenSource.Own, // An account stays on the forge and server it was added for: its token was saved for that place. - Forge = _existing?.Forge ?? ForgeKind.GitHub, - Server = _existing?.Server, + Forge = PlaceKind, + Server = _existing is null ? _chosen.Server?.AbsoluteUri : _existing.Server, LastSyncUtc = _existing?.LastSyncUtc, LastSyncSummary = _existing?.LastSyncSummary, }; diff --git a/gclo.ViewModels/ForgeTexts.cs b/gclo.ViewModels/ForgeTexts.cs new file mode 100644 index 0000000..6f28723 --- /dev/null +++ b/gclo.ViewModels/ForgeTexts.cs @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2026 James Maes (KofTwentyTwo) + * SPDX-License-Identifier: MIT + */ + +using gclo.Engine; + + +namespace gclo.ViewModels; + + +/// +/// What the desktop app says about a forge where it used to say "GitHub": the +/// page a token is made on, how the token is called there, and what it has to be +/// allowed to do. One place, so the wizard and an account's page say the same. +/// +internal static class ForgeTexts +{ + private static readonly Uri s_gitHubTokenPage = new("https://github.com/settings/tokens"); + + + + /// + /// The page a person makes a token on: GitHub's, or the one of the Gitea server + /// itself. Null when the place is not known (an account whose stored address + /// cannot be used): there is no page to point at then. + /// + public static Uri? TokenPage(ForgeEndpoint? place) + => place is null ? null + : place.Server is null ? s_gitHubTokenPage + : new Uri(place.Server, "user/settings/applications"); + + + + /// What the forge calls the token. + public static string TokenName(ForgeKind forge) + => forge == ForgeKind.GitHub ? "Personal access token" : "Access token"; + + + + /// What the token has to be allowed to do, as the end of a sentence. + public static string TokenNeeds(ForgeKind forge) + => forge == ForgeKind.GitHub + ? "needs repository read access" + : "needs read access to repositories, organizations and the user"; +} diff --git a/gclo.ViewModels/WorkspaceViewModel.cs b/gclo.ViewModels/WorkspaceViewModel.cs index 9a3f17b..8a7e321 100644 --- a/gclo.ViewModels/WorkspaceViewModel.cs +++ b/gclo.ViewModels/WorkspaceViewModel.cs @@ -173,6 +173,25 @@ public sealed partial class WorkspaceViewModel : ObservableObject, IDisposable /// Name shown for this workspace in navigation: the account's name, or "Quick Sync". public string DisplayName => _account?.Name ?? "Quick Sync"; + /// + /// How the place this workspace connects to is named: "GitHub" for Quick Sync + /// and a GitHub account, the server's host for an account on a Gitea server. For + /// an account whose stored place cannot be used, what says. + /// + public string PlaceName => _account is null ? ForgeEndpoint.GitHub.DisplayName : _account.DescribePlace(); + + /// The title of the connect card: where this workspace connects to. + public string ConnectTitle => $"Connect to {PlaceName}"; + + /// The page a token for this workspace is made on; null when the account's place is not known. + public Uri? TokenPage => ForgeTexts.TokenPage(Place); + + /// The place this workspace connects to; null for an account whose stored forge and server cannot be used. + private ForgeEndpoint? Place + => _account is null ? ForgeEndpoint.GitHub + : _account.TryGetEndpoint(out ForgeEndpoint? place, out _) ? place + : null; + /// /// True for an account workspace whose token is the default token from Settings /// (): the pane tags it, and Sync All names diff --git a/gclo/AboutDialog.xaml b/gclo/AboutDialog.xaml index d01315e..2ea0e6a 100644 --- a/gclo/AboutDialog.xaml +++ b/gclo/AboutDialog.xaml @@ -56,7 +56,7 @@ - + MinHeight is the tallest step (step 1 with the server address and its error), + and the width is fixed (a note that names a server wraps instead of widening + the dialog), so the dialog keeps one footprint across steps, and when a Gitea + server is chosen, instead of resizing and re-centering under the pointer (#31). --> + + Identity: the unique display name, an optional note, and for a new + account the place it lives (GitHub, or a Gitea server). --> + + + + + + + + + Access token: validated when Next is pressed, against the place the + account is on (step 1) and nowhere else. --> + + NavigateUri="{x:Bind ViewModel.TokenPage, Mode=OneWay}" + Visibility="{x:Bind local:StatusFormat.VisibleIfNotNull(ViewModel.TokenPage), Mode=OneWay}" /> + Text="{x:Bind ViewModel.TokenStepNote, Mode=OneWay}" /> { + /////////////////////////////////////////////////////////////////////////// + // The view model empties the token when step 1 changes the place the // + // account is on (#206). The box is mirrored by hand, so it is emptied // + // here: what it shows is what would be sent. // + /////////////////////////////////////////////////////////////////////////// + if(string.Equals(e.PropertyName, nameof(AccountWizardViewModel.Token), StringComparison.Ordinal) + && ViewModel.Token.Length == 0 && TokenBox.Password.Length > 0) + { + TokenBox.Password = ""; + } + if(string.Equals(e.PropertyName, nameof(AccountWizardViewModel.Step), StringComparison.Ordinal) && ViewModel.Step == 3) { // The panel was collapsed, so the ComboBox has no template (and no inner @@ -106,7 +114,7 @@ public sealed partial class AccountWizardDialog : ContentDialog - /// Next is locked while the token step is talking to GitHub. + /// Next is locked while the token step is talking to the server. public bool PrimaryEnabled(bool isValidatingToken) => !isValidatingToken; @@ -266,7 +274,7 @@ public sealed partial class AccountWizardDialog : ContentDialog { TextBlock errorText = ViewModel.Step switch { - 1 => NameErrorText, + 1 => ViewModel.NameError.Length > 0 ? NameErrorText : ServerErrorText, 2 => TokenErrorText, 3 => OrganizationErrorText, _ => TargetErrorText, diff --git a/gclo/ConnectionFormControl.xaml b/gclo/ConnectionFormControl.xaml index 8e67f91..7c90afd 100644 --- a/gclo/ConnectionFormControl.xaml +++ b/gclo/ConnectionFormControl.xaml @@ -24,6 +24,7 @@ PlaceholderText="ghp_…" PasswordChanged="TokenBox_PasswordChanged" /> @@ -38,7 +39,7 @@ HorizontalAlignment="Stretch" Header="Organization or account" x:Name="OrgBox" - ToolTipService.ToolTip="The GitHub organization or user account whose repositories are synced. Populated from the token; typing a name also works." + ToolTipService.ToolTip="The organization or user account whose repositories are synced. Populated from the token; typing a name also works." IsEditable="True" ItemsSource="{x:Bind ViewModel.Organizations, Mode=OneTime}" PlaceholderText="Paste a token first, or type an org" diff --git a/gclo/ConnectionFormControl.xaml.cs b/gclo/ConnectionFormControl.xaml.cs index b301916..326f807 100644 --- a/gclo/ConnectionFormControl.xaml.cs +++ b/gclo/ConnectionFormControl.xaml.cs @@ -96,6 +96,13 @@ public sealed partial class ConnectionFormControl : UserControl // (#182). // /////////////////////////////////////////////////////////////////////////////// TokenBox.IsEnabled = !savedWithAnAccount; + + // The page of the place this workspace connects to (#206); none when that place is not known. + if(ViewModel is not null) + { + TokenPageLink.NavigateUri = ViewModel.TokenPage; + TokenPageLink.Visibility = StatusFormat.VisibleIfNotNull(ViewModel.TokenPage); + } TokenBox.PlaceholderText = !savedWithAnAccount ? "ghp_…" : ViewModel!.UsesDefaultToken ? "No default token is saved" : "No token is saved for this account"; diff --git a/gclo/MainWindow.xaml.cs b/gclo/MainWindow.xaml.cs index 23ebe37..64676a9 100644 --- a/gclo/MainWindow.xaml.cs +++ b/gclo/MainWindow.xaml.cs @@ -563,10 +563,11 @@ public sealed partial class MainWindow : Window return null; } // The listers are the account's own: its token goes to the forge and server it was added for. + ForgeEndpoint? place = PlaceOf(account); return new WorkspaceViewModel( - UiTestFixture.WrapRepositoryLister(AccountListers.RepositoryListerFor(account)), + UiTestFixture.WrapRepositoryLister(AccountListers.RepositoryListerFor(account), place), UiTestFixture.WrapGitClient(new LibGit2GitClient()), - UiTestFixture.WrapOrganizationLister(AccountListers.OrganizationListerFor(account)), + UiTestFixture.WrapOrganizationLister(AccountListers.OrganizationListerFor(account), place), account: account, tokenVault: _tokenVault, accountsStore: _accountsStore, @@ -770,6 +771,12 @@ public sealed partial class MainWindow : Window + /// The place an account is on; null when its stored forge and server cannot be used. + private static ForgeEndpoint? PlaceOf(Account account) + => account.TryGetEndpoint(out ForgeEndpoint? place, out _) ? place : null; + + + /// /// Runs the account wizard: adding when is null, /// editing (seeded from the account and its vault token) otherwise. On save, @@ -777,14 +784,33 @@ public sealed partial class MainWindow : Window /// private async Task ShowAccountWizardAsync(Account? existing) { - // The vault is consulted only if step 2 needs the stored token for a - // validation call; the wizard never displays or retains it. - // Its token is checked against the place the account is on, never against another forge. - Func? storedToken = existing is null ? null : () => _tokenVault.TryRetrieve(existing.TokenEntryId); - IOrganizationLister organizations = existing is null ? new GitHubOrganizationLister() : AccountListers.OrganizationListerFor(existing); - var viewModel = new AccountWizardViewModel( - _accountsStore, organizations, _settings, existing, storedToken, _log, - defaultToken: () => _tokenVault.TryRetrieve(AppSettings.DefaultTokenVaultId)); + Func defaultToken = () => _tokenVault.TryRetrieve(AppSettings.DefaultTokenVaultId); + AccountWizardViewModel viewModel; + if(existing is null) + { + // A new account: step 1 chooses the place, and the token of step 2 goes to + // the lister of that place and to no other (#206). + viewModel = new AccountWizardViewModel( + _accountsStore, + place => UiTestFixture.WrapOrganizationLister(place.CreateOrganizationLister(), place), + _settings, + _log, + defaultToken); + } + else + { + // The vault is consulted only if step 2 needs the stored token for a + // validation call; the wizard never displays or retains it. + // Its token is checked against the place the account is on, never against another forge. + viewModel = new AccountWizardViewModel( + _accountsStore, + UiTestFixture.WrapOrganizationLister(AccountListers.OrganizationListerFor(existing), PlaceOf(existing)), + _settings, + existing, + () => _tokenVault.TryRetrieve(existing.TokenEntryId), + _log, + defaultToken); + } var dialog = new AccountWizardDialog( viewModel, () => WinRT.Interop.WindowNative.GetWindowHandle(this)) { @@ -833,8 +859,9 @@ public sealed partial class MainWindow : Window /// private async Task ShowSeededAccountWizardAsync(AccountWizardSeed seed) { + // A Quick Sync connection is a GitHub connection, and so is the account it is saved as. var viewModel = new AccountWizardViewModel( - _accountsStore, new GitHubOrganizationLister(), seed, _log, + _accountsStore, UiTestFixture.WrapOrganizationLister(new GitHubOrganizationLister()), seed, _log, defaultToken: () => _tokenVault.TryRetrieve(AppSettings.DefaultTokenVaultId)); var dialog = new AccountWizardDialog( viewModel, () => WinRT.Interop.WindowNative.GetWindowHandle(this)) diff --git a/gclo/StatusFormat.cs b/gclo/StatusFormat.cs index b92ea01..1199f7b 100644 --- a/gclo/StatusFormat.cs +++ b/gclo/StatusFormat.cs @@ -138,6 +138,12 @@ public static class StatusFormat + /// Visible only when there is a ; hides a link that has nowhere to go. + public static Visibility VisibleIfNotNull(object? value) + => value is null ? Visibility.Collapsed : Visibility.Visible; + + + /// InfoBar severity for a run outcome (None and Canceled read as informational). public static InfoBarSeverity SeverityFor(RunResultKind kind) => kind switch { diff --git a/gclo/UiTestFixture.cs b/gclo/UiTestFixture.cs index dbd91cc..7e52aeb 100644 --- a/gclo/UiTestFixture.cs +++ b/gclo/UiTestFixture.cs @@ -16,8 +16,9 @@ namespace gclo; /// /// Offline seam for the FlaUI suite (#54, #33): when the process runs with /// GCLO_UITEST_FIXTURE=1 and a workspace's token is exactly -/// , GitHub and git are answered by this fixture instead of the -/// network - a fixed organization with three repositories, and a git client that +/// , the forge and git are answered by this fixture instead of the +/// network - a fixed organization with three repositories (another one for a Gitea +/// server, with clone addresses on that server), and a git client that /// succeeds without touching disk. Any other token (even with the variable set) goes /// to the real services, so the existing offline-input tests keep their meaning /// (a dummy token still gets GitHub's 401). Production never sets the variable, and an @@ -32,6 +33,9 @@ internal static class UiTestFixture /// The organization the fixture lists. public const string Organization = "fixture-org"; + /// The organization the fixture lists for a Gitea server (#206). + public const string GiteaOrganization = "fixture-gitea-org"; + /// /// True when the process was started for the UI suite with the fixture enabled, and /// is not an installed build. @@ -68,13 +72,36 @@ internal static class UiTestFixture - public static IRepositoryLister WrapRepositoryLister(IRepositoryLister real) - => IsEnabled ? new RepositoryLister(real) : real; + /// + /// The lister the shell uses for (null: GitHub): the real + /// one, answered by the fixture for the fixture token when the fixture is enabled. + /// + public static IRepositoryLister WrapRepositoryLister(IRepositoryLister real, ForgeEndpoint? place = null) + => IsEnabled ? new RepositoryLister(real, RepositoriesAt(place)) : real; - public static IOrganizationLister WrapOrganizationLister(IOrganizationLister real) - => IsEnabled ? new OrganizationLister(real) : real; + /// The organization lister for , wrapped the same way. + public static IOrganizationLister WrapOrganizationLister(IOrganizationLister real, ForgeEndpoint? place = null) + => IsEnabled ? new OrganizationLister(real, place?.Server is null ? Organization : GiteaOrganization) : real; + + + + /// The fixture's repositories as the place would list them: on a Gitea server, with clone addresses on that server. + private static IReadOnlyList RepositoriesAt(ForgeEndpoint? place) + { + if(place?.Server is not Uri server) + { + return s_repositories; + } + + List onTheServer = []; + foreach(RepoDescriptor repository in s_repositories) + { + onTheServer.Add(repository with { CloneUrl = new Uri(server, $"{GiteaOrganization}/{repository.Name}.git").AbsoluteUri }); + } + return onTheServer; + } @@ -83,22 +110,22 @@ internal static class UiTestFixture - private sealed class RepositoryLister(IRepositoryLister real) : IRepositoryLister + private sealed class RepositoryLister(IRepositoryLister real, IReadOnlyList repositories) : IRepositoryLister { public Task> ListOrganizationRepositoriesAsync( string organization, string token, CancellationToken cancellationToken = default) - => string.Equals(token, Token -, StringComparison.Ordinal) ? Task.FromResult(s_repositories) + => string.Equals(token, Token, StringComparison.Ordinal) + ? Task.FromResult(repositories) : real.ListOrganizationRepositoriesAsync(organization, token, cancellationToken); } - private sealed class OrganizationLister(IOrganizationLister real) : IOrganizationLister + private sealed class OrganizationLister(IOrganizationLister real, string organization) : IOrganizationLister { public Task> ListOrganizationsAsync(string token, CancellationToken cancellationToken = default) - => string.Equals(token, Token -, StringComparison.Ordinal) ? Task.FromResult>([Organization]) + => string.Equals(token, Token, StringComparison.Ordinal) + ? Task.FromResult>([organization]) : real.ListOrganizationsAsync(token, cancellationToken); } diff --git a/gclo/WorkspacePage.xaml b/gclo/WorkspacePage.xaml index 75fd444..5a10252 100644 --- a/gclo/WorkspacePage.xaml +++ b/gclo/WorkspacePage.xaml @@ -115,7 +115,10 @@ BorderThickness="1" CornerRadius="8"> - + -- 2.54.0 From 59c66edaf241f79dbcb15e064c83fa574b3589da Mon Sep 17 00:00:00 2001 From: James Maes Date: Wed, 11 Nov 2026 02:17:15 -0600 Subject: [PATCH 2/3] fix(app): the wizard names the whole server address where the token is asked for, takes a place only with its lister, and does not move while a token is checked From the review of #207. - Step 2 named the host alone, and the normalized address was visible only after going back: two servers on one host read the same. The note of step 2 now gives the address in full, as it was normalized. - The place was taken before its lister: had the lister of the new place failed to be made, the wizard would have been on the new place with the lister and the token of the old one. The lister is made first. - While step 2 checks a token, Next and Back do nothing: the answer that is on its way is the answer for the place and the token the wizard still holds. The dialog already held its buttons; the view model now holds too. - An edit no longer puts the stored address into the (hidden) address box. - The message about the server goes away when the address is corrected. - An account's page heads its token box with what the forge calls a token. - Tests state what was missing: what step 2 is told when the place changes, that the account is saved at the place step 1 accepted whatever the choice is set to afterwards, the two rules above. 11 single-rule mutations of the change: all caught. - ADR 0010 and T16 corrected; the README's picture of the wizard shows the choice. Refs #206 Co-Authored-By: Claude Fable 5.2 Signed-off-by: James Maes --- README.md | 2 +- docs/adr/0010-forges-beside-github.md | 13 +- docs/images/gclo-wizard.png | Bin 41565 -> 49646 bytes docs/security/threat-model.md | 2 +- gclo.Engine.Tests/AccountWizardPlaceTests.cs | 167 ++++++++++++++++++- gclo.UiTests/SmokeTests.cs | 2 +- gclo.ViewModels/AccountWizardViewModel.cs | 35 +++- gclo.ViewModels/WorkspaceViewModel.cs | 3 + gclo/ConnectionFormControl.xaml.cs | 1 + 9 files changed, 206 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 6a34e07..b192044 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ On launch, gclo shows a brief branded splash, then the **Quick Sync** connect ca Quick Sync is ad-hoc — nothing is saved. For organizations you sync regularly, **Add account** stores a reusable profile: a name, where the repositories are, the organization, target folder, and parallelism, with the token kept in the Windows Credential Manager (never in a file). The first step asks where the repositories are: **GitHub**, or **a Gitea server** and its address (HTTPS only; a Forgejo server works the same way). The token of the next step is checked with that place and sent nowhere else, and an account stays on the place it was added for: to move one, delete it and add it again. Quick Sync connects to GitHub. Each account gets its own entry in the navigation pane and its own workspace; **Sync all** runs every saved account in turn. A status bar above the workspace is there for the whole pass, whichever workspace is on screen: it says which account is syncing, which of how many, and how far along it is, takes you to that account, and holds the result when the pass is over. The pane shows each account's live status as well. **Cancel sync all** stops the pass after the account that is syncing, and the result says how many accounts were not started. An account that could not be synced (it has no token, or its repositories could not be loaded) is marked in the pane and counted as needing attention, so a pass in which it was left out does not read as a success. An account's token is changed with **Edit account…** on the account's page (**Edit…** in the pane's menu); the token box on an account's page takes no input. Sync all syncs the repositories each account's page lists: **Refresh** on that page picks up repositories created since it was loaded.

- Add account wizard, step 1 of 4: account name and optional description + Add account wizard, step 1 of 4: account name, optional description, and where the repositories are, with a Gitea server chosen and its address entered

## Using the CLI diff --git a/docs/adr/0010-forges-beside-github.md b/docs/adr/0010-forges-beside-github.md index 1fe986c..dc3d273 100644 --- a/docs/adr/0010-forges-beside-github.md +++ b/docs/adr/0010-forges-beside-github.md @@ -126,8 +126,9 @@ were the two lister implementations and the failure type they throw. - **The place is asked first, on step 1, for a new account only.** GitHub is the default; a Gitea server needs its address, which is checked by the same rule as - on the command line (`ForgeEndpoint.TryCreate`) and shown afterwards as it was - normalized: that is the address the token goes to. An edit shows where the + on the command line (`ForgeEndpoint.TryCreate`). Step 2 names the address as it + was normalized, in full, where the token is asked for: that is the address the + token goes to. An edit shows where the account is and that it stays there. A Quick Sync connection saved as an account is a GitHub account: Quick Sync itself connects to GitHub only. - **The wizard has the lister of one place at a time.** The shell gives it a @@ -170,8 +171,8 @@ were the two lister implementations and the failure type they throw. by hand, as on GitHub. - The git client uses the same credential form as for GitHub (a fixed user name, the token as the password). Gitea accepts a token as the password with any user - name; this is to be confirmed against a live server before accounts can use a - Gitea server (#201, step 2). + name; this is still to be confirmed against a live server, before the release + that brings Gitea support (#201). - The engine's public surface grows (`ForgeKind`, `ForgeAccessException`, `ForgeAccessKind`, `GiteaServerUrl`, the two listers), and the base type of `GitHubAccessException` changes: a minor version for the package. Code that @@ -179,5 +180,5 @@ were the two lister implementations and the failure type they throw. up by name through reflection now finds two properties. - A server answers a renamed organization or account with a redirect, which gclo does not follow: the message says to use the new name. -- Accounts, the command line and the desktop app follow in their own changes - (#201). Until then the app and the CLI behave as before. +- Accounts, the command line and the desktop app came in their own changes + (#204, #206; the sections above). diff --git a/docs/images/gclo-wizard.png b/docs/images/gclo-wizard.png index 666db8d8d926cd426776b6a33906e2836b4291ea..1f130fdbb094fa91bdccd8007da25ae82a799cea 100644 GIT binary patch literal 49646 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

bdqJAL68w?SSoo|_|??j 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 sj&2kG49NsHCb%PW_97I0Zd;G%x1B}5txBvhE literal 41565 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

sUeJt@ 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 X&tiMBY)8Rer4Cy=Kuh+Yy!5{S9jyOI diff --git a/docs/security/threat-model.md b/docs/security/threat-model.md index 182ac30..848c434 100644 --- a/docs/security/threat-model.md +++ b/docs/security/threat-model.md @@ -100,7 +100,7 @@ flowchart LR | T12 | Repository | Repudiation | Unattributed change reaches `main` | Low | High | Signed, signed-off commits; squash-only through gated pull requests; no bypass actors | Mitigated | | T14 | PAT handling | Information disclosure | The token is offered to a host other than the one the repository was listed on: an existing folder whose `origin` points elsewhere, or a server named by the repository's own configuration during the WSL clone (for example an LFS endpoint) | Low | Critical | The git credential callback answers only a remote whose URL is HTTPS on the listed URL's host and port, and refuses every other remote with a clear error (a redirect that the listed host itself issues is followed by libgit2; that host already holds the token); a folder whose origin names another repository fails before any request; the WSL credential helper is scoped to the listed host (`credential.https://.helper`) and an existing WSL destination is pulled only when its stored origin is the listed URL (#139) | Mitigated | | T15 | Logs and terminal | Spoofing / Tampering | Text from a repository or a server (a file path may contain a newline, an escape sequence, or a character that reorders or hides text) forges an entry in the activity log or drives the terminal the CLI prints to | Low | Medium | `DisplayText` shows control, separator and format characters (direction marks and overrides, invisible characters, tag characters) and unpaired surrogates as visible `\uXXXX` text: every log message is one line, and an exception's lines are indented under their entry so none can start like one. The CLI writes every line through one place, `Terminal`, which also shows any character the console's code page cannot represent in that form, because a legacy code page turns some printable characters into control bytes; a test fails if any other CLI file writes to the console. The JSON modes rely on the JSON encoder (#143) | Mitigated for the log and the CLI. Not covered: the desktop app's own surfaces (row error text, the path-recovery dialog), where a newline is harmless but a direction override in a path the user is asked to approve is not; and text that merely looks like something else (look-alike letters, an escape written out literally) | -| T16 | Forge server (Gitea) | Information disclosure / Spoofing | The address of a server is typed by a user. A wrong address, a server that is hostile or misconfigured, or something between gclo and the server gets the account's token sent elsewhere, or answers in a way that sends clones (and the token with them) to another host, or makes gclo read without end | Low | Critical | `GiteaServerUrl`: HTTPS only, no credentials or query in the address, no setting for `http://`. Certificates are validated by the platform and that cannot be switched off. Every request goes to the configured address with the token in a header; redirects are not followed; the address of a next page is never taken from an answer; the client keeps no cookies. An address that is refused is not repeated in a message (it may hold a password or a token), and neither is a token. Clone addresses are built from the configured address, the owner and the name, and the server's `clone_url` is not read, so the git client's rule (the token only to the host of the clone address, T14) binds the token to the account's server. Answers are bounded in size, lists in pages and items, requests in time (reading the answer included). A server's own text reaches a message only as the reason of a refusal, on one line and cut to a length (T15). Accounts (#204): the forge and server of an account cannot be changed after it is added, so a saved token is not sent to an address somebody edited; `gclo sync --account` takes no other place from the command line; the default token (a GitHub token) is never handed to an account on another forge, and each forge has its own default token variable; accounts that are not on GitHub are kept in a file of their own, which a gclo from before forges neither reads nor rewrites, so that version cannot turn one into a GitHub account by writing back what it knows; the token of such an account is found only by that account at the address it was saved for (T13); an account whose stored forge or server cannot be used, or whose forge this version does not know, asks no server anything. The app's wizard (#206): the address is checked by the same rule and shown as it was normalized before a token is asked for; the wizard holds the lister of the chosen place only; a token typed for one place is emptied when another place is chosen, and the default token is not offered for, or sent to, a Gitea server. | Mitigated. The server the user names sees the token: that is the purpose. A machine that trusts an intercepting certificate authority is outside this (as for GitHub) | +| T16 | Forge server (Gitea) | Information disclosure / Spoofing | The address of a server is typed by a user. A wrong address, a server that is hostile or misconfigured, or something between gclo and the server gets the account's token sent elsewhere, or answers in a way that sends clones (and the token with them) to another host, or makes gclo read without end | Low | Critical | `GiteaServerUrl`: HTTPS only, no credentials or query in the address, no setting for `http://`. Certificates are validated by the platform and that cannot be switched off. Every request goes to the configured address with the token in a header; redirects are not followed; the address of a next page is never taken from an answer; the client keeps no cookies. An address that is refused is not repeated in a message (it may hold a password or a token), and neither is a token. Clone addresses are built from the configured address, the owner and the name, and the server's `clone_url` is not read, so the git client's rule (the token only to the host of the clone address, T14) binds the token to the account's server. Answers are bounded in size, lists in pages and items, requests in time (reading the answer included). A server's own text reaches a message only as the reason of a refusal, on one line and cut to a length (T15). Accounts (#204): the forge and server of an account cannot be changed after it is added, so a saved token is not sent to an address somebody edited; `gclo sync --account` takes no other place from the command line; the default token (a GitHub token) is never handed to an account on another forge, and each forge has its own default token variable; accounts that are not on GitHub are kept in a file of their own, which a gclo from before forges neither reads nor rewrites, so that version cannot turn one into a GitHub account by writing back what it knows; the token of such an account is found only by that account at the address it was saved for (T13); an account whose stored forge or server cannot be used, or whose forge this version does not know, asks no server anything. The app's wizard (#206): the address is checked by the same rule, and the step that asks for the token names it in full as it was normalized; the wizard holds the lister of the chosen place only; a token typed for one place is emptied when another place is chosen, and the default token is not offered for, or sent to, a Gitea server. | Mitigated. The server the user names sees the token: that is the purpose. A machine that trusts an intercepting certificate authority is outside this (as for GitHub) | | T13 | CLI / data directory | Tampering | A malicious `accounts.json`, `accounts-forges.json` or `settings.json` redirects targets, or names another place for an account | Low | Medium | Files live under the user profile; sanitized on load; target paths resolved and contained. The token of an account on a Gitea server is stored under a name made from the account's id, forge and server address as the file states them, so an account whose address was changed in the file finds no token for the new address, and a record that names another account's id finds none of that account's. What remains for someone who can write these files: a record made to look like a GitHub account can name the entry of a Gitea token (the name can be computed from the file), and that token is then offered to github.com, not to a host of their choosing. The app resolves a token for the place a workspace was opened for, so a file that is changed while the app runs does not bring the token of one place to the listers of another | Accepted (same-user boundary: whoever can write these files can read the tokens) | ## 7. Accepted risks diff --git a/gclo.Engine.Tests/AccountWizardPlaceTests.cs b/gclo.Engine.Tests/AccountWizardPlaceTests.cs index 18dea4f..a96ebe8 100644 --- a/gclo.Engine.Tests/AccountWizardPlaceTests.cs +++ b/gclo.Engine.Tests/AccountWizardPlaceTests.cs @@ -169,7 +169,8 @@ public sealed class AccountWizardPlaceTests : IDisposable "Stored in Windows Credential Manager, never in a file; needs read access to repositories, organizations and the user", wizard.TokenAdvice); Assert.Equal(new Uri("https://git.example.test/user/settings/applications"), wizard.TokenPage); - Assert.Equal("Next checks the token with git.example.test and lists the organizations it can see.", wizard.TokenStepNote); + // The whole address, not the host alone: it is what the token is about to be sent to. + Assert.Equal("Next checks the token with https://git.example.test/ and lists the organizations it can see.", wizard.TokenStepNote); Assert.Empty(_sent); // nothing was sent anywhere to get here wizard.Token = "gitea_token"; @@ -354,6 +355,165 @@ public sealed class AccountWizardPlaceTests : IDisposable + [Fact] + public async Task TwoServersOnOneHost_AreToldApart_WhereTheTokenIsAskedFor() + { + AccountWizardViewModel wizard = ChoosingWizard(); + wizard.Name = "Forge"; + wizard.IsOnGitea = true; + wizard.Server = "git.example.test/team-a"; + + await AdvanceToStepAsync(wizard, 2); + + Assert.Equal("Next checks the token with https://git.example.test/team-a/ and lists the organizations it can see.", wizard.TokenStepNote); + Assert.Equal(new Uri("https://git.example.test/team-a/user/settings/applications"), wizard.TokenPage); + } + + + + [Fact] + public async Task WhatStepTwoSays_FollowsThePlace_WhenItIsChosenAgain() + { + AccountWizardViewModel wizard = ChoosingWizard(); + wizard.Name = "Forge"; + wizard.Token = ""; + await AdvanceToStepAsync(wizard, 2); + Assert.False(await wizard.TryAdvanceAsync()); + Assert.Equal("Enter a token to continue.", wizard.TokenError); // said about the other place + var changed = new List(); + wizard.PropertyChanged += (_, e) => changed.Add(e.PropertyName); + + wizard.GoBack(); + wizard.IsOnGitea = true; + wizard.Server = Server; + await AdvanceToStepAsync(wizard, 2); + + // Step 2 is bound to these: without the notifications it would go on naming GitHub and linking GitHub's page. + Assert.All( + (string[]) + [ + nameof(AccountWizardViewModel.PlaceName), nameof(AccountWizardViewModel.TokenStepNote), nameof(AccountWizardViewModel.TokenPage), + nameof(AccountWizardViewModel.TokenHeader), nameof(AccountWizardViewModel.TokenPlaceholder), nameof(AccountWizardViewModel.TokenAdvice), + ], + name => Assert.Contains(name, changed, StringComparer.Ordinal)); + Assert.Equal("", wizard.TokenError); + } + + + + [Fact] + public async Task TheAccountIsSavedAtThePlaceStepOneAccepted_WhateverTheChoiceIsSetToAfterwards() + { + AccountWizardViewModel wizard = ChoosingWizard(); + wizard.Name = "Forge"; + wizard.IsOnGitea = true; + wizard.Server = Server; + await AdvanceToStepAsync(wizard, 2); + + // As a binding could set them while step 1 is not on screen: no place is taken without step 1. + wizard.IsOnGitea = false; + wizard.Server = "https://elsewhere.example.test"; + wizard.Token = "gitea_token"; + await AdvanceToStepAsync(wizard, 3); + wizard.Organization = "platform"; + await AdvanceToStepAsync(wizard, 4); + await wizard.SaveAsync(); + + Assert.Equal([("git.example.test", "gitea_token")], _sent); + Account saved = Assert.Single(new AccountsStore(_vault, _root).GetAll()); + Assert.Equal(ForgeKind.Gitea, saved.Forge); + Assert.Equal(Server, saved.Server); + } + + + + [Fact] + public async Task APlaceThatNoListerCanBeMadeFor_IsNotTaken() + { + var wizard = new AccountWizardViewModel( + _store, + place => place.Server is null ? ListerOf(place) : throw new InvalidOperationException("no lister for that place"), + _defaults); + wizard.Name = "Forge"; + wizard.Token = "ghp_typed_for_github"; + wizard.IsOnGitea = true; + wizard.Server = Server; + + await Assert.ThrowsAsync(wizard.TryAdvanceAsync); + + // The wizard is where it was: on GitHub, with GitHub's lister and the token typed for GitHub. + Assert.Equal(1, wizard.Step); + Assert.Equal("GitHub", wizard.PlaceName); + Assert.Equal("ghp_typed_for_github", wizard.Token); + + wizard.IsOnGitea = false; + await AdvanceToStepAsync(wizard, 3); + + Assert.Equal([("GitHub", "ghp_typed_for_github")], _sent); + } + + + + [Fact] + public async Task WhileATokenIsBeingChecked_TheWizardDoesNotMove() + { + var answer = new TaskCompletionSource>(TaskCreationOptions.RunContinuationsAsynchronously); + var wizard = new AccountWizardViewModel( + _store, + place => new FakeOrganizationLister + { + Handler = (token, _) => + { + _sent.Enqueue((place.DisplayName, token)); + return answer.Task; + }, + }, + _defaults); + wizard.Name = "Work"; + wizard.Token = "ghp_token"; + await AdvanceToStepAsync(wizard, 2); + + Task checking = wizard.TryAdvanceAsync(); + Assert.True(wizard.IsValidatingToken); + + // Neither back, nor on: the answer that is on its way is for this place and this token. + wizard.GoBack(); + Assert.Equal(2, wizard.Step); + Task again = wizard.TryAdvanceAsync(); + Assert.True(again.IsCompleted, "a second Next started another check instead of being refused"); + Assert.False(await again); + Assert.Equal(2, wizard.Step); + Assert.Single(_sent); + + answer.SetResult(["me", "acme"]); + + Assert.True(await checking); + Assert.Equal(3, wizard.Step); + Assert.Equal(["me", "acme"], wizard.Organizations); + + wizard.GoBack(); // and it moves again + Assert.Equal(2, wizard.Step); + } + + + + [Fact] + public async Task TheMessageAboutTheServer_GoesAway_WhenTheAddressIsCorrected() + { + AccountWizardViewModel wizard = ChoosingWizard(); + wizard.Name = "Forge"; + wizard.IsOnGitea = true; + wizard.Server = "http://git.example.test"; + Assert.False(await wizard.TryAdvanceAsync()); + Assert.Equal("The server address cannot be used: it has to begin with https://.", wizard.ServerError); + + wizard.Server = "https://git.example.test"; + + Assert.Equal("", wizard.ServerError); + } + + + // ---------------------------------------------------------------- the default token is a GitHub token [Fact] @@ -451,10 +611,11 @@ public sealed class AccountWizardPlaceTests : IDisposable Assert.False(wizard.CanChoosePlace); Assert.False(wizard.AsksForServer); - Assert.Equal(Server, wizard.Server); + Assert.Equal("", wizard.Server); // what is stored is not put into a box Assert.Equal( "This account is on git.example.test. That cannot be changed: its token was saved for that place.", wizard.PlaceNote); + Assert.Equal("Next checks the token with https://git.example.test/ and lists the organizations it can see.", wizard.TokenStepNote); Assert.Equal("git.example.test", wizard.PlaceName); Assert.Equal("Access token", wizard.TokenHeader); Assert.Equal("Leave empty to keep the stored token", wizard.TokenPlaceholder); @@ -539,9 +700,11 @@ public sealed class AccountWizardPlaceTests : IDisposable Assert.Equal("Connect to GitHub", quickSync.ConnectTitle); Assert.Equal(new Uri("https://github.com/settings/tokens"), quickSync.TokenPage); + Assert.Equal("Personal access token", quickSync.TokenHeader); Assert.Equal("Connect to git.example.test", onGitea.ConnectTitle); Assert.Equal(new Uri("https://git.example.test/user/settings/applications"), onGitea.TokenPage); + Assert.Equal("Access token", onGitea.TokenHeader); Assert.Equal("Connect to GitHub", onGitHub.ConnectTitle); Assert.Equal(new Uri("https://github.com/settings/tokens"), onGitHub.TokenPage); diff --git a/gclo.UiTests/SmokeTests.cs b/gclo.UiTests/SmokeTests.cs index cd8c8cf..f483e34 100644 --- a/gclo.UiTests/SmokeTests.cs +++ b/gclo.UiTests/SmokeTests.cs @@ -261,7 +261,7 @@ public sealed partial class SmokeTests : UiTestBase Session.ClickButton("Next"); Session.WaitForText("Step 2 of 4: Access token"); - Session.WaitForText("Next checks the token with git.example.test and lists the organizations it can see."); + Session.WaitForText("Next checks the token with https://git.example.test/ and lists the organizations it can see."); Session.EnterText(Session.WaitForElement("WizardTokenBox"), "gclo-uitest-fixture-token"); Session.ClickButton("Next"); diff --git a/gclo.ViewModels/AccountWizardViewModel.cs b/gclo.ViewModels/AccountWizardViewModel.cs index 775275a..52f9d8c 100644 --- a/gclo.ViewModels/AccountWizardViewModel.cs +++ b/gclo.ViewModels/AccountWizardViewModel.cs @@ -102,7 +102,6 @@ public sealed partial class AccountWizardViewModel : ObservableObject TargetRoot = existing.TargetRoot; CreateOrgSubfolder = existing.CreateOrgSubfolder; MaxConcurrency = existing.MaxConcurrency; - Server = existing.Server ?? ""; } } @@ -260,8 +259,9 @@ public sealed partial class AccountWizardViewModel : ObservableObject ///

/// The address of the Gitea server as typed; after step 1 accepted it, as it was - /// normalized, which is the address the token is sent to. For an edit, the - /// account's stored address. + /// normalized, which is the address the token is sent to. Empty for an edit: + /// where the account is, says, and what is stored is not + /// put into a box (an address that cannot be used may hold a password). /// [ObservableProperty] public partial string Server { get; set; } @@ -301,8 +301,13 @@ public sealed partial class AccountWizardViewModel : ObservableObject /// The page a token is made on; null when the account's place is not known. public Uri? TokenPage => ForgeTexts.TokenPage(Place); - /// Step 2's note on what Next does, naming the place the token is sent to. - public string TokenStepNote => $"Next checks the token with {PlaceName} and lists the organizations it can see."; + /// + /// Step 2's note on what Next does, naming the place the token is sent to: for a + /// server its whole address as it was normalized, not the host alone, since two + /// servers can share a host. + /// + public string TokenStepNote + => $"Next checks the token with {Place?.Server?.AbsoluteUri ?? PlaceName} and lists the organizations it can see."; /// Organization (or user) to sync; picked from or typed. [ObservableProperty] @@ -449,6 +454,10 @@ public sealed partial class AccountWizardViewModel : ObservableObject + partial void OnServerChanged(string value) => ServerError = ""; + + + partial void OnMaxConcurrencyChanged(int value) { int clamped = Math.Clamp(value, AppSettings.MinConcurrency, AppSettings.MaxConcurrency); @@ -469,6 +478,16 @@ public sealed partial class AccountWizardViewModel : ObservableObject ///
public async Task TryAdvanceAsync() { + if(IsValidatingToken) + { + /////////////////////////////////////////////////////////////////////////// + // A token is being checked with the place the wizard is on. Until the // + // answer is there the wizard stays as it is (GoBack too): the answer is // + // then the answer for the place and the token the wizard still holds. // + /////////////////////////////////////////////////////////////////////////// + return false; + } + switch(Step) { case 1: @@ -632,8 +651,8 @@ public sealed partial class AccountWizardViewModel : ObservableObject // sent to another, and an organization of one is not one of the other. The // // default token is a GitHub token, so it is not the choice anywhere else. // ////////////////////////////////////////////////////////////////////////////// + _orgLister = listerOf(place); // first: if there is no lister for the place, the wizard stays on the one it has _chosen = place; - _orgLister = listerOf(place); Token = ""; TokenError = ""; Organizations.Clear(); @@ -657,10 +676,10 @@ public sealed partial class AccountWizardViewModel : ObservableObject - /// Returns to the previous step; a no-op on the first step. + /// Returns to the previous step; a no-op on the first step, and while step 2 is checking a token. public void GoBack() { - if(!IsFirstStep) + if(!IsFirstStep && !IsValidatingToken) { Step--; } diff --git a/gclo.ViewModels/WorkspaceViewModel.cs b/gclo.ViewModels/WorkspaceViewModel.cs index 8a7e321..f3ef79f 100644 --- a/gclo.ViewModels/WorkspaceViewModel.cs +++ b/gclo.ViewModels/WorkspaceViewModel.cs @@ -180,6 +180,9 @@ public sealed partial class WorkspaceViewModel : ObservableObject, IDisposable ///
public string PlaceName => _account is null ? ForgeEndpoint.GitHub.DisplayName : _account.DescribePlace(); + /// What the token of this workspace's forge is called: the header of its token box. + public string TokenHeader => ForgeTexts.TokenName(_account?.Forge ?? ForgeKind.GitHub); + /// The title of the connect card: where this workspace connects to. public string ConnectTitle => $"Connect to {PlaceName}"; diff --git a/gclo/ConnectionFormControl.xaml.cs b/gclo/ConnectionFormControl.xaml.cs index 326f807..1e2a384 100644 --- a/gclo/ConnectionFormControl.xaml.cs +++ b/gclo/ConnectionFormControl.xaml.cs @@ -100,6 +100,7 @@ public sealed partial class ConnectionFormControl : UserControl // The page of the place this workspace connects to (#206); none when that place is not known. if(ViewModel is not null) { + TokenBox.Header = ViewModel.TokenHeader; TokenPageLink.NavigateUri = ViewModel.TokenPage; TokenPageLink.Visibility = StatusFormat.VisibleIfNotNull(ViewModel.TokenPage); } -- 2.54.0 From 7b9510a480807598e6ec1994ac5066a873630ab7 Mon Sep 17 00:00:00 2001 From: James Maes Date: Wed, 11 Nov 2026 05:51:22 -0600 Subject: [PATCH 3/3] fix(app): the wizard's choice of place reads GitHub and Gitea "A Gitea server" stood beside a bare "GitHub": an article and the word server on one side only. The two options are now named alike; the server address box that appears under Gitea says the rest. README sentence and picture follow. Refs #206 Co-Authored-By: Claude Fable 5.2 Signed-off-by: James Maes --- README.md | 2 +- docs/images/gclo-wizard.png | Bin 49646 -> 49104 bytes gclo/AccountWizardDialog.xaml | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b192044..2760e8c 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ On launch, gclo shows a brief branded splash, then the **Quick Sync** connect ca ### Saved accounts -Quick Sync is ad-hoc — nothing is saved. For organizations you sync regularly, **Add account** stores a reusable profile: a name, where the repositories are, the organization, target folder, and parallelism, with the token kept in the Windows Credential Manager (never in a file). The first step asks where the repositories are: **GitHub**, or **a Gitea server** and its address (HTTPS only; a Forgejo server works the same way). The token of the next step is checked with that place and sent nowhere else, and an account stays on the place it was added for: to move one, delete it and add it again. Quick Sync connects to GitHub. Each account gets its own entry in the navigation pane and its own workspace; **Sync all** runs every saved account in turn. A status bar above the workspace is there for the whole pass, whichever workspace is on screen: it says which account is syncing, which of how many, and how far along it is, takes you to that account, and holds the result when the pass is over. The pane shows each account's live status as well. **Cancel sync all** stops the pass after the account that is syncing, and the result says how many accounts were not started. An account that could not be synced (it has no token, or its repositories could not be loaded) is marked in the pane and counted as needing attention, so a pass in which it was left out does not read as a success. An account's token is changed with **Edit account…** on the account's page (**Edit…** in the pane's menu); the token box on an account's page takes no input. Sync all syncs the repositories each account's page lists: **Refresh** on that page picks up repositories created since it was loaded. +Quick Sync is ad-hoc — nothing is saved. For organizations you sync regularly, **Add account** stores a reusable profile: a name, where the repositories are, the organization, target folder, and parallelism, with the token kept in the Windows Credential Manager (never in a file). The first step asks where the repositories are: **GitHub** or **Gitea**, and for Gitea the address of the server (HTTPS only; a Forgejo server works the same way). The token of the next step is checked with that place and sent nowhere else, and an account stays on the place it was added for: to move one, delete it and add it again. Quick Sync connects to GitHub. Each account gets its own entry in the navigation pane and its own workspace; **Sync all** runs every saved account in turn. A status bar above the workspace is there for the whole pass, whichever workspace is on screen: it says which account is syncing, which of how many, and how far along it is, takes you to that account, and holds the result when the pass is over. The pane shows each account's live status as well. **Cancel sync all** stops the pass after the account that is syncing, and the result says how many accounts were not started. An account that could not be synced (it has no token, or its repositories could not be loaded) is marked in the pane and counted as needing attention, so a pass in which it was left out does not read as a success. An account's token is changed with **Edit account…** on the account's page (**Edit…** in the pane's menu); the token box on an account's page takes no input. Sync all syncs the repositories each account's page lists: **Refresh** on that page picks up repositories created since it was loaded.

Add account wizard, step 1 of 4: account name, optional description, and where the repositories are, with a Gitea server chosen and its address entered diff --git a/docs/images/gclo-wizard.png b/docs/images/gclo-wizard.png index 1f130fdbb094fa91bdccd8007da25ae82a799cea..ca352ce5f087495e07e5365dffb6c5c2d860aed8 100644 GIT binary patch literal 49104 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

bdqJAL68w?SSoo|_|??j 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 sj&2kG49NsHCb%PW_97I0Zd;G%x1B}5txBvhE diff --git a/gclo/AccountWizardDialog.xaml b/gclo/AccountWizardDialog.xaml index eb75446..095b9b3 100644 --- a/gclo/AccountWizardDialog.xaml +++ b/gclo/AccountWizardDialog.xaml @@ -61,7 +61,7 @@ SelectedIndex="{x:Bind ViewModel.PlaceIndex, Mode=TwoWay}" Visibility="{x:Bind local:StatusFormat.VisibleIf(ViewModel.CanChoosePlace)}"> - +