Repository navigation
Add repository and worktree identities - #14921
Draft
williammartin wants to merge 2 commits into
Draft
williammartin wants to merge 2 commits into
williammartin wants to merge 2 commits into
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 965836ef-19ac-4473-ba1f-e326ce0f106b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 965836ef-19ac-4473-ba1f-e326ce0f106b
2 of 6 tasks
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical issues can change authentication in unbound repositories, use mismatched accounts for Git and API requests, and silently lose bindings during concurrent updates.
9 open issues
Use the repository identity for agent-task authentication · New Resolve SSH aliases for persistent identity bindings · New Reject conflicting effective Git usernames during setup · New Lock binding updates across processes · New Limit helper reset and installation to bound repositories · New Retain usable bound tokens after transient refresh errors · New Allow unsetting bindings when checkout discovery fails · New Install credential helper settings for the gist host · New Skip the unsupported Windows binding fixture · New
What changed in this PR
This draft prototype extends one-command account selection with reusable identities shared across a repository’s main checkout and linked worktrees. It connects those choices to API authentication and independently invoked Git; it is explicitly not intended to merge as-is.
Changes:
- Adds identity management, repository selection, and experiment cleanup commands.
- Introduces persistent bindings, conditional Git includes, HTTPS credential selection, and declared SSH routes.
- Adds authentication, worktree, cleanup, and compatibility tests plus an isolated offline replay script.
| File | Description |
|---|---|
| script/multiaccount-review/02-repository.sh | Adds an isolated repository-family replay. |
| pkg/cmd/root/reset_experiment_test.go | Tests experiment reset and confirmation. |
| pkg/cmd/root/account.go | Updates account-selection error guidance. |
| pkg/cmd/root/account_git_test.go | Extends override tests to bound identities and SSH. |
| pkg/cmd/auth/use/worktrees_test.go | Tests shared worktree selection and conflicts. |
| pkg/cmd/auth/use/use.go | Adds repository binding and inspection commands. |
| pkg/cmd/auth/use/use_test.go | Tests selection validation and setup rollback. |
| pkg/cmd/auth/identity/ssh.go | Executes the selected SSH route. |
| pkg/cmd/auth/identity/identity.go | Adds identity management and dispatcher commands. |
| pkg/cmd/auth/identity/identity_test.go | Adds isolated command-test helpers. |
| pkg/cmd/auth/identity/delete.go | Adds confirmed deletion and experiment reset. |
| pkg/cmd/auth/identity/delete_test.go | Tests cleanup, cancellation, and rollback. |
| pkg/cmd/auth/gitcredential/identity_test.go | Tests bound-account HTTPS credentials. |
| pkg/cmd/auth/gitcredential/gist_production_test.go | Tests gist selection with production configuration. |
| pkg/cmd/auth/gitcredential/gist_identity_test.go | Tests canonical-host identity selection for gists. |
| pkg/cmd/auth/auth.go | Registers identity, selection, and reset commands. |
| internal/identity/worktree_transport_test.go | Tests API authentication from linked worktrees. |
| internal/identity/store.go | Implements identity storage, bindings, and Git integration. |
| internal/identity/store_test.go | Tests binding behavior and live identity edits. |
| internal/identity/ssh.go | Resolves persistent and invocation-specific SSH routes. |
| internal/identity/ssh_test.go | Tests SSH routing and user preservation. |
| internal/identity/ssh_command.go | Validates underlying SSH configuration. |
| internal/identity/hardening_test.go | Tests unsupported layouts and helper conflicts. |
| internal/identity/delete.go | Implements staged deletion and integration cleanup. |
| internal/identity/credentials.go | Resolves bound credentials and environment conflicts. |
| internal/identity/credentials_test.go | Tests identity-aware authentication and token conflicts. |
| internal/identity/account_test.go | Tests account-specific SSH route selection. |
| internal/identity/account_status.go | Reports missing saved-account registrations. |
| internal/gh/gh.go | Adds effective credential resolution to AuthConfig. |
| internal/config/config.go | Applies repository identities to API and Git credentials. |
| git/client.go | Preserves SSH environment settings for override validation. |
| api/identity_test.go | Tests bound identities and canonical gateway credentials. |
| api/identity_gateway_test.go | Tests gateway credential precedence. |
Review effort: Balanced
Comment on lines
+324
to
+325
| if selected != nil { | ||
| return identity.CredentialFor(c, selected) |
| if account != "" { | ||
| id, err = s.accountSSHRoute(host, owner, dir, overrideHost, account) | ||
| } else { | ||
| id, err = s.Resolve(host, owner, dir) |
Comment on lines
+354
to
+356
| if err := s.supportedCredentialHelpers(contextDir, b.Host); err != nil { | ||
| return err | ||
| } |
| } | ||
| } | ||
| } | ||
| bindings, err := s.Bindings() |
Comment on lines
+579
to
+580
| fmt.Fprintf(&config, "[credential %s]\n\thelper =\n\thelper = %s\n\tuseHttpPath = true\n", | ||
| gitQuote("https://"+host), gitQuote(helper)) |
| return gh.Credential{}, errors.New(warning) | ||
| } | ||
| credential, status, err := auth.TokenForUserWithRefresh(id.Host, id.Account) | ||
| if err != nil { |
Comment on lines
+489
to
+493
| normalized, normErr := normalizeBinding(b) | ||
| if normErr != nil { | ||
| return normErr | ||
| } | ||
| if normalized.Repository != "" { |
Comment on lines
+577
to
+580
| for _, host := range sortedKeys(hosts) { | ||
| helper := "!" + shellquote.Join(executable, "auth", "git-credential") | ||
| fmt.Fprintf(&config, "[credential %s]\n\thelper =\n\thelper = %s\n\tuseHttpPath = true\n", | ||
| gitQuote("https://"+host), gitQuote(helper)) |
Comment on lines
+3
to
+15
| import ( | ||
| "os" | ||
| "path/filepath" | ||
| "testing" | ||
|
|
||
| "github.com/cli/cli/v2/internal/config" | ||
| "github.com/cli/cli/v2/internal/identity" | ||
| "github.com/cli/cli/v2/pkg/httpmock" | ||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func TestResetExperimentDetachesIntegrationWithoutDeletingAccounts(t *testing.T) { |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Description
Follow on from #14920
A one-command override still leaves account selection to repeat whenever a developer returns to a repository. This PR adds reusable identities and a repository binding shared by the main checkout and its linked worktrees.
Layer 2 of an eight-part prototype review stack. This is a way to review the design and implementation in smaller pieces, not a proposal to merge the current stack as-is.
How did you test this change?
I created identities using both real accounts, inspected the generated Git files, and exercised repository-family selection, editing and cleanup. I verified the SSH aliases independently, cloned a private repository, and pushed with ordinary Git.
Create an identity and read its Git config file
multiaccount-layer-2-01a-Wiring---Layer-2---Identity-file-and-SSH-alias.mp4
A repository binding adds Git includes
multiaccount-layer-2-01b-Wiring---Layer-2---Repository-binding-files.mp4
Personal SSH identity: verified alias, private clone and ordinary Git push
multiaccount-layer-2-02-Personal-SSH-identity-verified-alias-private-clone-and-ordinary-Git-push.mp4
Delete and reset without signing out
multiaccount-layer-2-03-Delete-and-reset-without-signing-out.mp4
One identity for a repository family
multiaccount-layer-2-04-One-identity-for-a-repository-family.mp4
Reuse, edit, and unset an identity
multiaccount-layer-2-05-Reuse-edit-and-unset-an-identity.mp4
Key points
The identity file contains account metadata and Git preferences, not a credential. The global include loads
auth.gitconfig, whosegitdirconditions select the identity for the common Git directory and linked worktrees.An SSH alias is a declared connection route, not verified account ownership. The SSH recording checks the account separately with
ssh -T. Unset, deletion and experiment reset retain saved accounts and credentials.Notes for reviewers
This is the foundation layer. Directory and owner bindings, Git attribution, existing SSH-command compatibility and richer explanations are deliberately separate.
I am not sure an identity should support changing its account with
gh auth identity edit --account. The account feels like part of the identity definition rather than an editable preference. The current implementation allows it, but that is an unresolved design question.The recordings use the product code at this layer, not a later stack endpoint. The included offline replay scripts are supplementary and do not reproduce the live-account recordings.
Offline replay on macOS
Build this layer, then run its synthetic scenario with an absolute executable path. This is a replay instruction, not a claim that the command below was used to make the videos.
Authorship and follow-up
Who wrote this:
Who answers review comments:
Acceptance Criteria
Prototype behaviour under review:
Given a reusable identity references a saved account.
When I select it with
gh auth useinside a repository.Then the repository family uses that identity for API requests and independently invoked Git.
DO NOT MERGE