Skip to content

Fix NullReferenceException when cloning into a non-empty directory#2065

Draft
tyrielv wants to merge 3 commits into
microsoft:masterfrom
tyrielv:tyrielv/fix-clone-nonempty-nre
Draft

Fix NullReferenceException when cloning into a non-empty directory#2065
tyrielv wants to merge 3 commits into
microsoft:masterfrom
tyrielv:tyrielv/fix-clone-nonempty-nre

Conversation

@tyrielv

@tyrielv tyrielv commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

gvfs clone <url> <target> into a non-empty <target> directory now throws an unhandled NullReferenceException instead of reporting the expected error:

Cannot clone @ <target>: System.NullReferenceException: Object reference not set to an instance of an object.
   at GVFS.CommandLine.CloneVerb.Execute() + 0x9fc

v1.0 behavior (expected):

Cannot clone @ <target>
Error: Clone directory '<target>' exists and is not empty

Root cause

CloneVerb.Execute() unconditionally constructs a LibGit2RepoInvoker from enlistment.WorkingDirectoryBackingRoot to determine trustPackIndexes, even when TryCreateEnlistment failed (e.g. because the target directory already exists and is not empty). In that failure path enlistment is null, so the dereference throws.

Fix

Extracted the lookup into GetTrustPackIndexes(), which only touches enlistment when cloneResult.Success is true; otherwise it returns the default.

Testing

Added GVFS.UnitTests.CommandLine.CloneVerbTests:

  • TryCreateEnlistmentFailsWithoutEnlistmentWhenTargetDirectoryIsNotEmpty — confirms the failure precondition (null enlistment on non-empty target dir).
  • GetTrustPackIndexesDoesNotThrowWhenCloneFailedAndEnlistmentIsNull — reproduces the exact NullReferenceException when the fix is reverted, and passes with it in place.

Full unit test suite: 884 passed, 0 failed (11 pre-existing skips).

@tyrielv tyrielv marked this pull request as ready for review July 15, 2026 18:37
@tyrielv tyrielv enabled auto-merge July 15, 2026 18:37
Add a shared helper for transient config-only LibGit2Repo usage and switch short-lived call sites away from LibGit2RepoInvoker/object-store preload or duplicated try/catch blocks.

Assisted-by: GPT-5
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
tyrielv added 2 commits July 15, 2026 12:07
Replace the standalone LibGit2RepoExtensions static class with instance
and static GetConfigBoolOrDefault methods directly on LibGit2Repo. This
matches the existing repo.GetConfigBoolOrDefault(name, default)
convention already documented in AGENTS.md and used by
LibGit2RepoInvoker, and avoids an unnecessary extension-method
indirection for a class we own in the same assembly.

Also fixes a regression reintroduced by the earlier refactor: CloneVerb
called LibGit2Repo.GetConfigBoolOrDefault using enlistment.WorkingDirectoryBackingRoot
unconditionally, even when TryCreateEnlistment failed and enlistment was
null. The call is now correctly gated on cloneResult.Success.

Added a protected LibGit2Repo(ITracer tracer) constructor so test
doubles can inject a mock tracer and assert on RelatedWarning calls
from the new instance method, and rewrote the test file accordingly.

Assisted-by: Claude Sonnet 5
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
CloneVerb.Execute() unconditionally created a LibGit2RepoInvoker using
enlistment.WorkingDirectoryBackingRoot to determine trustPackIndexes,
even when TryCreateEnlistment had failed (e.g. because the target
directory already exists and is not empty). In that failure case
enlistment is null, so the failed clone produced:

  Cannot clone @ <target>: System.NullReferenceException: Object
  reference not set to an instance of an object.
     at GVFS.CommandLine.CloneVerb.Execute() + 0x9fc

instead of the expected v1.0 behavior:

  Cannot clone @ <target>
  Error: Clone directory '<target>' exists and is not empty

Extract the trustPackIndexes lookup into GetTrustPackIndexes(), which
only dereferences enlistment when cloneResult.Success is true. Also
widen TryCreateEnlistment/Result from private to internal so they are
directly unit-testable (GVFS assembly already grants InternalsVisibleTo
to GVFS.UnitTests).

Add GVFS.UnitTests.CommandLine.CloneVerbTests covering:
- TryCreateEnlistment fails with a null enlistment when the target
  directory is not empty (the failure precondition).
- GetTrustPackIndexes does not throw and returns the default when the
  clone failed and enlistment is null (the regression itself; this
  test reproduces the NullReferenceException when reverted).

Assisted-by: Claude Sonnet 5
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
@tyrielv tyrielv force-pushed the tyrielv/fix-clone-nonempty-nre branch from 37377d1 to f4a4a25 Compare July 15, 2026 19:11
@tyrielv tyrielv marked this pull request as draft July 15, 2026 19:55
auto-merge was automatically disabled July 15, 2026 19:55

Pull request was converted to draft

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