diff --git a/Scalar.FunctionalTests/Program.cs b/Scalar.FunctionalTests/Program.cs index c2872bac4d..c859c5b44b 100644 --- a/Scalar.FunctionalTests/Program.cs +++ b/Scalar.FunctionalTests/Program.cs @@ -34,18 +34,17 @@ public static void Main(string[] args) HashSet includeCategories = new HashSet(); HashSet excludeCategories = new HashSet(); + // Run all GitRepoTests with sparse mode + ScalarTestConfig.GitRepoTestsValidateWorkTree = + new object[] + { + new object[] { Settings.ValidateWorkingTreeMode.SparseMode }, + }; + if (runner.HasCustomArg("--full-suite")) { Console.WriteLine("Running the full suite of tests"); - List modes = new List(); - foreach (Settings.ValidateWorkingTreeMode mode in Enum.GetValues(typeof(Settings.ValidateWorkingTreeMode))) - { - modes.Add(new object[] { mode }); - } - - ScalarTestConfig.GitRepoTestsValidateWorkTree = modes.ToArray(); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { ScalarTestConfig.FileSystemRunners = FileSystemRunners.FileSystemRunner.AllWindowsRunners; @@ -57,22 +56,6 @@ public static void Main(string[] args) } else { - Settings.ValidateWorkingTreeMode validateMode = Settings.ValidateWorkingTreeMode.Full; - - if (runner.HasCustomArg("--sparse-mode")) - { - validateMode = Settings.ValidateWorkingTreeMode.SparseMode; - - // Only test the git commands in sparse mode for splitting out tests in builds - includeCategories.Add(Categories.GitCommands); - } - - ScalarTestConfig.GitRepoTestsValidateWorkTree = - new object[] - { - new object[] { validateMode }, - }; - if (runner.HasCustomArg("--extra-only")) { Console.WriteLine("Running only the tests marked as ExtraCoverage"); @@ -95,13 +78,15 @@ public static void Main(string[] args) includeCategories.Remove(Categories.ExtraCoverage); } + // Not just Mac, but no platform has status cache. + excludeCategories.Add(Categories.MacTODO.NeedsStatusCache); + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { excludeCategories.Add(Categories.MacTODO.NeedsNewFolderCreateNotification); excludeCategories.Add(Categories.MacTODO.NeedsScalarConfig); excludeCategories.Add(Categories.MacTODO.NeedsDehydrate); excludeCategories.Add(Categories.MacTODO.NeedsServiceVerb); - excludeCategories.Add(Categories.MacTODO.NeedsStatusCache); excludeCategories.Add(Categories.MacTODO.TestNeedsToLockFile); excludeCategories.Add(Categories.WindowsOnly); } diff --git a/Scalar.FunctionalTests/Settings.cs b/Scalar.FunctionalTests/Settings.cs index e3f0992e5b..7c18dc5a19 100644 --- a/Scalar.FunctionalTests/Settings.cs +++ b/Scalar.FunctionalTests/Settings.cs @@ -23,9 +23,6 @@ public static class Default public static string Commitish { get; set; } public static string ControlGitRepoRoot { get; set; } public static string EnlistmentRoot { get; set; } - public static string FastFetchBaseRoot { get; set; } - public static string FastFetchRoot { get; set; } - public static string FastFetchControl { get; set; } public static string PathToGit { get; set; } public static string PathToScalarService { get; set; } public static string BinaryFileNameExtension { get; set; } @@ -45,9 +42,6 @@ public static void Initialize() PathToBash = @"C:\Program Files\Git\bin\bash.exe"; ControlGitRepoRoot = @"C:\Repos\ScalarFunctionalTests\ControlRepo"; - FastFetchBaseRoot = @"C:\Repos\ScalarFunctionalTests\FastFetch"; - FastFetchRoot = Path.Combine(FastFetchBaseRoot, "test"); - FastFetchControl = Path.Combine(FastFetchBaseRoot, "control"); PathToScalarService = @"Scalar.Service.exe"; BinaryFileNameExtension = ".exe"; } @@ -58,9 +52,6 @@ public static void Initialize() "Scalar.FT"); EnlistmentRoot = Path.Combine(root, "test"); ControlGitRepoRoot = Path.Combine(root, "control"); - FastFetchBaseRoot = Path.Combine(root, "FastFetch"); - FastFetchRoot = Path.Combine(FastFetchBaseRoot, "test"); - FastFetchControl = Path.Combine(FastFetchBaseRoot, "control"); PathToScalar = "scalar"; PathToGit = "/usr/local/bin/git"; PathToBash = "/bin/bash"; diff --git a/Scalar.FunctionalTests/Should/FileSystemShouldExtensions.cs b/Scalar.FunctionalTests/Should/FileSystemShouldExtensions.cs index 6fc0064fe3..f1942a994a 100644 --- a/Scalar.FunctionalTests/Should/FileSystemShouldExtensions.cs +++ b/Scalar.FunctionalTests/Should/FileSystemShouldExtensions.cs @@ -60,9 +60,6 @@ public static string ShouldNotExistOnDisk(this string path, FileSystemRunner run public class FileAdapter { - private const int MaxWaitMS = 2000; - private const int ThreadSleepMS = 100; - private FileSystemRunner runner; public FileAdapter(string path, FileSystemRunner runner) @@ -260,6 +257,18 @@ private static bool IsMatchedPath(FileSystemInfo info, string repoRoot, string[] } string localPath = info.FullName.Substring(repoRoot.Length + 1); + int parentLength = localPath.LastIndexOf(System.IO.Path.DirectorySeparatorChar); + + string parentPath = null; + + if ((info.Attributes & FileAttributes.Directory) == FileAttributes.Directory) + { + parentPath = localPath; + } + else if (parentLength > 0) + { + parentPath = localPath.Substring(0, parentLength + 1); + } if (localPath.Equals(".git", StringComparison.OrdinalIgnoreCase)) { @@ -268,7 +277,7 @@ private static bool IsMatchedPath(FileSystemInfo info, string repoRoot, string[] return true; } - if (!localPath.Contains(System.IO.Path.DirectorySeparatorChar) && + if (parentPath == null && (info.Attributes & FileAttributes.Directory) != FileAttributes.Directory) { // If it is a file in the root folder, then include it. @@ -277,15 +286,15 @@ private static bool IsMatchedPath(FileSystemInfo info, string repoRoot, string[] foreach (string prefixDir in prefixes) { - if (localPath.StartsWith(prefixDir, StringComparison.OrdinalIgnoreCase)) + if (localPath.Equals(prefixDir, StringComparison.OrdinalIgnoreCase) || + localPath.StartsWith(prefixDir + System.IO.Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)) { return true; } - if (prefixDir.StartsWith(localPath, StringComparison.OrdinalIgnoreCase) && - Directory.Exists(info.FullName)) + if (parentPath != null && prefixDir.StartsWith(parentPath, StringComparison.OrdinalIgnoreCase)) { - // For example: localPath = "Scalar" and prefix is "Scalar\\Scalar". + // For example: localPath = "Scalar\\file.txt", parentPath="Scalar\\" and prefix is "Scalar\\Scalar". return true; } } @@ -305,6 +314,7 @@ private static void CompareDirectories( IEnumerable actualEntries = new DirectoryInfo(actualPath).EnumerateFileSystemInfos("*", SearchOption.AllDirectories); string dotGitFolder = System.IO.Path.DirectorySeparatorChar + TestConstants.DotGit.Root + System.IO.Path.DirectorySeparatorChar; + IEnumerator expectedEnumerator = expectedEntries .Where(x => !x.FullName.Contains(dotGitFolder)) .OrderBy(x => x.FullName) diff --git a/Scalar.FunctionalTests/Tests/EnlistmentPerFixture/CloneTests.cs b/Scalar.FunctionalTests/Tests/EnlistmentPerFixture/CloneTests.cs index ec1af062ae..3c10c01aa1 100644 --- a/Scalar.FunctionalTests/Tests/EnlistmentPerFixture/CloneTests.cs +++ b/Scalar.FunctionalTests/Tests/EnlistmentPerFixture/CloneTests.cs @@ -48,6 +48,7 @@ public void CloneWithLocalCachePathWithinSrc() [TestCase] [Category(Categories.MacOnly)] + [Category(Categories.NeedsUpdatesForNonVirtualizedMode)] public void CloneWithDefaultLocalCacheLocation() { FileSystemRunner fileSystem = FileSystemRunner.DefaultRunner; @@ -57,11 +58,15 @@ public void CloneWithDefaultLocalCacheLocation() string newEnlistmentRoot = ScalarFunctionalTestEnlistment.GetUniqueEnlistmentRoot(); ProcessStartInfo processInfo = new ProcessStartInfo(ScalarTestConfig.PathToScalar); + + // Needs update for non-virtualized mode: this used to have --no-mount to avoid an issue + // with registering the mount with the service. processInfo.Arguments = $"clone {Properties.Settings.Default.RepoToClone} {newEnlistmentRoot} --no-prefetch"; processInfo.WindowStyle = ProcessWindowStyle.Hidden; processInfo.CreateNoWindow = true; processInfo.UseShellExecute = false; processInfo.RedirectStandardOutput = true; + processInfo.WorkingDirectory = Properties.Settings.Default.EnlistmentRoot; ProcessResult result = ProcessHelper.Run(processInfo); result.ExitCode.ShouldEqual(0, result.Errors); diff --git a/Scalar.FunctionalTests/Tests/EnlistmentPerFixture/LooseObjectStepTests.cs b/Scalar.FunctionalTests/Tests/EnlistmentPerFixture/LooseObjectStepTests.cs index 21b38498e1..b648373dff 100644 --- a/Scalar.FunctionalTests/Tests/EnlistmentPerFixture/LooseObjectStepTests.cs +++ b/Scalar.FunctionalTests/Tests/EnlistmentPerFixture/LooseObjectStepTests.cs @@ -18,7 +18,7 @@ public class LooseObjectStepTests : TestsWithEnlistmentPerFixture // Set forcePerRepoObjectCache to true to avoid any of the tests inadvertently corrupting // the cache public LooseObjectStepTests() - : base(forcePerRepoObjectCache: false) + : base(forcePerRepoObjectCache: true, skipPrefetchDuringClone: false) { this.fileSystem = new SystemIORunner(); } @@ -31,6 +31,7 @@ public LooseObjectStepTests() [Order(1)] public void NoLooseObjectsDoesNothing() { + this.Enlistment.UnmountScalar(); this.DeleteFiles(this.GetLooseObjectFiles()); this.DeleteFiles(this.GetLooseObjectFiles()); diff --git a/Scalar.FunctionalTests/Tests/EnlistmentPerFixture/SparseTests.cs b/Scalar.FunctionalTests/Tests/EnlistmentPerFixture/SparseTests.cs deleted file mode 100644 index b4da2f7cf1..0000000000 --- a/Scalar.FunctionalTests/Tests/EnlistmentPerFixture/SparseTests.cs +++ /dev/null @@ -1,378 +0,0 @@ -using NUnit.Framework; -using Scalar.FunctionalTests.FileSystemRunners; -using Scalar.FunctionalTests.Should; -using Scalar.FunctionalTests.Tools; -using Scalar.Tests.Should; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace Scalar.FunctionalTests.Tests.EnlistmentPerFixture -{ - [TestFixture] - [Category(Categories.NeedsUpdatesForNonVirtualizedMode)] - public class SparseTests : TestsWithEnlistmentPerFixture - { - private FileSystemRunner fileSystem = new SystemIORunner(); - private ScalarProcess scalarProcess; - private string mainSparseFolder = Path.Combine("GVFS", "GVFS"); - private string[] allRootDirectories; - private string[] directoriesInMainFolder; - - [OneTimeSetUp] - public void Setup() - { - this.scalarProcess = new ScalarProcess(this.Enlistment); - this.allRootDirectories = Directory.GetDirectories(this.Enlistment.RepoRoot); - this.directoriesInMainFolder = Directory.GetDirectories(Path.Combine(this.Enlistment.RepoRoot, this.mainSparseFolder)); - } - - [TearDown] - public void TearDown() - { - GitProcess.Invoke(this.Enlistment.RepoRoot, "clean -xdf"); - GitProcess.Invoke(this.Enlistment.RepoRoot, "reset --hard"); - - foreach (string sparseFolder in this.scalarProcess.GetSparseFolders()) - { - this.scalarProcess.RemoveSparseFolders(sparseFolder); - } - - // Remove all sparse folders should make all folders appear again - string[] directories = Directory.GetDirectories(this.Enlistment.RepoRoot); - directories.ShouldMatchInOrder(this.allRootDirectories); - this.ValidateFoldersInSparseList(new string[0]); - } - - [TestCase, Order(1)] - public void BasicTestsAddingSparseFolder() - { - this.scalarProcess.AddSparseFolders(this.mainSparseFolder); - this.ValidateFoldersInSparseList(this.mainSparseFolder); - - string[] directories = Directory.GetDirectories(this.Enlistment.RepoRoot); - directories.Length.ShouldEqual(2); - directories[0].ShouldEqual(Path.Combine(this.Enlistment.RepoRoot, ".git")); - directories[1].ShouldEqual(Path.Combine(this.Enlistment.RepoRoot, "GVFS")); - - string folder = this.Enlistment.GetVirtualPathTo(this.mainSparseFolder); - folder.ShouldBeADirectory(this.fileSystem); - folder = this.Enlistment.GetVirtualPathTo(this.mainSparseFolder, "CommandLine"); - folder.ShouldBeADirectory(this.fileSystem); - - string file = this.Enlistment.GetVirtualPathTo("Readme.md"); - file.ShouldBeAFile(this.fileSystem); - - folder = this.Enlistment.GetVirtualPathTo("Scripts"); - folder.ShouldNotExistOnDisk(this.fileSystem); - folder = this.Enlistment.GetVirtualPathTo("GVFS", "GVFS.Mount"); - folder.ShouldNotExistOnDisk(this.fileSystem); - - string secondPath = Path.Combine("GVFS", "GVFS.Common", "Physical"); - this.scalarProcess.AddSparseFolders(secondPath); - folder = this.Enlistment.GetVirtualPathTo(secondPath); - folder.ShouldBeADirectory(this.fileSystem); - file = this.Enlistment.GetVirtualPathTo("GVFS", "GVFS.Common", "Enlistment.cs"); - file.ShouldBeAFile(this.fileSystem); - } - - [TestCase, Order(2)] - public void AddAndRemoveVariousPathsTests() - { - // Paths to validate [0] = path to pass to sparse [1] = expected path saved - string[][] paths = new[] - { - // AltDirectorySeparatorChar should get converted to DirectorySeparatorChar - new[] { string.Join(Path.AltDirectorySeparatorChar.ToString(), "GVFS", "GVFS"), this.mainSparseFolder }, - - // AltDirectorySeparatorChar should get trimmed - new[] { $"{Path.AltDirectorySeparatorChar}{string.Join(Path.AltDirectorySeparatorChar.ToString(), "GVFS", "Test")}{Path.AltDirectorySeparatorChar}", Path.Combine("GVFS", "Test") }, - - // DirectorySeparatorChar should get trimmed - new[] { $"{Path.DirectorySeparatorChar}{Path.Combine("GVFS", "More")}{Path.DirectorySeparatorChar}", Path.Combine("GVFS", "More") }, - - // spaces should get trimmed - new[] { $" {string.Join(Path.AltDirectorySeparatorChar.ToString(), "GVFS", "Other")} ", Path.Combine("GVFS", "Other") }, - }; - - foreach (string[] pathToValidate in paths) - { - this.ValidatePathAddsAndRemoves(pathToValidate[0], pathToValidate[1]); - } - } - - [TestCase, Order(3)] - public void AddingParentDirectoryShouldMakeItRecursive() - { - string childPath = Path.Combine(this.mainSparseFolder, "CommandLine"); - this.scalarProcess.AddSparseFolders(childPath); - string[] directories = Directory.GetDirectories(Path.Combine(this.Enlistment.RepoRoot, this.mainSparseFolder)); - directories.Length.ShouldEqual(1); - directories[0].ShouldEqual(Path.Combine(this.Enlistment.RepoRoot, childPath)); - this.ValidateFoldersInSparseList(childPath); - - this.scalarProcess.AddSparseFolders(this.mainSparseFolder); - directories = Directory.GetDirectories(Path.Combine(this.Enlistment.RepoRoot, this.mainSparseFolder)); - directories.Length.ShouldBeAtLeast(2); - directories.ShouldMatchInOrder(this.directoriesInMainFolder); - this.ValidateFoldersInSparseList(childPath, this.mainSparseFolder); - } - - [TestCase, Order(4)] - public void AddingSiblingFolderShouldNotMakeParentRecursive() - { - this.scalarProcess.AddSparseFolders(this.mainSparseFolder); - this.ValidateFoldersInSparseList(this.mainSparseFolder); - - // Add and remove sibling folder to main folder - string siblingPath = Path.Combine("GVFS", "FastFetch"); - this.scalarProcess.AddSparseFolders(siblingPath); - string folder = this.Enlistment.GetVirtualPathTo(siblingPath); - folder.ShouldBeADirectory(this.fileSystem); - this.ValidateFoldersInSparseList(this.mainSparseFolder, siblingPath); - - this.scalarProcess.RemoveSparseFolders(siblingPath); - folder.ShouldNotExistOnDisk(this.fileSystem); - folder = this.Enlistment.GetVirtualPathTo(this.mainSparseFolder); - folder.ShouldBeADirectory(this.fileSystem); - this.ValidateFoldersInSparseList(this.mainSparseFolder); - } - - [TestCase, Order(5)] - public void AddingSubfolderShouldKeepParentRecursive() - { - this.scalarProcess.AddSparseFolders(this.mainSparseFolder); - this.ValidateFoldersInSparseList(this.mainSparseFolder); - - // Add subfolder of main folder and make sure it stays recursive - string subFolder = Path.Combine(this.mainSparseFolder, "Properties"); - this.scalarProcess.AddSparseFolders(subFolder); - string folder = this.Enlistment.GetVirtualPathTo(subFolder); - folder.ShouldBeADirectory(this.fileSystem); - this.ValidateFoldersInSparseList(this.mainSparseFolder, subFolder); - - folder = this.Enlistment.GetVirtualPathTo(this.mainSparseFolder, "CommandLine"); - folder.ShouldBeADirectory(this.fileSystem); - } - - [TestCase, Order(6)] - [Category(Categories.WindowsOnly)] - public void CreatingFolderShouldAddToSparseListAndStartProjecting() - { - this.scalarProcess.AddSparseFolders(this.mainSparseFolder); - this.ValidateFoldersInSparseList(this.mainSparseFolder); - - string newFolderPath = Path.Combine(this.Enlistment.RepoRoot, "GVFS", "GVFS.Common"); - newFolderPath.ShouldNotExistOnDisk(this.fileSystem); - Directory.CreateDirectory(newFolderPath); - newFolderPath.ShouldBeADirectory(this.fileSystem); - string[] fileSystemEntries = Directory.GetFileSystemEntries(newFolderPath); - fileSystemEntries.Length.ShouldEqual(32); - this.ValidateFoldersInSparseList(this.mainSparseFolder, Path.Combine("GVFS", "GVFS.Common")); - - string projectedFolder = Path.Combine(newFolderPath, "Git"); - projectedFolder.ShouldBeADirectory(this.fileSystem); - fileSystemEntries = Directory.GetFileSystemEntries(projectedFolder); - fileSystemEntries.Length.ShouldEqual(13); - - string projectedFile = Path.Combine(newFolderPath, "ReturnCode.cs"); - projectedFile.ShouldBeAFile(this.fileSystem); - } - - [TestCase, Order(7)] - [Category(Categories.MacOnly)] - public void CreateFolderThenFileShouldAddToSparseListAndStartProjecting() - { - this.scalarProcess.AddSparseFolders(this.mainSparseFolder); - this.ValidateFoldersInSparseList(this.mainSparseFolder); - - string newFolderPath = Path.Combine(this.Enlistment.RepoRoot, "GVFS", "GVFS.Common"); - newFolderPath.ShouldNotExistOnDisk(this.fileSystem); - Directory.CreateDirectory(newFolderPath); - string newFilePath = Path.Combine(newFolderPath, "test.txt"); - File.WriteAllText(newFilePath, "New file content"); - newFolderPath.ShouldBeADirectory(this.fileSystem); - newFilePath.ShouldBeAFile(this.fileSystem); - string[] fileSystemEntries = Directory.GetFileSystemEntries(newFolderPath); - fileSystemEntries.Length.ShouldEqual(33); - this.ValidateFoldersInSparseList(this.mainSparseFolder, Path.Combine("GVFS", "GVFS.Common")); - - string projectedFolder = Path.Combine(newFolderPath, "Git"); - projectedFolder.ShouldBeADirectory(this.fileSystem); - fileSystemEntries = Directory.GetFileSystemEntries(projectedFolder); - fileSystemEntries.Length.ShouldEqual(13); - - string projectedFile = Path.Combine(newFolderPath, "ReturnCode.cs"); - projectedFile.ShouldBeAFile(this.fileSystem); - } - - [TestCase, Order(7)] - public void ReadFileThenChangingSparseFoldersShouldRemoveFileAndFolder() - { - string fileToRead = Path.Combine(this.Enlistment.RepoRoot, "Scripts", "RunFunctionalTests.bat"); - this.fileSystem.ReadAllText(fileToRead); - - this.scalarProcess.AddSparseFolders(this.mainSparseFolder); - this.ValidateFoldersInSparseList(this.mainSparseFolder); - - string folderPath = Path.Combine(this.Enlistment.RepoRoot, "Scripts"); - folderPath.ShouldNotExistOnDisk(this.fileSystem); - fileToRead.ShouldNotExistOnDisk(this.fileSystem); - } - - [TestCase, Order(8)] - public void CreateNewFileWillPreventRemoveSparseFolder() - { - this.scalarProcess.AddSparseFolders(this.mainSparseFolder, "Scripts"); - this.ValidateFoldersInSparseList(this.mainSparseFolder, "Scripts"); - - string fileToCreate = Path.Combine(this.Enlistment.RepoRoot, "Scripts", "newfile.txt"); - this.fileSystem.WriteAllText(fileToCreate, "New Contents"); - - string output = this.scalarProcess.RemoveSparseFolders(shouldSucceed: false, folders: "Scripts"); - output.ShouldContain("sparse was aborted"); - this.ValidateFoldersInSparseList(this.mainSparseFolder, "Scripts"); - - string folderPath = Path.Combine(this.Enlistment.RepoRoot, "Scripts"); - folderPath.ShouldBeADirectory(this.fileSystem); - string[] fileSystemEntries = Directory.GetFileSystemEntries(folderPath); - fileSystemEntries.Length.ShouldEqual(6); - fileToCreate.ShouldBeAFile(this.fileSystem); - - this.fileSystem.DeleteFile(fileToCreate); - } - - [TestCase, Order(9)] - public void ModifiedFileShouldNotAllowSparseFolderChange() - { - string modifiedPath = Path.Combine(this.Enlistment.RepoRoot, "Scripts", "RunFunctionalTests.bat"); - this.fileSystem.WriteAllText(modifiedPath, "New Contents"); - - string output = this.scalarProcess.AddSparseFolders(shouldSucceed: false, folders: this.mainSparseFolder); - output.ShouldContain("sparse was aborted"); - this.ValidateFoldersInSparseList(new string[0]); - } - - [TestCase, Order(10)] - public void ModifiedFileAndCommitThenChangingSparseFoldersShouldKeepFileAndFolder() - { - string modifiedPath = Path.Combine(this.Enlistment.RepoRoot, "Scripts", "RunFunctionalTests.bat"); - this.fileSystem.WriteAllText(modifiedPath, "New Contents"); - GitProcess.Invoke(this.Enlistment.RepoRoot, "add ."); - GitProcess.Invoke(this.Enlistment.RepoRoot, "commit -m Test"); - - this.scalarProcess.AddSparseFolders(this.mainSparseFolder); - this.ValidateFoldersInSparseList(this.mainSparseFolder); - - string folderPath = Path.Combine(this.Enlistment.RepoRoot, "Scripts"); - folderPath.ShouldBeADirectory(this.fileSystem); - modifiedPath.ShouldBeAFile(this.fileSystem); - } - - [TestCase, Order(11)] - public void DeleteFileAndCommitThenChangingSparseFoldersShouldKeepFolderAndFile() - { - string deletePath = Path.Combine(this.Enlistment.RepoRoot, "GVFS", "GVFS.Tests", "packages.config"); - this.fileSystem.DeleteFile(deletePath); - GitProcess.Invoke(this.Enlistment.RepoRoot, "add ."); - GitProcess.Invoke(this.Enlistment.RepoRoot, "commit -m Test"); - - this.scalarProcess.AddSparseFolders(this.mainSparseFolder); - this.ValidateFoldersInSparseList(this.mainSparseFolder); - - // File and folder should no longer be on disk because the file was deleted and the folder deleted becase it was empty - string folderPath = Path.Combine(this.Enlistment.RepoRoot, "GVFS", "GVFS.Tests"); - folderPath.ShouldNotExistOnDisk(this.fileSystem); - deletePath.ShouldNotExistOnDisk(this.fileSystem); - - // Folder and file should be on disk even though they are outside the sparse scope because the file is in the modified paths - GitProcess.Invoke(this.Enlistment.RepoRoot, "checkout HEAD~1"); - folderPath.ShouldBeADirectory(this.fileSystem); - deletePath.ShouldBeAFile(this.fileSystem); - } - - [TestCase, Order(12)] - public void CreateNewFileAndCommitThenRemoveSparseFolderShouldKeepFileAndFolder() - { - string folderToCreateFileIn = Path.Combine("GVFS", "GVFS.Hooks"); - this.scalarProcess.AddSparseFolders(this.mainSparseFolder, folderToCreateFileIn); - this.ValidateFoldersInSparseList(this.mainSparseFolder, folderToCreateFileIn); - - string fileToCreate = Path.Combine(this.Enlistment.RepoRoot, folderToCreateFileIn, "newfile.txt"); - this.fileSystem.WriteAllText(fileToCreate, "New Contents"); - GitProcess.Invoke(this.Enlistment.RepoRoot, "add ."); - GitProcess.Invoke(this.Enlistment.RepoRoot, "commit -m Test"); - - this.scalarProcess.RemoveSparseFolders(folderToCreateFileIn); - this.ValidateFoldersInSparseList(this.mainSparseFolder); - - string folderPath = Path.Combine(this.Enlistment.RepoRoot, folderToCreateFileIn); - folderPath.ShouldBeADirectory(this.fileSystem); - string[] fileSystemEntries = Directory.GetFileSystemEntries(folderPath); - fileSystemEntries.Length.ShouldEqual(1); - fileToCreate.ShouldBeAFile(this.fileSystem); - } - - [TestCase, Order(13)] - [Category(Categories.MacOnly)] - public void CreateFolderAndFileThatAreExcluded() - { - this.scalarProcess.AddSparseFolders(this.mainSparseFolder); - this.ValidateFoldersInSparseList(this.mainSparseFolder); - - // Create a file that should already be in the projection but excluded - string newFolderPath = Path.Combine(this.Enlistment.RepoRoot, "GVFS", "GVFS.Mount"); - newFolderPath.ShouldNotExistOnDisk(this.fileSystem); - Directory.CreateDirectory(newFolderPath); - string newFilePath = Path.Combine(newFolderPath, "Program.cs"); - File.WriteAllText(newFilePath, "New file content"); - newFolderPath.ShouldBeADirectory(this.fileSystem); - newFilePath.ShouldBeAFile(this.fileSystem); - string[] fileSystemEntries = Directory.GetFileSystemEntries(newFolderPath); - fileSystemEntries.Length.ShouldEqual(7); - - string projectedFolder = Path.Combine(newFolderPath, "Properties"); - projectedFolder.ShouldBeADirectory(this.fileSystem); - fileSystemEntries = Directory.GetFileSystemEntries(projectedFolder); - fileSystemEntries.Length.ShouldEqual(1); - - string projectedFile = Path.Combine(newFolderPath, "MountVerb.cs"); - projectedFile.ShouldBeAFile(this.fileSystem); - } - - private void ValidatePathAddsAndRemoves(string path, string expectedSparsePath) - { - this.scalarProcess.AddSparseFolders(path); - this.ValidateFoldersInSparseList(expectedSparsePath); - this.scalarProcess.RemoveSparseFolders(path); - this.ValidateFoldersInSparseList(new string[0]); - this.scalarProcess.AddSparseFolders(path); - this.ValidateFoldersInSparseList(expectedSparsePath); - this.scalarProcess.RemoveSparseFolders(expectedSparsePath); - this.ValidateFoldersInSparseList(new string[0]); - } - - private void ValidateFoldersInSparseList(params string[] folders) - { - StringBuilder folderErrors = new StringBuilder(); - HashSet actualSparseFolders = new HashSet(this.scalarProcess.GetSparseFolders()); - - foreach (string expectedFolder in folders) - { - if (!actualSparseFolders.Contains(expectedFolder)) - { - folderErrors.AppendLine($"{expectedFolder} not found in actual folder list"); - } - - actualSparseFolders.Remove(expectedFolder); - } - - foreach (string extraFolder in actualSparseFolders) - { - folderErrors.AppendLine($"{extraFolder} unexpected in folder list"); - } - - folderErrors.Length.ShouldEqual(0, folderErrors.ToString()); - } - } -} diff --git a/Scalar.FunctionalTests/Tests/GitCommands/AddStageTests.cs b/Scalar.FunctionalTests/Tests/GitCommands/AddStageTests.cs index 7c12b0e074..5434d6522f 100644 --- a/Scalar.FunctionalTests/Tests/GitCommands/AddStageTests.cs +++ b/Scalar.FunctionalTests/Tests/GitCommands/AddStageTests.cs @@ -8,7 +8,6 @@ namespace Scalar.FunctionalTests.Tests.GitCommands { [TestFixtureSource(typeof(GitRepoTests), nameof(GitRepoTests.ValidateWorkingTree))] [Category(Categories.GitCommands)] - [Category(Categories.NeedsUpdatesForNonVirtualizedMode)] public class AddStageTests : GitRepoTests { public AddStageTests(Settings.ValidateWorkingTreeMode validateWorkingTree) diff --git a/Scalar.FunctionalTests/Tests/GitCommands/CherryPickConflictTests.cs b/Scalar.FunctionalTests/Tests/GitCommands/CherryPickConflictTests.cs index 96548c8ad2..9eb1e1c051 100644 --- a/Scalar.FunctionalTests/Tests/GitCommands/CherryPickConflictTests.cs +++ b/Scalar.FunctionalTests/Tests/GitCommands/CherryPickConflictTests.cs @@ -5,7 +5,6 @@ namespace Scalar.FunctionalTests.Tests.GitCommands { [TestFixtureSource(typeof(GitRepoTests), nameof(GitRepoTests.ValidateWorkingTree))] [Category(Categories.GitCommands)] - [Category(Categories.NeedsUpdatesForNonVirtualizedMode)] public class CherryPickConflictTests : GitRepoTests { public CherryPickConflictTests(Settings.ValidateWorkingTreeMode validateWorkingTree) diff --git a/Scalar.FunctionalTests/Tests/GitCommands/DeleteEmptyFolderTests.cs b/Scalar.FunctionalTests/Tests/GitCommands/DeleteEmptyFolderTests.cs index caa3a13880..310af03c89 100644 --- a/Scalar.FunctionalTests/Tests/GitCommands/DeleteEmptyFolderTests.cs +++ b/Scalar.FunctionalTests/Tests/GitCommands/DeleteEmptyFolderTests.cs @@ -6,7 +6,6 @@ namespace Scalar.FunctionalTests.Tests.GitCommands { [TestFixtureSource(typeof(GitRepoTests), nameof(GitRepoTests.ValidateWorkingTree))] [Category(Categories.GitCommands)] - [Category(Categories.NeedsUpdatesForNonVirtualizedMode)] public class DeleteEmptyFolderTests : GitRepoTests { public DeleteEmptyFolderTests(Settings.ValidateWorkingTreeMode validateWorkingTree) diff --git a/Scalar.FunctionalTests/Tests/GitCommands/EnumerationMergeTest.cs b/Scalar.FunctionalTests/Tests/GitCommands/EnumerationMergeTest.cs index f916d4e1b6..076de7c1d8 100644 --- a/Scalar.FunctionalTests/Tests/GitCommands/EnumerationMergeTest.cs +++ b/Scalar.FunctionalTests/Tests/GitCommands/EnumerationMergeTest.cs @@ -5,7 +5,6 @@ namespace Scalar.FunctionalTests.Tests.GitCommands { [TestFixtureSource(typeof(GitRepoTests), nameof(GitRepoTests.ValidateWorkingTree))] [Category(Categories.GitCommands)] - [Category(Categories.NeedsUpdatesForNonVirtualizedMode)] public class EnumerationMergeTest : GitRepoTests { // Commit that found GvFlt Bug 12258777: Entries are sometimes skipped during diff --git a/Scalar.FunctionalTests/Tests/GitCommands/GitCommandsTests.cs b/Scalar.FunctionalTests/Tests/GitCommands/GitCommandsTests.cs index 4f08f04bee..ae53c2ef37 100644 --- a/Scalar.FunctionalTests/Tests/GitCommands/GitCommandsTests.cs +++ b/Scalar.FunctionalTests/Tests/GitCommands/GitCommandsTests.cs @@ -11,7 +11,6 @@ namespace Scalar.FunctionalTests.Tests.GitCommands { [TestFixtureSource(typeof(GitRepoTests), nameof(GitRepoTests.ValidateWorkingTree))] [Category(Categories.GitCommands)] - [Category(Categories.NeedsUpdatesForNonVirtualizedMode)] public class GitCommandsTests : GitRepoTests { public const string TopLevelFolderToCreate = "level1"; diff --git a/Scalar.FunctionalTests/Tests/GitCommands/GitRepoTests.cs b/Scalar.FunctionalTests/Tests/GitCommands/GitRepoTests.cs index 27878d8c17..647e04497f 100644 --- a/Scalar.FunctionalTests/Tests/GitCommands/GitRepoTests.cs +++ b/Scalar.FunctionalTests/Tests/GitCommands/GitRepoTests.cs @@ -70,9 +70,6 @@ public abstract class GitRepoTests "TrailingSlashTests", }; - // Add directory separator for matching paths since they should be directories - private static readonly string[] PathPrefixesForSparseMode = SparseModeFolders.Select(x => x + Path.DirectorySeparatorChar).ToArray(); - private bool enlistmentPerTest; private Settings.ValidateWorkingTreeMode validateWorkingTree; @@ -134,8 +131,9 @@ public virtual void SetupForTest() if (this.validateWorkingTree == Settings.ValidateWorkingTreeMode.SparseMode) { - new ScalarProcess(this.Enlistment).AddSparseFolders(SparseModeFolders); - this.pathPrefixes = PathPrefixesForSparseMode; + ScalarProcess scalar = new ScalarProcess(this.Enlistment); + scalar.SparseAdd(SparseModeFolders); + this.pathPrefixes = SparseModeFolders; } this.ValidateGitCommand("checkout " + this.ControlGitRepo.Commitish); @@ -199,7 +197,10 @@ protected virtual void CreateEnlistment() protected void CreateEnlistment(string commitish = null) { - this.Enlistment = ScalarFunctionalTestEnlistment.CloneAndMount(ScalarTestConfig.PathToScalar, commitish: commitish); + this.Enlistment = ScalarFunctionalTestEnlistment.CloneAndMount( + ScalarTestConfig.PathToScalar, + commitish: commitish, + fullClone: this.validateWorkingTree != Settings.ValidateWorkingTreeMode.SparseMode); GitProcess.Invoke(this.Enlistment.RepoRoot, "config advice.statusUoption false"); this.ControlGitRepo = ControlGitRepo.Create(commitish); this.ControlGitRepo.Initialize(); @@ -239,13 +240,14 @@ protected void RunGitCommand(string command, params object[] args) * are cases when git will delete these files during a merge outputting that it removed them * which the Scalar repo did not have to remove so the message is missing that output. */ - protected void RunGitCommand(string command, bool ignoreErrors = false, bool checkStatus = true) + protected void RunGitCommand(string command, bool ignoreErrors = false, bool checkStatus = true, string standardInput = null) { string controlRepoRoot = this.ControlGitRepo.RootPath; string scalarRepoRoot = this.Enlistment.RepoRoot; - ProcessResult expectedResult = GitProcess.InvokeProcess(controlRepoRoot, command); - ProcessResult actualResult = GitHelpers.InvokeGitAgainstScalarRepo(scalarRepoRoot, command); + ProcessResult expectedResult = GitProcess.InvokeProcess(controlRepoRoot, command, standardInput); + ProcessResult actualResult = GitHelpers.InvokeGitAgainstScalarRepo(scalarRepoRoot, command, input: standardInput); + if (!ignoreErrors) { GitHelpers.ErrorsShouldMatch(command, expectedResult, actualResult); diff --git a/Scalar.FunctionalTests/Tests/GitCommands/MergeConflictTests.cs b/Scalar.FunctionalTests/Tests/GitCommands/MergeConflictTests.cs index 37ff5324b6..d245d9d75b 100644 --- a/Scalar.FunctionalTests/Tests/GitCommands/MergeConflictTests.cs +++ b/Scalar.FunctionalTests/Tests/GitCommands/MergeConflictTests.cs @@ -6,7 +6,6 @@ namespace Scalar.FunctionalTests.Tests.GitCommands { [TestFixtureSource(typeof(GitRepoTests), nameof(GitRepoTests.ValidateWorkingTree))] [Category(Categories.GitCommands)] - [Category(Categories.NeedsUpdatesForNonVirtualizedMode)] public class MergeConflictTests : GitRepoTests { public MergeConflictTests(Settings.ValidateWorkingTreeMode validateWorkingTree) diff --git a/Scalar.FunctionalTests/Tests/GitCommands/RebaseConflictTests.cs b/Scalar.FunctionalTests/Tests/GitCommands/RebaseConflictTests.cs index ff4de8334f..639aafa3f6 100644 --- a/Scalar.FunctionalTests/Tests/GitCommands/RebaseConflictTests.cs +++ b/Scalar.FunctionalTests/Tests/GitCommands/RebaseConflictTests.cs @@ -5,7 +5,6 @@ namespace Scalar.FunctionalTests.Tests.GitCommands { [TestFixtureSource(typeof(GitRepoTests), nameof(GitRepoTests.ValidateWorkingTree))] [Category(Categories.GitCommands)] - [Category(Categories.NeedsUpdatesForNonVirtualizedMode)] public class RebaseConflictTests : GitRepoTests { public RebaseConflictTests(Settings.ValidateWorkingTreeMode validateWorkingTree) diff --git a/Scalar.FunctionalTests/Tests/GitCommands/RebaseTests.cs b/Scalar.FunctionalTests/Tests/GitCommands/RebaseTests.cs index 222a723c3d..3c064fc38b 100644 --- a/Scalar.FunctionalTests/Tests/GitCommands/RebaseTests.cs +++ b/Scalar.FunctionalTests/Tests/GitCommands/RebaseTests.cs @@ -5,7 +5,6 @@ namespace Scalar.FunctionalTests.Tests.GitCommands { [TestFixtureSource(typeof(GitRepoTests), nameof(GitRepoTests.ValidateWorkingTree))] [Category(Categories.GitCommands)] - [Category(Categories.NeedsUpdatesForNonVirtualizedMode)] public class RebaseTests : GitRepoTests { public RebaseTests(Settings.ValidateWorkingTreeMode validateWorkingTree) diff --git a/Scalar.FunctionalTests/Tests/GitCommands/ResetHardTests.cs b/Scalar.FunctionalTests/Tests/GitCommands/ResetHardTests.cs index db001fd6db..cbc31c640b 100644 --- a/Scalar.FunctionalTests/Tests/GitCommands/ResetHardTests.cs +++ b/Scalar.FunctionalTests/Tests/GitCommands/ResetHardTests.cs @@ -6,7 +6,6 @@ namespace Scalar.FunctionalTests.Tests.GitCommands { [TestFixtureSource(typeof(GitRepoTests), nameof(GitRepoTests.ValidateWorkingTree))] [Category(Categories.GitCommands)] - [Category(Categories.NeedsUpdatesForNonVirtualizedMode)] public class ResetHardTests : GitRepoTests { private const string ResetHardCommand = "reset --hard"; diff --git a/Scalar.FunctionalTests/Tests/GitCommands/ResetMixedTests.cs b/Scalar.FunctionalTests/Tests/GitCommands/ResetMixedTests.cs index ab4427c5f6..ab4160816b 100644 --- a/Scalar.FunctionalTests/Tests/GitCommands/ResetMixedTests.cs +++ b/Scalar.FunctionalTests/Tests/GitCommands/ResetMixedTests.cs @@ -6,7 +6,6 @@ namespace Scalar.FunctionalTests.Tests.GitCommands { [TestFixtureSource(typeof(GitRepoTests), nameof(GitRepoTests.ValidateWorkingTree))] [Category(Categories.GitCommands)] - [Category(Categories.NeedsUpdatesForNonVirtualizedMode)] public class ResetMixedTests : GitRepoTests { public ResetMixedTests(Settings.ValidateWorkingTreeMode validateWorkingTree) diff --git a/Scalar.FunctionalTests/Tests/GitCommands/ResetSoftTests.cs b/Scalar.FunctionalTests/Tests/GitCommands/ResetSoftTests.cs index 9490f3ec63..c7d96c5a8b 100644 --- a/Scalar.FunctionalTests/Tests/GitCommands/ResetSoftTests.cs +++ b/Scalar.FunctionalTests/Tests/GitCommands/ResetSoftTests.cs @@ -5,7 +5,6 @@ namespace Scalar.FunctionalTests.Tests.GitCommands { [TestFixtureSource(typeof(GitRepoTests), nameof(GitRepoTests.ValidateWorkingTree))] [Category(Categories.GitCommands)] - [Category(Categories.NeedsUpdatesForNonVirtualizedMode)] public class ResetSoftTests : GitRepoTests { public ResetSoftTests(Settings.ValidateWorkingTreeMode validateWorkingTree) diff --git a/Scalar.FunctionalTests/Tests/GitCommands/StatusTests.cs b/Scalar.FunctionalTests/Tests/GitCommands/StatusTests.cs index 58ae27da90..5190390d8c 100644 --- a/Scalar.FunctionalTests/Tests/GitCommands/StatusTests.cs +++ b/Scalar.FunctionalTests/Tests/GitCommands/StatusTests.cs @@ -2,7 +2,6 @@ using Scalar.FunctionalTests.FileSystemRunners; using Scalar.FunctionalTests.Properties; using Scalar.FunctionalTests.Should; -using Scalar.FunctionalTests.Tools; using Scalar.Tests.Should; using System; using System.IO; @@ -12,7 +11,6 @@ namespace Scalar.FunctionalTests.Tests.GitCommands { [TestFixtureSource(typeof(GitRepoTests), nameof(GitRepoTests.ValidateWorkingTree))] [Category(Categories.GitCommands)] - [Category(Categories.NeedsUpdatesForNonVirtualizedMode)] public class StatusTests : GitRepoTests { public StatusTests(Settings.ValidateWorkingTreeMode validateWorkingTree) diff --git a/Scalar.FunctionalTests/Tests/GitCommands/UpdateIndexTests.cs b/Scalar.FunctionalTests/Tests/GitCommands/UpdateIndexTests.cs index c2117af7f3..444edec354 100644 --- a/Scalar.FunctionalTests/Tests/GitCommands/UpdateIndexTests.cs +++ b/Scalar.FunctionalTests/Tests/GitCommands/UpdateIndexTests.cs @@ -6,7 +6,6 @@ namespace Scalar.FunctionalTests.Tests.GitCommands { [TestFixtureSource(typeof(GitRepoTests), nameof(GitRepoTests.ValidateWorkingTree))] [Category(Categories.GitCommands)] - [Category(Categories.NeedsUpdatesForNonVirtualizedMode)] public class UpdateIndexTests : GitRepoTests { public UpdateIndexTests(Settings.ValidateWorkingTreeMode validateWorkingTree) diff --git a/Scalar.FunctionalTests/Tests/GitCommands/UpdateRefTests.cs b/Scalar.FunctionalTests/Tests/GitCommands/UpdateRefTests.cs index 2f7866528c..79a98cd1c5 100644 --- a/Scalar.FunctionalTests/Tests/GitCommands/UpdateRefTests.cs +++ b/Scalar.FunctionalTests/Tests/GitCommands/UpdateRefTests.cs @@ -5,7 +5,6 @@ namespace Scalar.FunctionalTests.Tests.GitCommands { [TestFixtureSource(typeof(GitRepoTests), nameof(GitRepoTests.ValidateWorkingTree))] [Category(Categories.GitCommands)] - [Category(Categories.NeedsUpdatesForNonVirtualizedMode)] public class UpdateRefTests : GitRepoTests { public UpdateRefTests(Settings.ValidateWorkingTreeMode validateWorkingTree) diff --git a/Scalar.FunctionalTests/Tools/GitHelpers.cs b/Scalar.FunctionalTests/Tools/GitHelpers.cs index 4ae5e0e116..9f218bce0e 100644 --- a/Scalar.FunctionalTests/Tools/GitHelpers.cs +++ b/Scalar.FunctionalTests/Tools/GitHelpers.cs @@ -80,9 +80,10 @@ public static ProcessResult InvokeGitAgainstScalarRepo( string command, Dictionary environmentVariables = null, bool removeWaitingMessages = true, - bool removeUpgradeMessages = true) + bool removeUpgradeMessages = true, + string input = null) { - ProcessResult result = GitProcess.InvokeProcess(scalarRepoRoot, command, environmentVariables); + ProcessResult result = GitProcess.InvokeProcess(scalarRepoRoot, command, input, environmentVariables); string errors = result.Errors; if (!string.IsNullOrEmpty(errors) && (removeWaitingMessages || removeUpgradeMessages)) diff --git a/Scalar.FunctionalTests/Tools/GitProcess.cs b/Scalar.FunctionalTests/Tools/GitProcess.cs index 3b65ab95f8..dd6189d22b 100644 --- a/Scalar.FunctionalTests/Tools/GitProcess.cs +++ b/Scalar.FunctionalTests/Tools/GitProcess.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Text; namespace Scalar.FunctionalTests.Tools { @@ -11,6 +12,26 @@ public static string Invoke(string executionWorkingDirectory, string command) return InvokeProcess(executionWorkingDirectory, command).Output; } + public static ProcessResult InvokeProcess(string executionWorkingDirectory, string command, string inputData, Dictionary environmentVariables = null) + { + if (inputData == null) + { + return InvokeProcess(executionWorkingDirectory, command, environmentVariables); + } + + using (MemoryStream stream = new MemoryStream()) + { + using (StreamWriter writer = new StreamWriter(stream, Encoding.Default, bufferSize: 4096, leaveOpen: true)) + { + writer.Write(inputData); + } + + stream.Position = 0; + + return InvokeProcess(executionWorkingDirectory, command, environmentVariables, stream); + } + } + public static ProcessResult InvokeProcess(string executionWorkingDirectory, string command, Dictionary environmentVariables = null, Stream inputStream = null) { ProcessStartInfo processInfo = new ProcessStartInfo(Properties.Settings.Default.PathToGit); diff --git a/Scalar.FunctionalTests/Tools/ProcessHelper.cs b/Scalar.FunctionalTests/Tools/ProcessHelper.cs index 764041c792..5db805cf65 100644 --- a/Scalar.FunctionalTests/Tools/ProcessHelper.cs +++ b/Scalar.FunctionalTests/Tools/ProcessHelper.cs @@ -60,6 +60,7 @@ private static string StartProcess(Process executingProcess, Stream inputStream if (inputStream != null) { inputStream.CopyTo(executingProcess.StandardInput.BaseStream); + executingProcess.StandardInput.BaseStream.Close(); } if (executingProcess.StartInfo.RedirectStandardError) diff --git a/Scalar.FunctionalTests/Tools/ScalarFunctionalTestEnlistment.cs b/Scalar.FunctionalTests/Tools/ScalarFunctionalTestEnlistment.cs index 6344374b58..0f507e38ab 100644 --- a/Scalar.FunctionalTests/Tools/ScalarFunctionalTestEnlistment.cs +++ b/Scalar.FunctionalTests/Tools/ScalarFunctionalTestEnlistment.cs @@ -17,14 +17,16 @@ public class ScalarFunctionalTestEnlistment private const int SleepMSWaitingForStatusCheck = 100; private const int DefaultMaxWaitMSForStatusCheck = 5000; private static readonly string ZeroBackgroundOperations = "Background operations: 0" + Environment.NewLine; + private readonly bool fullClone; private ScalarProcess scalarProcess; - private ScalarFunctionalTestEnlistment(string pathToScalar, string enlistmentRoot, string repoUrl, string commitish, string localCacheRoot = null) + private ScalarFunctionalTestEnlistment(string pathToScalar, string enlistmentRoot, string repoUrl, string commitish, string localCacheRoot = null, bool fullClone = true) { this.EnlistmentRoot = enlistmentRoot; this.RepoUrl = repoUrl; this.Commitish = commitish; + this.fullClone = fullClone; if (localCacheRoot == null) { @@ -86,17 +88,18 @@ public static ScalarFunctionalTestEnlistment CloneAndMountWithPerRepoCache(strin { string enlistmentRoot = ScalarFunctionalTestEnlistment.GetUniqueEnlistmentRoot(); string localCache = ScalarFunctionalTestEnlistment.GetRepoSpecificLocalCacheRoot(enlistmentRoot); - return CloneAndMount(pathToGvfs, enlistmentRoot, null, localCache, skipPrefetch); + return CloneAndMount(pathToGvfs, enlistmentRoot, null, localCacheRoot: localCache, skipPrefetch: skipPrefetch); } public static ScalarFunctionalTestEnlistment CloneAndMount( string pathToGvfs, string commitish = null, string localCacheRoot = null, - bool skipPrefetch = false) + bool skipPrefetch = false, + bool fullClone = true) { string enlistmentRoot = ScalarFunctionalTestEnlistment.GetUniqueEnlistmentRoot(); - return CloneAndMount(pathToGvfs, enlistmentRoot, commitish, localCacheRoot, skipPrefetch); + return CloneAndMount(pathToGvfs, enlistmentRoot, commitish, localCacheRoot, skipPrefetch, fullClone); } public static ScalarFunctionalTestEnlistment CloneAndMountEnlistmentWithSpacesInPath(string pathToGvfs, string commitish = null) @@ -157,7 +160,7 @@ public void DeleteEnlistment() public void CloneAndMount(bool skipPrefetch) { - this.scalarProcess.Clone(this.RepoUrl, this.Commitish, skipPrefetch); + this.scalarProcess.Clone(this.RepoUrl, this.Commitish, skipPrefetch, fullClone: this.fullClone); GitProcess.Invoke(this.RepoRoot, "checkout " + this.Commitish); GitProcess.Invoke(this.RepoRoot, "branch --unset-upstream"); @@ -281,14 +284,15 @@ public string GetObjectPathTo(string objectHash) objectHash.Substring(2)); } - private static ScalarFunctionalTestEnlistment CloneAndMount(string pathToGvfs, string enlistmentRoot, string commitish, string localCacheRoot, bool skipPrefetch = false) + private static ScalarFunctionalTestEnlistment CloneAndMount(string pathToGvfs, string enlistmentRoot, string commitish, string localCacheRoot, bool skipPrefetch = false, bool fullClone = true) { ScalarFunctionalTestEnlistment enlistment = new ScalarFunctionalTestEnlistment( pathToGvfs, enlistmentRoot ?? GetUniqueEnlistmentRoot(), ScalarTestConfig.RepoToClone, commitish ?? Properties.Settings.Default.Commitish, - localCacheRoot ?? ScalarTestConfig.LocalCacheRoot); + localCacheRoot ?? ScalarTestConfig.LocalCacheRoot, + fullClone); try { diff --git a/Scalar.FunctionalTests/Tools/ScalarProcess.cs b/Scalar.FunctionalTests/Tools/ScalarProcess.cs index aa417eb019..e0194e0b2e 100644 --- a/Scalar.FunctionalTests/Tools/ScalarProcess.cs +++ b/Scalar.FunctionalTests/Tools/ScalarProcess.cs @@ -1,7 +1,8 @@ using Scalar.Tests.Should; -using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Text; namespace Scalar.FunctionalTests.Tools { @@ -27,13 +28,14 @@ public ScalarProcess(string pathToScalar, string enlistmentRoot, string localCac this.localCacheRoot = localCacheRoot; } - public void Clone(string repositorySource, string branchToCheckout, bool skipPrefetch) + public void Clone(string repositorySource, string branchToCheckout, bool skipPrefetch, bool fullClone = true) { // TODO: consider sparse clone for functional tests string args = string.Format( - "clone \"{0}\" \"{1}\" --full-clone --branch \"{2}\" --local-cache-path \"{3}\" {4}", + "clone \"{0}\" \"{1}\" {2} --branch \"{3}\" --local-cache-path \"{4}\" {5}", repositorySource, this.enlistmentRoot, + fullClone ? "--full-clone" : string.Empty, branchToCheckout, this.localCacheRoot, skipPrefetch ? "--no-prefetch" : string.Empty); @@ -56,52 +58,23 @@ public bool TryMount(out string output) return this.IsEnlistmentMounted(); } - public string AddSparseFolders(params string[] folders) - { - return this.SparseCommand(addFolders: true, shouldSucceed: true, folders: folders); - } - - public string AddSparseFolders(bool shouldSucceed, params string[] folders) - { - return this.SparseCommand(addFolders: true, shouldSucceed: shouldSucceed, folders: folders); - } - - public string RemoveSparseFolders(params string[] folders) + public string Prefetch(string args, bool failOnError, string standardInput = null) { - return this.SparseCommand(addFolders: false, shouldSucceed: true, folders: folders); + return this.CallScalar("prefetch \"" + this.enlistmentRoot + "\" " + args, failOnError ? SuccessExitCode : DoNotCheckExitCode, standardInput: standardInput); } - public string RemoveSparseFolders(bool shouldSucceed, params string[] folders) + public string SparseAdd(IEnumerable folders) { - return this.SparseCommand(addFolders: false, shouldSucceed: shouldSucceed, folders: folders); - } + StringBuilder sb = new StringBuilder(); - public string SparseCommand(bool addFolders, bool shouldSucceed, params string[] folders) - { - string action = addFolders ? "-a" : "-r"; - string folderList = string.Join(";", folders); - if (folderList.Contains(" ")) + foreach (string folder in folders) { - folderList = $"\"{folderList}\""; + sb.Append(folder.Replace(Path.DirectorySeparatorChar, TestConstants.GitPathSeparator) + .Trim(TestConstants.GitPathSeparator)); + sb.Append("\n"); } - return this.CallScalar($"sparse {this.enlistmentRoot} {action} {folderList}", expectedExitCode: shouldSucceed ? SuccessExitCode : ExitCodeShouldNotBeZero); - } - - public string[] GetSparseFolders() - { - string output = this.CallScalar($"sparse {this.enlistmentRoot} -l"); - if (output.StartsWith("No folders in sparse list.")) - { - return new string[0]; - } - - return output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); - } - - public string Prefetch(string args, bool failOnError, string standardInput = null) - { - return this.CallScalar("prefetch \"" + this.enlistmentRoot + "\" " + args, failOnError ? SuccessExitCode : DoNotCheckExitCode, standardInput: standardInput); + return this.CallScalar("sparse --add-stdin \"" + this.enlistmentRoot + "\" ", SuccessExitCode, standardInput: sb.ToString()); } public void Repair(bool confirm) diff --git a/Scalar/CommandLine/CloneVerb.cs b/Scalar/CommandLine/CloneVerb.cs index e175732784..b4ccccbef1 100644 --- a/Scalar/CommandLine/CloneVerb.cs +++ b/Scalar/CommandLine/CloneVerb.cs @@ -18,11 +18,25 @@ public class CloneVerb : ScalarVerb { private const string CloneVerbName = "clone"; + private JsonTracer tracer; + private ScalarEnlistment enlistment; + private CacheServerResolver cacheServerResolver; + private CacheServerInfo cacheServer; + private ServerScalarConfig serverScalarConfig; + private RetryConfig retryConfig; + private GitObjectsHttpRequestor objectRequestor; + private GitRepo gitRepo; + private ScalarGitObjects gitObjects; + private GitProcess git; + private GitRefs refs; + private ScalarContext context; + private PhysicalFileSystem fileSystem = new PhysicalFileSystem(); + [Value( - 0, - Required = true, - MetaName = "Repository URL", - HelpText = "The url of the repo")] + 0, + Required = true, + MetaName = "Repository URL", + HelpText = "The url of the repo")] public string RepositoryURL { get; set; } [Value( @@ -111,29 +125,63 @@ public override void Execute() this.CheckNotInsideExistingRepo(normalizedEnlistmentRootPath); this.BlockEmptyCacheServerUrl(this.CacheServerUrl); - try + Result cloneResult = new Result(false); + + using (this.tracer = new JsonTracer(ScalarConstants.ScalarEtwProviderName, "ScalarClone")) { - ScalarEnlistment enlistment; - Result cloneResult = new Result(false); + try + { + cloneResult = this.DoClone(fullEnlistmentRootPathParameter, normalizedEnlistmentRootPath); + } + catch (AggregateException e) + { + this.Output.WriteLine("Cannot clone @ {0}:", fullEnlistmentRootPathParameter); + foreach (Exception ex in e.Flatten().InnerExceptions) + { + this.Output.WriteLine("Exception: {0}", ex.ToString()); + } - CacheServerInfo cacheServer = null; - ServerScalarConfig serverScalarConfig = null; + cloneResult = new Result(false); + } + catch (Exception e) when (!(e is VerbAbortedException)) + { + this.ReportErrorAndExit("Cannot clone @ {0}: {1}", fullEnlistmentRootPathParameter, e.ToString()); + } - using (JsonTracer tracer = new JsonTracer(ScalarConstants.ScalarEtwProviderName, "ScalarClone")) + if (!cloneResult.Success) + { + this.tracer.RelatedError(cloneResult.ErrorMessage); + this.Output.WriteLine(); + this.Output.WriteLine("Cannot clone @ {0}", fullEnlistmentRootPathParameter); + this.Output.WriteLine("Error: {0}", cloneResult.ErrorMessage); + exitCode = (int)ReturnCode.GenericError; + } + } + + Environment.Exit(exitCode); + } + + private Result DoClone(string fullEnlistmentRootPathParameter, string normalizedEnlistmentRootPath) + { + Result cloneResult = null; + cloneResult = this.TryCreateEnlistment(fullEnlistmentRootPathParameter, normalizedEnlistmentRootPath, out this.enlistment); + + if (!cloneResult.Success) + { + this.tracer.RelatedError($"Error while creating enlistment: {cloneResult.ErrorMessage}"); + return cloneResult; + } + + this.tracer.AddLogFileEventListener( + ScalarEnlistment.GetNewScalarLogFileName(this.enlistment.ScalarLogsRoot, ScalarConstants.LogFileTypes.Clone), + EventLevel.Informational, + Keywords.Any); + this.tracer.WriteStartEvent( + this.enlistment.EnlistmentRoot, + this.enlistment.RepoUrl, + this.CacheServerUrl, + new EventMetadata { - cloneResult = this.TryCreateEnlistment(fullEnlistmentRootPathParameter, normalizedEnlistmentRootPath, out enlistment); - if (cloneResult.Success) - { - tracer.AddLogFileEventListener( - ScalarEnlistment.GetNewScalarLogFileName(enlistment.ScalarLogsRoot, ScalarConstants.LogFileTypes.Clone), - EventLevel.Informational, - Keywords.Any); - tracer.WriteStartEvent( - enlistment.EnlistmentRoot, - enlistment.RepoUrl, - this.CacheServerUrl, - new EventMetadata - { { "Branch", this.Branch }, { "LocalCacheRoot", this.LocalCacheRoot }, { "SingleBranch", this.SingleBranch }, @@ -141,129 +189,109 @@ public override void Execute() { "NoPrefetch", this.NoPrefetch }, { "Unattended", this.Unattended }, { "IsElevated", ScalarPlatform.Instance.IsElevated() }, - { "NamedPipeName", enlistment.NamedPipeName }, + { "NamedPipeName", this.enlistment.NamedPipeName }, { "ProcessID", Process.GetCurrentProcess().Id }, { nameof(this.EnlistmentRootPathParameter), this.EnlistmentRootPathParameter }, { nameof(fullEnlistmentRootPathParameter), fullEnlistmentRootPathParameter }, - }); + }); - CacheServerResolver cacheServerResolver = new CacheServerResolver(tracer, enlistment); - cacheServer = cacheServerResolver.ParseUrlOrFriendlyName(this.CacheServerUrl); + this.cacheServerResolver = new CacheServerResolver(this.tracer, this.enlistment); + this.cacheServer = this.cacheServerResolver.ParseUrlOrFriendlyName(this.CacheServerUrl); - string resolvedLocalCacheRoot; - if (string.IsNullOrWhiteSpace(this.LocalCacheRoot)) - { - string localCacheRootError; - if (!LocalCacheResolver.TryGetDefaultLocalCacheRoot(enlistment, out resolvedLocalCacheRoot, out localCacheRootError)) - { - this.ReportErrorAndExit( - tracer, - $"Failed to determine the default location for the local Scalar cache: `{localCacheRootError}`"); - } - } - else - { - resolvedLocalCacheRoot = Path.GetFullPath(this.LocalCacheRoot); - } - - this.Output.WriteLine("Clone parameters:"); - this.Output.WriteLine(" Repo URL: " + enlistment.RepoUrl); - this.Output.WriteLine(" Branch: " + (string.IsNullOrWhiteSpace(this.Branch) ? "Default" : this.Branch)); - this.Output.WriteLine(" Cache Server: " + cacheServer); - this.Output.WriteLine(" Local Cache: " + resolvedLocalCacheRoot); - this.Output.WriteLine(" Destination: " + enlistment.EnlistmentRoot); - this.Output.WriteLine(" FullClone: " + this.FullClone); - - string authErrorMessage; - if (!this.TryAuthenticate(tracer, enlistment, out authErrorMessage)) - { - this.ReportErrorAndExit(tracer, "Cannot clone because authentication failed: " + authErrorMessage); - } + string resolvedLocalCacheRoot; + if (string.IsNullOrWhiteSpace(this.LocalCacheRoot)) + { + if (!LocalCacheResolver.TryGetDefaultLocalCacheRoot(this.enlistment, out resolvedLocalCacheRoot, out string localCacheRootError)) + { + this.ReportErrorAndExit( + this.tracer, + $"Failed to determine the default location for the local Scalar cache: `{localCacheRootError}`"); + } + } + else + { + resolvedLocalCacheRoot = Path.GetFullPath(this.LocalCacheRoot); + } - RetryConfig retryConfig = this.GetRetryConfig(tracer, enlistment, TimeSpan.FromMinutes(RetryConfig.FetchAndCloneTimeoutMinutes)); - serverScalarConfig = this.QueryScalarConfig(tracer, enlistment, retryConfig); + this.Output.WriteLine("Clone parameters:"); + this.Output.WriteLine(" Repo URL: " + this.enlistment.RepoUrl); + this.Output.WriteLine(" Branch: " + (string.IsNullOrWhiteSpace(this.Branch) ? "Default" : this.Branch)); + this.Output.WriteLine(" Cache Server: " + this.cacheServer); + this.Output.WriteLine(" Local Cache: " + resolvedLocalCacheRoot); + this.Output.WriteLine(" Destination: " + this.enlistment.EnlistmentRoot); + this.Output.WriteLine(" FullClone: " + this.FullClone); - cacheServer = this.ResolveCacheServer(tracer, cacheServer, cacheServerResolver, serverScalarConfig); + string authErrorMessage; + if (!this.TryAuthenticate(this.tracer, this.enlistment, out authErrorMessage)) + { + this.ReportErrorAndExit(this.tracer, "Cannot clone because authentication failed: " + authErrorMessage); + } - this.ValidateClientVersions(tracer, enlistment, serverScalarConfig, showWarnings: true); + this.retryConfig = this.GetRetryConfig(this.tracer, this.enlistment, TimeSpan.FromMinutes(RetryConfig.FetchAndCloneTimeoutMinutes)); + this.serverScalarConfig = this.QueryScalarConfig(this.tracer, this.enlistment, this.retryConfig); - this.ShowStatusWhileRunning( - () => - { - cloneResult = this.TryClone(tracer, enlistment, cacheServer, retryConfig, serverScalarConfig, resolvedLocalCacheRoot); - return cloneResult.Success; - }, - "Cloning", - normalizedEnlistmentRootPath); - } + this.cacheServer = this.ResolveCacheServer(this.tracer, this.cacheServer, this.cacheServerResolver, this.serverScalarConfig); - if (!cloneResult.Success) - { - tracer.RelatedError(cloneResult.ErrorMessage); - } + this.ValidateClientVersions(this.tracer, this.enlistment, this.serverScalarConfig, showWarnings: true); + + using (this.objectRequestor = new GitObjectsHttpRequestor(this.tracer, this.enlistment, this.cacheServer, this.retryConfig)) + { + cloneResult = this.CreateScalarDirctories(resolvedLocalCacheRoot); + + if (!cloneResult.Success) + { + this.tracer.RelatedError(cloneResult.ErrorMessage); + return cloneResult; } - if (cloneResult.Success) + this.ShowStatusWhileRunning( + () => { - if (!this.NoPrefetch) - { - ReturnCode result = this.Execute( - enlistment, - verb => - { - verb.Commits = true; - verb.SkipVersionCheck = true; - verb.ResolvedCacheServer = cacheServer; - verb.ServerScalarConfig = serverScalarConfig; - }); - - if (result != ReturnCode.Success) - { - this.Output.WriteLine("\r\nError during prefetch @ {0}", fullEnlistmentRootPathParameter); - exitCode = (int)result; - } - } + cloneResult = this.CreateClone(); + return cloneResult.Success; + }, + "Cloning", + normalizedEnlistmentRootPath); + + if (!cloneResult.Success) + { + this.tracer.RelatedError(cloneResult.ErrorMessage); + return cloneResult; + } - this.Execute( - enlistment, + if (!this.NoPrefetch) + { + ReturnCode result = this.Execute( + this.enlistment, verb => { - verb.SkipMountedCheck = true; + verb.Commits = true; verb.SkipVersionCheck = true; - verb.ResolvedCacheServer = cacheServer; - verb.DownloadedScalarConfig = serverScalarConfig; + verb.ResolvedCacheServer = this.cacheServer; + verb.ServerScalarConfig = this.serverScalarConfig; }); - GitProcess git = new GitProcess(enlistment); - git.ForceCheckoutAllFiles(); - } - else - { - this.Output.WriteLine("\r\nCannot clone @ {0}", fullEnlistmentRootPathParameter); - this.Output.WriteLine("Error: {0}", cloneResult.ErrorMessage); - exitCode = (int)ReturnCode.GenericError; - } - } - catch (AggregateException e) - { - this.Output.WriteLine("Cannot clone @ {0}:", fullEnlistmentRootPathParameter); - foreach (Exception ex in e.Flatten().InnerExceptions) - { - this.Output.WriteLine("Exception: {0}", ex.ToString()); + if (result != ReturnCode.Success) + { + this.Output.WriteLine("\r\nError during prefetch @ {0}", fullEnlistmentRootPathParameter); + return cloneResult; + } } - exitCode = (int)ReturnCode.GenericError; - } - catch (VerbAbortedException) - { - throw; - } - catch (Exception e) - { - this.ReportErrorAndExit("Cannot clone @ {0}: {1}", fullEnlistmentRootPathParameter, e.ToString()); + this.Execute( + this.enlistment, + verb => + { + verb.SkipMountedCheck = true; + verb.SkipVersionCheck = true; + verb.ResolvedCacheServer = this.cacheServer; + verb.DownloadedScalarConfig = this.serverScalarConfig; + }); + + cloneResult = this.CheckoutRepo(); } - Environment.Exit(exitCode); + return cloneResult; } private Result TryCreateEnlistment( @@ -307,71 +335,62 @@ private Result TryCreateEnlistment( return new Result(true); } - private Result TryClone( - JsonTracer tracer, - ScalarEnlistment enlistment, - CacheServerInfo cacheServer, - RetryConfig retryConfig, - ServerScalarConfig serverScalarConfig, - string resolvedLocalCacheRoot) + private Result CreateScalarDirctories(string resolvedLocalCacheRoot) { - using (GitObjectsHttpRequestor objectRequestor = new GitObjectsHttpRequestor(tracer, enlistment, cacheServer, retryConfig)) + this.refs = this.objectRequestor.QueryInfoRefs(this.SingleBranch ? this.Branch : null); + + if (this.refs == null) { - GitRefs refs = objectRequestor.QueryInfoRefs(this.SingleBranch ? this.Branch : null); + return new Result("Could not query info/refs from: " + Uri.EscapeUriString(this.enlistment.RepoUrl)); + } - if (refs == null) - { - return new Result("Could not query info/refs from: " + Uri.EscapeUriString(enlistment.RepoUrl)); - } + if (this.Branch == null) + { + this.Branch = this.refs.GetDefaultBranch(); - if (this.Branch == null) + EventMetadata metadata = new EventMetadata(); + metadata.Add("Branch", this.Branch); + this.tracer.RelatedEvent(EventLevel.Informational, "CloneDefaultRemoteBranch", metadata); + } + else + { + if (!this.refs.HasBranch(this.Branch)) { - this.Branch = refs.GetDefaultBranch(); - EventMetadata metadata = new EventMetadata(); metadata.Add("Branch", this.Branch); - tracer.RelatedEvent(EventLevel.Informational, "CloneDefaultRemoteBranch", metadata); - } - else - { - if (!refs.HasBranch(this.Branch)) - { - EventMetadata metadata = new EventMetadata(); - metadata.Add("Branch", this.Branch); - tracer.RelatedEvent(EventLevel.Warning, "CloneBranchDoesNotExist", metadata); + this.tracer.RelatedEvent(EventLevel.Warning, "CloneBranchDoesNotExist", metadata); - string errorMessage = string.Format("Remote branch {0} not found in upstream origin", this.Branch); - return new Result(errorMessage); - } + string errorMessage = string.Format("Remote branch {0} not found in upstream origin", this.Branch); + return new Result(errorMessage); } + } - if (!enlistment.TryCreateEnlistmentFolders()) - { - string error = "Could not create enlistment directory"; - tracer.RelatedError(error); - return new Result(error); - } + if (!this.enlistment.TryCreateEnlistmentFolders()) + { + string error = "Could not create enlistment directory"; + this.tracer.RelatedError(error); + return new Result(error); + } - if (!ScalarPlatform.Instance.FileSystem.IsFileSystemSupported(enlistment.EnlistmentRoot, out string fsError)) - { - string error = $"FileSystem unsupported: {fsError}"; - tracer.RelatedError(error); - return new Result(error); - } + if (!ScalarPlatform.Instance.FileSystem.IsFileSystemSupported(this.enlistment.EnlistmentRoot, out string fsError)) + { + string error = $"FileSystem unsupported: {fsError}"; + this.tracer.RelatedError(error); + return new Result(error); + } - string localCacheError; - if (!this.TryDetermineLocalCacheAndInitializePaths(tracer, enlistment, serverScalarConfig, cacheServer, resolvedLocalCacheRoot, out localCacheError)) - { - tracer.RelatedError(localCacheError); - return new Result(localCacheError); - } + string localCacheError; + if (!this.TryDetermineLocalCacheAndInitializePaths(resolvedLocalCacheRoot, out localCacheError)) + { + this.tracer.RelatedError(localCacheError); + return new Result(localCacheError); + } - Directory.CreateDirectory(enlistment.GitObjectsRoot); - Directory.CreateDirectory(enlistment.GitPackRoot); - Directory.CreateDirectory(enlistment.BlobSizesRoot); + Directory.CreateDirectory(this.enlistment.GitObjectsRoot); + Directory.CreateDirectory(this.enlistment.GitPackRoot); + Directory.CreateDirectory(this.enlistment.BlobSizesRoot); - return this.CreateClone(tracer, enlistment, objectRequestor, refs, this.Branch); - } + return new Result(true); } private string GetCloneRoot(out string fullEnlistmentRootPathParameter) @@ -420,23 +439,17 @@ private void CheckNotInsideExistingRepo(string normalizedEnlistmentRootPath) } } - private bool TryDetermineLocalCacheAndInitializePaths( - ITracer tracer, - ScalarEnlistment enlistment, - ServerScalarConfig serverScalarConfig, - CacheServerInfo currentCacheServer, - string localCacheRoot, - out string errorMessage) + private bool TryDetermineLocalCacheAndInitializePaths(string localCacheRoot, out string errorMessage) { errorMessage = null; - LocalCacheResolver localCacheResolver = new LocalCacheResolver(enlistment); + LocalCacheResolver localCacheResolver = new LocalCacheResolver(this.enlistment); string error; string localCacheKey; if (!localCacheResolver.TryGetLocalCacheKeyFromLocalConfigOrRemoteCacheServers( - tracer, - serverScalarConfig, - currentCacheServer, + this.tracer, + this.serverScalarConfig, + this.cacheServer, localCacheRoot, localCacheKey: out localCacheKey, errorMessage: out error)) @@ -449,147 +462,92 @@ private bool TryDetermineLocalCacheAndInitializePaths( metadata.Add("localCacheRoot", localCacheRoot); metadata.Add("localCacheKey", localCacheKey); metadata.Add(TracingConstants.MessageKey.InfoMessage, "Initializing cache paths"); - tracer.RelatedEvent(EventLevel.Informational, "CloneVerb_TryDetermineLocalCacheAndInitializePaths", metadata); + this.tracer.RelatedEvent(EventLevel.Informational, "CloneVerb_TryDetermineLocalCacheAndInitializePaths", metadata); - enlistment.InitializeCachePathsFromKey(localCacheRoot, localCacheKey); + this.enlistment.InitializeCachePathsFromKey(localCacheRoot, localCacheKey); return true; } - private Result CreateClone( - ITracer tracer, - ScalarEnlistment enlistment, - GitObjectsHttpRequestor objectRequestor, - GitRefs refs, - string branch) + private Result CreateClone() { - Result initRepoResult = this.TryInitRepo(tracer, refs, enlistment); + Result initRepoResult = this.TryInitRepo(); if (!initRepoResult.Success) { return initRepoResult; } - PhysicalFileSystem fileSystem = new PhysicalFileSystem(); string errorMessage; - if (!this.TryCreateAlternatesFile(fileSystem, enlistment, out errorMessage)) + if (!this.TryCreateAlternatesFile(this.fileSystem, this.enlistment, out errorMessage)) { return new Result("Error configuring alternate: " + errorMessage); } - GitRepo gitRepo = new GitRepo(tracer, enlistment, fileSystem); - ScalarContext context = new ScalarContext(tracer, fileSystem, gitRepo, enlistment); - ScalarGitObjects gitObjects = new ScalarGitObjects(context, objectRequestor); + this.gitRepo = new GitRepo(this.tracer, this.enlistment, this.fileSystem); + this.context = new ScalarContext(this.tracer, this.fileSystem, this.gitRepo, this.enlistment); + this.gitObjects = new ScalarGitObjects(this.context, this.objectRequestor); if (!this.TryDownloadCommit( - refs.GetTipCommitId(branch), - enlistment, - objectRequestor, - gitObjects, - gitRepo, + this.refs.GetTipCommitId(this.Branch), + this.enlistment, + this.objectRequestor, + this.gitObjects, + this.gitRepo, out errorMessage)) { return new Result(errorMessage); } - if (!ScalarVerb.TrySetRequiredGitConfigSettings(enlistment) || - !ScalarVerb.TrySetOptionalGitConfigSettings(enlistment)) + if (!ScalarVerb.TrySetRequiredGitConfigSettings(this.enlistment) || + !ScalarVerb.TrySetOptionalGitConfigSettings(this.enlistment)) { return new Result("Unable to configure git repo"); } - CacheServerResolver cacheServerResolver = new CacheServerResolver(tracer, enlistment); - if (!cacheServerResolver.TrySaveUrlToLocalConfig(objectRequestor.CacheServer, out errorMessage)) + CacheServerResolver cacheServerResolver = new CacheServerResolver(this.tracer, this.enlistment); + if (!cacheServerResolver.TrySaveUrlToLocalConfig(this.objectRequestor.CacheServer, out errorMessage)) { return new Result("Unable to configure cache server: " + errorMessage); } - GitProcess git = new GitProcess(enlistment); - string originBranchName = "origin/" + branch; - GitProcess.Result createBranchResult = git.CreateBranchWithUpstream(branch, originBranchName); + this.git = new GitProcess(this.enlistment); + string originBranchName = "origin/" + this.Branch; + GitProcess.Result createBranchResult = this.git.CreateBranchWithUpstream(this.Branch, originBranchName); if (createBranchResult.ExitCodeIsFailure) { return new Result("Unable to create branch '" + originBranchName + "': " + createBranchResult.Errors + "\r\n" + createBranchResult.Output); } File.WriteAllText( - Path.Combine(enlistment.WorkingDirectoryBackingRoot, ScalarConstants.DotGit.Head), - "ref: refs/heads/" + branch); + Path.Combine(this.enlistment.WorkingDirectoryBackingRoot, ScalarConstants.DotGit.Head), + "ref: refs/heads/" + this.Branch); - if (!this.TryDownloadRootGitAttributes(enlistment, gitObjects, gitRepo, out errorMessage)) + if (!this.TryDownloadRootGitAttributes(this.enlistment, this.gitObjects, this.gitRepo, out errorMessage)) { return new Result(errorMessage); } - this.CreateGitScript(enlistment); - string installHooksError; - if (!HooksInstaller.InstallHooks(context, out installHooksError)) + if (!HooksInstaller.InstallHooks(this.context, out installHooksError)) { - tracer.RelatedError(installHooksError); + this.tracer.RelatedError(installHooksError); return new Result(installHooksError); } - if (this.FullClone) - { - BlobPrefetcher prefetcher = new BlobPrefetcher( - tracer, - enlistment, - objectRequestor, - fileList: new List() { "*" }, - folderList: null, - lastPrefetchArgs: null, - chunkSize: 4000, - searchThreadCount: Environment.ProcessorCount, - downloadThreadCount: Environment.ProcessorCount, - indexThreadCount: Environment.ProcessorCount); - prefetcher.PrefetchWithStats( - branch, - isBranch: true, - hydrateFilesAfterDownload: false, - matchedBlobCount: out int _, - downloadedBlobCount: out int _, - hydratedFileCount: out int _); - } - - GitProcess.Result forceCheckoutResult = git.ForceCheckout(branch); - if (forceCheckoutResult.ExitCodeIsFailure && forceCheckoutResult.Errors.IndexOf("unable to read tree") > 0) - { - // It is possible to have the above TryDownloadCommit() fail because we - // already have the commit and root tree we intend to check out, but - // don't have a tree further down the working directory. If we fail - // checkout here, its' because we don't have these trees and the - // read-object hook is not available yet. Force downloading the commit - // again and retry the checkout. - - if (!this.TryDownloadCommit( - refs.GetTipCommitId(branch), - enlistment, - objectRequestor, - gitObjects, - gitRepo, - out errorMessage, - checkLocalObjectCache: false)) - { - return new Result(errorMessage); - } - - forceCheckoutResult = git.ForceCheckout(branch); - } - - if (!RepoMetadata.TryInitialize(tracer, enlistment.DotScalarRoot, out errorMessage)) + if (!RepoMetadata.TryInitialize(this.tracer, this.enlistment.DotScalarRoot, out errorMessage)) { - tracer.RelatedError(errorMessage); + this.tracer.RelatedError(errorMessage); return new Result(errorMessage); } try { - RepoMetadata.Instance.SaveCloneMetadata(tracer, enlistment); - this.LogEnlistmentInfoAndSetConfigValues(tracer, git, enlistment); + RepoMetadata.Instance.SaveCloneMetadata(this.tracer, this.enlistment); + this.LogEnlistmentInfoAndSetConfigValues(this.tracer, this.git, this.enlistment); } catch (Exception e) { - tracer.RelatedError(e.ToString()); + this.tracer.RelatedError(e.ToString()); return new Result(e.Message); } finally @@ -600,61 +558,36 @@ private Result CreateClone( return new Result(true); } - // TODO(#1364): Don't call this method on POSIX platforms (or have it no-op on them) - private void CreateGitScript(ScalarEnlistment enlistment) - { - FileInfo gitCmd = new FileInfo(Path.Combine(enlistment.EnlistmentRoot, "git.cmd")); - using (FileStream fs = gitCmd.Create()) - using (StreamWriter writer = new StreamWriter(fs)) - { - writer.Write( -@" -@echo OFF -echo . -echo ^ -echo This repo was cloned using Scalar, and the git repo is in the 'src' directory -echo Switching you to the 'src' directory and rerunning your git command -echo  - -@echo ON -cd src -git %* -"); - } - - gitCmd.Attributes = FileAttributes.Hidden; - } - - private Result TryInitRepo(ITracer tracer, GitRefs refs, Enlistment enlistmentToInit) + private Result TryInitRepo() { - string repoPath = enlistmentToInit.WorkingDirectoryBackingRoot; - GitProcess.Result initResult = GitProcess.Init(enlistmentToInit); + string repoPath = this.enlistment.WorkingDirectoryBackingRoot; + GitProcess.Result initResult = GitProcess.Init(this.enlistment); if (initResult.ExitCodeIsFailure) { string error = string.Format("Could not init repo at to {0}: {1}", repoPath, initResult.Errors); - tracer.RelatedError(error); + this.tracer.RelatedError(error); return new Result(error); } - GitProcess.Result remoteAddResult = new GitProcess(enlistmentToInit).RemoteAdd("origin", enlistmentToInit.RepoUrl); + GitProcess.Result remoteAddResult = new GitProcess(this.enlistment).RemoteAdd("origin", this.enlistment.RepoUrl); if (remoteAddResult.ExitCodeIsFailure) { string error = string.Format("Could not add remote to {0}: {1}", repoPath, remoteAddResult.Errors); - tracer.RelatedError(error); + this.tracer.RelatedError(error); return new Result(error); } File.WriteAllText( Path.Combine(repoPath, ScalarConstants.DotGit.PackedRefs), - refs.ToPackedRefs()); + this.refs.ToPackedRefs()); if (!this.FullClone) { - GitProcess.Result sparseCheckoutResult = GitProcess.SparseCheckoutInit(enlistmentToInit); + GitProcess.Result sparseCheckoutResult = GitProcess.SparseCheckoutInit(this.enlistment); if (sparseCheckoutResult.ExitCodeIsFailure) { string error = string.Format("Could not init sparse-checkout at to {0}: {1}", repoPath, sparseCheckoutResult.Errors); - tracer.RelatedError(error); + this.tracer.RelatedError(error); return new Result(error); } } @@ -662,6 +595,43 @@ private Result TryInitRepo(ITracer tracer, GitRefs refs, Enlistment enlistmentTo return new Result(true); } + private Result CheckoutRepo() + { + if (this.FullClone) + { + BlobPrefetcher prefetcher = new BlobPrefetcher( + this.tracer, + this.enlistment, + this.objectRequestor, + fileList: new List() { "*" }, + folderList: null, + lastPrefetchArgs: null, + chunkSize: 4000, + searchThreadCount: Environment.ProcessorCount, + downloadThreadCount: Environment.ProcessorCount, + indexThreadCount: Environment.ProcessorCount); + prefetcher.PrefetchWithStats( + this.Branch, + isBranch: true, + hydrateFilesAfterDownload: false, + matchedBlobCount: out int _, + downloadedBlobCount: out int _, + hydratedFileCount: out int _); + + if (prefetcher.HasFailures) + { + return new Result("Failures while prefetching blobs"); + } + } + + if (this.git.ForceCheckout(this.Branch).ExitCodeIsFailure) + { + return new Result("Failed to checkout repo"); + } + + return new Result(true); + } + private class Result { public Result(bool success) diff --git a/Scalar/CommandLine/ScalarVerb.cs b/Scalar/CommandLine/ScalarVerb.cs index a52dd12038..f5c2c08516 100644 --- a/Scalar/CommandLine/ScalarVerb.cs +++ b/Scalar/CommandLine/ScalarVerb.cs @@ -117,7 +117,7 @@ public static bool TrySetRequiredGitConfigSettings(Enlistment enlistment) { "core.multiPackIndex", "true" }, { "core.preloadIndex", "true" }, { "core.safecrlf", "false" }, - { "core.untrackedCache", "true" }, + { "core.untrackedCache", "false" }, { "core.repositoryformatversion", "0" }, { "core.filemode", ScalarPlatform.Instance.FileSystem.SupportsFileMode ? "true" : "false" }, { GitConfigSetting.CoreVirtualizeObjectsName, "true" },