What's wrong
SyncGitHubRepoInfoForOwner (ProjectDirector/ProjectDirector.cs:1012-1024) builds a repo's dictionary key and its OwnerName from two sources, and neither is case-normalised:
- The
Options.Repos key comes from the API's canonical remoteRepo.FullName via GetFullyQualifiedRepoName(remoteRepo), e.g. ktsu-dev.Foo.
gitHubRepo.OwnerName = owner is the string the user typed in "Add New GitHub Owner" (:910), or one parsed from a clone's remote URL (:1188-1193), e.g. KTSU-DEV.
GitHub logins are case-insensitive, so the API calls succeed with KTSU-DEV. SemanticString equality is ordinal, though. Everything that later rebuilds a name from OwnerName + RepoName produces KTSU-DEV.Foo, which is not a key in Options.Repos:
| Site |
Effect |
UpdateClonedStatus (:1066) stores ClonedRepos[path] = "KTSU-DEV.Foo" |
FetchAllReposIfStale, Fetch All and Pull All call Options.Repos.TryGetValue(...), which fails. The repo is never fetched or pulled, and nothing is logged. |
ShowTopPanel (:704) ClonedRepos.ContainsValue(Options.BaseRepo) |
Finds nothing. The panel offers Clone for an already-cloned repo, and clicking it fails with "destination path already exists". Pull, Commit and Push are hidden. |
PairSiblings keys SimilarRepoDiffs by KTSU-DEV.Bar |
Clicking a similar repo calls SwitchPage(BaseRepo, "KTSU-DEV.Bar") (:1513), which reaches Options.Repos[compareRepo] in SwitchCompareBrowserPath (:2231) and throws KeyNotFoundException on the render thread. The Propagate popup throws the same way from FilePropagation.ResolveDestinations (repos[kvp.Key], FilePropagation.cs:96). |
No typing mistake is needed to hit this. If a clone's remote is https://github.com/KTSU-DEV/foo, Scan Dev Dir registers owner KTSU-DEV, and the next GitHub scan keys that owner's repos by the API's lowercase names.
Token lookup has the same problem: TokenStorage derives the persona from the owner string as given, so a token saved for ktsu-dev is not found for KTSU-DEV.
Evidence
A probe test built two repos the way SyncGitHubRepoInfoForOwner does (key from "ktsu-dev/Foo".Replace('/', '.'), OwnerName = "KTSU-DEV") and then called ProjectDirector.PairSiblings:
sibling key 'KTSU-DEV.Bar' in Options.Repos: False
Repos keys: ktsu-dev.Foo, ktsu-dev.Bar
Suggested fix
- In
SyncGitHubRepoInfoForOwner, set OwnerName from remoteRepo.Owner.Login rather than from the typed owner, so the key and the name come from one source.
- Store owners in canonical case: when an owner is added, replace the typed name with
User.Get(...).Login, and do the same in the dev-dir scan.
- Alternatively, compare names case-insensitively throughout. Changing the persona seed in
TokenStorage needs a migration.
- As a safety net, have
SwitchCompareBrowserPath and ResolveDestinations use TryGetValue instead of the indexer.
Acceptance criteria
- After adding
KTSU-DEV and scanning, that owner's cloned repos show Pull/Commit/Push and are auto-fetched.
- Clicking any similar repo opens the comparison without throwing.
What's wrong
SyncGitHubRepoInfoForOwner(ProjectDirector/ProjectDirector.cs:1012-1024) builds a repo's dictionary key and itsOwnerNamefrom two sources, and neither is case-normalised:Options.Reposkey comes from the API's canonicalremoteRepo.FullNameviaGetFullyQualifiedRepoName(remoteRepo), e.g.ktsu-dev.Foo.gitHubRepo.OwnerName = owneris the string the user typed in "Add New GitHub Owner" (:910), or one parsed from a clone's remote URL (:1188-1193), e.g.KTSU-DEV.GitHub logins are case-insensitive, so the API calls succeed with
KTSU-DEV.SemanticStringequality is ordinal, though. Everything that later rebuilds a name fromOwnerName+RepoNameproducesKTSU-DEV.Foo, which is not a key inOptions.Repos:UpdateClonedStatus(:1066) storesClonedRepos[path] = "KTSU-DEV.Foo"FetchAllReposIfStale, Fetch All and Pull All callOptions.Repos.TryGetValue(...), which fails. The repo is never fetched or pulled, and nothing is logged.ShowTopPanel(:704)ClonedRepos.ContainsValue(Options.BaseRepo)PairSiblingskeysSimilarRepoDiffsbyKTSU-DEV.BarSwitchPage(BaseRepo, "KTSU-DEV.Bar")(:1513), which reachesOptions.Repos[compareRepo]inSwitchCompareBrowserPath(:2231) and throwsKeyNotFoundExceptionon the render thread. The Propagate popup throws the same way fromFilePropagation.ResolveDestinations(repos[kvp.Key],FilePropagation.cs:96).No typing mistake is needed to hit this. If a clone's remote is
https://github.com/KTSU-DEV/foo, Scan Dev Dir registers ownerKTSU-DEV, and the next GitHub scan keys that owner's repos by the API's lowercase names.Token lookup has the same problem:
TokenStoragederives the persona from the owner string as given, so a token saved forktsu-devis not found forKTSU-DEV.Evidence
A probe test built two repos the way
SyncGitHubRepoInfoForOwnerdoes (key from"ktsu-dev/Foo".Replace('/', '.'),OwnerName = "KTSU-DEV") and then calledProjectDirector.PairSiblings:Suggested fix
SyncGitHubRepoInfoForOwner, setOwnerNamefromremoteRepo.Owner.Loginrather than from the typedowner, so the key and the name come from one source.User.Get(...).Login, and do the same in the dev-dir scan.TokenStorageneeds a migration.SwitchCompareBrowserPathandResolveDestinationsuseTryGetValueinstead of the indexer.Acceptance criteria
KTSU-DEVand scanning, that owner's cloned repos show Pull/Commit/Push and are auto-fetched.