diff --git a/src/Microsoft.Build.Tasks.Git.UnitTests/GitRepositoryTests.cs b/src/Microsoft.Build.Tasks.Git.UnitTests/GitRepositoryTests.cs index c0e502af..d86a3e84 100644 --- a/src/Microsoft.Build.Tasks.Git.UnitTests/GitRepositoryTests.cs +++ b/src/Microsoft.Build.Tasks.Git.UnitTests/GitRepositoryTests.cs @@ -95,19 +95,33 @@ public void TryFindRepository_Worktree_Realistic() Assert.Equal(mainGitDir.Path, location.CommonDirectory); Assert.Null(location.WorkingDirectory); + var repository = GitRepository.OpenRepository(location, GitEnvironment.Empty); + Assert.Equal(location.GitDirectory, repository.GitDirectory); + Assert.Equal(location.CommonDirectory, repository.CommonDirectory); + Assert.Null(repository.WorkingDirectory); + // start under worktree directory: Assert.True(GitRepository.TryFindRepository(worktreeSubDir.Path, out location)); - Assert.Equal(worktreeGitDir.Path, location.GitDirectory); Assert.Equal(mainGitDir.Path, location.CommonDirectory); Assert.Equal(worktreeDir.Path, location.WorkingDirectory); + repository = GitRepository.OpenRepository(location, GitEnvironment.Empty); + Assert.Equal(location.GitDirectory, repository.GitDirectory); + Assert.Equal(location.WorkingDirectory, repository.WorkingDirectory); + Assert.Equal(location.CommonDirectory, repository.CommonDirectory); + // start under worktree git directory (git config works from this dir, but git status requires work dir): Assert.True(GitRepository.TryFindRepository(worktreeGitSubDir.Path, out location)); Assert.Equal(worktreeGitDir.Path, location.GitDirectory); Assert.Equal(mainGitDir.Path, location.CommonDirectory); Assert.Null(location.WorkingDirectory); + + repository = GitRepository.OpenRepository(location, GitEnvironment.Empty); + Assert.Equal(location.GitDirectory, repository.GitDirectory); + Assert.Equal(location.CommonDirectory, repository.CommonDirectory); + Assert.Null(repository.WorkingDirectory); } [Fact] @@ -170,6 +184,31 @@ public void OpenRepository() Assert.Equal("0000000000000000000000000000000000000000", repository.GetHeadCommitSha()); } + [Fact] + public void OpenRepository_WorkingDirectorySpecifiedInConfig() + { + using var temp = new TempRoot(); + + var homeDir = temp.CreateDirectory(); + + var workingDir = temp.CreateDirectory(); + var workingDir2 = temp.CreateDirectory(); + var gitDir = workingDir.CreateDirectory(".git"); + + gitDir.CreateFile("HEAD"); + gitDir.CreateFile("config").WriteAllText("[core]worktree = " + workingDir2.Path.Replace(@"\", @"\\")); + + Assert.True(GitRepository.TryFindRepository(gitDir.Path, out var location)); + Assert.Equal(gitDir.Path, location.CommonDirectory); + Assert.Equal(gitDir.Path, location.GitDirectory); + Assert.Null(location.WorkingDirectory); + + var repository = GitRepository.OpenRepository(location, GitEnvironment.Empty); + Assert.Equal(gitDir.Path, repository.CommonDirectory); + Assert.Equal(gitDir.Path, repository.GitDirectory); + Assert.Equal(workingDir2.Path, repository.WorkingDirectory); + } + [Fact] public void OpenRepository_VersionNotSupported() { @@ -191,6 +230,72 @@ public void OpenRepository_VersionNotSupported() Assert.Throws(() => GitRepository.OpenRepository(src.Path, new GitEnvironment(homeDir.Path))); } + [Fact] + public void OpenRepository_Worktree_GitdirFileMissing() + { + using var temp = new TempRoot(); + + var mainWorkingDir = temp.CreateDirectory(); + var mainGitDir = mainWorkingDir.CreateDirectory(".git"); + mainGitDir.CreateFile("HEAD"); + + var worktreesDir = mainGitDir.CreateDirectory("worktrees"); + var worktreeGitDir = worktreesDir.CreateDirectory("myworktree"); + var worktreeDir = temp.CreateDirectory(); + var worktreeGitFile = worktreeDir.CreateFile(".git").WriteAllText("gitdir: " + worktreeGitDir + " \r\n\t\v"); + + worktreeGitDir.CreateFile("HEAD"); + worktreeGitDir.CreateFile("commondir").WriteAllText("../..\n"); + // gitdir file that links back to the worktree working directory is missing from worktreeGitDir + + Assert.True(GitRepository.TryFindRepository(worktreeDir.Path, out var location)); + Assert.Equal(worktreeGitDir.Path, location.GitDirectory); + Assert.Equal(mainGitDir.Path, location.CommonDirectory); + Assert.Equal(worktreeDir.Path, location.WorkingDirectory); + + var repository = GitRepository.OpenRepository(location, GitEnvironment.Empty); + Assert.Equal(repository.GitDirectory, location.GitDirectory); + Assert.Equal(repository.CommonDirectory, location.CommonDirectory); + Assert.Equal(repository.WorkingDirectory, location.WorkingDirectory); + } + + /// + /// The directory in gitdir file is ignored for the purposes of determining repository working directory. + /// + [Fact] + public void OpenRepository_Worktree_GitdirFileDifferentPath() + { + using var temp = new TempRoot(); + + var mainWorkingDir = temp.CreateDirectory(); + var mainGitDir = mainWorkingDir.CreateDirectory(".git"); + mainGitDir.CreateFile("HEAD"); + + var worktreesDir = mainGitDir.CreateDirectory("worktrees"); + var worktreeGitDir = worktreesDir.CreateDirectory("myworktree"); + var worktreeDir = temp.CreateDirectory(); + var worktreeGitFile = worktreeDir.CreateFile(".git").WriteAllText("gitdir: " + worktreeGitDir + " \r\n\t\v"); + + var worktreeDir2 = temp.CreateDirectory(); + var worktreeGitFile2 = worktreeDir2.CreateFile(".git").WriteAllText("gitdir: " + worktreeGitDir + " \r\n\t\v"); + + worktreeGitDir.CreateFile("HEAD"); + worktreeGitDir.CreateFile("commondir").WriteAllText("../..\n"); + worktreeGitDir.CreateFile("gitdir").WriteAllText(worktreeGitFile2.Path + " \r\n\t\v"); + + Assert.True(GitRepository.TryFindRepository(worktreeDir.Path, out var location)); + Assert.Equal(worktreeGitDir.Path, location.GitDirectory); + Assert.Equal(mainGitDir.Path, location.CommonDirectory); + Assert.Equal(worktreeDir.Path, location.WorkingDirectory); + + var repository = GitRepository.OpenRepository(location, GitEnvironment.Empty); + Assert.Equal(repository.GitDirectory, location.GitDirectory); + Assert.Equal(repository.CommonDirectory, location.CommonDirectory); + + // actual working dir is not affected: + Assert.Equal(worktreeDir.Path, location.WorkingDirectory); + } + [Fact] public void Submodules() { diff --git a/src/Microsoft.Build.Tasks.Git/GitDataReader/GitRepository.cs b/src/Microsoft.Build.Tasks.Git/GitDataReader/GitRepository.cs index 30ee72bc..73be8c8f 100644 --- a/src/Microsoft.Build.Tasks.Git/GitDataReader/GitRepository.cs +++ b/src/Microsoft.Build.Tasks.Git/GitDataReader/GitRepository.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; @@ -19,7 +18,6 @@ internal sealed class GitRepository private const string CommonDirFileName = "commondir"; private const string GitDirName = ".git"; private const string GitDirPrefix = "gitdir: "; - private const string GitDirFileName = "gitdir"; // See https://git-scm.com/docs/gitrepository-layout#Documentation/gitrepository-layout.txt-HEAD internal const string GitHeadFileName = "HEAD"; @@ -131,46 +129,10 @@ public static GitRepository OpenRepository(GitRepositoryLocation location, GitEn private static string? GetWorkingDirectory(GitConfig config, GitRepositoryLocation location) { - // Working trees cannot have the same common directory and git directory. - // 'gitdir' file in a git directory indicates a working tree. - - var gitdirFilePath = Path.Combine(location.GitDirectory, GitDirFileName); - - var isLinkedWorkingTree = PathUtils.ToPosixDirectoryPath(location.CommonDirectory) != PathUtils.ToPosixDirectoryPath(location.GitDirectory) && - File.Exists(gitdirFilePath); - - if (isLinkedWorkingTree) - { - // https://git-scm.com/docs/gitrepository-layout#Documentation/gitrepository-layout.txt-worktreesltidgtgitdir - - string workingDirectory; - try - { - workingDirectory = File.ReadAllText(gitdirFilePath); - } - catch (Exception e) when (!(e is IOException)) - { - throw new IOException(e.Message, e); - } - - workingDirectory = workingDirectory.TrimEnd(CharUtils.AsciiWhitespace); - - // Path in gitdir file must be absolute. - if (!PathUtils.IsAbsolute(workingDirectory)) - { - throw new InvalidDataException(string.Format(Resources.PathSpecifiedInFileIsNotAbsolute, gitdirFilePath, workingDirectory)); - } - - try - { - return Path.GetFullPath(workingDirectory); - } - catch - { - throw new InvalidDataException(string.Format(Resources.PathSpecifiedInFileIsInvalid, gitdirFilePath, workingDirectory)); - } - } + // TODO (https://github.com/dotnet/sourcelink/issues/301): + // GIT_WORK_TREE environment variable can also override working directory. + // Working directory can be overridden by a config option. // See https://git-scm.com/docs/git-config#Documentation/git-config.txt-coreworktree string? value = config.GetVariableValue("core", "worktree"); if (value != null) diff --git a/src/Microsoft.Build.Tasks.Git/GitOperations.cs b/src/Microsoft.Build.Tasks.Git/GitOperations.cs index ed9d36a7..4cf550f4 100644 --- a/src/Microsoft.Build.Tasks.Git/GitOperations.cs +++ b/src/Microsoft.Build.Tasks.Git/GitOperations.cs @@ -357,7 +357,7 @@ internal sealed class DirectoryNode public readonly string Name; public readonly List OrderedChildren; - // set on nodes that represent submodule working directory: + // set on nodes that represent working directory of the repository or a submodule: public Lazy? Matcher; public DirectoryNode(string name, List orderedChildren)