From fd1cfc6d1552a6e2588639ba2903c45e79757a11 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Fri, 28 Sep 2018 10:09:46 -0400 Subject: [PATCH 1/2] CloneVerb: Force download of commit and trees on failed initial checkout --- GVFS/GVFS/CommandLine/CloneVerb.cs | 24 ++++++++++++++++++++++++ GVFS/GVFS/CommandLine/GVFSVerb.cs | 27 ++++++++++++++------------- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/GVFS/GVFS/CommandLine/CloneVerb.cs b/GVFS/GVFS/CommandLine/CloneVerb.cs index 5d86b909f..9078e688c 100644 --- a/GVFS/GVFS/CommandLine/CloneVerb.cs +++ b/GVFS/GVFS/CommandLine/CloneVerb.cs @@ -567,6 +567,30 @@ private Result CreateClone( } GitProcess.Result forceCheckoutResult = git.ForceCheckout(branch); + if (forceCheckoutResult.HasErrors && 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, it may be due to not having 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, + ignoreIfRootExists: false)) + { + return new Result(errorMessage); + } + + forceCheckoutResult = git.ForceCheckout(branch); + } + if (forceCheckoutResult.HasErrors) { string[] errorLines = forceCheckoutResult.Errors.Split('\n'); diff --git a/GVFS/GVFS/CommandLine/GVFSVerb.cs b/GVFS/GVFS/CommandLine/GVFSVerb.cs index 325e2dcee..38ee3e1e6 100644 --- a/GVFS/GVFS/CommandLine/GVFSVerb.cs +++ b/GVFS/GVFS/CommandLine/GVFSVerb.cs @@ -176,15 +176,15 @@ protected bool ShowStatusWhileRunning( } protected bool ShowStatusWhileRunning( - Func action, - string message, + Func action, + string message, bool suppressGvfsLogMessage = false) { string gvfsLogEnlistmentRoot = null; if (!suppressGvfsLogMessage) { string errorMessage; - GVFSPlatform.Instance.TryGetGVFSEnlistmentRoot(this.EnlistmentRootPathParameter, out gvfsLogEnlistmentRoot, out errorMessage); + GVFSPlatform.Instance.TryGetGVFSEnlistmentRoot(this.EnlistmentRootPathParameter, out gvfsLogEnlistmentRoot, out errorMessage); } return this.ShowStatusWhileRunning(action, message, gvfsLogEnlistmentRoot); @@ -261,7 +261,7 @@ protected GVFSConfig QueryGVFSConfig(ITracer tracer, GVFSEnlistment enlistment, } return gvfsConfig; - } + } protected void ValidateClientVersions(ITracer tracer, GVFSEnlistment enlistment, GVFSConfig gvfsConfig, bool showWarnings) { @@ -401,9 +401,10 @@ protected bool TryDownloadCommit( GitObjectsHttpRequestor objectRequestor, GVFSGitObjects gitObjects, GitRepo repo, - out string error) + out string error, + bool ignoreIfRootExists = true) { - if (!repo.CommitAndRootTreeExists(commitId)) + if (!ignoreIfRootExists || !repo.CommitAndRootTreeExists(commitId)) { if (!gitObjects.TryDownloadCommit(commitId)) { @@ -740,7 +741,7 @@ public ForExistingEnlistment(bool validateOrigin = true) : base(validateOrigin) MetaName = "Enlistment Root Path", HelpText = "Full or relative path to the GVFS enlistment root")] public override string EnlistmentRootPathParameter { get; set; } - + public sealed override void Execute() { this.ValidatePathParameter(this.EnlistmentRootPathParameter); @@ -855,7 +856,7 @@ private void EnsureLocalCacheIsHealthy( if (File.Exists(alternatesFilePath)) { try - { + { using (Stream stream = fileSystem.OpenFileStream( alternatesFilePath, FileMode.Open, @@ -922,7 +923,7 @@ private void EnsureLocalCacheIsHealthy( gvfsConfig = this.QueryGVFSConfig(tracer, enlistment, retryConfig); } - + string localCacheKey; LocalCacheResolver localCacheResolver = new LocalCacheResolver(enlistment); if (!localCacheResolver.TryGetLocalCacheKeyFromLocalConfigOrRemoteCacheServers( @@ -1009,7 +1010,7 @@ private GVFSEnlistment CreateEnlistment(string enlistmentRootPath) if (GVFSPlatform.Instance.IsUnderConstruction) { hooksPath = "hooksUnderConstruction"; - } + } else { hooksPath = ProcessHelper.WhereDirectory(GVFSPlatform.Instance.Constants.GVFSHooksExecutableName); @@ -1017,8 +1018,8 @@ private GVFSEnlistment CreateEnlistment(string enlistmentRootPath) { this.ReportErrorAndExit("Could not find " + GVFSPlatform.Instance.Constants.GVFSHooksExecutableName); } - } - + } + GVFSEnlistment enlistment = null; try { @@ -1030,7 +1031,7 @@ private GVFSEnlistment CreateEnlistment(string enlistmentRootPath) { enlistment = GVFSEnlistment.CreateWithoutRepoUrlFromDirectory(enlistmentRootPath, gitBinPath, hooksPath); } - + if (enlistment == null) { this.ReportErrorAndExit( From f97b5cdcacfb6ea019ba092a532a619fd50c37da Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Mon, 1 Oct 2018 15:42:21 -0400 Subject: [PATCH 2/2] SharedCacheTests: add test for multi-enlistment checkout failures --- .../MultiEnlistmentTests/SharedCacheTests.cs | 27 +++++++++++++++++-- GVFS/GVFS/CommandLine/CloneVerb.cs | 4 +-- GVFS/GVFS/CommandLine/GVFSVerb.cs | 4 +-- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/GVFS/GVFS.FunctionalTests/Tests/MultiEnlistmentTests/SharedCacheTests.cs b/GVFS/GVFS.FunctionalTests/Tests/MultiEnlistmentTests/SharedCacheTests.cs index de37e9070..2a55c4c5e 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/MultiEnlistmentTests/SharedCacheTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/MultiEnlistmentTests/SharedCacheTests.cs @@ -96,7 +96,7 @@ public void CloneCleansUpStaleMetadataLock() enlistment1.Status().ShouldContain("Mount status: Ready"); enlistment2.Status().ShouldContain("Mount status: Ready"); } - + [TestCase] public void ParallelReadsInASharedCache() { @@ -265,7 +265,30 @@ public void MountUsesNewLocalCacheKeyWhenLocalCacheDeleted() newObjectsRoot.ShouldContain(newCacheKey); newObjectsRoot.ShouldBeADirectory(this.fileSystem); - this.AlternatesFileShouldHaveGitObjectsRoot(enlistment); + this.AlternatesFileShouldHaveGitObjectsRoot(enlistment); + } + + [TestCase] + public void SecondCloneSucceedsWithMissingTrees() + { + string newCachePath = Path.Combine(this.localCacheParentPath, ".customGvfsCache2"); + GVFSFunctionalTestEnlistment enlistment1 = this.CreateNewEnlistment(localCacheRoot: newCachePath); + File.ReadAllText(Path.Combine(enlistment1.RepoRoot, WellKnownFile)); + + this.AlternatesFileShouldHaveGitObjectsRoot(enlistment1); + + string packDir = Path.Combine(enlistment1.GetObjectRoot(this.fileSystem), "pack"); + string[] packDirFiles = Directory.EnumerateFiles(packDir, "*", SearchOption.AllDirectories).ToArray(); + for (int i = 0; i < packDirFiles.Length; i++) + { + this.fileSystem.DeleteFile(Path.Combine(packDir, packDirFiles[i])); + } + + // Enumerate the root directory, populating the tip commit and root tree + this.fileSystem.EnumerateDirectory(enlistment1.RepoRoot); + + GVFSFunctionalTestEnlistment enlistment2 = this.CreateNewEnlistment(localCacheRoot: newCachePath); + this.fileSystem.EnumerateDirectory(enlistment2.RepoRoot); } // Override OnTearDownEnlistmentsDeleted rathern than using [TearDown] as the enlistments need to be unmounted before diff --git a/GVFS/GVFS/CommandLine/CloneVerb.cs b/GVFS/GVFS/CommandLine/CloneVerb.cs index 9078e688c..ef59eea86 100644 --- a/GVFS/GVFS/CommandLine/CloneVerb.cs +++ b/GVFS/GVFS/CommandLine/CloneVerb.cs @@ -572,7 +572,7 @@ private Result CreateClone( // 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, it may be due to not having these trees and the + // 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. @@ -583,7 +583,7 @@ private Result CreateClone( gitObjects, gitRepo, out errorMessage, - ignoreIfRootExists: false)) + checkLocalObjectCache: false)) { return new Result(errorMessage); } diff --git a/GVFS/GVFS/CommandLine/GVFSVerb.cs b/GVFS/GVFS/CommandLine/GVFSVerb.cs index 38ee3e1e6..cb69014d1 100644 --- a/GVFS/GVFS/CommandLine/GVFSVerb.cs +++ b/GVFS/GVFS/CommandLine/GVFSVerb.cs @@ -402,9 +402,9 @@ protected bool TryDownloadCommit( GVFSGitObjects gitObjects, GitRepo repo, out string error, - bool ignoreIfRootExists = true) + bool checkLocalObjectCache = true) { - if (!ignoreIfRootExists || !repo.CommitAndRootTreeExists(commitId)) + if (!checkLocalObjectCache || !repo.CommitAndRootTreeExists(commitId)) { if (!gitObjects.TryDownloadCommit(commitId)) {