Skip to content

IsClonedAsync() returns true for an empty directory inside another repository's working tree, so "clone if not cloned" skips the clone and later verbs run against the outer repo #185

Description

@matt-edmondson

What's wrong

GitRepository.IsClonedAsync() is documented as deciding "whether LocalPath currently holds a git working tree", but it delegates to GitProbes.IsWorkTreeAsync, which runs git rev-parse --is-inside-work-tree:

  • GitIntegration/GitRepository.cs:93-111 (at 647dbbc)
  • GitIntegration/Execution/GitProbes.cs:37
GitResult<string> result = await new GitTextBuilder(runner, path, "rev-parse", "--is-inside-work-tree")
	.TryExecuteAsync(cancellationToken).ConfigureAwait(false);
return result.Success && string.Equals(result.Value, "true", StringComparison.Ordinal);

--is-inside-work-tree walks up the directory tree, so it answers true for any directory under an ancestor working tree, including an empty one:

$ git init outer && mkdir outer/sub
$ git -C outer/sub rev-parse --is-inside-work-tree
true

Failure scenario

A launcher enumerates repositories, maps each to a GitRepository with LocalPath = <root>/<RepoName>, and follows the "if not cloned, clone" pattern. If <root> is itself inside a git working tree (for example a dev folder that is a repo, or a monorepo checkout), then:

  1. IsClonedAsync() returns true for the empty <root>/SomeRepo directory, so the clone is skipped. A manual Clone(...) into the same path still succeeds, which shows the path was never a repository.
  2. Status(), Log(), Commit() and the other verbs on that GitRepository then run against the outer repository, with no error.

The reviewer reproduced this against a build of HEAD with git 2.43: IsClonedAsync printed True for an empty directory inside another repo, and cloning into that directory then succeeded.

The repo has already fixed this for Init(). GitInitBuilder.cs:~133 notes that --is-inside-work-tree "wrongly reports AlreadyExisted = true for a plain subdirectory of an existing repository", and #138 / c246cc6 added GitInitBuilder.IsRepositoryRoot. IsClonedAsync still has the upward-walk problem.

Suggested fix

  • Make IsClonedAsync answer "is LocalPath the root of a working tree". Either:
    • require rev-parse --show-cdup to exit 0 with empty output, or
    • reuse the --is-bare-repository --git-dir --show-cdup probe and GitInitBuilder.IsRepositoryRoot.
  • Leave IGitClient.IsRepositoryAsync / OpenAsync on the "inside" semantics, since their contract really is "inside a working tree".

Acceptance criteria

Related, but a different symptom: #178 (verbs misbehave when LocalPath is a subdirectory).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions