예제 #1
0
        private Result CreateClone(
            ITracer tracer,
            GVFSEnlistment enlistment,
            GitObjectsHttpRequestor objectRequestor,
            GitRefs refs,
            string branch)
        {
            Result initRepoResult = this.TryInitRepo(tracer, refs, enlistment);

            if (!initRepoResult.Success)
            {
                return(initRepoResult);
            }

            PhysicalFileSystem fileSystem = new PhysicalFileSystem();
            string             errorMessage;

            if (!this.TryCreateAlternatesFile(fileSystem, enlistment, out errorMessage))
            {
                return(new Result("Error configuring alternate: " + errorMessage));
            }

            GitRepo        gitRepo    = new GitRepo(tracer, enlistment, fileSystem);
            GVFSContext    context    = new GVFSContext(tracer, fileSystem, gitRepo, enlistment);
            GVFSGitObjects gitObjects = new GVFSGitObjects(context, objectRequestor);

            if (!this.TryDownloadCommit(
                    refs.GetTipCommitId(branch),
                    enlistment,
                    objectRequestor,
                    gitObjects,
                    gitRepo,
                    out errorMessage))
            {
                return(new Result(errorMessage));
            }

            if (!GVFSVerb.TrySetRequiredGitConfigSettings(enlistment) ||
                !GVFSVerb.TrySetOptionalGitConfigSettings(enlistment))
            {
                return(new Result("Unable to configure git repo"));
            }

            CacheServerResolver cacheServerResolver = new CacheServerResolver(tracer, enlistment);

            if (!cacheServerResolver.TrySaveUrlToLocalConfig(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);
            if (createBranchResult.HasErrors)
            {
                return(new Result("Unable to create branch '" + originBranchName + "': " + createBranchResult.Errors + "\r\n" + createBranchResult.Output));
            }

            File.WriteAllText(
                Path.Combine(enlistment.WorkingDirectoryRoot, GVFSConstants.DotGit.Head),
                "ref: refs/heads/" + branch);

            if (!this.TryDownloadRootGitAttributes(enlistment, gitObjects, gitRepo, out errorMessage))
            {
                return(new Result(errorMessage));
            }

            this.CreateGitScript(enlistment);

            string installHooksError;

            if (!HooksInstaller.InstallHooks(context, out installHooksError))
            {
                tracer.RelatedError(installHooksError);
                return(new Result(installHooksError));
            }

            GitProcess.Result forceCheckoutResult = git.ForceCheckout(branch);
            if (forceCheckoutResult.HasErrors)
            {
                string[]      errorLines     = forceCheckoutResult.Errors.Split('\n');
                StringBuilder checkoutErrors = new StringBuilder();
                foreach (string gitError in errorLines)
                {
                    if (IsForceCheckoutErrorCloneFailure(gitError))
                    {
                        checkoutErrors.AppendLine(gitError);
                    }
                }

                if (checkoutErrors.Length > 0)
                {
                    string error = "Could not complete checkout of branch: " + branch + ", " + checkoutErrors.ToString();
                    tracer.RelatedError(error);
                    return(new Result(error));
                }
            }

            if (!RepoMetadata.TryInitialize(tracer, enlistment.DotGVFSRoot, out errorMessage))
            {
                tracer.RelatedError(errorMessage);
                return(new Result(errorMessage));
            }

            try
            {
                RepoMetadata.Instance.SaveCloneMetadata(tracer, enlistment);
                this.LogEnlistmentInfoAndSetConfigValues(tracer, git, enlistment);
            }
            catch (Exception e)
            {
                tracer.RelatedError(e.ToString());
                return(new Result(e.Message));
            }
            finally
            {
                RepoMetadata.Shutdown();
            }

            // Prepare the working directory folder for GVFS last to ensure that gvfs mount will fail if gvfs clone has failed
            string prepFileSystemError;

            if (!GVFSPlatform.Instance.KernelDriver.TryPrepareFolderForCallbacks(enlistment.WorkingDirectoryRoot, out prepFileSystemError))
            {
                tracer.RelatedError(prepFileSystemError);
                return(new Result(prepFileSystemError));
            }

            return(new Result(true));
        }
예제 #2
0
        public static bool TryGetMaxGoodPrefetchTimestamp(
            ITracer tracer,
            GVFSEnlistment enlistment,
            PhysicalFileSystem fileSystem,
            GitObjects gitObjects,
            out long maxGoodTimestamp,
            out string error)
        {
            fileSystem.CreateDirectory(enlistment.GitPackRoot);

            string[] packs = gitObjects.ReadPackFileNames(enlistment.GitPackRoot, GVFSConstants.PrefetchPackPrefix);
            List <PrefetchPackInfo> orderedPacks = packs
                                                   .Where(pack => GetTimestamp(pack).HasValue)
                                                   .Select(pack => new PrefetchPackInfo(GetTimestamp(pack).Value, pack))
                                                   .OrderBy(packInfo => packInfo.Timestamp)
                                                   .ToList();

            maxGoodTimestamp = -1;

            int firstBadPack = -1;

            for (int i = 0; i < orderedPacks.Count; ++i)
            {
                long   timestamp = orderedPacks[i].Timestamp;
                string packPath  = orderedPacks[i].Path;
                string idxPath   = Path.ChangeExtension(packPath, ".idx");
                if (!fileSystem.FileExists(idxPath))
                {
                    EventMetadata metadata = new EventMetadata();
                    metadata.Add("pack", packPath);
                    metadata.Add("idxPath", idxPath);
                    metadata.Add("timestamp", timestamp);
                    GitProcess.Result indexResult = gitObjects.IndexPackFile(packPath);
                    if (indexResult.ExitCodeIsFailure)
                    {
                        firstBadPack = i;

                        metadata.Add("Errors", indexResult.Errors);
                        tracer.RelatedWarning(metadata, $"{nameof(TryGetMaxGoodPrefetchTimestamp)}: Found pack file that's missing idx file, and failed to regenerate idx");
                        break;
                    }
                    else
                    {
                        maxGoodTimestamp = timestamp;

                        metadata.Add(TracingConstants.MessageKey.InfoMessage, $"{nameof(TryGetMaxGoodPrefetchTimestamp)}: Found pack file that's missing idx file, and regenerated idx");
                        tracer.RelatedEvent(EventLevel.Informational, $"{nameof(TryGetMaxGoodPrefetchTimestamp)}_RebuildIdx", metadata);
                    }
                }
                else
                {
                    maxGoodTimestamp = timestamp;
                }
            }

            if (firstBadPack != -1)
            {
                const int MaxDeleteRetries      = 200; // 200 * IoFailureRetryDelayMS (50ms) = 10 seconds
                const int RetryLoggingThreshold = 40;  // 40 * IoFailureRetryDelayMS (50ms) = 2 seconds

                // Before we delete _any_ pack-files, we need to delete the multi-pack-index, which
                // may refer to those packs.

                EventMetadata metadata = new EventMetadata();
                string        midxPath = Path.Combine(enlistment.GitPackRoot, "multi-pack-index");
                metadata.Add("path", midxPath);
                metadata.Add(TracingConstants.MessageKey.InfoMessage, $"{nameof(TryGetMaxGoodPrefetchTimestamp)} deleting multi-pack-index");
                tracer.RelatedEvent(EventLevel.Informational, $"{nameof(TryGetMaxGoodPrefetchTimestamp)}_DeleteMultiPack_index", metadata);
                if (!fileSystem.TryWaitForDelete(tracer, midxPath, IoFailureRetryDelayMS, MaxDeleteRetries, RetryLoggingThreshold))
                {
                    error = $"Unable to delete {midxPath}";
                    return(false);
                }

                // Delete packs and indexes in reverse order so that if prefetch is killed, subseqeuent prefetch commands will
                // find the right starting spot.
                for (int i = orderedPacks.Count - 1; i >= firstBadPack; --i)
                {
                    string packPath = orderedPacks[i].Path;
                    string idxPath  = Path.ChangeExtension(packPath, ".idx");

                    metadata = new EventMetadata();
                    metadata.Add("path", idxPath);
                    metadata.Add(TracingConstants.MessageKey.InfoMessage, $"{nameof(TryGetMaxGoodPrefetchTimestamp)} deleting bad idx file");
                    tracer.RelatedEvent(EventLevel.Informational, $"{nameof(TryGetMaxGoodPrefetchTimestamp)}_DeleteBadIdx", metadata);
                    if (!fileSystem.TryWaitForDelete(tracer, idxPath, IoFailureRetryDelayMS, MaxDeleteRetries, RetryLoggingThreshold))
                    {
                        error = $"Unable to delete {idxPath}";
                        return(false);
                    }

                    metadata = new EventMetadata();
                    metadata.Add("path", packPath);
                    metadata.Add(TracingConstants.MessageKey.InfoMessage, $"{nameof(TryGetMaxGoodPrefetchTimestamp)} deleting bad pack file");
                    tracer.RelatedEvent(EventLevel.Informational, $"{nameof(TryGetMaxGoodPrefetchTimestamp)}_DeleteBadPack", metadata);
                    if (!fileSystem.TryWaitForDelete(tracer, packPath, IoFailureRetryDelayMS, MaxDeleteRetries, RetryLoggingThreshold))
                    {
                        error = $"Unable to delete {packPath}";
                        return(false);
                    }
                }
            }

            error = null;
            return(true);
        }
예제 #3
0
        private void PostFetchJob(List <string> packIndexes)
        {
            try
            {
                using (FileBasedLock postFetchFileLock = new FileBasedLock(
                           this.context.FileSystem,
                           this.context.Tracer,
                           Path.Combine(this.context.Enlistment.GitObjectsRoot, PostFetchLock),
                           this.context.Enlistment.EnlistmentRoot,
                           overwriteExistingLock: true))
                {
                    if (!postFetchFileLock.TryAcquireLockAndDeleteOnClose())
                    {
                        this.context.Tracer.RelatedInfo(PostFetchTelemetryKey + ": Skipping post-fetch work since another process holds the lock");
                        return;
                    }

                    if (!this.gitObjects.TryWriteMultiPackIndex(this.context.Tracer, this.context.Enlistment, this.context.FileSystem))
                    {
                        this.context.Tracer.RelatedWarning(
                            metadata: null,
                            message: PostFetchTelemetryKey + ": Failed to generate midx for new packfiles",
                            keywords: Keywords.Telemetry);
                    }

                    if (packIndexes == null || packIndexes.Count == 0)
                    {
                        this.context.Tracer.RelatedInfo(PostFetchTelemetryKey + ": Skipping commit-graph write due to no new packfiles");
                        return;
                    }

                    using (ITracer activity = this.context.Tracer.StartActivity("TryWriteGitCommitGraph", EventLevel.Informational, Keywords.Telemetry, metadata: null))
                    {
                        GitProcess        process = new GitProcess(this.context.Enlistment);
                        GitProcess.Result result  = process.WriteCommitGraph(this.context.Enlistment.GitObjectsRoot, packIndexes);

                        if (result.HasErrors)
                        {
                            this.context.Tracer.RelatedWarning(
                                metadata: null,
                                message: PostFetchTelemetryKey + ": Failed to generate commit-graph for new packfiles:" + result.Errors,
                                keywords: Keywords.Telemetry);
                            return;
                        }
                    }
                }
            }
            catch (ThreadAbortException)
            {
                this.context.Tracer.RelatedInfo("Aborting post-fetch job due to ThreadAbortException");
            }
            catch (IOException e)
            {
                this.context.Tracer.RelatedWarning(
                    metadata: this.CreateEventMetadata(null, e),
                    message: PostFetchTelemetryKey + ": IOException while running post-fetch job: " + e.Message,
                    keywords: Keywords.Telemetry);
            }
            catch (Exception e)
            {
                this.context.Tracer.RelatedError(
                    metadata: this.CreateEventMetadata(null, e),
                    message: PostFetchTelemetryKey + ": Exception while running post-fetch job: " + e.Message,
                    keywords: Keywords.Telemetry);
                Environment.Exit((int)ReturnCode.GenericError);
            }
        }
예제 #4
0
        private void PrefetchBlobs(ITracer tracer, GVFSEnlistment enlistment, GitObjectsHttpRequestor blobRequestor, CacheServerInfo cacheServer)
        {
            BlobPrefetcher blobPrefetcher = new BlobPrefetcher(
                tracer,
                enlistment,
                blobRequestor,
                ChunkSize,
                SearchThreadCount,
                DownloadThreadCount,
                IndexThreadCount);

            string error;

            if (!BlobPrefetcher.TryLoadFolderList(enlistment, this.Folders, this.FoldersListFile, blobPrefetcher.FolderList, out error))
            {
                this.ReportErrorAndExit(tracer, error);
            }

            if (!BlobPrefetcher.TryLoadFileList(enlistment, this.Files, blobPrefetcher.FileList, out error))
            {
                this.ReportErrorAndExit(tracer, error);
            }

            if (blobPrefetcher.FolderList.Count == 0 &&
                blobPrefetcher.FileList.Count == 0)
            {
                this.ReportErrorAndExit(tracer, "Did you mean to fetch all blobs? If so, specify `--files *` to confirm.");
            }

            if (this.HydrateFiles)
            {
                if (!this.CheckIsMounted(verbose: true))
                {
                    this.ReportErrorAndExit("You can only specify --hydrate if the repo is mounted. Run 'gvfs mount' and try again.");
                }
            }

            GitProcess gitProcess = new GitProcess(enlistment);

            GitProcess.Result result = gitProcess.RevParse(GVFSConstants.DotGit.HeadName);
            if (result.HasErrors)
            {
                tracer.RelatedError(result.Errors);
                this.Output.WriteLine(result.Errors);
                Environment.ExitCode = (int)ReturnCode.GenericError;
                return;
            }

            int matchedBlobCount    = 0;
            int downloadedBlobCount = 0;
            int readFileCount       = 0;

            string      headCommitId = result.Output;
            Func <bool> doPrefetch   =
                () =>
            {
                try
                {
                    blobPrefetcher.PrefetchWithStats(
                        headCommitId.Trim(),
                        isBranch: false,
                        readFilesAfterDownload: this.HydrateFiles,
                        matchedBlobCount: out matchedBlobCount,
                        downloadedBlobCount: out downloadedBlobCount,
                        readFileCount: out readFileCount);
                    return(!blobPrefetcher.HasFailures);
                }
                catch (BlobPrefetcher.FetchException e)
                {
                    tracer.RelatedError(e.Message);
                    return(false);
                }
            };

            if (this.Verbose)
            {
                doPrefetch();
            }
            else
            {
                string message =
                    this.HydrateFiles
                    ? "Fetching blobs and hydrating files "
                    : "Fetching blobs ";
                this.ShowStatusWhileRunning(doPrefetch, message + this.GetCacheServerDisplay(cacheServer));
            }

            if (blobPrefetcher.HasFailures)
            {
                Environment.ExitCode = 1;
            }
            else
            {
                Console.WriteLine();
                Console.WriteLine("Stats:");
                Console.WriteLine("  Matched blobs:    " + matchedBlobCount);
                Console.WriteLine("  Already cached:   " + (matchedBlobCount - downloadedBlobCount));
                Console.WriteLine("  Downloaded:       " + downloadedBlobCount);
                if (this.HydrateFiles)
                {
                    Console.WriteLine("  Hydrated files:   " + readFileCount);
                }
            }
        }
예제 #5
0
        private Result CreateClone(
            ITracer tracer,
            GVFSEnlistment enlistment,
            GitObjectsHttpRequestor objectRequestor,
            GitRefs refs,
            string branch)
        {
            Result initRepoResult = this.TryInitRepo(tracer, refs, enlistment);

            if (!initRepoResult.Success)
            {
                return(initRepoResult);
            }

            PhysicalFileSystem fileSystem = new PhysicalFileSystem();
            string             errorMessage;

            if (!this.TryCreateAlternatesFile(fileSystem, enlistment, out errorMessage))
            {
                return(new Result("Error configuring alternate: " + errorMessage));
            }

            GitRepo        gitRepo    = new GitRepo(tracer, enlistment, fileSystem);
            GVFSContext    context    = new GVFSContext(tracer, fileSystem, gitRepo, enlistment);
            GVFSGitObjects gitObjects = new GVFSGitObjects(context, objectRequestor);

            if (!this.TryDownloadCommit(
                    refs.GetTipCommitId(branch),
                    enlistment,
                    objectRequestor,
                    gitObjects,
                    gitRepo,
                    out errorMessage))
            {
                return(new Result(errorMessage));
            }

            if (!GVFSVerb.TrySetRequiredGitConfigSettings(enlistment) ||
                !GVFSVerb.TrySetOptionalGitConfigSettings(enlistment))
            {
                return(new Result("Unable to configure git repo"));
            }

            CacheServerResolver cacheServerResolver = new CacheServerResolver(tracer, enlistment);

            if (!cacheServerResolver.TrySaveUrlToLocalConfig(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);
            if (createBranchResult.ExitCodeIsFailure)
            {
                return(new Result("Unable to create branch '" + originBranchName + "': " + createBranchResult.Errors + "\r\n" + createBranchResult.Output));
            }

            File.WriteAllText(
                Path.Combine(enlistment.WorkingDirectoryBackingRoot, GVFSConstants.DotGit.Head),
                "ref: refs/heads/" + branch);

            if (!this.TryDownloadRootGitAttributes(enlistment, gitObjects, gitRepo, out errorMessage))
            {
                return(new Result(errorMessage));
            }

            this.CreateGitScript(enlistment);

            string installHooksError;

            if (!HooksInstaller.InstallHooks(context, out installHooksError))
            {
                tracer.RelatedError(installHooksError);
                return(new Result(installHooksError));
            }

            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 (forceCheckoutResult.ExitCodeIsFailure)
            {
                string[]      errorLines     = forceCheckoutResult.Errors.Split('\n');
                StringBuilder checkoutErrors = new StringBuilder();
                foreach (string gitError in errorLines)
                {
                    if (IsForceCheckoutErrorCloneFailure(gitError))
                    {
                        checkoutErrors.AppendLine(gitError);
                    }
                }

                if (checkoutErrors.Length > 0)
                {
                    string error = "Could not complete checkout of branch: " + branch + ", " + checkoutErrors.ToString();
                    tracer.RelatedError(error);
                    return(new Result(error));
                }
            }

            if (!RepoMetadata.TryInitialize(tracer, enlistment.DotGVFSRoot, out errorMessage))
            {
                tracer.RelatedError(errorMessage);
                return(new Result(errorMessage));
            }

            try
            {
                RepoMetadata.Instance.SaveCloneMetadata(tracer, enlistment);
                this.LogEnlistmentInfoAndSetConfigValues(tracer, git, enlistment);
            }
            catch (Exception e)
            {
                tracer.RelatedError(e.ToString());
                return(new Result(e.Message));
            }
            finally
            {
                RepoMetadata.Shutdown();
            }

            // Prepare the working directory folder for GVFS last to ensure that gvfs mount will fail if gvfs clone has failed
            Exception exception;
            string    prepFileSystemError;

            if (!GVFSPlatform.Instance.KernelDriver.TryPrepareFolderForCallbacks(enlistment.WorkingDirectoryBackingRoot, out prepFileSystemError, out exception))
            {
                EventMetadata metadata = new EventMetadata();
                metadata.Add(nameof(prepFileSystemError), prepFileSystemError);
                if (exception != null)
                {
                    metadata.Add("Exception", exception.ToString());
                }

                tracer.RelatedError(metadata, $"{nameof(this.CreateClone)}: TryPrepareFolderForCallbacks failed");
                return(new Result(prepFileSystemError));
            }

            return(new Result(true));
        }
예제 #6
0
        private bool CheckGitStatus(ITracer tracer, GVFSEnlistment enlistment, bool fullDehydrate)
        {
            if (!this.NoStatus)
            {
                this.WriteMessage(tracer, "Running git status before dehydrating to make sure you don't have any pending changes.");
                if (fullDehydrate)
                {
                    this.WriteMessage(tracer, "If this takes too long, you can abort and run dehydrate with --no-status to skip this safety check.");
                }

                this.Output.WriteLine();

                bool isMounted = false;
                GitProcess.Result statusResult = null;
                if (!this.ShowStatusWhileRunning(
                        () =>
                {
                    if (this.ExecuteGVFSVerb <StatusVerb>(tracer) != ReturnCode.Success)
                    {
                        return(false);
                    }

                    isMounted = true;

                    GitProcess git = new GitProcess(enlistment);
                    statusResult = git.Status(allowObjectDownloads: false, useStatusCache: false, showUntracked: true);
                    if (statusResult.ExitCodeIsFailure)
                    {
                        return(false);
                    }

                    if (!statusResult.Output.Contains("nothing to commit, working tree clean"))
                    {
                        return(false);
                    }

                    return(true);
                },
                        "Running git status",
                        suppressGvfsLogMessage: true))
                {
                    this.Output.WriteLine();

                    if (!isMounted)
                    {
                        this.WriteMessage(tracer, "Failed to run git status because the repo is not mounted");
                        if (fullDehydrate)
                        {
                            this.WriteMessage(tracer, "Either mount first, or run with --no-status");
                        }
                    }
                    else if (statusResult.ExitCodeIsFailure)
                    {
                        this.WriteMessage(tracer, "Failed to run git status: " + statusResult.Errors);
                    }
                    else
                    {
                        this.WriteMessage(tracer, statusResult.Output);
                        this.WriteMessage(tracer, "git status reported that you have dirty files");
                        if (fullDehydrate)
                        {
                            this.WriteMessage(tracer, "Either commit your changes or run dehydrate with --no-status");
                        }
                        else
                        {
                            this.WriteMessage(tracer, "Either commit your changes or reset and clean your working directory.");
                        }
                    }

                    this.ReportErrorAndExit(tracer, "Dehydrate was aborted");
                    return(false);
                }
                else
                {
                    return(true);
                }
            }

            return(false);
        }
예제 #7
0
        /// <param name="branchOrCommit">A specific branch to filter for, or null for all branches returned from info/refs</param>
        public override void FastFetch(string branchOrCommit, bool isBranch)
        {
            if (string.IsNullOrWhiteSpace(branchOrCommit))
            {
                throw new FetchException("Must specify branch or commit to fetch");
            }

            GitRefs refs = null;
            string  commitToFetch;

            if (isBranch)
            {
                refs = this.ObjectRequestor.QueryInfoRefs(branchOrCommit);
                if (refs == null)
                {
                    throw new FetchException("Could not query info/refs from: {0}", this.Enlistment.RepoUrl);
                }
                else if (refs.Count == 0)
                {
                    throw new FetchException("Could not find branch {0} in info/refs from: {1}", branchOrCommit, this.Enlistment.RepoUrl);
                }

                commitToFetch = refs.GetTipCommitId(branchOrCommit);
            }
            else
            {
                commitToFetch = branchOrCommit;
            }

            this.DownloadMissingCommit(commitToFetch, this.GitObjects);

            // Configure pipeline
            // Checkout uses DiffHelper when running checkout.Start(), which we use instead of LsTreeHelper like in FetchHelper.cs
            // Checkout diff output => FindMissingBlobs => BatchDownload => IndexPack => Checkout available blobs
            CheckoutJob            checkout    = new CheckoutJob(this.checkoutThreadCount, this.FolderList, commitToFetch, this.Tracer, this.Enlistment);
            FindMissingBlobsJob    blobFinder  = new FindMissingBlobsJob(this.SearchThreadCount, checkout.RequiredBlobs, checkout.AvailableBlobShas, this.Tracer, this.Enlistment);
            BatchObjectDownloadJob downloader  = new BatchObjectDownloadJob(this.DownloadThreadCount, this.ChunkSize, blobFinder.DownloadQueue, checkout.AvailableBlobShas, this.Tracer, this.Enlistment, this.ObjectRequestor, this.GitObjects);
            IndexPackJob           packIndexer = new IndexPackJob(this.IndexThreadCount, downloader.AvailablePacks, checkout.AvailableBlobShas, this.Tracer, this.GitObjects);

            // Start pipeline
            downloader.Start();
            blobFinder.Start();
            checkout.Start();

            blobFinder.WaitForCompletion();
            this.HasFailures |= blobFinder.HasFailures;

            // Delay indexing. It interferes with FindMissingBlobs, and doesn't help Bootstrapping.
            packIndexer.Start();

            downloader.WaitForCompletion();
            this.HasFailures |= downloader.HasFailures;

            packIndexer.WaitForCompletion();
            this.HasFailures |= packIndexer.HasFailures;

            // Since pack indexer is the last to finish before checkout finishes, it should propagate completion.
            // This prevents availableObjects from completing before packIndexer can push its objects through this link.
            checkout.AvailableBlobShas.CompleteAdding();
            checkout.WaitForCompletion();
            this.HasFailures |= checkout.HasFailures;

            if (!this.SkipConfigUpdate && !this.HasFailures)
            {
                this.UpdateRefs(branchOrCommit, isBranch, refs);

                if (isBranch)
                {
                    // Update the refspec before setting the upstream or git will complain the remote branch doesn't exist
                    this.HasFailures |= !this.UpdateRefSpec(this.Tracer, this.Enlistment, branchOrCommit, refs);

                    using (ITracer activity = this.Tracer.StartActivity("SetUpstream", EventLevel.Informational))
                    {
                        string            remoteBranch = refs.GetBranchRefPairs().Single().Key;
                        GitProcess        git          = new GitProcess(this.Enlistment);
                        GitProcess.Result result       = git.SetUpstream(branchOrCommit, remoteBranch);
                        if (result.HasErrors)
                        {
                            activity.RelatedError("Could not set upstream for {0} to {1}: {2}", branchOrCommit, remoteBranch, result.Errors);
                            this.HasFailures = true;
                        }
                    }
                }

                bool shouldSignIndex = !this.GetIsIndexSigningOff();

                // Update the index
                EventMetadata updateIndexMetadata = new EventMetadata();
                updateIndexMetadata.Add("IndexSigningIsOff", shouldSignIndex);
                using (ITracer activity = this.Tracer.StartActivity("UpdateIndex", EventLevel.Informational, Keywords.Telemetry, updateIndexMetadata))
                {
                    Index             sourceIndex = this.GetSourceIndex();
                    GitIndexGenerator indexGen    = new GitIndexGenerator(this.Tracer, this.Enlistment, shouldSignIndex);
                    indexGen.CreateFromHeadTree(indexVersion: 2);
                    this.HasFailures |= indexGen.HasFailures;

                    if (!indexGen.HasFailures)
                    {
                        Index newIndex = new Index(
                            this.Enlistment.EnlistmentRoot,
                            this.Tracer,
                            Path.Combine(this.Enlistment.DotGitRoot, GVFSConstants.DotGit.IndexName),
                            readOnly: false);

                        // Update from disk only if the caller says it is ok via command line
                        // or if we updated the whole tree and know that all files are up to date
                        bool allowIndexMetadataUpdateFromWorkingTree = this.allowIndexMetadataUpdateFromWorkingTree || checkout.UpdatedWholeTree;
                        newIndex.UpdateFileSizesAndTimes(checkout.AddedOrEditedLocalFiles, allowIndexMetadataUpdateFromWorkingTree, shouldSignIndex, sourceIndex);
                    }
                }
            }
        }
예제 #8
0
        private void PrefetchBlobs(ITracer tracer, GVFSEnlistment enlistment, GitObjectsHttpRequestor blobRequestor, CacheServerInfo cacheServer)
        {
            FetchHelper fetchHelper = new FetchHelper(
                tracer,
                enlistment,
                blobRequestor,
                ChunkSize,
                SearchThreadCount,
                DownloadThreadCount,
                IndexThreadCount);

            string error;

            if (!FetchHelper.TryLoadFolderList(enlistment, this.Folders, this.FoldersListFile, fetchHelper.FolderList, out error))
            {
                this.ReportErrorAndExit(tracer, error);
            }

            if (!FetchHelper.TryLoadFileList(enlistment, this.Files, fetchHelper.FileList, out error))
            {
                this.ReportErrorAndExit(tracer, error);
            }

            if (fetchHelper.FolderList.Count == 0 &&
                fetchHelper.FileList.Count == 0)
            {
                this.ReportErrorAndExit(tracer, "Did you mean to fetch all blobs? If so, specify `--files *` to confirm.");
            }

            GitProcess gitProcess = new GitProcess(enlistment);

            GitProcess.Result result = gitProcess.RevParse(GVFSConstants.DotGit.HeadName);
            if (result.HasErrors)
            {
                tracer.RelatedError(result.Errors);
                this.Output.WriteLine(result.Errors);
                Environment.ExitCode = (int)ReturnCode.GenericError;
                return;
            }

            int matchedBlobs    = 0;
            int downloadedBlobs = 0;

            string      headCommitId = result.Output;
            Func <bool> doPrefetch   =
                () =>
            {
                try
                {
                    fetchHelper.FastFetchWithStats(
                        headCommitId.Trim(),
                        isBranch: false,
                        matchedBlobs: out matchedBlobs,
                        downloadedBlobs: out downloadedBlobs);
                    return(!fetchHelper.HasFailures);
                }
                catch (FetchHelper.FetchException e)
                {
                    tracer.RelatedError(e.Message);
                    return(false);
                }
            };

            if (this.Verbose)
            {
                doPrefetch();
            }
            else
            {
                this.ShowStatusWhileRunning(doPrefetch, "Fetching blobs " + this.GetCacheServerDisplay(cacheServer));
            }

            if (fetchHelper.HasFailures)
            {
                Environment.ExitCode = 1;
            }
            else
            {
                Console.WriteLine("Your filter matched {0} blob(s), and {1} new blob(s) were downloaded.", matchedBlobs, downloadedBlobs);
            }
        }
예제 #9
0
        /// <summary>
        /// Rebuild the status cache. This will run the background status to
        /// generate status results, and update the serialized status cache
        /// file.
        /// </summary>
        private bool TryRebuildStatusCache()
        {
            try
            {
                this.context.FileSystem.CreateDirectory(this.context.Enlistment.GitStatusCacheFolder);
            }
            catch (Exception ex) when(ex is IOException || ex is UnauthorizedAccessException)
            {
                EventMetadata metadata = new EventMetadata();

                metadata.Add("Area", EtwArea);
                metadata.Add("Exception", ex.ToString());

                this.context.Tracer.RelatedWarning(
                    metadata,
                    string.Format("GitStatusCache is unable to create git status cache folder at {0}.", this.context.Enlistment.GitStatusCacheFolder));
                return(false);
            }

            // The status cache is regenerated on mount. This means that even if the write to temp file
            // and rename operation doesn't complete (due to a system crash), and there is a torn write,
            // GSD is still protected because a new status cache file will be generated on mount.
            string tmpStatusFilePath = Path.Combine(this.context.Enlistment.GitStatusCacheFolder, Path.GetRandomFileName() + "_status.tmp");

            GitProcess.Result statusResult = null;

            // Do not modify this block unless you completely understand the comments and code within
            {
                // We MUST set the state to Rebuilding _immediately before_ we call the `git status` command. That allows us to
                // check afterwards if anything happened during the status command that should invalidate the cache, and we
                // can discard its results if that happens.
                this.cacheState = CacheState.Rebuilding;

                GitProcess git = this.context.Enlistment.CreateGitProcess();
                statusResult = git.SerializeStatus(
                    allowObjectDownloads: true,
                    serializePath: tmpStatusFilePath);
            }

            bool rebuildSucceeded = false;

            if (statusResult.ExitCodeIsSuccess)
            {
                lock (this.cacheFileLock)
                {
                    // Only update the cache if our state is still Rebuilding. Otherwise, this indicates that another call
                    // to Invalidate came in, and moved the state back to Dirty.
                    if (this.cacheState == CacheState.Rebuilding)
                    {
                        rebuildSucceeded = this.MoveCacheFileToFinalLocation(tmpStatusFilePath);
                        if (rebuildSucceeded)
                        {
                            // We have to check the state once again, because it could have been invalidated while we were
                            // copying the file in the previous step. Here we do it as a CompareExchange to minimize any further races.
                            if (Interlocked.CompareExchange(ref this.cacheState, CacheState.Clean, CacheState.Rebuilding) != CacheState.Rebuilding)
                            {
                                // We did not succeed in setting the state to Clean. Note that we have already overwritten the on disk cache,
                                // but all users of the cache file first check the cacheState, and since the cacheState is not Clean, no one
                                // should ever read it.

                                rebuildSucceeded = false;
                            }
                        }

                        if (!rebuildSucceeded)
                        {
                            this.cacheState = CacheState.Dirty;
                        }
                    }
                }

                if (!rebuildSucceeded)
                {
                    try
                    {
                        this.context.FileSystem.DeleteFile(tmpStatusFilePath);
                    }
                    catch (Exception ex) when(ex is IOException || ex is UnauthorizedAccessException)
                    {
                        EventMetadata metadata = new EventMetadata();

                        metadata.Add("Area", EtwArea);
                        metadata.Add("Exception", ex.ToString());

                        this.context.Tracer.RelatedError(
                            metadata,
                            string.Format("GitStatusCache is unable to delete temporary status cache file at {0}.", tmpStatusFilePath));
                    }
                }
            }
            else
            {
                this.statistics.RecordBackgroundStatusScanError();
                this.context.Tracer.RelatedInfo("GitStatusCache.TryRebuildStatusCache: Error generating status: {0}", statusResult.Errors);
            }

            return(rebuildSucceeded);
        }
예제 #10
0
        /// <param name="branchOrCommit">A specific branch to filter for, or null for all branches returned from info/refs</param>
        public override void FastFetch(string branchOrCommit, bool isBranch)
        {
            if (string.IsNullOrWhiteSpace(branchOrCommit))
            {
                throw new FetchException("Must specify branch or commit to fetch");
            }

            GitRefs refs = null;
            string  commitToFetch;

            if (isBranch)
            {
                refs = this.HttpGitObjects.QueryInfoRefs(branchOrCommit);
                if (refs == null)
                {
                    throw new FetchException("Could not query info/refs from: {0}", this.Enlistment.RepoUrl);
                }
                else if (refs.Count == 0)
                {
                    throw new FetchException("Could not find branch {0} in info/refs from: {1}", branchOrCommit, this.Enlistment.RepoUrl);
                }

                commitToFetch = refs.GetTipCommitIds().Single();
            }
            else
            {
                commitToFetch = branchOrCommit;
            }

            this.DownloadMissingCommit(commitToFetch, this.GitObjects);

            // Configure pipeline
            // Checkout uses DiffHelper when running checkout.Start(), which we use instead of LsTreeHelper like in FetchHelper.cs
            // Checkout diff output => FindMissingBlobs => BatchDownload => IndexPack => Checkout available blobs
            CheckoutJob            checkout    = new CheckoutJob(this.checkoutThreadCount, this.PathWhitelist, commitToFetch, this.Tracer, this.Enlistment);
            FindMissingBlobsJob    blobFinder  = new FindMissingBlobsJob(this.SearchThreadCount, checkout.RequiredBlobs, checkout.AvailableBlobShas, this.Tracer, this.Enlistment);
            BatchObjectDownloadJob downloader  = new BatchObjectDownloadJob(this.DownloadThreadCount, this.ChunkSize, blobFinder.DownloadQueue, checkout.AvailableBlobShas, this.Tracer, this.Enlistment, this.HttpGitObjects, this.GitObjects);
            IndexPackJob           packIndexer = new IndexPackJob(this.IndexThreadCount, downloader.AvailablePacks, checkout.AvailableBlobShas, this.Tracer, this.GitObjects);

            // Start pipeline
            downloader.Start();
            blobFinder.Start();
            checkout.Start();

            blobFinder.WaitForCompletion();
            this.HasFailures |= blobFinder.HasFailures;

            // Delay indexing. It interferes with FindMissingBlobs, and doesn't help Bootstrapping.
            packIndexer.Start();

            downloader.WaitForCompletion();
            this.HasFailures |= downloader.HasFailures;

            packIndexer.WaitForCompletion();
            this.HasFailures |= packIndexer.HasFailures;

            // Since pack indexer is the last to finish before checkout finishes, it should propagate completion.
            // This prevents availableObjects from completing before packIndexer can push its objects through this link.
            checkout.AvailableBlobShas.CompleteAdding();
            checkout.WaitForCompletion();
            this.HasFailures |= checkout.HasFailures;

            if (!this.SkipConfigUpdate && !this.HasFailures)
            {
                this.UpdateRefs(branchOrCommit, isBranch, refs);

                if (isBranch)
                {
                    // Update the refspec before setting the upstream or git will complain the remote branch doesn't exist
                    this.HasFailures |= !RefSpecHelpers.UpdateRefSpec(this.Tracer, this.Enlistment, branchOrCommit, refs);

                    using (ITracer activity = this.Tracer.StartActivity("SetUpstream", EventLevel.Informational))
                    {
                        string            remoteBranch = refs.GetBranchRefPairs().Single().Key;
                        GitProcess        git          = new GitProcess(this.Enlistment);
                        GitProcess.Result result       = git.SetUpstream(branchOrCommit, remoteBranch);
                        if (result.HasErrors)
                        {
                            activity.RelatedError("Could not set upstream for {0} to {1}: {2}", branchOrCommit, remoteBranch, result.Errors);
                            this.HasFailures = true;
                        }
                    }
                }

                // Update the index
                using (ITracer activity = this.Tracer.StartActivity("UpdateIndex", EventLevel.Informational))
                {
                    // The first bit of core.gvfs is set if index signing is turned off.
                    const uint CoreGvfsUnsignedIndexFlag = 1;

                    GitProcess git = new GitProcess(this.Enlistment);

                    // Only update the index if index signing is turned off.

                    // The first bit of core.gvfs is set if signing is turned off.
                    GitProcess.Result configCoreGvfs = git.GetFromConfig("core.gvfs");
                    uint valueCoreGvfs;

                    // No errors getting the configuration *and it is either "true" or numeric with the right bit set.
                    bool indexSigningIsTurnedOff =
                        !configCoreGvfs.HasErrors &&
                        !string.IsNullOrEmpty(configCoreGvfs.Output) &&
                        (configCoreGvfs.Output.Equals("true", StringComparison.OrdinalIgnoreCase) ||
                         (uint.TryParse(configCoreGvfs.Output, out valueCoreGvfs) &&
                          ((valueCoreGvfs & CoreGvfsUnsignedIndexFlag) == CoreGvfsUnsignedIndexFlag)));

                    // Create the index object now so it can track the current index
                    Index index = indexSigningIsTurnedOff ? new Index(this.Enlistment.EnlistmentRoot, activity) : null;

                    GitProcess.Result result;
                    using (ITracer updateIndexActivity = this.Tracer.StartActivity("ReadTree", EventLevel.Informational))
                    {
                        result = git.ReadTree("HEAD");
                    }

                    if (result.HasErrors)
                    {
                        activity.RelatedError("Could not read HEAD tree to update index: " + result.Errors);
                        this.HasFailures = true;
                    }
                    else
                    {
                        if (index != null)
                        {
                            // Update from disk only if the caller says it is ok via command line
                            // or if we updated the whole tree and know that all files are up to date
                            bool allowIndexMetadataUpdateFromWorkingTree = this.allowIndexMetadataUpdateFromWorkingTree || checkout.UpdatedWholeTree;

                            // Update
                            index.UpdateFileSizesAndTimes(allowIndexMetadataUpdateFromWorkingTree);
                        }
                        else
                        {
                            activity.RelatedEvent(EventLevel.Informational, "Core.gvfs is not set, so not updating index entries with file times and sizes.", null);
                        }
                    }
                }
            }
        }