What happens
GitHubProvider.GetRepositoriesAsync (GitIntegration/GitHubProvider.cs:68) calls client.Repository.GetAllForUser(Owner), which is GET /users/{login}/repos. That endpoint returns public repositories only, and supplying a token does not widen it.
AzureDevOpsProvider.GetRepositoriesAsync has no such restriction — its enumeration returns everything the supplied token can see.
So two implementations of one IGitHostingProvider method differ in coverage in a way a caller cannot detect from the interface. A caller pointed at a GitHub organisation with a valid token silently gets a partial list; the same code against Azure DevOps gets a complete one. The XML doc at GitHubProvider.cs:56-66 states this outright — "A caller that needs private repositories for a specific owner has no equivalent through this provider today" — so it is a known limitation rather than a surprise, but it is a limitation worth closing rather than only documenting.
Why it was left this way
The doc comment records the reasoning, and it is sound as far as it goes: switching to GET /user/repos would stop honouring GitProvider.Owner, because that endpoint always describes the token's own repositories regardless of which owner was configured. That would break callers who name someone else's owner on purpose, which is a worse failure than under-reporting.
But /user/repos is not the only alternative.
Suggested approach
GitHub distinguishes users from organisations, and the organisation case has an endpoint that honours both the owner and the token:
GET /orgs/{org}/repos (Octokit: client.Repository.GetAllForOrg(org)) returns private repositories the authenticated token can see, scoped to the named org. For an org owner this closes the gap completely with no change to Owner semantics.
- For a user owner,
/users/{login}/repos stays the only owner-honouring endpoint, so the public-only limit is real there. Where the configured Owner is the token's own login, GET /user/repos with affiliation=owner is exactly equivalent plus private repositories, and can be used for that case specifically.
A reasonable implementation:
- Resolve whether
Owner names a user or an organisation (GET /users/{login} reports type, or attempt the org call and treat NotFoundException as "not an org").
- Organisation →
GetAllForOrg.
- User matching the authenticated login (from
GET /user) → GetAllForCurrentUser with affiliation=owner.
- Any other user → the current
GetAllForUser, still public-only, which is genuinely all GitHub offers.
Whatever shape is chosen, the resulting coverage should be stated on IGitHostingProvider.GetRepositoriesAsync in terms the interface owns, rather than each provider describing its own behaviour and leaving callers to compare them.
Testing
The hosting layer has no integration tier by design, so this is covered the way the rest of the layer is: FakeHttpMessageHandler through GitProvider's internal Handler seam, with fixtures for the org, own-user and other-user branches, and a fixture for the NotFoundException fallback if the probe approach is used.
Rationale
Enumerating repositories is the entry point to almost everything else this layer does — a caller lists repositories, then opens or clones one, then reads its pull requests. Silently omitting an organisation's private repositories means the most common real-world case (a private org repo, with a token that can plainly see it) is invisible, and the caller has no signal that anything was left out.
What happens
GitHubProvider.GetRepositoriesAsync(GitIntegration/GitHubProvider.cs:68) callsclient.Repository.GetAllForUser(Owner), which isGET /users/{login}/repos. That endpoint returns public repositories only, and supplying a token does not widen it.AzureDevOpsProvider.GetRepositoriesAsynchas no such restriction — its enumeration returns everything the supplied token can see.So two implementations of one
IGitHostingProvidermethod differ in coverage in a way a caller cannot detect from the interface. A caller pointed at a GitHub organisation with a valid token silently gets a partial list; the same code against Azure DevOps gets a complete one. The XML doc atGitHubProvider.cs:56-66states this outright — "A caller that needs private repositories for a specific owner has no equivalent through this provider today" — so it is a known limitation rather than a surprise, but it is a limitation worth closing rather than only documenting.Why it was left this way
The doc comment records the reasoning, and it is sound as far as it goes: switching to
GET /user/reposwould stop honouringGitProvider.Owner, because that endpoint always describes the token's own repositories regardless of which owner was configured. That would break callers who name someone else's owner on purpose, which is a worse failure than under-reporting.But
/user/reposis not the only alternative.Suggested approach
GitHub distinguishes users from organisations, and the organisation case has an endpoint that honours both the owner and the token:
GET /orgs/{org}/repos(Octokit:client.Repository.GetAllForOrg(org)) returns private repositories the authenticated token can see, scoped to the named org. For an org owner this closes the gap completely with no change toOwnersemantics./users/{login}/reposstays the only owner-honouring endpoint, so the public-only limit is real there. Where the configuredOwneris the token's own login,GET /user/reposwithaffiliation=owneris exactly equivalent plus private repositories, and can be used for that case specifically.A reasonable implementation:
Ownernames a user or an organisation (GET /users/{login}reportstype, or attempt the org call and treatNotFoundExceptionas "not an org").GetAllForOrg.GET /user) →GetAllForCurrentUserwithaffiliation=owner.GetAllForUser, still public-only, which is genuinely all GitHub offers.Whatever shape is chosen, the resulting coverage should be stated on
IGitHostingProvider.GetRepositoriesAsyncin terms the interface owns, rather than each provider describing its own behaviour and leaving callers to compare them.Testing
The hosting layer has no integration tier by design, so this is covered the way the rest of the layer is:
FakeHttpMessageHandlerthroughGitProvider's internalHandlerseam, with fixtures for the org, own-user and other-user branches, and a fixture for theNotFoundExceptionfallback if the probe approach is used.Rationale
Enumerating repositories is the entry point to almost everything else this layer does — a caller lists repositories, then opens or clones one, then reads its pull requests. Silently omitting an organisation's private repositories means the most common real-world case (a private org repo, with a token that can plainly see it) is invisible, and the caller has no signal that anything was left out.