/// <summary> /// Updates entries in the current index with file sizes and times /// Algorithm: /// 1) If there was an index in place when this object was constructed, then: /// a) Copy all valid entries (below) from the previous index to the new index /// b) Conditionally (below) get times/sizes from the working tree for files not updated from the previous index /// /// 2) If there was no index in place, conditionally populate all entries from disk /// /// Conditions: /// - Working tree is only searched if allowUpdateFromWorkingTree is specified /// - A valid entry is an entry that exist and has a non-zero creation time (ctime) /// </summary> /// <param name="addedOrEditedLocalFiles">A collection of added or edited files</param> /// <param name="allowUpdateFromWorkingTree">Set to true if the working tree is known good and can be used during the update.</param> /// <param name="backupIndex">An optional index to source entry values from</param> public void UpdateFileSizesAndTimes(BlockingCollection <string> addedOrEditedLocalFiles, bool allowUpdateFromWorkingTree, bool shouldSignIndex, Index backupIndex = null) { if (this.readOnly) { throw new InvalidOperationException("Cannot update a readonly index."); } using (ITracer activity = this.tracer.StartActivity("UpdateFileSizesAndTimes", EventLevel.Informational, Keywords.Telemetry, null)) { File.Copy(this.indexPath, this.updatedIndexPath, overwrite: true); this.Parse(); bool anyEntriesUpdated = false; using (MemoryMappedFile mmf = this.GetMemoryMappedFile()) using (MemoryMappedViewAccessor indexView = mmf.CreateViewAccessor()) { // Only populate from the previous index if we believe it's good to populate from // For now, a current FastFetch version marker is the only criteria if (backupIndex != null) { if (this.IsFastFetchVersionMarkerCurrent()) { using (this.tracer.StartActivity("UpdateFileInformationFromPreviousIndex", EventLevel.Informational, Keywords.Telemetry, null)) { anyEntriesUpdated |= this.UpdateFileInformationForAllEntries(indexView, backupIndex, allowUpdateFromWorkingTree); } if (addedOrEditedLocalFiles != null) { // always update these files from disk or the index won't have good information // for them and they'll show as modified even those not actually modified. anyEntriesUpdated |= this.UpdateFileInformationFromDiskForFiles(indexView, addedOrEditedLocalFiles); } } } else if (allowUpdateFromWorkingTree) { // If we didn't update from a previous index, update from the working tree if allowed. anyEntriesUpdated |= this.UpdateFileInformationFromWorkingTree(indexView); } indexView.Flush(); } if (anyEntriesUpdated) { this.MoveUpdatedIndexToFinalLocation(shouldSignIndex); } else { File.Delete(this.updatedIndexPath); } } }
/// <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); } } } }
private bool UpdateFileInformationForAllEntries(MemoryMappedViewAccessor indexView, Index otherIndex, bool shouldAlsoTryPopulateFromDisk) { long updatedEntriesFromOtherIndex = 0; long updatedEntriesFromDisk = 0; using (MemoryMappedFile mmf = otherIndex.GetMemoryMappedFile()) using (MemoryMappedViewAccessor otherIndexView = mmf.CreateViewAccessor()) { Parallel.ForEach( this.indexEntryOffsets, entry => { string currentIndexFilename = entry.Key; long currentIndexOffset = entry.Value; if (!IndexEntry.HasInitializedCTimeEntry(indexView, currentIndexOffset)) { long otherIndexOffset; if (otherIndex.indexEntryOffsets.TryGetValue(currentIndexFilename, out otherIndexOffset)) { if (IndexEntry.HasInitializedCTimeEntry(otherIndexView, otherIndexOffset)) { IndexEntry currentIndexEntry = new IndexEntry(indexView, currentIndexOffset); IndexEntry otherIndexEntry = new IndexEntry(otherIndexView, otherIndexOffset); currentIndexEntry.CtimeSeconds = otherIndexEntry.CtimeSeconds; currentIndexEntry.CtimeNanosecondFraction = otherIndexEntry.CtimeNanosecondFraction; currentIndexEntry.MtimeSeconds = otherIndexEntry.MtimeSeconds; currentIndexEntry.MtimeNanosecondFraction = otherIndexEntry.MtimeNanosecondFraction; currentIndexEntry.Dev = otherIndexEntry.Dev; currentIndexEntry.Ino = otherIndexEntry.Ino; currentIndexEntry.Uid = otherIndexEntry.Uid; currentIndexEntry.Gid = otherIndexEntry.Gid; currentIndexEntry.Size = otherIndexEntry.Size; Interlocked.Increment(ref updatedEntriesFromOtherIndex); } } else if (shouldAlsoTryPopulateFromDisk) { string localPath = FromGitRelativePathToDotnetFullPath(currentIndexFilename, this.repoRoot); if (NativeMethods.TryStatFileAndUpdateIndex(this.tracer, localPath, indexView, entry.Value)) { Interlocked.Increment(ref updatedEntriesFromDisk); } } } }); } this.tracer.RelatedEvent( EventLevel.Informational, "UpdateIndexFileInformation", new EventMetadata() { { "UpdatedFromOtherIndex", updatedEntriesFromOtherIndex }, { "UpdatedFromDisk", updatedEntriesFromDisk } }, Keywords.Telemetry); return((updatedEntriesFromOtherIndex > 0) || (updatedEntriesFromDisk > 0)); }