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:
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.
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).
What's wrong
GitRepository.IsClonedAsync()is documented as deciding "whetherLocalPathcurrently holds a git working tree", but it delegates toGitProbes.IsWorkTreeAsync, which runsgit rev-parse --is-inside-work-tree:GitIntegration/GitRepository.cs:93-111(at 647dbbc)GitIntegration/Execution/GitProbes.cs:37--is-inside-work-treewalks up the directory tree, so it answerstruefor any directory under an ancestor working tree, including an empty one:Failure scenario
A launcher enumerates repositories, maps each to a
GitRepositorywithLocalPath = <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:IsClonedAsync()returnstruefor the empty<root>/SomeRepodirectory, so the clone is skipped. A manualClone(...)into the same path still succeeds, which shows the path was never a repository.Status(),Log(),Commit()and the other verbs on thatGitRepositorythen run against the outer repository, with no error.The reviewer reproduced this against a build of HEAD with git 2.43:
IsClonedAsyncprintedTruefor an empty directory inside another repo, and cloning into that directory then succeeded.The repo has already fixed this for
Init().GitInitBuilder.cs:~133notes that--is-inside-work-tree"wrongly reports AlreadyExisted = true for a plain subdirectory of an existing repository", and #138 / c246cc6 addedGitInitBuilder.IsRepositoryRoot.IsClonedAsyncstill has the upward-walk problem.Suggested fix
IsClonedAsyncanswer "isLocalPaththe root of a working tree". Either:rev-parse --show-cdupto exit 0 with empty output, or--is-bare-repository --git-dir --show-cdupprobe andGitInitBuilder.IsRepositoryRoot.IGitClient.IsRepositoryAsync/OpenAsyncon the "inside" semantics, since their contract really is "inside a working tree".Acceptance criteria
IsClonedAsync()returnsfalsefor an existing directory, empty or not, that is a subdirectory of another repository's working tree.truefor a clone root, including a submodule, a linked worktree, and a--separate-git-dircheckout. These are the cases Init() reports AlreadyExisted = false when re-initializing a submodule, worktree or --separate-git-dir repository, hiding an ignored --initial-branch #138 had to handle.Related, but a different symptom: #178 (verbs misbehave when
LocalPathis a subdirectory).