Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ jobs:

- name: Install .NET SDK
if: steps.skip.outputs.result != 'true'
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
uses: actions/setup-dotnet@0f4a354dd5547a28c70b7853dbd95d5b67886e73 # v6.1.0
with:
global-json-file: src/global.json

Expand Down
23 changes: 14 additions & 9 deletions GVFS/GVFS.UnitTests/Common/Git/AuthenticationConfigurationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class AuthenticationConfigurationTests
[SetUp]
public void Setup()
{
this.gitPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Git", "cmd", "git.exe");
this.gitPath = GitTestHelper.FindGitOnPath();
this.root = Path.Combine(Path.GetTempPath(), nameof(AuthenticationConfigurationTests) + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(Path.Combine(this.root, ".git", "objects"));
Directory.CreateDirectory(Path.Combine(this.root, ".git", "refs", "heads"));
Expand Down Expand Up @@ -521,7 +521,7 @@ public void ProductionFillCarriesItsOwnEnvironmentIntoTheCollector()
{
File.AppendAllText(Path.Combine(this.root, ".git", "config"),
"[credential]\nhelper =\ninteractive = never\n");
PlumbingGitProcess git = new PlumbingGitProcess(this.gitPath, this.root);
PlumbingGitProcess git = new PlumbingGitProcess(this.GetGitPath(), this.root);
MockTracer tracer = new MockTracer();
Assert.That(git.TryGetCredential(tracer, "https://dev.azure.com/org/repo", out _, out _, out _, timeoutMs: 5000), Is.False);
Assert.That(git.CollectedEnvironment, Is.SameAs(git.ResultEnvironment));
Expand Down Expand Up @@ -551,7 +551,7 @@ public void FailedCredentialFillBeforeCloneCreatesTheWorkingDirectoryCollectsCon
{ "GIT_TERMINAL_PROMPT", "0" },
{ "GCM_VALIDATE", "0" },
};
PreCloneGitProcess git = new PreCloneGitProcess(this.gitPath, workingDirectory, environment);
PreCloneGitProcess git = new PreCloneGitProcess(this.GetGitPath(), workingDirectory, environment);
MockTracer tracer = new MockTracer();

Assert.That(git.TryGetCredential(tracer, "https://dev.azure.com/org/repo", out _, out _, out _), Is.False);
Expand All @@ -571,18 +571,23 @@ public void FailedCredentialFillBeforeCloneCreatesTheWorkingDirectoryCollectsCon
this.AssertNoSecret(snapshot);
}

private string GetGitPath()
{
if (!File.Exists(this.gitPath))
{
Assert.Fail("The authentication configuration integration tests require Git on PATH.");
}

return this.gitPath;
}

private EventMetadata Collect(
string url,
Action<ProcessStartInfo> customize = null,
Dictionary<string, string> credentialFillEnvironment = null,
ITracer tracer = null)
{
if (!File.Exists(this.gitPath))
{
Assert.Fail("The authentication configuration integration tests require Git for Windows.");
}

GitProcess git = new GitProcess(this.gitPath, this.root);
GitProcess git = new GitProcess(this.GetGitPath(), this.root);
return AuthenticationConfiguration.Collect(url, command =>
{
Process process = git.GetGitProcess(command, this.root, Path.Combine(this.root, ".git"), false, null, false);
Expand Down
6 changes: 3 additions & 3 deletions GVFS/GVFS.UnitTests/Common/Git/GitProcessCredentialTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,10 @@ public void CredentialFillDoesNotRetryWhenTheTimeoutCannotCoverTheDelay()
[TestCase(false, Timeout.Infinite)]
public void CredentialFillHandlesARealLockedGitConfig(bool releaseAfterFirstAttempt, int timeoutMs)
{
string gitPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Git", "cmd", "git.exe");
if (!File.Exists(gitPath))
string gitPath = GitTestHelper.FindGitOnPath();
if (gitPath == null)
{
Assert.Ignore("Git is not installed at the expected path.");
Assert.Ignore("Git was not found on PATH.");
}

string repoPath = Path.Combine(Path.GetTempPath(), nameof(GitProcessCredentialTests) + Guid.NewGuid().ToString("N"));
Expand Down
6 changes: 3 additions & 3 deletions GVFS/GVFS.UnitTests/Common/RetryConfigTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -595,10 +595,10 @@ public void LibGit2ReadsConfigWithIncludeConditionsItEvaluates(string condition)

private static string GetGitPathOrIgnore()
{
string gitPath = "C:\\Program Files\\Git\\cmd\\git.exe";
if (!File.Exists(gitPath))
string gitPath = GitTestHelper.FindGitOnPath();
if (gitPath == null)
{
Assert.Ignore("Git not found at expected path — skipping integration test");
Assert.Ignore("Git was not found on PATH.");
}

return gitPath;
Expand Down
28 changes: 14 additions & 14 deletions GVFS/GVFS.UnitTests/Common/WorktreeEnlistmentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,8 @@ public void TearDown()
}
}

private GVFSEnlistment CreateWorktreeEnlistment()
private GVFSEnlistment CreateWorktreeEnlistment(string gitBinPath = @"mock:\git")
{
string gitBinPath = GVFSPlatform.Instance.GitInstallation.GetInstalledGitBinPath()
?? @"C:\Program Files\Git\cmd\git.exe";
return GVFSEnlistment.CreateForWorktree(
this.primaryRoot, gitBinPath, authentication: null,
GVFSEnlistment.TryGetWorktreeInfo(this.worktreePath),
Expand Down Expand Up @@ -206,7 +204,7 @@ public void RepoUrlIsReadFromSharedConfig()
public void WorktreeEnlistmentIdIsEmptyWhenNothingIsPersisted()
{
this.AppendToSharedConfig("[gvfs]\n\tenlistment-id = primaryenlistmentid\n");
GVFSEnlistment enlistment = this.CreateWorktreeEnlistment();
GVFSEnlistment enlistment = this.CreateWorktreeEnlistment(GetGitBinPath());

enlistment.GetEnlistmentId().ShouldEqual(string.Empty);
File.Exists(GetRepoMetadataPath(enlistment.DotGVFSRoot)).ShouldBeFalse("Reading the id must not create worktree repo metadata");
Expand All @@ -218,10 +216,10 @@ public void WorktreeEnlistmentIdIsDerivedFromPrimaryRepoMetadata()
{
this.AppendToSharedConfig("[gvfs]\n\tenlistment-id = sharedconfigenlistmentid\n");
EditRepoMetadata(this.PrimaryDotGVFSRoot, metadata => metadata.SetEntry(RepoMetadata.Keys.EnlistmentId, "primaryenlistmentid"));
GVFSEnlistment enlistment = this.CreateWorktreeEnlistment();
GVFSEnlistment enlistment = this.CreateWorktreeEnlistment(GetGitBinPath());

enlistment.GetEnlistmentId().ShouldEqual(DerivedEnlistmentIdOfAgentWorktree);
this.CreateWorktreeEnlistment().GetEnlistmentId().ShouldEqual(DerivedEnlistmentIdOfAgentWorktree);
this.CreateWorktreeEnlistment(GetGitBinPath()).GetEnlistmentId().ShouldEqual(DerivedEnlistmentIdOfAgentWorktree);
RepoMetadata.Instance.ShouldBeNull("Reading the id must not initialize RepoMetadata.Instance");
}

Expand Down Expand Up @@ -308,7 +306,7 @@ public void WorktreeMetadataCreatesEnlistmentIdWhenNoneIsGivenOrStored(string st
[TestCase]
public void RequiredSettingsEnableWorktreeConfigAndWritePerWorktreeValues()
{
GVFSEnlistment enlistment = this.CreateWorktreeEnlistment();
GVFSEnlistment enlistment = this.CreateWorktreeEnlistment(GetGitBinPath());

string error;
RequiredGitConfig.TrySetRequiredSettings(
Expand All @@ -331,7 +329,7 @@ public void RequiredSettingsRepairCopiedWorktreeValueAndSharedValueOfPrimary()
{
this.AppendToSharedConfig("[status]\n\tdeserializePath = C:/sibling/status.dat\n");
File.WriteAllText(this.WorktreeConfigPath, "[status]\n\tdeserializePath = C:/sibling/status.dat\n");
GVFSEnlistment enlistment = this.CreateWorktreeEnlistment();
GVFSEnlistment enlistment = this.CreateWorktreeEnlistment(GetGitBinPath());

string error;
RequiredGitConfig.TrySetRequiredSettings(
Expand All @@ -348,7 +346,7 @@ public void RequiredSettingsDisableNullPerWorktreeValueInWorktreeConfig()
{
this.AppendToSharedConfig("[extensions]\n\tworktreeConfig = true\n[status]\n\tdeserializePath = C:/primary/status.dat\n");
File.WriteAllText(this.WorktreeConfigPath, "[status]\n\tdeserializePath = C:/worktree/status.dat\n");
GVFSEnlistment enlistment = this.CreateWorktreeEnlistment();
GVFSEnlistment enlistment = this.CreateWorktreeEnlistment(GetGitBinPath());

string error;
RequiredGitConfig.TrySetRequiredSettings(
Expand All @@ -365,7 +363,7 @@ public void RequiredSettingsDisableNullPerWorktreeValueInWorktreeConfig()
public void RequiredSettingsRemoveRequiredOverridesFromWorktreeConfig()
{
File.WriteAllText(this.WorktreeConfigPath, "[core]\n\tautocrlf = true\n[gvfs]\n\tmount-id = worktreemountid\n");
GVFSEnlistment enlistment = this.CreateWorktreeEnlistment();
GVFSEnlistment enlistment = this.CreateWorktreeEnlistment(GetGitBinPath());

string error;
RequiredGitConfig.TrySetRequiredSettings(
Expand All @@ -382,7 +380,7 @@ public void RequiredSettingsRemoveRequiredOverridesFromWorktreeConfig()
public void RequiredSettingsReplaceDuplicateWorktreeConfigExtensionValues()
{
this.AppendToSharedConfig("[extensions]\n\tworktreeConfig = true\n\tworktreeConfig = false\n");
GVFSEnlistment enlistment = this.CreateWorktreeEnlistment();
GVFSEnlistment enlistment = this.CreateWorktreeEnlistment(GetGitBinPath());

string error;
RequiredGitConfig.TrySetRequiredSettings(enlistment, new Dictionary<string, string>(), out error).ShouldBeTrue(error);
Expand Down Expand Up @@ -429,7 +427,7 @@ public void PrimaryRequiredSettingsDoNotEnableWorktreeConfigWithoutLinkedWorktre
public void RequiredSettingsWriteNothingWhenAppliedAgain()
{
GVFSEnlistment primary = new GVFSEnlistment(this.primaryRoot, "https://mock/repo", GetGitBinPath(), authentication: null);
GVFSEnlistment worktree = this.CreateWorktreeEnlistment();
GVFSEnlistment worktree = this.CreateWorktreeEnlistment(GetGitBinPath());

string error;
RequiredGitConfig.TrySetRequiredSettings(primary, out error).ShouldBeTrue(error);
Expand All @@ -449,7 +447,7 @@ public void RequiredSettingsWriteNothingWhenAppliedAgain()
public void RequiredSettingsFailWhenWorktreeConfigIsCorrupt()
{
File.WriteAllText(this.WorktreeConfigPath, "[status\n");
GVFSEnlistment enlistment = this.CreateWorktreeEnlistment();
GVFSEnlistment enlistment = this.CreateWorktreeEnlistment(GetGitBinPath());

string error;
RequiredGitConfig.TrySetRequiredSettings(
Expand All @@ -470,7 +468,9 @@ public void RequiredSettingsFailWhenWorktreeConfigIsCorrupt()

private static string GetGitBinPath()
{
return GVFSPlatform.Instance.GitInstallation.GetInstalledGitBinPath() ?? @"C:\Program Files\Git\cmd\git.exe";
string gitPath = GitTestHelper.FindGitOnPath();
Assert.That(gitPath, Is.Not.Null, "The required-settings tests require Git on PATH.");
return gitPath;
}

private static string GetRepoMetadataPath(string dotGVFSRoot)
Expand Down
12 changes: 6 additions & 6 deletions GVFS/GVFS.UnitTests/Git/GitProcessTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -776,10 +776,10 @@ public void QuoteGitPath(string input, string expected)
[TestCase(Timeout.Infinite)]
public void GitCommandWaitsForOutputCallbacks(int timeoutMs)
{
string gitPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Git", "cmd", "git.exe");
if (!File.Exists(gitPath))
string gitPath = GitTestHelper.FindGitOnPath();
if (gitPath == null)
{
Assert.Ignore("Git is not installed at the expected path.");
Assert.Ignore("Git was not found on PATH.");
}

OutputCallbackGitProcess gitProcess = new OutputCallbackGitProcess(gitPath);
Expand Down Expand Up @@ -826,10 +826,10 @@ public void QuoteGitPath_RoundTripThroughProcess()
"path\\with\\backslashes\\file.txt",
};

string gitPath = "C:\\Program Files\\Git\\cmd\\git.exe";
if (!System.IO.File.Exists(gitPath))
string gitPath = GitTestHelper.FindGitOnPath();
if (gitPath == null)
{
Assert.Ignore("Git not found at expected path — skipping integration test");
Assert.Ignore("Git was not found on PATH.");
}

foreach (string testPath in testPaths)
Expand Down
79 changes: 79 additions & 0 deletions GVFS/GVFS.UnitTests/Git/GitTestHelperTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using NUnit.Framework;
using System;
using System.IO;

namespace GVFS.UnitTests.Git
{
[TestFixture]
[NonParallelizable]
public class GitTestHelperTests
{
private string root;

[SetUp]
public void SetUp()
{
this.root = Path.Combine(Path.GetTempPath(), nameof(GitTestHelperTests) + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(this.root);
}

[TearDown]
public void TearDown()
{
Directory.Delete(this.root, recursive: true);
}

[TestCase("First Git")]
[TestCase("First; Git")]
public void FindsTheFirstGitInQuotedPathEntriesWithSpaces(string firstDirectoryName)
{
string firstDirectory = Path.Combine(this.root, firstDirectoryName);
string secondDirectory = Path.Combine(this.root, "Second Git");
Directory.CreateDirectory(firstDirectory);
Directory.CreateDirectory(secondDirectory);
string expectedGit = Path.Combine(firstDirectory, "git.exe");
File.WriteAllText(expectedGit, string.Empty);
File.WriteAllText(Path.Combine(secondDirectory, "git.exe"), string.Empty);
string searchPath = string.Join(Path.PathSeparator,
Path.Combine(this.root, "missing"), "\"" + firstDirectory + "\"", secondDirectory);

Assert.That(GitTestHelper.FindGitOnPath(searchPath), Is.EqualTo(expectedGit));
}

[TestCase(null)]
[TestCase("")]
[TestCase(";;")]
[TestCase("\"\"; ;")]
public void EmptyPathEntriesDoNotSearchTheWorkingDirectory(string searchPath)
{
File.WriteAllText(Path.Combine(this.root, "git.exe"), string.Empty);
string originalDirectory = Environment.CurrentDirectory;
try
{
Environment.CurrentDirectory = this.root;

Assert.That(GitTestHelper.FindGitOnPath(searchPath), Is.Null);
}
finally
{
Environment.CurrentDirectory = originalDirectory;
}
}

[Test]
public void RelativePathEntriesReturnAnAbsolutePath()
{
string expectedGit = Path.Combine(this.root, "git.exe");
File.WriteAllText(expectedGit, string.Empty);
string searchPath = Path.GetRelativePath(Environment.CurrentDirectory, this.root);

Assert.That(GitTestHelper.FindGitOnPath(searchPath), Is.EqualTo(expectedGit).IgnoreCase);
}

[Test]
public void MissingGitReturnsNull()
{
Assert.That(GitTestHelper.FindGitOnPath(this.root), Is.Null);
}
}
}
54 changes: 54 additions & 0 deletions GVFS/GVFS.UnitTests/GitTestHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.IO;

namespace GVFS.UnitTests
{
internal static class GitTestHelper
{
internal static string FindGitOnPath()
{
return FindGitOnPath(Environment.GetEnvironmentVariable("PATH"));
}

internal static string FindGitOnPath(string searchPath)
{
if (string.IsNullOrEmpty(searchPath))
{
return null;
}

int entryStart = 0;
bool quoted = false;
for (int index = 0; index <= searchPath.Length; ++index)
{
if (index < searchPath.Length)
{
if (searchPath[index] == '"')
{
quoted = !quoted;
}

if (searchPath[index] != Path.PathSeparator || quoted)
{
continue;
}
}

string directory = searchPath.Substring(entryStart, index - entryStart).Trim('"');
entryStart = index + 1;
if (string.IsNullOrWhiteSpace(directory))
{
continue;
}

string gitPath = Path.Combine(directory, "git.exe");
if (File.Exists(gitPath))
{
return Path.GetFullPath(gitPath);
}
}

return null;
}
}
}
Loading