Repository navigation
Closed
Labels
ai: doneAI-reviewed and acceptedAI-reviewed and acceptedfeatureNew feature or capabilityNew feature or capabilitystack: goRelated to Go codeRelated to Go code
Description
Goal
A repository gets the right git identity automatically from its remote, and a single repository
can be pinned to another profile deliberately.
Context
- Trigger: second milestone, depends on the parser and layout from the navigation core issue.
- Affected:
internal/profile,internal/gitconfig,internal/git,cmd/gitcd.
Verified git behaviour this design rests on
Probed on git 2.54.0 (Apple Git-157) with throwaway repositories, GIT_CONFIG_GLOBAL and
GIT_CONFIG_NOSYSTEM=1:
includeIfinside an included file resolves, so gitcd can own one generated file.includeIf "hasconfig:remote.*.url:<glob>"works from inside that generated file and matched a
repository lying outside any layout root. Rules do not depend on the path.- The
hasconfigglob matches the URL as written.git@github.com:acme/**did not match
https://github.com/acme/thing.git. One block per URL form is needed. - A later
includeIfblock wins over an earlier one. - A
user.emailin the global file placed after the include line beats everyincludeIfvalue. ~/.gitconfigon the target machine already carriesuser.name,user.email,
user.signingkey,gpg.formatandcommit.gpgsignbefore an existing[include], and there
is no~/.config/git/config.initmerges into an existing file, it never creates a fresh one.gitdir/i,onbranch,hasconfig:remote.*.urlanduser.useConfigOnlyexist in the man page
of the installed git.
Config surface
profiles:
private:
user.name: Simon Ostendorf
user.email: simon@example.com
user.signingkey: ssh-ed25519 AAAA...
gpg.format: ssh
commit.gpgsign: "true"
ado:
user.name: Simon Ostendorf
user.email: simon@work.example
rules:
- host: dev.azure.com
profile: ado
- host: github.com
owner: ostendorf-cloud
profile: private
defaultProfile: privateBooleans are quoted, the values are git config strings. The loader rejects a remote.* key in a
profile, because a file included through hasconfig may not carry remote URLs, and validates
every key against git's section.key syntax.
Two mechanisms, one job each
- Path and remote based, the default.
gitcd applyrenders one file per profile under
~/.config/gitcd/profiles/<name>.gitconfigand one~/.config/gitcd/includes.gitconfig. Per
rule it emits agitdir/i:block for the rendered layout glob and ahasconfigblock per URL
form. Thegitdirblock catches a repository whose remote was renamed or removed, the
hasconfigblocks catch one placed outside the layout. Blocks are emitted in specificity
order, default before host before owner before repository, because the later block wins. YAML
rule order is not emit order. - Per repository, the pinned override, and nothing else.
gitcd profile set <name>writes the
keys plus a markergitcd.profile = <name>and the written key list undergitcd.keysinto
one repository's local config. Local config beats an include by git's own precedence. The
marker is what lets a laterdoctortell a deliberate pin from stale config.
~/.gitconfig gets one include.path line, added through git config --global --add so git
chooses the file, after a backup. init is the only command that touches it, and it refuses when
a global identity key sits after the include, because that key would win silently.
Seams to define here, consumed by every later milestone
Repo: path, gitdir, remote, worktree flag, expected profile, matched rule, effective identity
with its origin.- The resolver returns the matched rule, not only the profile name.
profile show, the clone
output anddoctorall need the why. internal/gitconfigrenders content and never writes. Writes go throughinternal/git, which
shells out togit config --global --addandgit -C <path> config --local. Never write
.git/configby path, inside a linked worktree that path is wrong.
Acceptance criteria
-
internal/profileresolves a remote to a profile and returns the matched rule. -
internal/gitconfigrenders the profile files and the include file, golden file tested,
and writes nothing itself. - Blocks are emitted in specificity order, with a test proving a general rule listed last in
YAML does not override a specific one. - A
hasconfigblock is emitted per URL form, with a test per form. - The rendered base profile sets
user.useConfigOnly = true, with an opt out in config. -
gitcd initadds exactly oneinclude.pathline throughgit config --global --add, after
a backup, and refuses when a global identity key sits after the include. -
gitcd applywrites the managed files, and--dry-runprints them instead. -
gitcd profile set <name>writes the keys plusgitcd.profileandgitcd.keyslocally,
throughgit -C <path> config --local, never by writing.git/configby path. -
gitcd profile unsetremoves exactly the keys recorded ingitcd.keysand reports
leftovers. -
gitcd profile show [path]prints the expected profile, the matched rule and the effective
user.emailanduser.signingkeywith their origin. -
gitcd clone --profile <name>pins at clone time throughgit clone -c. - After every clone, the effective identity is read back and printed.
- The
Repovalue and the resolver seam exist and are used bycloneandprofile show. - Tests cover all of the above with a controlled
HOMEandGIT_CONFIG_NOSYSTEM=1. -
task checkpasses.
Out of scope
list,cd,doctor,adopt,status,sync.- Managing an allowed signers file for SSH signature verification.
Open question to resolve while implementing
- Whether
url.<base>.insteadOfrewriting happens beforehasconfigmatching. The man page reads
as raw URLs, unverified. Probe it, it changes which URL forms must be emitted.
Metadata
Metadata
Assignees
Labels
ai: doneAI-reviewed and acceptedAI-reviewed and acceptedfeatureNew feature or capabilityNew feature or capabilitystack: goRelated to Go codeRelated to Go code
Activity
fix(gitconfig): emit a hasconfig block for every stored URL form
fix(cli): record the markers a pinned clone writes