diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 8dca590066..a30be08797 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -109,7 +109,7 @@ jobs: - name: Run functional tests shell: cmd run: | - SET PATH=C:\Program Files\GVFS;%PATH% + SET PATH=C:\Program Files\VFS for Git;%PATH% SET GIT_TRACE2_PERF=C:\temp\git-trace2.log ft\GVFS.FunctionalTests.exe /result:TestResult.xml --ci diff --git a/.github/workflows/release-winget.yaml b/.github/workflows/release-winget.yaml index 710bc94e70..2f4c79437a 100644 --- a/.github/workflows/release-winget.yaml +++ b/.github/workflows/release-winget.yaml @@ -5,35 +5,18 @@ on: jobs: release: - runs-on: ubuntu-latest + runs-on: windows-latest steps: - - id: update-winget - name: Update winget repository - uses: mjcheetham/update-winget@v1.2.2 - with: - id: Microsoft.VFSforGit - token: ${{ secrets.WINGET_TOKEN }} - releaseAsset: SetupGVFS.([0-9.]*)\.exe - manifestText: | - PackageIdentifier: {{id}} - PackageVersion: {{version}} - PackageName: VFS for Git - Publisher: Microsoft Corporation - Moniker: vfs-for-git - PackageUrl: https://aka.ms/vfs-for-git - Tags: - - vfs for git - - vfs-for-git - - vfsforgit - - gvfs - License: Copyright (C) Microsoft Corporation - ShortDescription: Virtual File System for Git - a tool to scale Git for monorepo scenarios. - Installers: - - Architecture: x64 - InstallerUrl: {{url}} - InstallerType: inno - InstallerSha256: {{sha256}} - PackageLocale: en-US - ManifestType: singleton - ManifestVersion: 1.0.0 - alwaysUsePullRequest: true \ No newline at end of file + - name: Publish manifest with winget-create + run: | + # Get correct release asset + $github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json + $asset = $github.release.assets | Where-Object -Property name -match 'SetupGVFS[\d\.]*.exe' + + # Remove 'v' from the version + $version = $github.release.tag_name -replace ".v","" + + # Download and run wingetcreate + Invoke-WebRequest https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe + .\wingetcreate.exe update Microsoft.VFSforGit -u $asset.browser_download_url -v $version -o manifests -t "${{ secrets.WINGET_TOKEN }}" -s + shell: powershell \ No newline at end of file diff --git a/GVFS/FastFetch/CheckoutPrefetcher.cs b/GVFS/FastFetch/CheckoutPrefetcher.cs index 8b85ba794f..e8a48a9418 100644 --- a/GVFS/FastFetch/CheckoutPrefetcher.cs +++ b/GVFS/FastFetch/CheckoutPrefetcher.cs @@ -72,85 +72,99 @@ public override void Prefetch(string branchOrCommit, bool isBranch) commitToFetch = branchOrCommit; } - this.DownloadMissingCommit(commitToFetch, this.GitObjects); - - // Configure pipeline - // Checkout uses DiffHelper when running checkout.Start(), which we use instead of LsTreeHelper - // Checkout diff output => FindBlobs => BatchDownload => IndexPack => Checkout available blobs - CheckoutStage checkout = new CheckoutStage(this.checkoutThreadCount, this.FolderList, commitToFetch, this.Tracer, this.Enlistment, this.forceCheckout); - FindBlobsStage blobFinder = new FindBlobsStage(this.SearchThreadCount, checkout.RequiredBlobs, checkout.AvailableBlobShas, this.Tracer, this.Enlistment); - BatchObjectDownloadStage downloader = new BatchObjectDownloadStage(this.DownloadThreadCount, this.ChunkSize, blobFinder.MissingBlobs, checkout.AvailableBlobShas, this.Tracer, this.Enlistment, this.ObjectRequestor, this.GitObjects); - IndexPackStage packIndexer = new IndexPackStage(this.IndexThreadCount, downloader.AvailablePacks, checkout.AvailableBlobShas, this.Tracer, this.GitObjects); + using (new IndexLock(this.Enlistment.EnlistmentRoot, this.Tracer)) + { + this.DownloadMissingCommit(commitToFetch, this.GitObjects); - // Start pipeline - downloader.Start(); - blobFinder.Start(); - checkout.Start(); + // Configure pipeline + // Checkout uses DiffHelper when running checkout.Start(), which we use instead of LsTreeHelper + // Checkout diff output => FindBlobs => BatchDownload => IndexPack => Checkout available blobs + CheckoutStage checkout = new CheckoutStage(this.checkoutThreadCount, this.FolderList, commitToFetch, this.Tracer, this.Enlistment, this.forceCheckout); + FindBlobsStage blobFinder = new FindBlobsStage(this.SearchThreadCount, checkout.RequiredBlobs, checkout.AvailableBlobShas, this.Tracer, this.Enlistment); + BatchObjectDownloadStage downloader = new BatchObjectDownloadStage(this.DownloadThreadCount, this.ChunkSize, blobFinder.MissingBlobs, checkout.AvailableBlobShas, this.Tracer, this.Enlistment, this.ObjectRequestor, this.GitObjects); + IndexPackStage packIndexer = new IndexPackStage(this.IndexThreadCount, downloader.AvailablePacks, checkout.AvailableBlobShas, this.Tracer, this.GitObjects); - blobFinder.WaitForCompletion(); - this.HasFailures |= blobFinder.HasFailures; + // Start pipeline + downloader.Start(); + blobFinder.Start(); + checkout.Start(); - // Delay indexing. It interferes with FindMissingBlobs, and doesn't help Bootstrapping. - packIndexer.Start(); + blobFinder.WaitForCompletion(); + this.HasFailures |= blobFinder.HasFailures; - downloader.WaitForCompletion(); - this.HasFailures |= downloader.HasFailures; + // Delay indexing. It interferes with FindMissingBlobs, and doesn't help Bootstrapping. + packIndexer.Start(); - packIndexer.WaitForCompletion(); - this.HasFailures |= packIndexer.HasFailures; + downloader.WaitForCompletion(); + this.HasFailures |= downloader.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; + packIndexer.WaitForCompletion(); + this.HasFailures |= packIndexer.HasFailures; - if (!this.SkipConfigUpdate && !this.HasFailures) - { - this.UpdateRefs(branchOrCommit, isBranch, refs); + // 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 (isBranch) + if (!this.SkipConfigUpdate && !this.HasFailures) { - // 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); + bool shouldSignIndex = !this.GetIsIndexSigningOff(); - using (ITracer activity = this.Tracer.StartActivity("SetUpstream", EventLevel.Informational)) + // Update the index - note that this will take some time + EventMetadata updateIndexMetadata = new EventMetadata(); + updateIndexMetadata.Add("IndexSigningIsOff", shouldSignIndex); + using (ITracer activity = this.Tracer.StartActivity("UpdateIndex", EventLevel.Informational, Keywords.Telemetry, updateIndexMetadata)) { - string remoteBranch = refs.GetBranchRefPairs().Single().Key; - GitProcess git = new GitProcess(this.Enlistment); - GitProcess.Result result = git.SetUpstream(branchOrCommit, remoteBranch); - if (result.ExitCodeIsFailure) + Index sourceIndex = this.GetSourceIndex(); + GitIndexGenerator indexGen = new GitIndexGenerator(this.Tracer, this.Enlistment, shouldSignIndex); + indexGen.CreateFromRef(commitToFetch, indexVersion: 2, isFinal: false); + this.HasFailures |= indexGen.HasFailures; + + if (!indexGen.HasFailures) { - activity.RelatedError("Could not set upstream for {0} to {1}: {2}", branchOrCommit, remoteBranch, result.Errors); - this.HasFailures = true; + Index newIndex = new Index( + this.Enlistment.EnlistmentRoot, + this.Tracer, + indexGen.TemporaryIndexFilePath, + 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); + + // All the slow stuff is over, so we will now move the final index into .git\index, shortly followed by + // updating the ref files and releasing index.lock. + string indexPath = Path.Combine(this.Enlistment.DotGitRoot, GVFSConstants.DotGit.IndexName); + this.Tracer.RelatedEvent(EventLevel.Informational, "MoveUpdatedIndexToFinalLocation", new EventMetadata() { { "UpdatedIndex", indexGen.TemporaryIndexFilePath }, { "Index", indexPath } }); + File.Delete(indexPath); + File.Move(indexGen.TemporaryIndexFilePath, indexPath); + newIndex.WriteFastFetchIndexVersionMarker(); } } - } - - 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) + if (!this.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); + 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.ExitCodeIsFailure) + { + activity.RelatedError("Could not set upstream for {0} to {1}: {2}", branchOrCommit, remoteBranch, result.Errors); + this.HasFailures = true; + } + } + } } } } @@ -183,18 +197,10 @@ protected override void UpdateRefs(string branchOrCommit, bool isBranch, GitRefs private Index GetSourceIndex() { string indexPath = Path.Combine(this.Enlistment.DotGitRoot, GVFSConstants.DotGit.IndexName); - string backupIndexPath = Path.Combine(this.Enlistment.DotGitRoot, GVFSConstants.DotGit.IndexName + ".backup"); if (File.Exists(indexPath)) { - // Note that this moves the current index, leaving nothing behind - // This is intentional as we only need it for the purpose of updating the - // new index and leaving it behind can make updating slower. - this.Tracer.RelatedEvent(EventLevel.Informational, "CreateBackup", new EventMetadata() { { "BackupIndexName", backupIndexPath } }); - File.Delete(backupIndexPath); - File.Move(indexPath, backupIndexPath); - - Index output = new Index(this.Enlistment.EnlistmentRoot, this.Tracer, backupIndexPath, readOnly: true); + Index output = new Index(this.Enlistment.EnlistmentRoot, this.Tracer, indexPath, readOnly: true); output.Parse(); return output; } diff --git a/GVFS/FastFetch/Index.cs b/GVFS/FastFetch/Index.cs index b02c1362f2..5704611d32 100644 --- a/GVFS/FastFetch/Index.cs +++ b/GVFS/FastFetch/Index.cs @@ -39,9 +39,7 @@ public class Index private readonly bool readOnly; - // Index paths private readonly string indexPath; - private readonly string updatedIndexPath; private readonly ITracer tracer; private readonly string repoRoot; @@ -63,15 +61,6 @@ public Index( this.indexPath = indexFullPath; this.readOnly = readOnly; - if (this.readOnly) - { - this.updatedIndexPath = this.indexPath; - } - else - { - this.updatedIndexPath = Path.Combine(repoRoot, GVFSConstants.DotGit.Root, UpdatedIndexName); - } - this.versionMarkerFile = Path.Combine(this.repoRoot, GVFSConstants.DotGit.Root, ".fastfetch", "VersionMarker"); } @@ -92,8 +81,8 @@ public Index( /// /// A collection of added or edited files /// Set to true if the working tree is known good and can be used during the update. - /// An optional index to source entry values from - public void UpdateFileSizesAndTimes(BlockingCollection addedOrEditedLocalFiles, bool allowUpdateFromWorkingTree, bool shouldSignIndex, Index backupIndex = null) + /// An optional index to source entry values from + public void UpdateFileSizesAndTimes(BlockingCollection addedOrEditedLocalFiles, bool allowUpdateFromWorkingTree, bool shouldSignIndex, Index sourceIndex = null) { if (this.readOnly) { @@ -102,8 +91,6 @@ public void UpdateFileSizesAndTimes(BlockingCollection addedOrEditedLoca 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; @@ -113,13 +100,13 @@ public void UpdateFileSizesAndTimes(BlockingCollection addedOrEditedLoca { // 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 (sourceIndex != null) { if (this.IsFastFetchVersionMarkerCurrent()) { using (this.tracer.StartActivity("UpdateFileInformationFromPreviousIndex", EventLevel.Informational, Keywords.Telemetry, null)) { - anyEntriesUpdated |= this.UpdateFileInformationForAllEntries(indexView, backupIndex, allowUpdateFromWorkingTree); + anyEntriesUpdated |= this.UpdateFileInformationForAllEntries(indexView, sourceIndex, allowUpdateFromWorkingTree); } if (addedOrEditedLocalFiles != null) @@ -139,22 +126,18 @@ public void UpdateFileSizesAndTimes(BlockingCollection addedOrEditedLoca indexView.Flush(); } - if (anyEntriesUpdated) + if (shouldSignIndex) { - this.MoveUpdatedIndexToFinalLocation(shouldSignIndex); - } - else - { - File.Delete(this.updatedIndexPath); + this.SignIndex(); } } } public void Parse() { - using (ITracer activity = this.tracer.StartActivity("ParseIndex", EventLevel.Informational, Keywords.Telemetry, new EventMetadata() { { "Index", this.updatedIndexPath } })) + using (ITracer activity = this.tracer.StartActivity("ParseIndex", EventLevel.Informational, Keywords.Telemetry, new EventMetadata() { { "Index", this.indexPath } })) { - using (Stream indexStream = new FileStream(this.updatedIndexPath, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (Stream indexStream = new FileStream(this.indexPath, FileMode.Open, FileAccess.Read, FileShare.Read)) { this.ParseIndex(indexStream); } @@ -173,7 +156,7 @@ private static string FromGitRelativePathToDotnetFullPath(string path, string re private MemoryMappedFile GetMemoryMappedFile() { - return MemoryMappedFile.CreateFromFile(this.updatedIndexPath, FileMode.Open); + return MemoryMappedFile.CreateFromFile(this.indexPath, FileMode.Open); } private bool UpdateFileInformationFromWorkingTree(MemoryMappedViewAccessor indexView) @@ -296,37 +279,28 @@ private bool UpdateFileInformationForAllEntries(MemoryMappedViewAccessor indexVi return (updatedEntriesFromOtherIndex > 0) || (updatedEntriesFromDisk > 0); } - private void MoveUpdatedIndexToFinalLocation(bool shouldSignIndex) + private void SignIndex() { - if (shouldSignIndex) + using (ITracer activity = this.tracer.StartActivity("SignIndex", EventLevel.Informational, Keywords.Telemetry, metadata: null)) { - using (ITracer activity = this.tracer.StartActivity("SignIndex", EventLevel.Informational, Keywords.Telemetry, metadata: null)) + using (FileStream fs = File.Open(this.indexPath, FileMode.Open, FileAccess.ReadWrite)) { - using (FileStream fs = File.Open(this.updatedIndexPath, FileMode.Open, FileAccess.ReadWrite)) + // Truncate the old hash off. The Index class is expected to preserve any existing hash. + fs.SetLength(fs.Length - 20); + using (HashingStream hashStream = new HashingStream(fs)) { - // Truncate the old hash off. The Index class is expected to preserve any existing hash. - fs.SetLength(fs.Length - 20); - using (HashingStream hashStream = new HashingStream(fs)) - { - fs.Position = 0; - hashStream.CopyTo(Stream.Null); - byte[] hash = hashStream.Hash; + fs.Position = 0; + hashStream.CopyTo(Stream.Null); + byte[] hash = hashStream.Hash; - // The fs pointer is now where the old hash used to be. Perfect. :) - fs.Write(hash, 0, hash.Length); - } + // The fs pointer is now where the old hash used to be. Perfect. :) + fs.Write(hash, 0, hash.Length); } } } - - this.tracer.RelatedEvent(EventLevel.Informational, "MoveUpdatedIndexToFinalLocation", new EventMetadata() { { "UpdatedIndex", this.updatedIndexPath }, { "Index", this.indexPath } }); - File.Delete(this.indexPath); - File.Move(this.updatedIndexPath, this.indexPath); - - this.WriteFastFetchIndexVersionMarker(); } - private void WriteFastFetchIndexVersionMarker() + public void WriteFastFetchIndexVersionMarker() { if (File.Exists(this.versionMarkerFile)) { @@ -375,7 +349,7 @@ private void ParseIndex(Stream indexStream) this.entryCount = this.ReadUInt32(buffer, indexStream); - this.tracer.RelatedEvent(EventLevel.Informational, "IndexData", new EventMetadata() { { "Index", this.updatedIndexPath }, { "Version", this.IndexVersion }, { "entryCount", this.entryCount } }, Keywords.Telemetry); + this.tracer.RelatedEvent(EventLevel.Informational, "IndexData", new EventMetadata() { { "Index", this.indexPath }, { "Version", this.IndexVersion }, { "entryCount", this.entryCount } }, Keywords.Telemetry); this.indexEntryOffsets = new Dictionary((int)this.entryCount, GVFSPlatform.Instance.Constants.PathComparer); diff --git a/GVFS/FastFetch/IndexLock.cs b/GVFS/FastFetch/IndexLock.cs new file mode 100644 index 0000000000..aee87733aa --- /dev/null +++ b/GVFS/FastFetch/IndexLock.cs @@ -0,0 +1,87 @@ +using GVFS.Common; +using GVFS.Common.Git; +using GVFS.Common.Prefetch; +using GVFS.Common.Tracing; +using System; +using System.IO; + +namespace FastFetch +{ + /// + /// A mechanism for holding the 'index.lock' on a repository for the time it takes to update the index + /// and working tree. It attempts to create the file in the constructor and throws if that fails. + /// It closes and deletes index.lock on dispose. + /// + /// + /// + /// This class should not have to exist. If FastFetch was in compliance with the git way of doing + /// business, then would work like this: + /// + /// + /// + /// It would open index.lock like this does - with CreateNew, before it started messing with the working tree. + /// + /// + /// It would have just one class responsible for writing the new index into index.lock (now it has two, + /// and ). And this combined class would write in the + /// file size and timestamp information from the appropriate sources as it goes. + /// + /// + /// It would then reread index.lock (without closing it) and calculate the hash. + /// + /// + /// It would then delete the old index file, close index.lock, and move it to index. + /// + /// + /// + /// This is all in contrast to how it works now, where it has separate operations for updating + /// the working tree, creating an index with no size/timestamp information, and then rewriting + /// it with that information. + /// + /// + /// This class is just a bodge job to make it so that we can leave the code pretty much as-is (and reduce + /// the risk of breaking things) and still get the protection we need against simultaneous git commands + /// being run. + /// + /// + public class IndexLock + : IDisposable + { + private string lockFilePath; + private FileStream lockFileStream; + + public IndexLock(string repositoryRoot, ITracer tracer) + { + this.lockFilePath = Path.Combine(repositoryRoot, GVFSConstants.DotGit.IndexLock); + try + { + this.lockFileStream = File.Open(lockFilePath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None); + } + catch (Exception ex) + { + tracer.RelatedError("Unable to create: {0}: {1}", lockFilePath, ex.Message); + throw new BlobPrefetcher.FetchException("Could not acquire index.lock."); + } + } + + /// > + public void Dispose() + { + if (this.lockFilePath == null) + { + return; + } + + if (this.lockFileStream == null) + { + throw new ObjectDisposedException(nameof(IndexLock)); + } + + this.lockFileStream.Dispose(); + this.lockFileStream = null; + + File.Delete(this.lockFilePath); + this.lockFilePath = null; + } + } +} diff --git a/GVFS/GVFS.Common/GVFS.Common.csproj b/GVFS/GVFS.Common/GVFS.Common.csproj index 9cc9a5cb8c..e96c88ba78 100644 --- a/GVFS/GVFS.Common/GVFS.Common.csproj +++ b/GVFS/GVFS.Common/GVFS.Common.csproj @@ -8,9 +8,8 @@ - - - + + @@ -19,13 +18,11 @@ - + - + diff --git a/GVFS/GVFS.Common/Git/GitAuthentication.cs b/GVFS/GVFS.Common/Git/GitAuthentication.cs index bed1564686..d82053ef56 100644 --- a/GVFS/GVFS.Common/Git/GitAuthentication.cs +++ b/GVFS/GVFS.Common/Git/GitAuthentication.cs @@ -91,9 +91,27 @@ public void RejectCredentials(ITracer tracer, string credentialString) { lock (this.gitAuthLock) { + string cachedCredentialAtStartOfReject = this.cachedCredentialString; // Don't stomp a different credential - if (credentialString == this.cachedCredentialString && this.cachedCredentialString != null) + if (credentialString == cachedCredentialAtStartOfReject && cachedCredentialAtStartOfReject != null) { + // We can't assume that the credential store's cached credential is the same as the one we have. + // Reload the credential from the store to ensure we're rejecting the correct one. + int attemptsBeforeCheckingExistingCredential = this.numberOfAttempts; + if (this.TryCallGitCredential(tracer, out string getCredentialError)) + { + if (this.cachedCredentialString != cachedCredentialAtStartOfReject) + { + // If the store already had a different credential, we don't want to reject it without trying it. + this.isCachedCredentialStringApproved = false; + return; + } + } + else + { + tracer.RelatedWarning(getCredentialError); + } + // If we can we should pass the actual username/password values we used (and found to be invalid) // to `git-credential reject` so the credential helpers can attempt to check if they're erasing // the expected credentials, if they so choose to. @@ -121,7 +139,12 @@ public void RejectCredentials(ITracer tracer, string credentialString) this.cachedCredentialString = null; this.isCachedCredentialStringApproved = false; - this.UpdateBackoff(); + + // Backoff may have already been incremented by a failure in TryCallGitCredential + if (attemptsBeforeCheckingExistingCredential == this.numberOfAttempts) + { + this.UpdateBackoff(); + } } } } diff --git a/GVFS/GVFS.Common/Git/GitIndexGenerator.cs b/GVFS/GVFS.Common/Git/GitIndexGenerator.cs index 8f9d7b29cb..19c5981358 100644 --- a/GVFS/GVFS.Common/Git/GitIndexGenerator.cs +++ b/GVFS/GVFS.Common/Git/GitIndexGenerator.cs @@ -53,21 +53,43 @@ public GitIndexGenerator(ITracer tracer, Enlistment enlistment, bool shouldHashI this.enlistment = enlistment; this.shouldHashIndex = shouldHashIndex; - this.indexLockPath = Path.Combine(enlistment.DotGitRoot, GVFSConstants.DotGit.IndexName + GVFSConstants.DotGit.LockExtension); + // The extension 'lock2' is chosen simply to not be '.lock' because, although this class reasonably + // conforms to how index.lock is supposed to be used, its callers continue to do things to the tree + // and the working tree and even the before this class comes along and after this class has been released. + // FastFetch.IndexLock bodges around this by creating an empty file in the index.lock position, so we + // need to create a different file. See FastFetch.IndexLock for a proposed design to fix this. + // + // Note that there are two callers of this - one is from FastFetch, which we just discussed, and the + // other is from the 'gvfs repair' verb. That environment is special in that it only runs on unmounted + // repo's, so 'index.lock' is irrelevant as a locking mechanism in that context. There can't be git + // commands to lock out. + this.indexLockPath = Path.Combine(enlistment.DotGitRoot, GVFSConstants.DotGit.IndexName + ".lock2"); } + public string TemporaryIndexFilePath => this.indexLockPath; + public bool HasFailures { get; private set; } - public void CreateFromHeadTree(uint indexVersion) + /// Builds an index from scratch based on the current head pointer. + /// The index version see https://git-scm.com/docs/index-format for details on what this means. + /// + /// If true, the index file will be written during this operation. If not, the new index will be + /// left in . + /// + /// + /// The index created by this class has no data from the working tree, so when 'git status' is run, it + /// will calculate the hash of everything in the working tree. + /// + public void CreateFromRef(string refName, uint indexVersion, bool isFinal) { using (ITracer updateIndexActivity = this.tracer.StartActivity("CreateFromHeadTree", EventLevel.Informational)) { - Thread entryWritingThread = new Thread(() => this.WriteAllEntries(indexVersion)); + Thread entryWritingThread = new Thread(() => this.WriteAllEntries(indexVersion, isFinal)); entryWritingThread.Start(); GitProcess git = new GitProcess(this.enlistment); GitProcess.Result result = git.LsTree( - GVFSConstants.DotGit.HeadName, + refName, this.EnqueueEntriesFromLsTree, recursive: true, showAllTrees: false); @@ -92,7 +114,7 @@ private void EnqueueEntriesFromLsTree(string line) } } - private void WriteAllEntries(uint version) + private void WriteAllEntries(uint version, bool isFinal) { try { @@ -117,7 +139,10 @@ private void WriteAllEntries(uint version) } this.AppendIndexSha(); - this.ReplaceExistingIndex(); + if (isFinal) + { + this.ReplaceExistingIndex(); + } } catch (Exception e) { @@ -126,17 +151,6 @@ private void WriteAllEntries(uint version) } } - private string GetDirectoryNameForGitPath(string filename) - { - int idx = filename.LastIndexOf('/'); - if (idx < 0) - { - return "/"; - } - - return filename.Substring(0, idx + 1); - } - private void WriteEntry(BinaryWriter writer, uint version, string sha, string filename, ref uint lastStringLength) { long startPosition = writer.BaseStream.Position; diff --git a/GVFS/GVFS.FunctionalTests/GVFS.FunctionalTests.csproj b/GVFS/GVFS.FunctionalTests/GVFS.FunctionalTests.csproj index eb0a937ccb..0a64e74400 100644 --- a/GVFS/GVFS.FunctionalTests/GVFS.FunctionalTests.csproj +++ b/GVFS/GVFS.FunctionalTests/GVFS.FunctionalTests.csproj @@ -1,4 +1,4 @@ - + net461 @@ -10,8 +10,8 @@ - - + + diff --git a/GVFS/GVFS.FunctionalTests/Settings.cs b/GVFS/GVFS.FunctionalTests/Settings.cs index a2b2871632..1abc3b8519 100644 --- a/GVFS/GVFS.FunctionalTests/Settings.cs +++ b/GVFS/GVFS.FunctionalTests/Settings.cs @@ -45,7 +45,7 @@ public static void Initialize() Commitish = @"FunctionalTests/20201014"; EnlistmentRoot = @"C:\Repos\GVFSFunctionalTests\enlistment"; - PathToGVFS = @"C:\Program Files\GVFS\GVFS.exe"; + PathToGVFS = @"C:\Program Files\VFS for Git\GVFS.exe"; PathToGit = @"C:\Program Files\Git\cmd\git.exe"; PathToBash = @"C:\Program Files\Git\bin\bash.exe"; @@ -53,7 +53,7 @@ public static void Initialize() FastFetchBaseRoot = @"C:\Repos\GVFSFunctionalTests\FastFetch"; FastFetchRoot = Path.Combine(FastFetchBaseRoot, "test"); FastFetchControl = Path.Combine(FastFetchBaseRoot, "control"); - PathToGVFSService = @"C:\Program Files\GVFS\GVFS.Service.exe"; + PathToGVFSService = @"C:\Program Files\VFS for Git\GVFS.Service.exe"; BinaryFileNameExtension = ".exe"; } } diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/PackfileMaintenanceStepTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/PackfileMaintenanceStepTests.cs deleted file mode 100644 index 2a12eebc80..0000000000 --- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/PackfileMaintenanceStepTests.cs +++ /dev/null @@ -1,134 +0,0 @@ -using GVFS.FunctionalTests.FileSystemRunners; -using GVFS.FunctionalTests.Tools; -using GVFS.Tests.Should; -using NUnit.Framework; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture -{ - [TestFixture] - public class PackfileMaintenanceStepTests : TestsWithEnlistmentPerFixture - { - private FileSystemRunner fileSystem; - - // Set forcePerRepoObjectCache to true to avoid any of the tests inadvertently corrupting - // the cache - public PackfileMaintenanceStepTests() - : base(forcePerRepoObjectCache: true) - { - this.fileSystem = new SystemIORunner(); - } - - private string GitObjectRoot => this.Enlistment.GetObjectRoot(this.fileSystem); - private string PackRoot => this.Enlistment.GetPackRoot(this.fileSystem); - - [TestCase, Order(1)] - public void ExpireClonePack() - { - this.GetPackSizes(out int packCount, out long maxSize, out long minSize, out long totalSize); - - // We should have at least two packs: - // - // 1. the pack-.pack from clone. - // 2. a prefetch--.pack from prefetch. - // - // The prefetch pack is newer, and covers all the objects in the clone pack, - // so the clone pack will be expired when we run the step. - - Directory.GetFiles(this.PackRoot, "*.keep") - .Count() - .ShouldEqual(1); - - packCount.ShouldEqual(2, message: "Incorrect packfile layout for expire test"); - - // Ensure we have a multi-pack-index (not created on clone) - GitProcess.InvokeProcess( - this.Enlistment.RepoRoot, - $"multi-pack-index write --object-dir={this.GitObjectRoot}"); - - this.Enlistment.PackfileMaintenanceStep(); - - List packs = this.GetPackfiles(); - - packs.Count.ShouldEqual(1, $"incorrect number of packs after first step: {packs.Count}"); - - Path.GetFileName(packs[0]) - .StartsWith("prefetch-") - .ShouldBeTrue($"packsBetween[0] should start with 'prefetch-': {packs[0]}"); - } - - [TestCase, Order(2)] - public void RepackAllToOnePack() - { - // Create new pack(s) by prefetching blobs for a folder. - // This generates a number of packs, based on the processor number (for parallel downloads). - this.Enlistment.Prefetch($"--folders {Path.Combine("GVFS", "GVFS")}"); - - // Create a multi-pack-index that covers the prefetch packs - // (The post-fetch job creates a multi-pack-index only after a --commits prefetch) - GitProcess.InvokeProcess( - this.Enlistment.RepoRoot, - $"multi-pack-index write --object-dir={this.GitObjectRoot}"); - - // Run the step to ensure we don't have any packs that will be expired during the repack step - this.Enlistment.PackfileMaintenanceStep(); - - this.GetPackSizes(out int afterPrefetchPackCount, out long maxSize, out long minSize, out long totalSize); - - // Cannot be sure of the count, as the prefetch uses parallel threads to get multiple packs - afterPrefetchPackCount.ShouldBeAtLeast(2); - - this.Enlistment.PackfileMaintenanceStep(batchSize: totalSize - minSize + 1); - } - - [TestCase, Order(3)] - public void ExpireAllButOneAndKeep() - { - string prefetchPack = Directory.GetFiles(this.PackRoot, "prefetch-*.pack") - .FirstOrDefault(); - - prefetchPack.ShouldNotBeNull(); - - // We should expire all packs except the one we just created, - // and the prefetch pack which is marked as ".keep" - this.Enlistment.PackfileMaintenanceStep(); - - List packsAfter = this.GetPackfiles(); - - packsAfter.Count.ShouldEqual(2, $"incorrect number of packs after final expire step: {packsAfter.Count}"); - packsAfter.Contains(prefetchPack).ShouldBeTrue($"packsAfter does not contain prefetch pack ({prefetchPack})"); - } - - private List GetPackfiles() - { - return Directory.GetFiles(this.PackRoot, "*.pack").ToList(); - } - - private void GetPackSizes(out int packCount, out long maxSize, out long minSize, out long totalSize) - { - totalSize = 0; - maxSize = 0; - minSize = long.MaxValue; - packCount = 0; - - foreach (string file in this.GetPackfiles()) - { - packCount++; - long size = new FileInfo(Path.Combine(this.PackRoot, file)).Length; - totalSize += size; - - if (size > maxSize) - { - maxSize = size; - } - - if (size < minSize) - { - minSize = size; - } - } - } - } -} diff --git a/GVFS/GVFS.FunctionalTests/Tools/ProjFSFilterInstaller.cs b/GVFS/GVFS.FunctionalTests/Tools/ProjFSFilterInstaller.cs index c56ea8f38f..71b4717287 100644 --- a/GVFS/GVFS.FunctionalTests/Tools/ProjFSFilterInstaller.cs +++ b/GVFS/GVFS.FunctionalTests/Tools/ProjFSFilterInstaller.cs @@ -9,7 +9,7 @@ public class ProjFSFilterInstaller private const string GVFSServiceName = "GVFS.Service"; private const string ProjFSServiceName = "prjflt"; private const string OptionalFeatureName = "Client-ProjFS"; - private const string GVFSInstallPath = @"C:\Program Files\GVFS"; + private const string GVFSInstallPath = @"C:\Program Files\VFS for Git"; private const string NativeProjFSLibInstallLocation = GVFSInstallPath + @"\ProjFS\ProjectedFSLib.dll"; private const string PrjfltInfName = "prjflt.inf"; diff --git a/GVFS/GVFS.FunctionalTests/Windows/Tests/ServiceTests.cs b/GVFS/GVFS.FunctionalTests/Windows/Tests/ServiceTests.cs index ddbca48c19..18b705c0b7 100644 --- a/GVFS/GVFS.FunctionalTests/Windows/Tests/ServiceTests.cs +++ b/GVFS/GVFS.FunctionalTests/Windows/Tests/ServiceTests.cs @@ -16,7 +16,7 @@ namespace GVFS.FunctionalTests.Windows.Tests [Category(Categories.ExtraCoverage)] public class ServiceTests : TestsWithEnlistmentPerFixture { - private const string NativeLibPath = @"C:\Program Files\GVFS\ProjectedFSLib.dll"; + private const string NativeLibPath = @"C:\Program Files\VFS for Git\ProjectedFSLib.dll"; private const string PrjFltAutoLoggerKey = "SYSTEM\\CurrentControlSet\\Control\\WMI\\Autologger\\Microsoft-Windows-ProjFS-Filter-Log"; private const string PrjFltAutoLoggerStartValue = "Start"; diff --git a/GVFS/GVFS.GVFlt/GVFS.GVFlt.csproj b/GVFS/GVFS.GVFlt/GVFS.GVFlt.csproj index 16074079a8..a436520f9d 100644 --- a/GVFS/GVFS.GVFlt/GVFS.GVFlt.csproj +++ b/GVFS/GVFS.GVFlt/GVFS.GVFlt.csproj @@ -5,7 +5,7 @@ - + diff --git a/GVFS/GVFS.Installers/GVFS.Installers.csproj b/GVFS/GVFS.Installers/GVFS.Installers.csproj index da65ffbe46..1f5b692d1c 100644 --- a/GVFS/GVFS.Installers/GVFS.Installers.csproj +++ b/GVFS/GVFS.Installers/GVFS.Installers.csproj @@ -41,7 +41,7 @@ - + diff --git a/GVFS/GVFS.Installers/Setup.iss b/GVFS/GVFS.Installers/Setup.iss index f12f289fc8..ca93ac9142 100644 --- a/GVFS/GVFS.Installers/Setup.iss +++ b/GVFS/GVFS.Installers/Setup.iss @@ -3,9 +3,9 @@ ; General documentation on how to use InnoSetup scripts: http://www.jrsoftware.org/ishelp/index.php -#define MyAppName "GVFS" +#define MyAppName "VFS for Git" #define MyAppInstallerVersion GetFileVersion(LayoutDir + "\GVFS.exe") -#define MyAppPublisher "Microsoft Corporation" +#define MyAppPublisher "Microsoft" #define MyAppPublisherURL "http://www.microsoft.com" #define MyAppURL "https://github.com/microsoft/VFSForGit" #define MyAppExeName "GVFS.exe" diff --git a/GVFS/GVFS.Installers/info.bat b/GVFS/GVFS.Installers/info.bat index 335fc269e7..b068f9c692 100644 --- a/GVFS/GVFS.Installers/info.bat +++ b/GVFS/GVFS.Installers/info.bat @@ -3,10 +3,10 @@ SETLOCAL SET SYS_PRJFLT=C:\Windows\System32\drivers\prjflt.sys SET SYS_PROJFSLIB=C:\Windows\System32\ProjectedFSLib.dll -SET VFS_PROJFSLIB=C:\Program Files\GVFS\ProjectedFSLib.dll -SET VFS_BUND_PRJFLT=C:\Program Files\GVFS\Filter\PrjFlt.sys -SET VFS_BUND_PROJFSLIB=C:\Program Files\GVFS\ProjFS\ProjectedFSLib.dll -SET VFS_EXEC=C:\Program Files\GVFS\GVFS.exe +SET VFS_PROJFSLIB=C:\Program Files\VFS for Git\ProjectedFSLib.dll +SET VFS_BUND_PRJFLT=C:\Program Files\VFS for Git\Filter\PrjFlt.sys +SET VFS_BUND_PROJFSLIB=C:\Program Files\VFS for Git\ProjFS\ProjectedFSLib.dll +SET VFS_EXEC=C:\Program Files\VFS for Git\GVFS.exe SET GIT_EXEC=C:\Program Files\Git\cmd\git.exe ECHO Checking ProjFS Windows feature... diff --git a/GVFS/GVFS.Installers/install.bat b/GVFS/GVFS.Installers/install.bat index fb92991537..c629c75bc1 100644 --- a/GVFS/GVFS.Installers/install.bat +++ b/GVFS/GVFS.Installers/install.bat @@ -16,4 +16,4 @@ ECHO Installing Git for Windows... %GIT_INSTALLER% /LOG="%LOGDIR%\git.log" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /ALLOWDOWNGRADE=1 ECHO Installing VFS for Git... -%GVFS_INSTALLER% /LOG="%LOGDIR%\gvfs.log" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART +%GVFS_INSTALLER% /LOG="%LOGDIR%\gvfs.log" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /DIR="C:\Program Files\VFS for Git" diff --git a/GVFS/GVFS.Payload/GVFS.Payload.csproj b/GVFS/GVFS.Payload/GVFS.Payload.csproj index a976360f65..1df5a32108 100644 --- a/GVFS/GVFS.Payload/GVFS.Payload.csproj +++ b/GVFS/GVFS.Payload/GVFS.Payload.csproj @@ -29,7 +29,7 @@ - + diff --git a/GVFS/GVFS.Payload/layout.bat b/GVFS/GVFS.Payload/layout.bat index 85036a5018..de1c0df81d 100644 --- a/GVFS/GVFS.Payload/layout.bat +++ b/GVFS/GVFS.Payload/layout.bat @@ -38,7 +38,7 @@ SET VCRUNTIME=%4 SET OUTPUT=%5 SET ROOT=%~dp0..\.. -SET BUILD_OUT=%ROOT%\..\out +SET BUILD_OUT="%ROOT%\..\out" SET MANAGED_OUT_FRAGMENT=bin\%CONFIGURATION%\net461\win-x64 SET NATIVE_OUT_FRAGMENT=bin\x64\%CONFIGURATION% diff --git a/GVFS/GVFS.Service/GVFS.Service.csproj b/GVFS/GVFS.Service/GVFS.Service.csproj index 99fa85b3ee..8211413262 100644 --- a/GVFS/GVFS.Service/GVFS.Service.csproj +++ b/GVFS/GVFS.Service/GVFS.Service.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/GVFS/GVFS.UnitTests/Git/GitAuthenticationTests.cs b/GVFS/GVFS.UnitTests/Git/GitAuthenticationTests.cs index 648d6c4636..c083ac5862 100644 --- a/GVFS/GVFS.UnitTests/Git/GitAuthenticationTests.cs +++ b/GVFS/GVFS.UnitTests/Git/GitAuthenticationTests.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using GVFS.Common.Git; using GVFS.Tests; @@ -245,6 +246,33 @@ public void DontStoreDifferentCredentialFromCachedValue() gitProcess.StoredCredentials.Single().Key.ShouldEqual("mock://repoUrl"); } + [TestCase] + public void RejectionShouldNotBeSentIfUnderlyingTokenHasChanged() + { + MockTracer tracer = new MockTracer(); + MockGitProcess gitProcess = this.GetGitProcess(); + + GitAuthentication dut = new GitAuthentication(gitProcess, "mock://repoUrl"); + dut.TryInitializeAndRequireAuth(tracer, out _); + + // Get and store an initial value that will be cached + string authString; + dut.TryGetCredentials(tracer, out authString, out _).ShouldBeTrue(); + dut.ApproveCredentials(tracer, authString); + + // Change the underlying token + gitProcess.SetExpectedCommandResult( + $"{AzureDevOpsUseHttpPathString} credential fill", + () => new GitProcess.Result("username=username\r\npassword=password" + Guid.NewGuid() + "\r\n", string.Empty, GitProcess.Result.SuccessCode)); + + // Try and reject it. We should get a new token, but without forwarding the rejection to the + // underlying credential store + dut.RejectCredentials(tracer, authString); + dut.TryGetCredentials(tracer, out var newAuthString, out _).ShouldBeTrue(); + newAuthString.ShouldNotEqual(authString); + gitProcess.CredentialRejections.ShouldBeEmpty(); + } + private MockGitProcess GetGitProcess() { MockGitProcess gitProcess = new MockGitProcess(); diff --git a/GVFS/GVFS.UnitTests/Mock/Git/MockGitProcess.cs b/GVFS/GVFS.UnitTests/Mock/Git/MockGitProcess.cs index 26923e1803..29f2651ba6 100644 --- a/GVFS/GVFS.UnitTests/Mock/Git/MockGitProcess.cs +++ b/GVFS/GVFS.UnitTests/Mock/Git/MockGitProcess.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; namespace GVFS.UnitTests.Mock.Git @@ -91,7 +92,7 @@ protected override Result InvokeGitImpl( return new Result(string.Empty, string.Empty, Result.GenericFailureCode); } - Predicate commandMatchFunction = + Func commandMatchFunction = (CommandInfo commandInfo) => { if (commandInfo.MatchPrefix) @@ -104,7 +105,7 @@ protected override Result InvokeGitImpl( } }; - CommandInfo matchedCommand = this.expectedCommandInfos.Find(commandMatchFunction); + CommandInfo matchedCommand = this.expectedCommandInfos.Last(commandMatchFunction); matchedCommand.ShouldNotBeNull("Unexpected command: " + command); return matchedCommand.Result(); diff --git a/GVFS/GVFS.Virtualization/GVFS.Virtualization.csproj b/GVFS/GVFS.Virtualization/GVFS.Virtualization.csproj index 0097850b8f..bc5fa77f87 100644 --- a/GVFS/GVFS.Virtualization/GVFS.Virtualization.csproj +++ b/GVFS/GVFS.Virtualization/GVFS.Virtualization.csproj @@ -11,7 +11,7 @@ - + diff --git a/GVFS/GVFS.Virtualization/Projection/GitIndexProjection.GitIndexParser.cs b/GVFS/GVFS.Virtualization/Projection/GitIndexProjection.GitIndexParser.cs index 20c451cbc6..c05348e2b9 100644 --- a/GVFS/GVFS.Virtualization/Projection/GitIndexProjection.GitIndexParser.cs +++ b/GVFS/GVFS.Virtualization/Projection/GitIndexProjection.GitIndexParser.cs @@ -238,8 +238,8 @@ private FileSystemTaskResult ParseIndex( uint entryCount = this.ReadFromIndexHeader(); // Don't want to flood the logs on large indexes so only log every 500ms - const int LoggingTicksThreshold = 5000000; - long nextLogTicks = DateTime.UtcNow.Ticks + LoggingTicksThreshold; + const int LoggingTicksThreshold = 500; + int nextLogTicks = Environment.TickCount + LoggingTicksThreshold; SortedFolderEntries.InitializePools(tracer, entryCount); LazyUTF8String.InitializePools(tracer, entryCount); @@ -329,10 +329,11 @@ private FileSystemTaskResult ParseIndex( return result; } - if (DateTime.UtcNow.Ticks > nextLogTicks) + int curTicks = Environment.TickCount; + if (curTicks - nextLogTicks > 0) { tracer.RelatedInfo($"{i}/{entryCount} index entries parsed."); - nextLogTicks = DateTime.UtcNow.Ticks + LoggingTicksThreshold; + nextLogTicks = curTicks + LoggingTicksThreshold; } } diff --git a/GVFS/GVFS/CommandLine/GVFSVerb.cs b/GVFS/GVFS/CommandLine/GVFSVerb.cs index 8449b5bab1..cd74600e43 100644 --- a/GVFS/GVFS/CommandLine/GVFSVerb.cs +++ b/GVFS/GVFS/CommandLine/GVFSVerb.cs @@ -312,6 +312,11 @@ public static bool TrySetRequiredGitConfigSettings(Enlistment enlistment) // Disable the builtin FS Monitor in case it was enabled globally. { "core.useBuiltinFSMonitor", "false" }, + + // Set the GCM credential method to use OAuth tokens. + // See https://github.com/git-ecosystem/git-credential-manager/blob/release/docs/configuration.md#credentialazreposcredentialtype + // for more information. + { "credential.azreposCredentialType", "oauth" }, }; if (!TrySetConfig(enlistment, requiredSettings, isRequired: true)) diff --git a/GVFS/GVFS/RepairJobs/GitIndexRepairJob.cs b/GVFS/GVFS/RepairJobs/GitIndexRepairJob.cs index 20dbb63844..153634badc 100644 --- a/GVFS/GVFS/RepairJobs/GitIndexRepairJob.cs +++ b/GVFS/GVFS/RepairJobs/GitIndexRepairJob.cs @@ -46,7 +46,7 @@ public override FixResult TryFixIssues(List messages) } GitIndexGenerator indexGen = new GitIndexGenerator(this.Tracer, this.Enlistment, shouldHashIndex: false); - indexGen.CreateFromHeadTree(indexVersion: 4); + indexGen.CreateFromRef(GVFSConstants.DotGit.HeadName, indexVersion: 4, isFinal: true); if (indexGen.HasFailures || this.TryParseIndex(this.indexPath, messages) != IssueType.None) { diff --git a/Readme.md b/Readme.md index 3dc9dbfdd0..2ee0517b93 100644 --- a/Readme.md +++ b/Readme.md @@ -1,5 +1,7 @@ # VFS for Git +**Notice:** With the release of VFS for Git 2.32, VFS for Git is in maintenance mode. Only required updates as a reaction to critical security vulnerabilities will prompt a release. + |Branch|Unit Tests|Functional Tests|Large Repo Perf|Large Repo Build| |:--:|:--:|:--:|:--:|:--:| |**master**|[![Build status](https://dev.azure.com/gvfs/ci/_apis/build/status/CI%20-%20Windows?branchName=master)](https://dev.azure.com/gvfs/ci/_build/latest?definitionId=7&branchName=master)|[![Build status](https://dev.azure.com/gvfs/ci/_apis/build/status/CI%20-%20Windows%20-%20Full%20Functional%20Tests?branchName=master)](https://dev.azure.com/gvfs/ci/_build/latest?definitionId=6&branchName=master)|[![Build status](https://dev.azure.com/mseng/AzureDevOps/_apis/build/status/GVFS/GitHub%20VFSForGit%20Large%20Repo%20Perf%20Tests?branchName=master)](https://dev.azure.com/mseng/AzureDevOps/_build/latest?definitionId=7179&branchName=master)|[![Build status](https://dev.azure.com/mseng/AzureDevOps/_apis/build/status/GVFS/GitHub%20VFSForGit%20Large%20Repo%20Build?branchName=master)](https://dev.azure.com/mseng/AzureDevOps/_build/latest?definitionId=7180&branchName=master)| @@ -22,8 +24,20 @@ in Git, Scalar offers a clearer path forward for all large monorepos. ## Installing VFS for Git -* VFS for Git requires Windows 10 Anniversary Update (Windows 10 version 1607) or later -* Run the latest GVFS and Git for Windows installers from https://github.com/Microsoft/VFSForGit/releases +VFS for Git requires Windows 10 Anniversary Update (Windows 10 version 1607) or later. + +To install, use [`winget`](https://github.com/microsoft/winget-cli) to install the +[`microsoft/git` fork of Git](https://github.com/microsoft/git) and VFS for Git +using: + +``` +winget install --id Microsoft.Git +winget install --id Microsoft.VFSforGit +``` + +You will need to continue using the `microsoft/git` version of Git, and it +will notify you when new versions are available. + ## Building VFS for Git diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..869fdfe2b2 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). + + diff --git a/Version.props b/Version.props index 09487168e3..6ee26e84bc 100644 --- a/Version.props +++ b/Version.props @@ -18,7 +18,7 @@ including taking version numbers 2.X.Y from upstream and updating .W if we have any hotfixes to microsoft/git. --> - 2.20210817.4 + 2.20211115.1 v2.31.0.vfs.0.1 diff --git a/scripts/Build.bat b/scripts/Build.bat index a8f263e78a..637b1e18e9 100644 --- a/scripts/Build.bat +++ b/scripts/Build.bat @@ -1,5 +1,5 @@ @ECHO OFF -CALL %~dp0\InitializeEnvironment.bat || EXIT /b 10 +CALL "%~dp0\InitializeEnvironment.bat" || EXIT /b 10 SETLOCAL SETLOCAL EnableDelayedExpansion @@ -44,7 +44,7 @@ IF NOT EXIST "%NUGET_EXEC%" ( REM Acquire vswhere to find VS installations reliably SET VSWHERE_VER=2.6.7 "%NUGET_EXEC%" install vswhere -Version %VSWHERE_VER% || exit /b 1 -SET VSWHERE_EXEC=%VFS_PACKAGESDIR%\vswhere.%VSWHERE_VER%\tools\vswhere.exe +SET VSWHERE_EXEC="%VFS_PACKAGESDIR%\vswhere.%VSWHERE_VER%\tools\vswhere.exe" REM Assumes default installation location for Windows 10 SDKs IF NOT EXIST "C:\Program Files (x86)\Windows Kits\10\Include\10.0.16299.0" ( @@ -69,7 +69,7 @@ IF NOT DEFINED MSBUILD_EXEC ( ECHO ^********************** ECHO ^* Restoring Packages * ECHO ^********************** -"%MSBUILD_EXEC%" %VFS_SRCDIR%\GVFS.sln ^ +"%MSBUILD_EXEC%" "%VFS_SRCDIR%\GVFS.sln" ^ /t:Restore ^ /v:%VERBOSITY% ^ /p:Configuration=%CONFIGURATION% || GOTO ERROR @@ -77,7 +77,7 @@ ECHO ^********************** ECHO ^********************* ECHO ^* Building Solution * ECHO ^********************* -"%MSBUILD_EXEC%" %VFS_SRCDIR%\GVFS.sln ^ +"%MSBUILD_EXEC%" "%VFS_SRCDIR%\GVFS.sln" ^ /t:Build ^ /v:%VERBOSITY% ^ /p:Configuration=%CONFIGURATION% || GOTO ERROR diff --git a/scripts/RunFunctionalTests.bat b/scripts/RunFunctionalTests.bat index 7ad871217d..80ab7e1e4c 100644 --- a/scripts/RunFunctionalTests.bat +++ b/scripts/RunFunctionalTests.bat @@ -5,7 +5,7 @@ IF "%1"=="" (SET "CONFIGURATION=Debug") ELSE (SET "CONFIGURATION=%1") REM Ensure GVFS installation is on the PATH for the Functional Tests to find SETLOCAL -SET PATH=C:\Program Files\GVFS;C:\Program Files\Git\cmd;%PATH% +SET PATH=C:\Program Files\VFS for Git\;C:\Program Files\GVFS;C:\Program Files\Git\cmd;%PATH% ECHO PATH = %PATH%