Skip to content

fix: Skip redundant git config writes in SignIn when already correct#2298

Open
derrickstolee wants to merge 2 commits intogit-ecosystem:mainfrom
derrickstolee:fix/signin-redundant-config-writes
Open

fix: Skip redundant git config writes in SignIn when already correct#2298
derrickstolee wants to merge 2 commits intogit-ecosystem:mainfrom
derrickstolee:fix/signin-redundant-config-writes

Conversation

@derrickstolee
Copy link
Contributor

@derrickstolee derrickstolee commented Mar 20, 2026

Summary

  • Fixes a bug where git fetch on Azure Repos would unconditionally write git config --global credential.azrepos:org/<org>.username and git config --local --unset credential.azrepos:org/<org>.username on every invocation, even when the binding state was already correct.
  • SignIn now checks whether the desired state is in place and skips config writes when nothing needs to change.
  • Adds SetCallCount/UnsetCallCount counters to TestGitConfiguration so tests can assert whether config operations actually fired.

Root cause

On every git fetch, git calls credential approve after successful authentication, which invokes StoreCredentialAsyncSignIn. The else branch of SignIn (global absent or matches signing-in user) called Bind(global) + Unbind(local) unconditionally. In the common single-user steady state (A | - → A | -) this issued two no-op git config writes per fetch.

Behavior changes

State before SignIn Before fix After fix
A | - (steady state) Set(global) + Unset(local) no writes
A | A Set(global) + Unset(local) Unset(local) only
A | B Set(global) + Unset(local) Unset(local) only
B | A (steady state) Bind(local) (no-op) no writes

Test plan

  • 4 new tests assert exact write counts for each no-op/minimal-write case
  • All 165 existing Microsoft.AzureRepos.Tests tests pass

derrickstolee and others added 2 commits March 20, 2026 13:47
Context:
TestGitConfiguration.Set and Unset directly manipulate in-memory
dictionaries. Tests can assert final dictionary state easily, but
have no way to detect whether redundant writes occurred — a write
that sets a key to its already-current value, or unsets a key that
is already absent.

Justification:
Simple integer counters on TestGitConfiguration give tests a
lightweight, zero-noise way to detect whether config operations
fired at all, without introducing a mocking framework or separate
spy class. Counters are reset on construction so they reflect only
the writes within a single test.

Implementation:
Added SetCallCount and UnsetCallCount as auto-incremented public
properties. Each increments at the top of the corresponding method,
before any dictionary logic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Context:
On every `git fetch`, git calls `credential approve` after successful
authentication, which invokes GCM's StoreCredentialAsync. For OAuth
flows against Azure Repos, this calls SignIn(orgName, userName) to
record the user-to-org binding in git config.

In the common single-user steady state — global binding already set
to the authenticated user, no local override — SignIn was still
issuing `git config --global credential.azrepos:org/<org>.username`
and `git config --local --unset credential.azrepos:org/<org>.username`
on every invocation, even though neither write was needed.

Justification:
The else branch of SignIn that handles "global absent or matches"
called Bind(global) + Unbind(local) unconditionally, without first
checking whether the state was already correct. The fix adds minimal
guards: skip Bind(global) if global is already set to the signing-in
user, skip Unbind(local) if local is already absent. This eliminates
the round-trips to git in the steady state without changing the
outcome in any other case.

The same pattern applies to the B|A case (different user holds the
global binding, local is already set to the signing-in user): that
branch also skipped the write check, so it is guarded here too.

Implementation:
Modified SignIn to skip writes when already in the desired state:
  A | -  ->  A | -   no writes (previously Set(global)+Unset(local))
  A | A  ->  A | -   only Unset(local), Set(global) skipped
  A | B  ->  A | -   only Unset(local), Set(global) skipped
  B | A  ->  B | A   no writes (already correct, was also correct)

Added four tests to AzureReposBindingManagerTests using the new
SetCallCount/UnsetCallCount counters to assert the precise number
of config writes for each case.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@derrickstolee derrickstolee requested review from a team as code owners March 20, 2026 17:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant