Example #1
0
        private void EnqueueOperationsFromDiffTreeLine(ITracer activity, string line)
        {
            if (!line.StartsWith(":"))
            {
                // Diff-tree starts with metadata we can ignore.
                // Real diff lines always start with a colon
                return;
            }

            DiffTreeResult result = DiffTreeResult.ParseFromDiffTreeLine(line);

            if (!this.ShouldIncludeResult(result))
            {
                return;
            }

            if (result.Operation == DiffTreeResult.Operations.Unknown ||
                result.Operation == DiffTreeResult.Operations.Unmerged ||
                result.Operation == DiffTreeResult.Operations.CopyEdit ||
                result.Operation == DiffTreeResult.Operations.RenameEdit)
            {
                EventMetadata metadata = new EventMetadata();
                metadata.Add(nameof(result.TargetPath), result.TargetPath);
                metadata.Add(nameof(line), line);
                activity.RelatedError(metadata, "Unexpected diff operation: " + result.Operation);
                this.HasFailures = true;
                return;
            }

            // Separate and enqueue all directory operations first.
            if (result.SourceIsDirectory || result.TargetIsDirectory)
            {
                switch (result.Operation)
                {
                case DiffTreeResult.Operations.Delete:
                    if (!this.stagedDirectoryOperations.Add(result))
                    {
                        EventMetadata metadata = new EventMetadata();
                        metadata.Add(nameof(result.TargetPath), result.TargetPath);
                        metadata.Add(TracingConstants.MessageKey.WarningMessage, "A case change was attempted. It will not be reflected in the working directory.");
                        activity.RelatedEvent(EventLevel.Warning, "CaseConflict", metadata);
                    }

                    break;

                case DiffTreeResult.Operations.Add:
                case DiffTreeResult.Operations.Modify:
                    if (!this.stagedDirectoryOperations.Add(result))
                    {
                        EventMetadata metadata = new EventMetadata();
                        metadata.Add(nameof(result.TargetPath), result.TargetPath);
                        metadata.Add(TracingConstants.MessageKey.WarningMessage, "A case change was attempted. It will not be reflected in the working directory.");
                        activity.RelatedEvent(EventLevel.Warning, "CaseConflict", metadata);

                        // Replace the delete with the add to make sure we don't delete a folder from under ourselves
                        this.stagedDirectoryOperations.Remove(result);
                        this.stagedDirectoryOperations.Add(result);
                    }

                    break;

                default:
                    activity.RelatedError("Unexpected diff operation from line: {0}", line);
                    break;
                }
            }
            else
            {
                switch (result.Operation)
                {
                case DiffTreeResult.Operations.Delete:
                    this.EnqueueFileDeleteOperation(activity, result.TargetPath);

                    break;

                case DiffTreeResult.Operations.Modify:
                case DiffTreeResult.Operations.Add:
                    this.EnqueueFileAddOperation(activity, result);
                    break;

                default:
                    activity.RelatedError("Unexpected diff operation from line: {0}", line);
                    break;
                }
            }
        }
        /// <param name="endOfFile">Length of the file, not required on the Mac platform</param>
        public override FileSystemResult UpdatePlaceholderIfNeeded(
            string relativePath,
            DateTime creationTime,
            DateTime lastAccessTime,
            DateTime lastWriteTime,
            DateTime changeTime,
            FileAttributes fileAttributes,
            long endOfFile,
            string shaContentId,
            UpdatePlaceholderType updateFlags,
            out UpdateFailureReason failureReason)
        {
            UpdateFailureCause failureCause = UpdateFailureCause.NoFailure;

            // TODO(#223): Add functional tests that include:
            //     - Mode + content changes between commits
            //     - Mode only changes (without any change to content, see issue #223)
            GitIndexProjection.FileType fileType;
            ushort fileMode;

            this.FileSystemCallbacks.GitIndexProjection.GetFileTypeAndMode(relativePath, out fileType, out fileMode);

            if (fileType == GitIndexProjection.FileType.Regular)
            {
                Result result = this.virtualizationInstance.UpdatePlaceholderIfNeeded(
                    relativePath,
                    PlaceholderVersionId,
                    ToVersionIdByteArray(ConvertShaToContentId(shaContentId)),
                    fileMode,
                    (UpdateType)updateFlags,
                    out failureCause);

                failureReason = (UpdateFailureReason)failureCause;
                return(new FileSystemResult(ResultToFSResult(result), unchecked ((int)result)));
            }
            else if (fileType == GitIndexProjection.FileType.SymLink)
            {
                string symLinkTarget;
                if (this.TryGetSymLinkTarget(shaContentId, out symLinkTarget))
                {
                    Result result = this.virtualizationInstance.ReplacePlaceholderFileWithSymLink(
                        relativePath,
                        symLinkTarget,
                        (UpdateType)updateFlags,
                        out failureCause);

                    this.FileSystemCallbacks.OnFileSymLinkCreated(relativePath);

                    failureReason = (UpdateFailureReason)failureCause;
                    return(new FileSystemResult(ResultToFSResult(result), unchecked ((int)result)));
                }

                EventMetadata metadata = this.CreateEventMetadata(relativePath);
                metadata.Add(nameof(shaContentId), shaContentId);
                this.Context.Tracer.RelatedError(metadata, $"{nameof(this.UpdatePlaceholderIfNeeded)}: Failed to read contents of symlink object");
                failureReason = UpdateFailureReason.NoFailure;
                return(new FileSystemResult(FSResult.IOError, 0));
            }
            else
            {
                EventMetadata metadata = this.CreateEventMetadata(relativePath);
                metadata.Add(nameof(fileType), fileType);
                metadata.Add(nameof(fileMode), fileMode);
                this.Context.Tracer.RelatedError(metadata, $"{nameof(this.UpdatePlaceholderIfNeeded)}: Unsupported fileType");
                failureReason = UpdateFailureReason.NoFailure;
                return(new FileSystemResult(FSResult.IOError, 0));
            }
        }
        private Result OnGetFileStream(
            ulong commandId,
            string relativePath,
            byte[] providerId,
            byte[] contentId,
            int triggeringProcessId,
            string triggeringProcessName,
            IntPtr fileHandle)
        {
            try
            {
                if (contentId == null)
                {
                    this.Context.Tracer.RelatedError($"{nameof(this.OnGetFileStream)} called with null contentId, path: " + relativePath);
                    return(Result.EInvalidOperation);
                }

                if (providerId == null)
                {
                    this.Context.Tracer.RelatedError($"{nameof(this.OnGetFileStream)} called with null epochId, path: " + relativePath);
                    return(Result.EInvalidOperation);
                }

                string sha = GetShaFromContentId(contentId);
                byte   placeholderVersion = GetPlaceholderVersionFromProviderId(providerId);

                EventMetadata metadata = this.CreateEventMetadata(relativePath);
                metadata.Add(nameof(triggeringProcessId), triggeringProcessId);
                metadata.Add(nameof(triggeringProcessName), triggeringProcessName);
                metadata.Add(nameof(sha), sha);
                metadata.Add(nameof(placeholderVersion), placeholderVersion);
                metadata.Add(nameof(commandId), commandId);
                ITracer activity = this.Context.Tracer.StartActivity("GetFileStream", EventLevel.Verbose, Keywords.Telemetry, metadata);

                if (placeholderVersion != FileSystemVirtualizer.PlaceholderVersion)
                {
                    activity.RelatedError(metadata, nameof(this.OnGetFileStream) + ": Unexpected placeholder version");
                    activity.Dispose();

                    // TODO(#1362): Is this the correct Result to return?
                    return(Result.EIOError);
                }

                try
                {
                    if (!this.GitObjects.TryCopyBlobContentStream(
                            sha,
                            CancellationToken.None,
                            GVFSGitObjects.RequestSource.FileStreamCallback,
                            (stream, blobLength) =>
                    {
                        // TODO(#1361): Find a better solution than reading from the stream one byte at at time
                        byte[] buffer = new byte[4096];
                        uint bufferIndex = 0;
                        int nextByte = stream.ReadByte();
                        int bytesWritten = 0;
                        while (nextByte != -1)
                        {
                            while (bufferIndex < buffer.Length && nextByte != -1)
                            {
                                buffer[bufferIndex] = (byte)nextByte;
                                nextByte = stream.ReadByte();
                                ++bufferIndex;
                            }

                            Result result = this.virtualizationInstance.WriteFileContents(
                                fileHandle,
                                buffer,
                                bufferIndex);
                            if (result != Result.Success)
                            {
                                activity.RelatedError(metadata, $"{nameof(this.virtualizationInstance.WriteFileContents)} failed, error: " + result.ToString("X") + "(" + result.ToString("G") + ")");
                                throw new GetFileStreamException(result);
                            }

                            if (bufferIndex == buffer.Length)
                            {
                                bufferIndex = 0;
                                bytesWritten += buffer.Length;
                            }
                        }
                        bytesWritten += Convert.ToInt32(bufferIndex);

                        if (bytesWritten != blobLength)
                        {
                            // If the read size does not match the expected size print an error and add the file to ModifiedPaths.dat
                            // This allows the user to see that something went wrong with file hydration
                            // Unfortunitely we must do this check *after* the file is hydrated since the header isn't corrupt for trunctated objects on mac
                            this.Context.Tracer.RelatedError($"Read {relativePath} to {bytesWritten}, not expected size of {blobLength}");
                            this.FileSystemCallbacks.OnFailedFileHydration(relativePath);
                        }
                    }))
                    {
                        activity.RelatedError(metadata, $"{nameof(this.OnGetFileStream)}: TryCopyBlobContentStream failed");

                        // TODO(#1362): Is this the correct Result to return?
                        return(Result.EFileNotFound);
                    }
                }
                catch (GetFileStreamException e)
                {
                    return(e.Result);
                }

                this.FileSystemCallbacks.OnPlaceholderFileHydrated(triggeringProcessName);
                return(Result.Success);
            }
            catch (Exception e)
            {
                EventMetadata metadata = this.CreateEventMetadata(relativePath, e);
                metadata.Add(nameof(triggeringProcessId), triggeringProcessId);
                metadata.Add(nameof(triggeringProcessName), triggeringProcessName);
                metadata.Add(nameof(commandId), commandId);
                this.LogUnhandledExceptionAndExit(nameof(this.OnGetFileStream), metadata);
            }

            return(Result.EIOError);
        }
Example #4
0
        public virtual GitProcess.Result IndexPackFile(string packfilePath, GitProcess gitProcess)
        {
            string tempIdxPath = Path.ChangeExtension(packfilePath, TempIdxExtension);
            string idxPath     = Path.ChangeExtension(packfilePath, ".idx");

            Exception indexPackException = null;

            try
            {
                if (gitProcess == null)
                {
                    gitProcess = new GitProcess(this.Enlistment);
                }

                GitProcess.Result result = gitProcess.IndexPack(packfilePath, tempIdxPath);
                if (result.ExitCodeIsFailure)
                {
                    Exception exception;
                    if (!this.fileSystem.TryDeleteFile(tempIdxPath, exception: out exception))
                    {
                        EventMetadata metadata = CreateEventMetadata(exception);
                        metadata.Add("tempIdxPath", tempIdxPath);
                        this.Tracer.RelatedWarning(metadata, $"{nameof(this.IndexPackFile)}: Failed to cleanup temp idx file after index pack failure");
                    }
                }
                else
                {
                    if (this.Enlistment.FlushFileBuffersForPacks)
                    {
                        Exception exception;
                        string    error;
                        if (!this.TryFlushFileBuffers(tempIdxPath, out exception, out error))
                        {
                            EventMetadata metadata = CreateEventMetadata(exception);
                            metadata.Add("packfilePath", packfilePath);
                            metadata.Add("tempIndexPath", tempIdxPath);
                            metadata.Add("error", error);
                            this.Tracer.RelatedWarning(metadata, $"{nameof(this.IndexPackFile)}: Failed to flush temp idx file buffers");
                        }
                    }

                    this.fileSystem.MoveAndOverwriteFile(tempIdxPath, idxPath);
                }

                return(result);
            }
            catch (Win32Exception e)
            {
                indexPackException = e;
            }
            catch (IOException e)
            {
                indexPackException = e;
            }
            catch (UnauthorizedAccessException e)
            {
                indexPackException = e;
            }

            EventMetadata failureMetadata = CreateEventMetadata(indexPackException);

            failureMetadata.Add("packfilePath", packfilePath);
            failureMetadata.Add("tempIdxPath", tempIdxPath);
            failureMetadata.Add("idxPath", idxPath);

            this.fileSystem.TryDeleteFile(tempIdxPath, metadataKey: nameof(tempIdxPath), metadata: failureMetadata);
            this.fileSystem.TryDeleteFile(idxPath, metadataKey: nameof(idxPath), metadata: failureMetadata);

            this.Tracer.RelatedWarning(failureMetadata, $"{nameof(this.IndexPackFile): Exception caught while trying to index pack file}");

            return(new GitProcess.Result(
                       string.Empty,
                       indexPackException != null ? indexPackException.Message : "Failed to index pack file",
                       GitProcess.Result.GenericFailureCode));
        }
Example #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));
        }
        protected override void Execute(GVFSEnlistment enlistment)
        {
            using (JsonTracer tracer = new JsonTracer(GVFSConstants.GVFSEtwProviderName, "Prefetch"))
            {
                if (this.Verbose)
                {
                    tracer.AddDiagnosticConsoleEventListener(EventLevel.Informational, Keywords.Any);
                }

                string cacheServerUrl = CacheServerResolver.GetUrlFromConfig(enlistment);

                tracer.AddLogFileEventListener(
                    GVFSEnlistment.GetNewGVFSLogFileName(enlistment.GVFSLogsRoot, GVFSConstants.LogFileTypes.Prefetch),
                    EventLevel.Informational,
                    Keywords.Any);
                tracer.WriteStartEvent(
                    enlistment.EnlistmentRoot,
                    enlistment.RepoUrl,
                    cacheServerUrl);

                RetryConfig retryConfig = this.GetRetryConfig(tracer, enlistment, TimeSpan.FromMinutes(RetryConfig.FetchAndCloneTimeoutMinutes));

                CacheServerInfo cacheServer = this.ResolvedCacheServer;
                GVFSConfig      gvfsConfig  = this.GVFSConfig;
                if (!this.SkipVersionCheck)
                {
                    string authErrorMessage;
                    if (!this.ShowStatusWhileRunning(
                            () => enlistment.Authentication.TryRefreshCredentials(tracer, out authErrorMessage),
                            "Authenticating"))
                    {
                        this.ReportErrorAndExit(tracer, "Unable to prefetch because authentication failed");
                    }

                    if (gvfsConfig == null)
                    {
                        gvfsConfig = this.QueryGVFSConfig(tracer, enlistment, retryConfig);
                    }

                    if (cacheServer == null)
                    {
                        CacheServerResolver cacheServerResolver = new CacheServerResolver(tracer, enlistment);
                        cacheServer = cacheServerResolver.ResolveNameFromRemote(cacheServerUrl, gvfsConfig);
                    }

                    this.ValidateClientVersions(tracer, enlistment, gvfsConfig, showWarnings: false);

                    this.Output.WriteLine("Configured cache server: " + cacheServer);
                }

                this.InitializeLocalCacheAndObjectsPaths(tracer, enlistment, retryConfig, gvfsConfig, cacheServer);

                try
                {
                    EventMetadata metadata = new EventMetadata();
                    metadata.Add("Commits", this.Commits);
                    metadata.Add("Files", this.Files);
                    metadata.Add("Folders", this.Folders);
                    metadata.Add("FoldersListFile", this.FoldersListFile);
                    metadata.Add("HydrateFiles", this.HydrateFiles);
                    tracer.RelatedEvent(EventLevel.Informational, "PerformPrefetch", metadata);

                    GitObjectsHttpRequestor objectRequestor = new GitObjectsHttpRequestor(tracer, enlistment, cacheServer, retryConfig);

                    if (this.Commits)
                    {
                        if (!string.IsNullOrWhiteSpace(this.Files) ||
                            !string.IsNullOrWhiteSpace(this.Folders) ||
                            !string.IsNullOrWhiteSpace(this.FoldersListFile))
                        {
                            this.ReportErrorAndExit(tracer, "You cannot prefetch commits and blobs at the same time.");
                        }

                        if (this.HydrateFiles)
                        {
                            this.ReportErrorAndExit(tracer, "You can only specify --hydrate with --files or --folders");
                        }

                        this.PrefetchCommits(tracer, enlistment, objectRequestor, cacheServer);
                    }
                    else
                    {
                        this.PrefetchBlobs(tracer, enlistment, objectRequestor, cacheServer);
                    }
                }
                catch (VerbAbortedException)
                {
                    throw;
                }
                catch (AggregateException aggregateException)
                {
                    this.Output.WriteLine(
                        "Cannot prefetch {0}. " + ConsoleHelper.GetGVFSLogMessage(enlistment.EnlistmentRoot),
                        enlistment.EnlistmentRoot);
                    foreach (Exception innerException in aggregateException.Flatten().InnerExceptions)
                    {
                        tracer.RelatedError(
                            new EventMetadata
                        {
                            { "Verb", typeof(PrefetchVerb).Name },
                            { "Exception", innerException.ToString() }
                        },
                            $"Unhandled {innerException.GetType().Name}: {innerException.Message}");
                    }

                    Environment.ExitCode = (int)ReturnCode.GenericError;
                }
                catch (Exception e)
                {
                    this.Output.WriteLine(
                        "Cannot prefetch {0}. " + ConsoleHelper.GetGVFSLogMessage(enlistment.EnlistmentRoot),
                        enlistment.EnlistmentRoot);
                    tracer.RelatedError(
                        new EventMetadata
                    {
                        { "Verb", typeof(PrefetchVerb).Name },
                        { "Exception", e.ToString() }
                    },
                        $"Unhandled {e.GetType().Name}: {e.Message}");

                    Environment.ExitCode = (int)ReturnCode.GenericError;
                }
            }
        }
Example #7
0
        private FileSystemTaskResult ExecuteBackgroundOperation(FileSystemTask gitUpdate)
        {
            EventMetadata metadata = new EventMetadata();

            FileSystemTaskResult result;

            switch (gitUpdate.Operation)
            {
            case FileSystemTask.OperationType.OnFileCreated:
            case FileSystemTask.OperationType.OnFailedPlaceholderDelete:
            case FileSystemTask.OperationType.OnFileHardLinkCreated:
            case FileSystemTask.OperationType.OnFileSymLinkCreated:
                metadata.Add("virtualPath", gitUpdate.VirtualPath);
                result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.VirtualPath);
                break;

            case FileSystemTask.OperationType.OnFileRenamed:
                metadata.Add("oldVirtualPath", gitUpdate.OldVirtualPath);
                metadata.Add("virtualPath", gitUpdate.VirtualPath);
                result = FileSystemTaskResult.Success;
                if (!string.IsNullOrEmpty(gitUpdate.OldVirtualPath) && !IsPathInsideDotGit(gitUpdate.OldVirtualPath))
                {
                    if (this.newlyCreatedFileAndFolderPaths.Contains(gitUpdate.OldVirtualPath))
                    {
                        result = this.TryRemoveModifiedPath(gitUpdate.OldVirtualPath, isFolder: false);
                    }
                    else
                    {
                        result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.OldVirtualPath);
                    }
                }

                if (result == FileSystemTaskResult.Success &&
                    !string.IsNullOrEmpty(gitUpdate.VirtualPath) &&
                    !IsPathInsideDotGit(gitUpdate.VirtualPath))
                {
                    result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.VirtualPath);
                }

                break;

            case FileSystemTask.OperationType.OnFilePreDelete:
                // This code assumes that the current implementations of FileSystemVirtualizer will call either
                // the PreDelete or the Delete not both so if a new implementation starts calling both
                // this will need to be cleaned up to not duplicate the work that is being done.
                metadata.Add("virtualPath", gitUpdate.VirtualPath);
                if (this.newlyCreatedFileAndFolderPaths.Contains(gitUpdate.VirtualPath))
                {
                    string fullPathToFile = Path.Combine(this.context.Enlistment.WorkingDirectoryRoot, gitUpdate.VirtualPath);

                    // Because this is a predelete message the file could still be on disk when we make this check
                    // so we retry for a limited time before deciding the delete didn't happen
                    bool fileDeleted = CheckConditionWithRetry(() => !this.context.FileSystem.FileExists(fullPathToFile), NumberOfRetriesCheckingForDeleted, MillisecondsToSleepBeforeCheckingForDeleted);
                    if (fileDeleted)
                    {
                        result = this.TryRemoveModifiedPath(gitUpdate.VirtualPath, isFolder: false);
                    }
                    else
                    {
                        result = FileSystemTaskResult.Success;
                    }
                }
                else
                {
                    result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.VirtualPath);
                }

                break;

            case FileSystemTask.OperationType.OnFileDeleted:
                // This code assumes that the current implementations of FileSystemVirtualizer will call either
                // the PreDelete or the Delete not both so if a new implementation starts calling both
                // this will need to be cleaned up to not duplicate the work that is being done.
                metadata.Add("virtualPath", gitUpdate.VirtualPath);
                if (this.newlyCreatedFileAndFolderPaths.Contains(gitUpdate.VirtualPath))
                {
                    result = this.TryRemoveModifiedPath(gitUpdate.VirtualPath, isFolder: false);
                }
                else
                {
                    result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.VirtualPath);
                }

                break;

            case FileSystemTask.OperationType.OnFileOverwritten:
            case FileSystemTask.OperationType.OnFileSuperseded:
            case FileSystemTask.OperationType.OnFileConvertedToFull:
            case FileSystemTask.OperationType.OnFailedPlaceholderUpdate:
                metadata.Add("virtualPath", gitUpdate.VirtualPath);
                result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.VirtualPath);
                break;

            case FileSystemTask.OperationType.OnFolderCreated:
                metadata.Add("virtualPath", gitUpdate.VirtualPath);
                result = this.TryAddModifiedPath(gitUpdate.VirtualPath, isFolder: true);
                break;

            case FileSystemTask.OperationType.OnFolderRenamed:
                result = FileSystemTaskResult.Success;
                metadata.Add("oldVirtualPath", gitUpdate.OldVirtualPath);
                metadata.Add("virtualPath", gitUpdate.VirtualPath);

                if (!string.IsNullOrEmpty(gitUpdate.OldVirtualPath) &&
                    this.newlyCreatedFileAndFolderPaths.Contains(gitUpdate.OldVirtualPath))
                {
                    result = this.TryRemoveModifiedPath(gitUpdate.OldVirtualPath, isFolder: true);
                }

                // An empty destination path means the folder was renamed to somewhere outside of the repo
                // Note that only full folders can be moved\renamed, and so there will already be a recursive
                // sparse-checkout entry for the virtualPath of the folder being moved (meaning that no
                // additional work is needed for any files\folders inside the folder being moved)
                if (result == FileSystemTaskResult.Success && !string.IsNullOrEmpty(gitUpdate.VirtualPath))
                {
                    this.AddToNewlyCreatedList(gitUpdate.VirtualPath, isFolder: true);
                    result = this.TryAddModifiedPath(gitUpdate.VirtualPath, isFolder: true);
                    if (result == FileSystemTaskResult.Success)
                    {
                        Queue <string> relativeFolderPaths = new Queue <string>();
                        relativeFolderPaths.Enqueue(gitUpdate.VirtualPath);

                        // Remove old paths from modified paths if in the newly created list
                        while (relativeFolderPaths.Count > 0)
                        {
                            string folderPath = relativeFolderPaths.Dequeue();
                            if (result == FileSystemTaskResult.Success)
                            {
                                try
                                {
                                    foreach (DirectoryItemInfo itemInfo in this.context.FileSystem.ItemsInDirectory(Path.Combine(this.context.Enlistment.WorkingDirectoryRoot, folderPath)))
                                    {
                                        string itemVirtualPath    = Path.Combine(folderPath, itemInfo.Name);
                                        string oldItemVirtualPath = gitUpdate.OldVirtualPath + itemVirtualPath.Substring(gitUpdate.VirtualPath.Length);

                                        this.AddToNewlyCreatedList(itemVirtualPath, isFolder: itemInfo.IsDirectory);
                                        if (this.newlyCreatedFileAndFolderPaths.Contains(oldItemVirtualPath))
                                        {
                                            result = this.TryRemoveModifiedPath(oldItemVirtualPath, isFolder: itemInfo.IsDirectory);
                                        }

                                        if (itemInfo.IsDirectory)
                                        {
                                            relativeFolderPaths.Enqueue(itemVirtualPath);
                                        }
                                    }
                                }
                                catch (DirectoryNotFoundException)
                                {
                                    // DirectoryNotFoundException can occur when the renamed folder (or one of its children) is
                                    // deleted prior to the background thread running
                                    EventMetadata exceptionMetadata = new EventMetadata();
                                    exceptionMetadata.Add("Area", "ExecuteBackgroundOperation");
                                    exceptionMetadata.Add("Operation", gitUpdate.Operation.ToString());
                                    exceptionMetadata.Add("oldVirtualPath", gitUpdate.OldVirtualPath);
                                    exceptionMetadata.Add("virtualPath", gitUpdate.VirtualPath);
                                    exceptionMetadata.Add(TracingConstants.MessageKey.InfoMessage, "DirectoryNotFoundException while traversing folder path");
                                    exceptionMetadata.Add("folderPath", folderPath);
                                    this.context.Tracer.RelatedEvent(EventLevel.Informational, "DirectoryNotFoundWhileUpdatingModifiedPaths", exceptionMetadata);
                                }
                                catch (IOException e)
                                {
                                    metadata.Add("Details", "IOException while traversing folder path");
                                    metadata.Add("folderPath", folderPath);
                                    metadata.Add("Exception", e.ToString());
                                    result = FileSystemTaskResult.RetryableError;
                                    break;
                                }
                                catch (UnauthorizedAccessException e)
                                {
                                    metadata.Add("Details", "UnauthorizedAccessException while traversing folder path");
                                    metadata.Add("folderPath", folderPath);
                                    metadata.Add("Exception", e.ToString());
                                    result = FileSystemTaskResult.RetryableError;
                                    break;
                                }
                            }
                            else
                            {
                                break;
                            }
                        }
                    }
                }

                break;

            case FileSystemTask.OperationType.OnFolderPreDelete:
                // This code assumes that the current implementations of FileSystemVirtualizer will call either
                // the PreDelete or the Delete not both so if a new implementation starts calling both
                // this will need to be cleaned up to not duplicate the work that is being done.
                metadata.Add("virtualPath", gitUpdate.VirtualPath);
                if (this.newlyCreatedFileAndFolderPaths.Contains(gitUpdate.VirtualPath))
                {
                    string fullPathToFolder = Path.Combine(this.context.Enlistment.WorkingDirectoryRoot, gitUpdate.VirtualPath);

                    // Because this is a predelete message the file could still be on disk when we make this check
                    // so we retry for a limited time before deciding the delete didn't happen
                    bool folderDeleted = CheckConditionWithRetry(() => !this.context.FileSystem.DirectoryExists(fullPathToFolder), NumberOfRetriesCheckingForDeleted, MillisecondsToSleepBeforeCheckingForDeleted);
                    if (folderDeleted)
                    {
                        result = this.TryRemoveModifiedPath(gitUpdate.VirtualPath, isFolder: true);
                    }
                    else
                    {
                        result = FileSystemTaskResult.Success;
                    }
                }
                else
                {
                    result = this.TryAddModifiedPath(gitUpdate.VirtualPath, isFolder: true);
                }

                break;

            case FileSystemTask.OperationType.OnFolderDeleted:
                // This code assumes that the current implementations of FileSystemVirtualizer will call either
                // the PreDelete or the Delete not both so if a new implementation starts calling both
                // this will need to be cleaned up to not duplicate the work that is being done.
                metadata.Add("virtualPath", gitUpdate.VirtualPath);
                if (this.newlyCreatedFileAndFolderPaths.Contains(gitUpdate.VirtualPath))
                {
                    result = this.TryRemoveModifiedPath(gitUpdate.VirtualPath, isFolder: true);
                }
                else
                {
                    result = this.TryAddModifiedPath(gitUpdate.VirtualPath, isFolder: true);
                }

                break;

            case FileSystemTask.OperationType.OnFolderFirstWrite:
                result = FileSystemTaskResult.Success;
                break;

            case FileSystemTask.OperationType.OnIndexWriteRequiringModifiedPathsValidation:
                result = this.GitIndexProjection.AddMissingModifiedFiles();
                break;

            case FileSystemTask.OperationType.OnPlaceholderCreationsBlockedForGit:
                this.GitIndexProjection.ClearNegativePathCacheIfPollutedByGit();
                result = FileSystemTaskResult.Success;
                break;

            default:
                throw new InvalidOperationException("Invalid background operation");
            }

            if (result != FileSystemTaskResult.Success)
            {
                metadata.Add("Area", "ExecuteBackgroundOperation");
                metadata.Add("Operation", gitUpdate.Operation.ToString());
                metadata.Add(TracingConstants.MessageKey.WarningMessage, "Background operation failed");
                metadata.Add(nameof(result), result.ToString());
                this.context.Tracer.RelatedEvent(EventLevel.Warning, "FailedBackgroundOperation", metadata);
            }

            return(result);
        }
Example #8
0
        private void HandleDehydrateFolders(NamedPipeMessages.Message message, NamedPipeServer.Connection connection)
        {
            NamedPipeMessages.DehydrateFolders.Request request = new NamedPipeMessages.DehydrateFolders.Request(message);

            EventMetadata metadata = new EventMetadata();

            metadata.Add(nameof(request.Folders), request.Folders);
            metadata.Add(TracingConstants.MessageKey.InfoMessage, "Received dehydrate folders request");
            this.tracer.RelatedEvent(EventLevel.Informational, nameof(this.HandleDehydrateFolders), metadata);

            NamedPipeMessages.DehydrateFolders.Response response;
            if (this.currentState == MountState.Ready)
            {
                response = new NamedPipeMessages.DehydrateFolders.Response(NamedPipeMessages.DehydrateFolders.DehydratedResult);
                string[]      folders          = request.Folders.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
                StringBuilder resetFolderPaths = new StringBuilder();
                foreach (string folder in folders)
                {
                    if (this.fileSystemCallbacks.TryDehydrateFolder(folder, out string errorMessage))
                    {
                        response.SuccessfulFolders.Add(folder);
                    }
                    else
                    {
                        response.FailedFolders.Add($"{folder}\0{errorMessage}");
                    }

                    resetFolderPaths.Append($"\"{folder.Replace(Path.DirectorySeparatorChar, GVFSConstants.GitPathSeparator)}\" ");
                }

                // Since modified paths could have changed with the dehydrate, the paths that were dehydrated need to be reset in the index
                string     resetPaths = resetFolderPaths.ToString();
                GitProcess gitProcess = new GitProcess(this.enlistment);

                EventMetadata resetIndexMetadata = new EventMetadata();
                resetIndexMetadata.Add(nameof(resetPaths), resetPaths);

                GitProcess.Result refreshIndexResult;
                this.resetForDehydrateInProgress = true;
                try
                {
                    // Because we've set resetForDehydrateInProgress to true, this call to 'git reset' will also force
                    // the projection to be updated (required because 'git reset' will adjust the skip worktree bits in
                    // the index).
                    refreshIndexResult = gitProcess.Reset(GVFSConstants.DotGit.HeadName, resetPaths);
                }
                finally
                {
                    this.resetForDehydrateInProgress = false;
                }

                resetIndexMetadata.Add(nameof(refreshIndexResult.ExitCode), refreshIndexResult.ExitCode);
                resetIndexMetadata.Add(nameof(refreshIndexResult.Output), refreshIndexResult.Output);
                resetIndexMetadata.Add(nameof(refreshIndexResult.Errors), refreshIndexResult.Errors);
                resetIndexMetadata.Add(TracingConstants.MessageKey.InfoMessage, $"{nameof(this.HandleDehydrateFolders)}: Reset git index");
                this.tracer.RelatedEvent(EventLevel.Informational, $"{nameof(this.HandleDehydrateFolders)}_ResetIndex", resetIndexMetadata);
            }
            else
            {
                response = new NamedPipeMessages.DehydrateFolders.Response(NamedPipeMessages.DehydrateFolders.MountNotReadyResult);
            }

            connection.TrySendResponse(response.CreateMessage());
        }
Example #9
0
        private void EnqueueOperationsFromDiffTreeLine(ITracer activity, string repoRoot, string line)
        {
            if (!line.StartsWith(":"))
            {
                // Diff-tree starts with metadata we can ignore.
                // Real diff lines always start with a colon
                return;
            }

            DiffTreeResult result = DiffTreeResult.ParseFromDiffTreeLine(line, repoRoot);

            if (!this.ResultIsInWhitelist(result))
            {
                return;
            }

            if (result.Operation == DiffTreeResult.Operations.Unknown ||
                result.Operation == DiffTreeResult.Operations.Unmerged)
            {
                EventMetadata metadata = new EventMetadata();
                metadata.Add("Path", result.TargetFilename);
                metadata.Add("ErrorMessage", "Unexpected diff operation: " + result.Operation);
                activity.RelatedError(metadata);
                this.HasFailures = true;
                return;
            }

            // Separate and enqueue all directory operations first.
            if (result.SourceIsDirectory || result.TargetIsDirectory)
            {
                switch (result.Operation)
                {
                case DiffTreeResult.Operations.Delete:
                    if (!this.stagedDirectoryOperations.Add(result))
                    {
                        EventMetadata metadata = new EventMetadata();
                        metadata.Add("Filename", result.TargetFilename);
                        metadata.Add("Message", "A case change was attempted. It will not be reflected in the working directory.");
                        activity.RelatedEvent(EventLevel.Warning, "CaseConflict", metadata);
                    }

                    break;

                case DiffTreeResult.Operations.RenameEdit:
                    if (!this.stagedDirectoryOperations.Add(result))
                    {
                        // This could happen if a directory was deleted and an existing directory was renamed to replace it, but with a different case.
                        EventMetadata metadata = new EventMetadata();
                        metadata.Add("Filename", result.TargetFilename);
                        metadata.Add("Message", "A case change was attempted. It will not be reflected in the working directory.");
                        activity.RelatedEvent(EventLevel.Warning, "CaseConflict", metadata);

                        // The target of RenameEdit is always akin to an Add, so replacing the delete is the safer thing to do.
                        this.stagedDirectoryOperations.Remove(result);
                        this.stagedDirectoryOperations.Add(result);
                    }

                    if (!result.TargetIsDirectory)
                    {
                        // Handle when a directory becomes a file.
                        // Files becoming directories is handled by HandleAllDirectoryOperations
                        this.EnqueueFileAddOperation(activity, result);
                    }

                    break;

                case DiffTreeResult.Operations.Add:
                case DiffTreeResult.Operations.Modify:
                case DiffTreeResult.Operations.CopyEdit:
                    if (!this.stagedDirectoryOperations.Add(result))
                    {
                        EventMetadata metadata = new EventMetadata();
                        metadata.Add("Filename", result.TargetFilename);
                        metadata.Add("Message", "A case change was attempted. It will not be reflected in the working directory.");
                        activity.RelatedEvent(EventLevel.Warning, "CaseConflict", metadata);

                        // Replace the delete with the add to make sure we don't delete a folder from under ourselves
                        this.stagedDirectoryOperations.Remove(result);
                        this.stagedDirectoryOperations.Add(result);
                    }

                    break;

                default:
                    activity.RelatedError("Unexpected diff operation from line: {0}", line);
                    break;
                }
            }
            else
            {
                switch (result.Operation)
                {
                case DiffTreeResult.Operations.Delete:
                    this.EnqueueFileDeleteOperation(activity, result.TargetFilename);

                    break;

                case DiffTreeResult.Operations.RenameEdit:

                    this.EnqueueFileAddOperation(activity, result);
                    this.EnqueueFileDeleteOperation(activity, result.SourceFilename);

                    break;

                case DiffTreeResult.Operations.Modify:
                case DiffTreeResult.Operations.CopyEdit:
                case DiffTreeResult.Operations.Add:
                    this.EnqueueFileAddOperation(activity, result);
                    break;

                default:
                    activity.RelatedError("Unexpected diff operation from line: {0}", line);
                    break;
                }
            }
        }
Example #10
0
            private void EnsureLocalCacheIsHealthy(
                ITracer tracer,
                GVFSEnlistment enlistment,
                RetryConfig retryConfig,
                GVFSConfig gvfsConfig,
                CacheServerInfo cacheServer)
            {
                if (!Directory.Exists(enlistment.LocalCacheRoot))
                {
                    try
                    {
                        tracer.RelatedInfo($"{nameof(this.EnsureLocalCacheIsHealthy)}: Local cache root: {enlistment.LocalCacheRoot} missing, recreating it");
                        Directory.CreateDirectory(enlistment.LocalCacheRoot);
                    }
                    catch (Exception e)
                    {
                        EventMetadata metadata = new EventMetadata();
                        metadata.Add("Exception", e.ToString());
                        metadata.Add("enlistment.LocalCacheRoot", enlistment.LocalCacheRoot);
                        tracer.RelatedError(metadata, $"{nameof(this.EnsureLocalCacheIsHealthy)}: Exception while trying to create local cache root");

                        this.ReportErrorAndExit(tracer, "Failed to create local cache: " + enlistment.LocalCacheRoot);
                    }
                }

                // Validate that the GitObjectsRoot directory is on disk, and that the GVFS repo is configured to use it.
                // If the directory is missing (and cannot be found in the mapping file) a new key for the repo will be added
                // to the mapping file and used for BOTH the GitObjectsRoot and BlobSizesRoot
                PhysicalFileSystem fileSystem = new PhysicalFileSystem();

                if (Directory.Exists(enlistment.GitObjectsRoot))
                {
                    bool gitObjectsRootInAlternates = false;

                    string alternatesFilePath = this.GetAlternatesPath(enlistment);
                    if (File.Exists(alternatesFilePath))
                    {
                        try
                        {
                            using (Stream stream = fileSystem.OpenFileStream(
                                       alternatesFilePath,
                                       FileMode.Open,
                                       FileAccess.Read,
                                       FileShare.ReadWrite,
                                       callFlushFileBuffers: false))
                            {
                                using (StreamReader reader = new StreamReader(stream))
                                {
                                    while (!reader.EndOfStream)
                                    {
                                        string alternatesLine = reader.ReadLine();
                                        if (string.Equals(alternatesLine, enlistment.GitObjectsRoot, StringComparison.OrdinalIgnoreCase))
                                        {
                                            gitObjectsRootInAlternates = true;
                                        }
                                    }
                                }
                            }
                        }
                        catch (Exception e)
                        {
                            EventMetadata exceptionMetadata = new EventMetadata();
                            exceptionMetadata.Add("Exception", e.ToString());
                            tracer.RelatedError(exceptionMetadata, $"{nameof(this.EnsureLocalCacheIsHealthy)}: Exception while trying to validate alternates file");

                            this.ReportErrorAndExit(tracer, $"Failed to validate that alternates file includes git objects root: {e.Message}");
                        }
                    }
                    else
                    {
                        tracer.RelatedInfo($"{nameof(this.EnsureLocalCacheIsHealthy)}: Alternates file not found");
                    }

                    if (!gitObjectsRootInAlternates)
                    {
                        tracer.RelatedInfo($"{nameof(this.EnsureLocalCacheIsHealthy)}: GitObjectsRoot ({enlistment.GitObjectsRoot}) missing from alternates files, recreating alternates");
                        string error;
                        if (!this.TryCreateAlternatesFile(fileSystem, enlistment, out error))
                        {
                            this.ReportErrorAndExit(tracer, $"Failed to update alternates file to include git objects root: {error}");
                        }
                    }
                }
                else
                {
                    tracer.RelatedInfo($"{nameof(this.EnsureLocalCacheIsHealthy)}: GitObjectsRoot ({enlistment.GitObjectsRoot}) missing, determining new root");

                    if (cacheServer == null)
                    {
                        cacheServer = CacheServerResolver.GetCacheServerFromConfig(enlistment);
                    }

                    string error;
                    if (gvfsConfig == null)
                    {
                        if (retryConfig == null)
                        {
                            if (!RetryConfig.TryLoadFromGitConfig(tracer, enlistment, out retryConfig, out error))
                            {
                                this.ReportErrorAndExit(tracer, "Failed to determine GVFS timeout and max retries: " + error);
                            }
                        }

                        gvfsConfig = this.QueryGVFSConfig(tracer, enlistment, retryConfig);
                    }

                    string             localCacheKey;
                    LocalCacheResolver localCacheResolver = new LocalCacheResolver(enlistment);
                    if (!localCacheResolver.TryGetLocalCacheKeyFromLocalConfigOrRemoteCacheServers(
                            tracer,
                            gvfsConfig,
                            cacheServer,
                            enlistment.LocalCacheRoot,
                            localCacheKey: out localCacheKey,
                            errorMessage: out error))
                    {
                        this.ReportErrorAndExit(tracer, $"Previous git objects root ({enlistment.GitObjectsRoot}) not found, and failed to determine new local cache key: {error}");
                    }

                    EventMetadata metadata = new EventMetadata();
                    metadata.Add("localCacheRoot", enlistment.LocalCacheRoot);
                    metadata.Add("localCacheKey", localCacheKey);
                    metadata.Add(TracingConstants.MessageKey.InfoMessage, "Initializing and persisting updated paths");
                    tracer.RelatedEvent(EventLevel.Informational, "GVFSVerb_EnsureLocalCacheIsHealthy_InitializePathsFromKey", metadata);
                    enlistment.InitializeCachePathsFromKey(enlistment.LocalCacheRoot, localCacheKey);

                    tracer.RelatedInfo($"{nameof(this.EnsureLocalCacheIsHealthy)}: Creating GitObjectsRoot ({enlistment.GitObjectsRoot}), GitPackRoot ({enlistment.GitPackRoot}), and BlobSizesRoot ({enlistment.BlobSizesRoot})");
                    try
                    {
                        Directory.CreateDirectory(enlistment.GitObjectsRoot);
                        Directory.CreateDirectory(enlistment.GitPackRoot);
                    }
                    catch (Exception e)
                    {
                        EventMetadata exceptionMetadata = new EventMetadata();
                        exceptionMetadata.Add("Exception", e.ToString());
                        exceptionMetadata.Add("enlistment.LocalCacheRoot", enlistment.LocalCacheRoot);
                        exceptionMetadata.Add("enlistment.GitObjectsRoot", enlistment.GitObjectsRoot);
                        exceptionMetadata.Add("enlistment.GitPackRoot", enlistment.GitPackRoot);
                        exceptionMetadata.Add("enlistment.BlobSizesRoot", enlistment.BlobSizesRoot);
                        tracer.RelatedError(exceptionMetadata, $"{nameof(this.InitializeLocalCacheAndObjectsPaths)}: Exception while trying to create objects, pack, and sizes folders");

                        this.ReportErrorAndExit(tracer, "Failed to create objects, pack, and sizes folders");
                    }

                    tracer.RelatedInfo($"{nameof(this.EnsureLocalCacheIsHealthy)}: Creating new alternates file");
                    if (!this.TryCreateAlternatesFile(fileSystem, enlistment, out error))
                    {
                        this.ReportErrorAndExit(tracer, $"Failed to update alterates file with new objects path: {error}");
                    }

                    tracer.RelatedInfo($"{nameof(this.EnsureLocalCacheIsHealthy)}: Saving git objects root ({enlistment.GitObjectsRoot}) in repo metadata");
                    RepoMetadata.Instance.SetGitObjectsRoot(enlistment.GitObjectsRoot);

                    tracer.RelatedInfo($"{nameof(this.EnsureLocalCacheIsHealthy)}: Saving blob sizes root ({enlistment.BlobSizesRoot}) in repo metadata");
                    RepoMetadata.Instance.SetBlobSizesRoot(enlistment.BlobSizesRoot);
                }

                // Validate that the BlobSizesRoot folder is on disk.
                // Note that if a user performed an action that resulted in the entire .gvfscache being deleted, the code above
                // for validating GitObjectsRoot will have already taken care of generating a new key and setting a new enlistment.BlobSizesRoot path
                if (!Directory.Exists(enlistment.BlobSizesRoot))
                {
                    tracer.RelatedInfo($"{nameof(this.EnsureLocalCacheIsHealthy)}: BlobSizesRoot ({enlistment.BlobSizesRoot}) not found, re-creating");
                    try
                    {
                        Directory.CreateDirectory(enlistment.BlobSizesRoot);
                    }
                    catch (Exception e)
                    {
                        EventMetadata exceptionMetadata = new EventMetadata();
                        exceptionMetadata.Add("Exception", e.ToString());
                        exceptionMetadata.Add("enlistment.BlobSizesRoot", enlistment.BlobSizesRoot);
                        tracer.RelatedError(exceptionMetadata, $"{nameof(this.InitializeLocalCacheAndObjectsPaths)}: Exception while trying to create blob sizes folder");

                        this.ReportErrorAndExit(tracer, "Failed to create blob sizes folder");
                    }
                }
            }
Example #11
0
        public virtual bool TryCopyBlobToFile(string sha, IEnumerable <string> destinations, out long bytesWritten)
        {
            IntPtr objHandle;

            if (Native.RevParseSingle(out objHandle, this.repoHandle, sha) != Native.SuccessCode)
            {
                bytesWritten = 0;
                EventMetadata metadata = new EventMetadata();
                metadata.Add("ObjectSha", sha);
                metadata.Add("ErrorMessage", "Couldn't find object");
                this.tracer.RelatedError(metadata);
                return(false);
            }

            try
            {
                // Avoid marshalling raw content by using byte* and native writes
                unsafe
                {
                    switch (Native.Object.GetType(objHandle))
                    {
                    case Native.ObjectTypes.Blob:
                        byte *originalData = Native.Blob.GetRawContent(objHandle);
                        long  originalSize = Native.Blob.GetRawSize(objHandle);

                        foreach (string destination in destinations)
                        {
                            try
                            {
                                using (SafeFileHandle fileHandle = OpenForWrite(this.tracer, destination))
                                {
                                    if (fileHandle.IsInvalid)
                                    {
                                        throw new Win32Exception(Marshal.GetLastWin32Error());
                                    }

                                    byte *data    = originalData;
                                    long  size    = originalSize;
                                    uint  written = 0;
                                    while (size > 0)
                                    {
                                        uint toWrite = size < uint.MaxValue ? (uint)size : uint.MaxValue;
                                        if (!Native.WriteFile(fileHandle, data, toWrite, out written, IntPtr.Zero))
                                        {
                                            throw new Win32Exception(Marshal.GetLastWin32Error());
                                        }

                                        size -= written;
                                        data  = data + written;
                                    }
                                }
                            }
                            catch (Exception e)
                            {
                                this.tracer.RelatedError("Exception writing {0}: {1}", destination, e);
                                throw;
                            }
                        }

                        bytesWritten = originalSize * destinations.Count();
                        break;

                    default:
                        throw new NotSupportedException("Copying object types other than blobs is not supported.");
                    }
                }
            }
            finally
            {
                Native.Object.Free(objHandle);
            }

            return(true);
        }
Example #12
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);
                    }
                }
            }
        }
Example #13
0
        private void HandleAllDirectoryOperations()
        {
            DiffTreeResult treeOp;

            while (this.diff.DirectoryOperations.TryDequeue(out treeOp))
            {
                if (this.HasFailures)
                {
                    return;
                }

                switch (treeOp.Operation)
                {
                case DiffTreeResult.Operations.Modify:
                case DiffTreeResult.Operations.Add:
                    try
                    {
                        Directory.CreateDirectory(treeOp.TargetFilename);
                    }
                    catch (Exception ex)
                    {
                        EventMetadata metadata = new EventMetadata();
                        metadata.Add("Operation", "CreateDirectory");
                        metadata.Add("Path", treeOp.TargetFilename);
                        metadata.Add("ErrorMessage", ex.Message);
                        this.tracer.RelatedError(metadata);
                        this.HasFailures = true;
                    }

                    break;

                case DiffTreeResult.Operations.Delete:
                    try
                    {
                        if (Directory.Exists(treeOp.TargetFilename))
                        {
                            PhysicalFileSystem.RecursiveDelete(treeOp.TargetFilename);
                        }
                    }
                    catch (Exception ex)
                    {
                        // We are deleting directories and subdirectories in parallel
                        if (Directory.Exists(treeOp.TargetFilename))
                        {
                            EventMetadata metadata = new EventMetadata();
                            metadata.Add("Operation", "DeleteDirectory");
                            metadata.Add("Path", treeOp.TargetFilename);
                            metadata.Add("ErrorMessage", ex.Message);
                            this.tracer.RelatedError(metadata);
                            this.HasFailures = true;
                        }
                    }

                    break;

                case DiffTreeResult.Operations.RenameEdit:
                    try
                    {
                        // If target is file, just delete the source.
                        if (!treeOp.TargetIsDirectory)
                        {
                            if (Directory.Exists(treeOp.SourceFilename))
                            {
                                PhysicalFileSystem.RecursiveDelete(treeOp.SourceFilename);
                            }
                        }
                        else
                        {
                            // If target is directory, delete any source file and add
                            if (!treeOp.SourceIsDirectory)
                            {
                                if (File.Exists(treeOp.SourceFilename))
                                {
                                    File.Delete(treeOp.SourceFilename);
                                }

                                goto case DiffTreeResult.Operations.Add;
                            }
                            else
                            {
                                // Source and target are directory, do a move and let later steps handle any sub-edits.
                                Directory.Move(treeOp.SourceFilename, treeOp.TargetFilename);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        EventMetadata metadata = new EventMetadata();
                        metadata.Add("Operation", "RenameDirectory");
                        metadata.Add("Path", treeOp.TargetFilename);
                        metadata.Add("ErrorMessage", ex.Message);
                        this.tracer.RelatedError(metadata);
                        this.HasFailures = true;
                    }

                    break;

                default:
                    this.tracer.RelatedError("Ignoring unexpected Tree Operation {0}: {1}", treeOp.TargetFilename, treeOp.Operation);
                    continue;
                }

                if (Interlocked.Increment(ref this.directoryOpCount) % NumOperationsPerStatus == 0)
                {
                    EventMetadata metadata = new EventMetadata();
                    metadata.Add("DirectoryOperationsQueued", this.diff.DirectoryOperations.Count);
                    metadata.Add("DirectoryOperationsCompleted", this.directoryOpCount);
                    this.tracer.RelatedEvent(EventLevel.Informational, "CheckoutStatus", metadata);
                }
            }
        }
Example #14
0
        private void HandleRequest(ITracer tracer, string request, NamedPipeServer.Connection connection)
        {
            NamedPipeMessages.Message message = NamedPipeMessages.Message.FromString(request);
            if (string.IsNullOrWhiteSpace(message.Header))
            {
                return;
            }

            using (ITracer activity = this.tracer.StartActivity(message.Header, EventLevel.Informational, new EventMetadata {
                { "request", request }
            }))
            {
                switch (message.Header)
                {
                case NamedPipeMessages.RegisterRepoRequest.Header:
                    try
                    {
                        NamedPipeMessages.RegisterRepoRequest mountRequest = NamedPipeMessages.RegisterRepoRequest.FromMessage(message);
                        RegisterRepoHandler mountHandler = new RegisterRepoHandler(activity, this.repoRegistry, connection, mountRequest);
                        mountHandler.Run();
                    }
                    catch (SerializationException ex)
                    {
                        activity.RelatedError("Could not deserialize mount request: {0}", ex.Message);
                    }

                    break;

                case NamedPipeMessages.UnregisterRepoRequest.Header:
                    try
                    {
                        NamedPipeMessages.UnregisterRepoRequest unmountRequest = NamedPipeMessages.UnregisterRepoRequest.FromMessage(message);
                        UnregisterRepoHandler unmountHandler = new UnregisterRepoHandler(activity, this.repoRegistry, connection, unmountRequest);
                        unmountHandler.Run();
                    }
                    catch (SerializationException ex)
                    {
                        activity.RelatedError("Could not deserialize unmount request: {0}", ex.Message);
                    }

                    break;

                case NamedPipeMessages.AttachGvFltRequest.Header:
                    try
                    {
                        NamedPipeMessages.AttachGvFltRequest attachRequest = NamedPipeMessages.AttachGvFltRequest.FromMessage(message);
                        AttachGvFltHandler attachHandler = new AttachGvFltHandler(activity, connection, attachRequest);
                        attachHandler.Run();
                    }
                    catch (SerializationException ex)
                    {
                        activity.RelatedError("Could not deserialize attach volume request: {0}", ex.Message);
                    }

                    break;

                case NamedPipeMessages.ExcludeFromAntiVirusRequest.Header:
                    try
                    {
                        NamedPipeMessages.ExcludeFromAntiVirusRequest excludeFromAntiVirusRequest = NamedPipeMessages.ExcludeFromAntiVirusRequest.FromMessage(message);
                        ExcludeFromAntiVirusHandler excludeHandler = new ExcludeFromAntiVirusHandler(activity, connection, excludeFromAntiVirusRequest);
                        excludeHandler.Run();
                    }
                    catch (SerializationException ex)
                    {
                        activity.RelatedError("Could not deserialize exclude from antivirus request: {0}", ex.Message);
                    }

                    break;

                default:
                    EventMetadata metadata = new EventMetadata();
                    metadata.Add("Area", EtwArea);
                    metadata.Add("Header", message.Header);
                    this.tracer.RelatedWarning(metadata, "HandleNewConnection: Unknown request", Keywords.Telemetry);

                    connection.TrySendResponse(NamedPipeMessages.UnknownRequest);
                    break;
                }
            }
        }
Example #15
0
        protected GitEndPointResponseData SendRequest(
            long requestId,
            Uri requestUri,
            HttpMethod httpMethod,
            string requestContent,
            MediaTypeWithQualityHeaderValue acceptType = null)
        {
            string authString;
            string errorMessage;

            if (!this.authentication.TryGetCredentials(this.Tracer, out authString, out errorMessage))
            {
                return(new GitEndPointResponseData(
                           HttpStatusCode.Unauthorized,
                           new GitObjectsHttpException(HttpStatusCode.Unauthorized, errorMessage),
                           shouldRetry: false));
            }

            HttpRequestMessage request = new HttpRequestMessage(httpMethod, requestUri);

            request.Headers.UserAgent.Add(this.userAgentHeader);
            request.Headers.Authorization = new AuthenticationHeaderValue("Basic", authString);

            if (acceptType != null)
            {
                request.Headers.Accept.Add(acceptType);
            }

            if (requestContent != null)
            {
                request.Content = new StringContent(requestContent, Encoding.UTF8, "application/json");
            }

            EventMetadata responseMetadata = new EventMetadata();

            responseMetadata.Add("RequestId", requestId);

            try
            {
                HttpResponseMessage response = this.client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).GetAwaiter().GetResult();

                responseMetadata.Add("CacheName", GetSingleHeaderOrEmpty(response.Headers, "X-Cache-Name"));
                responseMetadata.Add("StatusCode", response.StatusCode);

                if (response.StatusCode == HttpStatusCode.OK)
                {
                    string contentType = GetSingleHeaderOrEmpty(response.Content.Headers, "Content-Type");
                    responseMetadata.Add("ContentType", contentType);

                    this.authentication.ConfirmCredentialsWorked(authString);
                    Stream responseStream = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult();
                    return(new GitEndPointResponseData(response.StatusCode, contentType, responseStream));
                }
                else
                {
                    errorMessage = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
                    int statusInt = (int)response.StatusCode;

                    if (string.IsNullOrWhiteSpace(errorMessage))
                    {
                        if (response.StatusCode == HttpStatusCode.Unauthorized)
                        {
                            if (this.authentication.RevokeAndCheckCanRetry(authString))
                            {
                                return(new GitEndPointResponseData(
                                           response.StatusCode,
                                           new GitObjectsHttpException(response.StatusCode, "Server returned error code 401 (Unauthorized). Your PAT may be expired."),
                                           shouldRetry: true));
                            }
                            else
                            {
                                return(new GitEndPointResponseData(
                                           response.StatusCode,
                                           new GitObjectsHttpException(response.StatusCode, "Server returned error code 401 (Unauthorized) after successfully renewing your PAT. You may not have access to this repo"),
                                           shouldRetry: false));
                            }
                        }
                        else
                        {
                            errorMessage = string.Format("Server returned error code {0} ({1})", statusInt, response.StatusCode);
                        }
                    }

                    return(new GitEndPointResponseData(response.StatusCode, new GitObjectsHttpException(response.StatusCode, errorMessage), ShouldRetry(response.StatusCode)));
                }
            }
            catch (TaskCanceledException)
            {
                errorMessage = string.Format("Request to {0} timed out", requestUri);
                return(new GitEndPointResponseData(HttpStatusCode.RequestTimeout, new GitObjectsHttpException(HttpStatusCode.RequestTimeout, errorMessage), shouldRetry: true));
            }
            catch (WebException ex)
            {
                return(new GitEndPointResponseData(HttpStatusCode.InternalServerError, ex, shouldRetry: true));
            }
            finally
            {
                this.Tracer.RelatedEvent(EventLevel.Informational, "NetworkResponse", responseMetadata);
            }
        }
Example #16
0
        public virtual bool TryCopyBlobContentStream(string blobSha, Action <Stream, long> writeAction)
        {
            string blobPath = Path.Combine(
                this.enlistment.GitObjectsRoot,
                blobSha.Substring(0, 2),
                blobSha.Substring(2));

            bool corruptLooseObject = false;

            try
            {
                if (File.Exists(blobPath))
                {
                    using (Stream file = new FileStream(blobPath, FileMode.Open, FileAccess.Read, FileShare.Read))
                    {
                        // The DeflateStream header starts 2 bytes into the gzip header, but they are otherwise compatible
                        file.Position = 2;
                        using (DeflateStream deflate = new DeflateStream(file, CompressionMode.Decompress))
                        {
                            long size;
                            if (!ReadLooseObjectHeader(deflate, out size))
                            {
                                corruptLooseObject = true;
                                return(false);
                            }

                            writeAction(deflate, size);
                            return(true);
                        }
                    }
                }
            }
            catch (InvalidDataException ex)
            {
                corruptLooseObject = true;

                EventMetadata metadata = new EventMetadata();
                metadata.Add("blobPath", blobPath);
                metadata.Add("Exception", ex.ToString());
                metadata.Add("ErrorMessage", "TryCopyBlobContentStream: Failed to stream blob (InvalidDataException)");
                this.tracer.RelatedError(metadata);

                return(false);
            }
            catch (IOException ex)
            {
                EventMetadata metadata = new EventMetadata();
                metadata.Add("blobPath", blobPath);
                metadata.Add("Exception", ex.ToString());
                metadata.Add("ErrorMessage", "TryCopyBlobContentStream: Failed to stream blob from disk");
                this.tracer.RelatedError(metadata);

                return(false);
            }
            finally
            {
                if (corruptLooseObject)
                {
                    string corruptBlobsFolderPath = Path.Combine(this.enlistment.EnlistmentRoot, GVFSConstants.DotGVFS.CorruptObjectsPath);
                    string corruptBlobPath        = Path.Combine(corruptBlobsFolderPath, Path.GetRandomFileName());

                    EventMetadata metadata = new EventMetadata();
                    metadata.Add("blobPath", blobPath);
                    metadata.Add("corruptBlobPath", corruptBlobPath);
                    metadata.Add("Message", "TryCopyBlobContentStream: Renaming corrupt loose object");
                    this.tracer.RelatedEvent(EventLevel.Informational, "TryCopyBlobContentStream_RenameCorruptObject", metadata);

                    try
                    {
                        this.fileSystem.CreateDirectory(corruptBlobsFolderPath);
                        File.Move(blobPath, corruptBlobPath);
                    }
                    catch (Exception e)
                    {
                        metadata = new EventMetadata();
                        metadata.Add("blobPath", blobPath);
                        metadata.Add("blobBackupPath", corruptBlobPath);
                        metadata.Add("Exception", e.ToString());
                        metadata.Add("Message", "TryCopyBlobContentStream: Failed to rename corrupt loose object");
                        this.tracer.RelatedEvent(EventLevel.Warning, "TryCopyBlobContentStream_RenameCorruptObjectFailed", metadata);
                    }
                }
            }

            bool copyBlobResult;

            if (!this.libgit2RepoPool.TryInvoke(repo => repo.TryCopyBlob(blobSha, writeAction), out copyBlobResult))
            {
                return(false);
            }

            return(copyBlobResult);
        }
Example #17
0
        public Dictionary <string, RepoRegistration> ReadRegistry()
        {
            Dictionary <string, RepoRegistration> allRepos = new Dictionary <string, RepoRegistration>(StringComparer.OrdinalIgnoreCase);

            using (Stream stream = this.fileSystem.OpenFileStream(
                       Path.Combine(this.registryParentFolderPath, RegistryName),
                       FileMode.OpenOrCreate,
                       FileAccess.Read,
                       FileShare.Read,
                       callFlushFileBuffers: false))
            {
                using (StreamReader reader = new StreamReader(stream))
                {
                    string versionString = reader.ReadLine();
                    int    version;
                    if (!int.TryParse(versionString, out version) ||
                        version > RegistryVersion)
                    {
                        if (versionString != null)
                        {
                            EventMetadata metadata = new EventMetadata();
                            metadata.Add("Area", EtwArea);
                            metadata.Add("OnDiskVersion", versionString);
                            metadata.Add("ExpectedVersion", versionString);
                            this.tracer.RelatedError(metadata, "ReadRegistry: Unsupported version");
                        }

                        return(allRepos);
                    }

                    while (!reader.EndOfStream)
                    {
                        string entry = reader.ReadLine();
                        if (entry.Length > 0)
                        {
                            try
                            {
                                RepoRegistration registration = RepoRegistration.FromJson(entry);

                                string errorMessage;
                                string normalizedEnlistmentRootPath = registration.EnlistmentRoot;
                                if (GVFSPlatform.Instance.FileSystem.TryGetNormalizedPath(registration.EnlistmentRoot, out normalizedEnlistmentRootPath, out errorMessage))
                                {
                                    if (!normalizedEnlistmentRootPath.Equals(registration.EnlistmentRoot, StringComparison.OrdinalIgnoreCase))
                                    {
                                        EventMetadata metadata = new EventMetadata();
                                        metadata.Add("registration.EnlistmentRoot", registration.EnlistmentRoot);
                                        metadata.Add(nameof(normalizedEnlistmentRootPath), normalizedEnlistmentRootPath);
                                        metadata.Add(TracingConstants.MessageKey.InfoMessage, $"{nameof(this.ReadRegistry)}: Mapping registered enlistment root to final path");
                                        this.tracer.RelatedEvent(EventLevel.Informational, $"{nameof(this.ReadRegistry)}_NormalizedPathMapping", metadata);
                                    }
                                }
                                else
                                {
                                    EventMetadata metadata = new EventMetadata();
                                    metadata.Add("registration.EnlistmentRoot", registration.EnlistmentRoot);
                                    metadata.Add("NormalizedEnlistmentRootPath", normalizedEnlistmentRootPath);
                                    metadata.Add("ErrorMessage", errorMessage);
                                    this.tracer.RelatedWarning(metadata, $"{nameof(this.ReadRegistry)}: Failed to get normalized path name for registed enlistment root");
                                }

                                if (normalizedEnlistmentRootPath != null)
                                {
                                    allRepos[normalizedEnlistmentRootPath] = registration;
                                }
                            }
                            catch (Exception e)
                            {
                                EventMetadata metadata = new EventMetadata();
                                metadata.Add("Area", EtwArea);
                                metadata.Add("entry", entry);
                                metadata.Add("Exception", e.ToString());
                                this.tracer.RelatedError(metadata, "ReadRegistry: Failed to read entry");
                            }
                        }
                    }
                }
            }

            return(allRepos);
        }
Example #18
0
        private void HandleAllDirectoryOperations()
        {
            DiffTreeResult treeOp;

            while (this.diff.DirectoryOperations.TryDequeue(out treeOp))
            {
                if (this.HasFailures)
                {
                    return;
                }

                switch (treeOp.Operation)
                {
                case DiffTreeResult.Operations.Modify:
                case DiffTreeResult.Operations.Add:
                    try
                    {
                        Directory.CreateDirectory(treeOp.TargetPath);
                    }
                    catch (Exception ex)
                    {
                        EventMetadata metadata = new EventMetadata();
                        metadata.Add("Operation", "CreateDirectory");
                        metadata.Add(nameof(treeOp.TargetPath), treeOp.TargetPath);
                        this.tracer.RelatedError(metadata, ex.Message);
                        this.HasFailures = true;
                    }

                    break;

                case DiffTreeResult.Operations.Delete:
                    try
                    {
                        if (Directory.Exists(treeOp.TargetPath))
                        {
                            PhysicalFileSystem.RecursiveDelete(treeOp.TargetPath);
                        }
                    }
                    catch (Exception ex)
                    {
                        // We are deleting directories and subdirectories in parallel
                        if (Directory.Exists(treeOp.TargetPath))
                        {
                            EventMetadata metadata = new EventMetadata();
                            metadata.Add("Operation", "DeleteDirectory");
                            metadata.Add(nameof(treeOp.TargetPath), treeOp.TargetPath);
                            this.tracer.RelatedError(metadata, ex.Message);
                            this.HasFailures = true;
                        }
                    }

                    break;

                default:
                    this.tracer.RelatedError("Ignoring unexpected Tree Operation {0}: {1}", treeOp.TargetPath, treeOp.Operation);
                    continue;
                }

                if (Interlocked.Increment(ref this.directoryOpCount) % NumOperationsPerStatus == 0)
                {
                    EventMetadata metadata = new EventMetadata();
                    metadata.Add("DirectoryOperationsQueued", this.diff.DirectoryOperations.Count);
                    metadata.Add("DirectoryOperationsCompleted", this.directoryOpCount);
                    this.tracer.RelatedEvent(EventLevel.Informational, "CheckoutStatus", metadata);
                }
            }
        }
Example #19
0
        public FileSystemCallbacks(
            GVFSContext context,
            GVFSGitObjects gitObjects,
            RepoMetadata repoMetadata,
            BlobSizes blobSizes,
            GitIndexProjection gitIndexProjection,
            BackgroundFileSystemTaskRunner backgroundFileSystemTaskRunner,
            FileSystemVirtualizer fileSystemVirtualizer,
            GitStatusCache gitStatusCache = null)
        {
            this.logsHeadFileProperties = null;

            this.context = context;
            this.fileSystemVirtualizer = fileSystemVirtualizer;

            this.placeHolderCreationCount       = new ConcurrentDictionary <string, PlaceHolderCreateCounter>(StringComparer.OrdinalIgnoreCase);
            this.newlyCreatedFileAndFolderPaths = new ConcurrentHashSet <string>(StringComparer.OrdinalIgnoreCase);

            string error;

            if (!ModifiedPathsDatabase.TryLoadOrCreate(
                    this.context.Tracer,
                    Path.Combine(this.context.Enlistment.DotGVFSRoot, GVFSConstants.DotGVFS.Databases.ModifiedPaths),
                    this.context.FileSystem,
                    out this.modifiedPaths,
                    out error))
            {
                throw new InvalidRepoException(error);
            }

            this.BlobSizes = blobSizes;
            this.BlobSizes.Initialize();

            PlaceholderListDatabase placeholders;

            if (!PlaceholderListDatabase.TryCreate(
                    this.context.Tracer,
                    Path.Combine(this.context.Enlistment.DotGVFSRoot, GVFSConstants.DotGVFS.Databases.PlaceholderList),
                    this.context.FileSystem,
                    out placeholders,
                    out error))
            {
                throw new InvalidRepoException(error);
            }

            this.GitIndexProjection = gitIndexProjection ?? new GitIndexProjection(
                context,
                gitObjects,
                this.BlobSizes,
                repoMetadata,
                fileSystemVirtualizer,
                placeholders,
                this.modifiedPaths);

            if (backgroundFileSystemTaskRunner != null)
            {
                this.backgroundFileSystemTaskRunner = backgroundFileSystemTaskRunner;
                this.backgroundFileSystemTaskRunner.SetCallbacks(
                    this.PreBackgroundOperation,
                    this.ExecuteBackgroundOperation,
                    this.PostBackgroundOperation);
            }
            else
            {
                this.backgroundFileSystemTaskRunner = new BackgroundFileSystemTaskRunner(
                    this.context,
                    this.PreBackgroundOperation,
                    this.ExecuteBackgroundOperation,
                    this.PostBackgroundOperation,
                    Path.Combine(context.Enlistment.DotGVFSRoot, GVFSConstants.DotGVFS.Databases.BackgroundFileSystemTasks));
            }

            this.enableGitStatusCache = gitStatusCache != null;

            // If the status cache is not enabled, create a dummy GitStatusCache that will never be initialized
            // This lets us from having to add null checks to callsites into GitStatusCache.
            this.gitStatusCache = gitStatusCache ?? new GitStatusCache(context, TimeSpan.Zero);

            this.logsHeadPath = Path.Combine(this.context.Enlistment.LocalStorageRoot, GVFSConstants.DotGit.Logs.Head);

            EventMetadata metadata = new EventMetadata();

            metadata.Add("placeholders.Count", placeholders.EstimatedCount);
            metadata.Add("background.Count", this.backgroundFileSystemTaskRunner.Count);
            metadata.Add(TracingConstants.MessageKey.InfoMessage, $"{nameof(FileSystemCallbacks)} created");
            this.context.Tracer.RelatedEvent(EventLevel.Informational, $"{nameof(FileSystemCallbacks)}_Constructor", metadata);
        }
Example #20
0
        private static bool TryCopyNativeLibToAppDirectory(ITracer tracer, PhysicalFileSystem fileSystem, string gvfsAppDirectory)
        {
            string installFilePath;
            string appFilePath;

            GetNativeLibPaths(gvfsAppDirectory, out installFilePath, out appFilePath);

            EventMetadata pathMetadata = CreateEventMetadata();

            pathMetadata.Add(nameof(gvfsAppDirectory), gvfsAppDirectory);
            pathMetadata.Add(nameof(installFilePath), installFilePath);
            pathMetadata.Add(nameof(appFilePath), appFilePath);

            if (fileSystem.FileExists(installFilePath))
            {
                tracer.RelatedEvent(EventLevel.Informational, $"{nameof(TryCopyNativeLibToAppDirectory)}_CopyingNativeLib", pathMetadata);

                try
                {
                    fileSystem.CopyFile(installFilePath, appFilePath, overwrite: true);

                    try
                    {
                        Common.NativeMethods.FlushFileBuffers(appFilePath);
                    }
                    catch (Win32Exception e)
                    {
                        EventMetadata metadata = CreateEventMetadata(e);
                        metadata.Add(nameof(appFilePath), appFilePath);
                        metadata.Add(nameof(installFilePath), installFilePath);
                        tracer.RelatedWarning(metadata, $"{nameof(TryCopyNativeLibToAppDirectory)}: Win32Exception while trying to flush file buffers", Keywords.Telemetry);
                    }
                }
                catch (UnauthorizedAccessException e)
                {
                    EventMetadata metadata = CreateEventMetadata(e);
                    tracer.RelatedError(metadata, $"{nameof(TryCopyNativeLibToAppDirectory)}: UnauthorizedAccessException caught while trying to copy native lib");
                    return(false);
                }
                catch (DirectoryNotFoundException e)
                {
                    EventMetadata metadata = CreateEventMetadata(e);
                    tracer.RelatedError(metadata, $"{nameof(TryCopyNativeLibToAppDirectory)}: DirectoryNotFoundException caught while trying to copy native lib");
                    return(false);
                }
                catch (FileNotFoundException e)
                {
                    EventMetadata metadata = CreateEventMetadata(e);
                    tracer.RelatedError(metadata, $"{nameof(TryCopyNativeLibToAppDirectory)}: FileNotFoundException caught while trying to copy native lib");
                    return(false);
                }
                catch (IOException e)
                {
                    EventMetadata metadata = CreateEventMetadata(e);
                    tracer.RelatedWarning(metadata, $"{nameof(TryCopyNativeLibToAppDirectory)}: IOException caught while trying to copy native lib");

                    if (fileSystem.FileExists(appFilePath))
                    {
                        tracer.RelatedWarning(
                            CreateEventMetadata(),
                            "Could not copy native lib to app directory, but file already exists, continuing with install",
                            Keywords.Telemetry);
                    }
                    else
                    {
                        tracer.RelatedError($"{nameof(TryCopyNativeLibToAppDirectory)}: Failed to copy native lib to app directory");
                        return(false);
                    }
                }
            }
            else
            {
                tracer.RelatedError(pathMetadata, $"{nameof(TryCopyNativeLibToAppDirectory)}: Native lib does not exist in install directory");
                return(false);
            }

            return(true);
        }
Example #21
0
        private int ExecuteWithExitCode()
        {
            // CmdParser doesn't strip quotes, and Path.Combine will throw
            this.GitBinPath = this.GitBinPath.Replace("\"", string.Empty);
            if (!GVFSPlatform.Instance.GitInstallation.GitExists(this.GitBinPath))
            {
                Console.WriteLine(
                    "Could not find git.exe {0}",
                    !string.IsNullOrWhiteSpace(this.GitBinPath) ? "at " + this.GitBinPath : "on %PATH%");
                return(ExitFailure);
            }

            if (this.Commit != null && this.Branch != null)
            {
                Console.WriteLine("Cannot specify both a commit sha and a branch name.");
                return(ExitFailure);
            }

            if (this.ForceCheckout && !this.Checkout)
            {
                Console.WriteLine("Cannot use --force-checkout option without --checkout option.");
                return(ExitFailure);
            }

            this.SearchThreadCount   = this.SearchThreadCount > 0 ? this.SearchThreadCount : Environment.ProcessorCount;
            this.DownloadThreadCount = this.DownloadThreadCount > 0 ? this.DownloadThreadCount : Math.Min(Environment.ProcessorCount, MaxDefaultDownloadThreads);
            this.IndexThreadCount    = this.IndexThreadCount > 0 ? this.IndexThreadCount : Environment.ProcessorCount;
            this.CheckoutThreadCount = this.CheckoutThreadCount > 0 ? this.CheckoutThreadCount : Environment.ProcessorCount;

            this.GitBinPath = !string.IsNullOrWhiteSpace(this.GitBinPath) ? this.GitBinPath : GVFSPlatform.Instance.GitInstallation.GetInstalledGitBinPath();

            GitEnlistment enlistment = GitEnlistment.CreateFromCurrentDirectory(this.GitBinPath);

            if (enlistment == null)
            {
                Console.WriteLine("Must be run within a git repo");
                return(ExitFailure);
            }

            string commitish = this.Commit ?? this.Branch;

            if (string.IsNullOrWhiteSpace(commitish))
            {
                GitProcess.Result result = new GitProcess(enlistment).GetCurrentBranchName();
                if (result.HasErrors || string.IsNullOrWhiteSpace(result.Output))
                {
                    Console.WriteLine("Could not retrieve current branch name: " + result.Errors);
                    return(ExitFailure);
                }

                commitish = result.Output.Trim();
            }

            Guid parentActivityId = Guid.Empty;

            if (!string.IsNullOrWhiteSpace(this.ParentActivityId) && !Guid.TryParse(this.ParentActivityId, out parentActivityId))
            {
                Console.WriteLine("The ParentActivityId provided (" + this.ParentActivityId + ") is not a valid GUID.");
            }

            using (JsonTracer tracer = new JsonTracer("Microsoft.Git.FastFetch", parentActivityId, "FastFetch", enlistmentId: null, mountId: null, disableTelemetry: true))
            {
                if (this.Verbose)
                {
                    tracer.AddDiagnosticConsoleEventListener(EventLevel.Informational, Keywords.Any);
                }
                else
                {
                    tracer.AddPrettyConsoleEventListener(EventLevel.Error, Keywords.Any);
                }

                string fastfetchLogFile = Enlistment.GetNewLogFileName(enlistment.FastFetchLogRoot, "fastfetch");
                tracer.AddLogFileEventListener(fastfetchLogFile, EventLevel.Informational, Keywords.Any);

                CacheServerInfo cacheServer = new CacheServerInfo(this.GetRemoteUrl(enlistment), null);

                tracer.WriteStartEvent(
                    enlistment.EnlistmentRoot,
                    enlistment.RepoUrl,
                    cacheServer.Url,
                    new EventMetadata
                {
                    { "TargetCommitish", commitish },
                    { "Checkout", this.Checkout },
                });

                string error;
                if (!enlistment.Authentication.TryInitialize(tracer, enlistment, out error))
                {
                    tracer.RelatedError(error);
                    Console.WriteLine(error);
                    return(ExitFailure);
                }

                RetryConfig    retryConfig = new RetryConfig(this.MaxAttempts, TimeSpan.FromMinutes(RetryConfig.FetchAndCloneTimeoutMinutes));
                BlobPrefetcher prefetcher  = this.GetFolderPrefetcher(tracer, enlistment, cacheServer, retryConfig);
                if (!BlobPrefetcher.TryLoadFolderList(enlistment, this.FolderList, this.FolderListFile, prefetcher.FolderList, out error))
                {
                    tracer.RelatedError(error);
                    Console.WriteLine(error);
                    return(ExitFailure);
                }

                bool isSuccess;

                try
                {
                    Func <bool> doPrefetch =
                        () =>
                    {
                        try
                        {
                            bool isBranch = this.Commit == null;
                            prefetcher.Prefetch(commitish, isBranch);
                            return(!prefetcher.HasFailures);
                        }
                        catch (BlobPrefetcher.FetchException e)
                        {
                            tracer.RelatedError(e.Message);
                            return(false);
                        }
                    };
                    if (this.Verbose)
                    {
                        isSuccess = doPrefetch();
                    }
                    else
                    {
                        isSuccess = ConsoleHelper.ShowStatusWhileRunning(
                            doPrefetch,
                            "Fetching",
                            output: Console.Out,
                            showSpinner: !Console.IsOutputRedirected,
                            gvfsLogEnlistmentRoot: null);

                        Console.WriteLine();
                        Console.WriteLine("See the full log at " + fastfetchLogFile);
                    }

                    isSuccess &= !prefetcher.HasFailures;
                }
                catch (AggregateException e)
                {
                    isSuccess = false;
                    foreach (Exception ex in e.Flatten().InnerExceptions)
                    {
                        tracer.RelatedError(ex.ToString());
                    }
                }
                catch (Exception e)
                {
                    isSuccess = false;
                    tracer.RelatedError(e.ToString());
                }

                EventMetadata stopMetadata = new EventMetadata();
                stopMetadata.Add("Success", isSuccess);
                tracer.Stop(stopMetadata);

                return(isSuccess ? ExitSuccess : ExitFailure);
            }
        }
Example #22
0
        private static bool TryEnableProjFSOptionalFeature(ITracer tracer, PhysicalFileSystem fileSystem)
        {
            EventMetadata metadata = CreateEventMetadata();

            const int ProjFSNotAnOptionalFeature = 2;
            const int ProjFSEnabled  = 3;
            const int ProjFSDisabled = 4;

            ProcessResult getOptionalFeatureResult = CallPowershellCommand(
                "$var=(Get-WindowsOptionalFeature -Online -FeatureName " + OptionalFeatureName + ");  if($var -eq $null){exit " +
                ProjFSNotAnOptionalFeature + "}else{if($var.State -eq 'Enabled'){exit " + ProjFSEnabled + "}else{exit " + ProjFSDisabled + "}}");

            bool projFSEnabled = false;

            switch (getOptionalFeatureResult.ExitCode)
            {
            case ProjFSNotAnOptionalFeature:
                tracer.RelatedError($"{nameof(TryEnableProjFSOptionalFeature)}: {OptionalFeatureName} optional feature is missing");
                break;

            case ProjFSEnabled:
                tracer.RelatedEvent(
                    EventLevel.Informational,
                    $"{nameof(TryEnableProjFSOptionalFeature)}_ClientProjFSAlreadyEnabled",
                    metadata,
                    Keywords.Network);
                projFSEnabled = true;
                break;

            case ProjFSDisabled:
                ProcessResult enableOptionalFeatureResult = CallPowershellCommand("try {Enable-WindowsOptionalFeature -Online -FeatureName " + OptionalFeatureName + " -NoRestart}catch{exit 1}");
                if (enableOptionalFeatureResult.ExitCode == 0)
                {
                    metadata.Add(TracingConstants.MessageKey.InfoMessage, "Enabled ProjFS optional feature");
                    tracer.RelatedEvent(EventLevel.Informational, $"{nameof(TryEnableProjFSOptionalFeature)}_ClientProjFSDisabled", metadata);
                    projFSEnabled = true;
                    break;
                }

                metadata.Add("enableOptionalFeatureResult.ExitCode", enableOptionalFeatureResult.ExitCode);
                metadata.Add("enableOptionalFeatureResult.Output", enableOptionalFeatureResult.Output);
                metadata.Add("enableOptionalFeatureResult.Errors", enableOptionalFeatureResult.Errors);
                tracer.RelatedError(metadata, $"{nameof(TryEnableProjFSOptionalFeature)}: Failed to enable optional feature");
                break;

            default:
                metadata.Add("getOptionalFeatureResult.ExitCode", getOptionalFeatureResult.ExitCode);
                metadata.Add("getOptionalFeatureResult.Output", getOptionalFeatureResult.Output);
                metadata.Add("getOptionalFeatureResult.Errors", getOptionalFeatureResult.Errors);
                tracer.RelatedError(metadata, $"{nameof(TryEnableProjFSOptionalFeature)}: Unexpected result");
                break;
            }

            if (projFSEnabled)
            {
                if (IsNativeLibInstalled(tracer, fileSystem))
                {
                    return(true);
                }

                tracer.RelatedError($"{nameof(TryEnableProjFSOptionalFeature)}: {OptionalFeatureName} enabled, but native ProjFS library is not on path");
            }

            return(false);
        }
Example #23
0
        private Result TryClone(
            JsonTracer tracer,
            GVFSEnlistment enlistment,
            CacheServerInfo cacheServer,
            RetryConfig retryConfig,
            ServerGVFSConfig serverGVFSConfig,
            string resolvedLocalCacheRoot)
        {
            Result pipeResult;

            using (NamedPipeServer pipeServer = this.StartNamedPipe(tracer, enlistment, out pipeResult))
            {
                if (!pipeResult.Success)
                {
                    return(pipeResult);
                }

                using (GitObjectsHttpRequestor objectRequestor = new GitObjectsHttpRequestor(tracer, enlistment, cacheServer, retryConfig))
                {
                    GitRefs refs = objectRequestor.QueryInfoRefs(this.SingleBranch ? this.Branch : null);

                    if (refs == null)
                    {
                        return(new Result("Could not query info/refs from: " + Uri.EscapeUriString(enlistment.RepoUrl)));
                    }

                    if (this.Branch == null)
                    {
                        this.Branch = refs.GetDefaultBranch();

                        EventMetadata metadata = new EventMetadata();
                        metadata.Add("Branch", this.Branch);
                        tracer.RelatedEvent(EventLevel.Informational, "CloneDefaultRemoteBranch", metadata);
                    }
                    else
                    {
                        if (!refs.HasBranch(this.Branch))
                        {
                            EventMetadata metadata = new EventMetadata();
                            metadata.Add("Branch", this.Branch);
                            tracer.RelatedEvent(EventLevel.Warning, "CloneBranchDoesNotExist", metadata);

                            string errorMessage = string.Format("Remote branch {0} not found in upstream origin", this.Branch);
                            return(new Result(errorMessage));
                        }
                    }

                    if (!enlistment.TryCreateEnlistmentSubFolders())
                    {
                        string error = "Could not create enlistment directories";
                        tracer.RelatedError(error);
                        return(new Result(error));
                    }

                    if (!GVFSPlatform.Instance.FileSystem.IsFileSystemSupported(enlistment.EnlistmentRoot, out string fsError))
                    {
                        string error = $"FileSystem unsupported: {fsError}";
                        tracer.RelatedError(error);
                        return(new Result(error));
                    }

                    string localCacheError;
                    if (!this.TryDetermineLocalCacheAndInitializePaths(tracer, enlistment, serverGVFSConfig, cacheServer, resolvedLocalCacheRoot, out localCacheError))
                    {
                        tracer.RelatedError(localCacheError);
                        return(new Result(localCacheError));
                    }

                    // There's no need to use CreateDirectoryAccessibleByAuthUsers as these directories will inherit
                    // the ACLs used to create LocalCacheRoot
                    Directory.CreateDirectory(enlistment.GitObjectsRoot);
                    Directory.CreateDirectory(enlistment.GitPackRoot);
                    Directory.CreateDirectory(enlistment.BlobSizesRoot);

                    return(this.CreateClone(tracer, enlistment, objectRequestor, refs, this.Branch));
                }
            }
        }
Example #24
0
        private FileSystemTaskResult ExecuteBackgroundOperation(FileSystemTask gitUpdate)
        {
            EventMetadata metadata = new EventMetadata();

            FileSystemTaskResult result;

            switch (gitUpdate.Operation)
            {
            case FileSystemTask.OperationType.OnFileCreated:
            case FileSystemTask.OperationType.OnFailedPlaceholderDelete:
                metadata.Add("virtualPath", gitUpdate.VirtualPath);
                result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.VirtualPath);
                break;

            case FileSystemTask.OperationType.OnFileRenamed:
                metadata.Add("oldVirtualPath", gitUpdate.OldVirtualPath);
                metadata.Add("virtualPath", gitUpdate.VirtualPath);
                result = FileSystemTaskResult.Success;
                if (!string.IsNullOrEmpty(gitUpdate.OldVirtualPath) && !IsPathInsideDotGit(gitUpdate.OldVirtualPath))
                {
                    result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.OldVirtualPath);
                }

                if (result == FileSystemTaskResult.Success &&
                    !string.IsNullOrEmpty(gitUpdate.VirtualPath) &&
                    !IsPathInsideDotGit(gitUpdate.VirtualPath))
                {
                    result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.VirtualPath);
                }

                break;

            case FileSystemTask.OperationType.OnFileDeleted:
                metadata.Add("virtualPath", gitUpdate.VirtualPath);
                result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.VirtualPath);
                break;

            case FileSystemTask.OperationType.OnFileOverwritten:
            case FileSystemTask.OperationType.OnFileSuperseded:
            case FileSystemTask.OperationType.OnFileConvertedToFull:
            case FileSystemTask.OperationType.OnFailedPlaceholderUpdate:
                metadata.Add("virtualPath", gitUpdate.VirtualPath);
                result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.VirtualPath);
                break;

            case FileSystemTask.OperationType.OnFolderCreated:
                metadata.Add("virtualPath", gitUpdate.VirtualPath);
                result = this.TryAddModifiedPath(gitUpdate.VirtualPath, isFolder: true);
                break;

            case FileSystemTask.OperationType.OnFolderRenamed:
                result = FileSystemTaskResult.Success;
                metadata.Add("oldVirtualPath", gitUpdate.OldVirtualPath);
                metadata.Add("virtualPath", gitUpdate.VirtualPath);

                // An empty destination path means the folder was renamed to somewhere outside of the repo
                // Note that only full folders can be moved\renamed, and so there will already be a recursive
                // sparse-checkout entry for the virtualPath of the folder being moved (meaning that no
                // additional work is needed for any files\folders inside the folder being moved)
                if (!string.IsNullOrEmpty(gitUpdate.VirtualPath))
                {
                    result = this.TryAddModifiedPath(gitUpdate.VirtualPath, isFolder: true);
                    if (result == FileSystemTaskResult.Success)
                    {
                        Queue <string> relativeFolderPaths = new Queue <string>();
                        relativeFolderPaths.Enqueue(gitUpdate.VirtualPath);

                        // Add all the files in the renamed folder to the always_exclude file
                        while (relativeFolderPaths.Count > 0)
                        {
                            string folderPath = relativeFolderPaths.Dequeue();
                            if (result == FileSystemTaskResult.Success)
                            {
                                try
                                {
                                    foreach (DirectoryItemInfo itemInfo in this.context.FileSystem.ItemsInDirectory(Path.Combine(this.context.Enlistment.WorkingDirectoryRoot, folderPath)))
                                    {
                                        string itemVirtualPath = Path.Combine(folderPath, itemInfo.Name);
                                        if (itemInfo.IsDirectory)
                                        {
                                            relativeFolderPaths.Enqueue(itemVirtualPath);
                                        }
                                        else
                                        {
                                            string oldItemVirtualPath = gitUpdate.OldVirtualPath + itemVirtualPath.Substring(gitUpdate.VirtualPath.Length);
                                            result = this.TryAddModifiedPath(itemVirtualPath, isFolder: false);
                                        }
                                    }
                                }
                                catch (DirectoryNotFoundException)
                                {
                                    // DirectoryNotFoundException can occur when the renamed folder (or one of its children) is
                                    // deleted prior to the background thread running
                                    EventMetadata exceptionMetadata = new EventMetadata();
                                    exceptionMetadata.Add("Area", "ExecuteBackgroundOperation");
                                    exceptionMetadata.Add("Operation", gitUpdate.Operation.ToString());
                                    exceptionMetadata.Add("oldVirtualPath", gitUpdate.OldVirtualPath);
                                    exceptionMetadata.Add("virtualPath", gitUpdate.VirtualPath);
                                    exceptionMetadata.Add(TracingConstants.MessageKey.InfoMessage, "DirectoryNotFoundException while traversing folder path");
                                    exceptionMetadata.Add("folderPath", folderPath);
                                    this.context.Tracer.RelatedEvent(EventLevel.Informational, "DirectoryNotFoundWhileUpdatingAlwaysExclude", exceptionMetadata);
                                }
                                catch (IOException e)
                                {
                                    metadata.Add("Details", "IOException while traversing folder path");
                                    metadata.Add("folderPath", folderPath);
                                    metadata.Add("Exception", e.ToString());
                                    result = FileSystemTaskResult.RetryableError;
                                    break;
                                }
                                catch (UnauthorizedAccessException e)
                                {
                                    metadata.Add("Details", "UnauthorizedAccessException while traversing folder path");
                                    metadata.Add("folderPath", folderPath);
                                    metadata.Add("Exception", e.ToString());
                                    result = FileSystemTaskResult.RetryableError;
                                    break;
                                }
                            }
                            else
                            {
                                break;
                            }
                        }
                    }
                }

                break;

            case FileSystemTask.OperationType.OnFolderDeleted:
                metadata.Add("virtualPath", gitUpdate.VirtualPath);
                result = this.TryAddModifiedPath(gitUpdate.VirtualPath, isFolder: true);
                break;

            case FileSystemTask.OperationType.OnFolderFirstWrite:
                result = FileSystemTaskResult.Success;
                break;

            case FileSystemTask.OperationType.OnIndexWriteWithoutProjectionChange:
                result = this.GitIndexProjection.AddMissingModifiedFiles();
                break;

            case FileSystemTask.OperationType.OnPlaceholderCreationsBlockedForGit:
                this.GitIndexProjection.ClearNegativePathCacheIfPollutedByGit();
                result = FileSystemTaskResult.Success;
                break;

            default:
                throw new InvalidOperationException("Invalid background operation");
            }

            if (result != FileSystemTaskResult.Success)
            {
                metadata.Add("Area", "ExecuteBackgroundOperation");
                metadata.Add("Operation", gitUpdate.Operation.ToString());
                metadata.Add(TracingConstants.MessageKey.WarningMessage, "Background operation failed");
                metadata.Add(nameof(result), result.ToString());
                this.context.Tracer.RelatedEvent(EventLevel.Warning, "FailedBackgroundOperation", metadata);
            }

            return(result);
        }
Example #25
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);
        }
Example #26
0
        private void HandleRequest(ITracer tracer, string request, NamedPipeServer.Connection connection)
        {
            NamedPipeMessages.Message message = NamedPipeMessages.Message.FromString(request);
            if (string.IsNullOrWhiteSpace(message.Header))
            {
                return;
            }

            using (ITracer activity = this.tracer.StartActivity(message.Header, EventLevel.Informational, new EventMetadata {
                { "request", request }
            }))
            {
                switch (message.Header)
                {
                case NamedPipeMessages.MountRepoRequest.Header:
                    try
                    {
                        NamedPipeMessages.MountRepoRequest mountRequest = NamedPipeMessages.MountRepoRequest.FromMessage(message);
                        MountHandler mountHandler = new MountHandler(activity, this.repoRegistry, connection, mountRequest);
                        mountHandler.Run();
                    }
                    catch (SerializationException ex)
                    {
                        activity.RelatedError("Could not deserialize mount request: {0}", ex.Message);
                    }

                    break;

                case NamedPipeMessages.UnmountRepoRequest.Header:
                    try
                    {
                        NamedPipeMessages.UnmountRepoRequest unmountRequest = NamedPipeMessages.UnmountRepoRequest.FromMessage(message);
                        UnmountHandler unmountHandler = new UnmountHandler(activity, this.repoRegistry, connection, unmountRequest);
                        unmountHandler.Run();
                    }
                    catch (SerializationException ex)
                    {
                        activity.RelatedError("Could not deserialize unmount request: {0}", ex.Message);
                    }

                    break;

                case NamedPipeMessages.Notification.Request.Header:
                    try
                    {
                        NamedPipeMessages.Notification.Request notificationRequest = NamedPipeMessages.Notification.Request.FromMessage(message);
                        NotificationHandler.Instance.SendNotification(activity, notificationRequest);
                    }
                    catch (SerializationException ex)
                    {
                        activity.RelatedError("Could not deserialize notification request: {0}", ex.Message);
                    }

                    break;

                default:
                    EventMetadata metadata = new EventMetadata();
                    metadata.Add("Area", EtwArea);
                    metadata.Add("Header", message.Header);
                    metadata.Add("ErrorMessage", "HandleNewConnection: Unknown request");
                    this.tracer.RelatedError(metadata);

                    connection.TrySendResponse(NamedPipeMessages.UnknownRequest);
                    break;
                }
            }
        }
        /// <summary>
        /// Gets the target of the symbolic link.
        /// </summary>
        /// <param name="sha">SHA of the loose object containing the target path of the symbolic link</param>
        /// <param name="symLinkTarget">Target path of the symbolic link</param>
        private bool TryGetSymLinkTarget(string sha, out string symLinkTarget)
        {
            symLinkTarget = null;

            string symLinkBlobContents = null;

            try
            {
                if (!this.GitObjects.TryCopyBlobContentStream(
                        sha,
                        CancellationToken.None,
                        GVFSGitObjects.RequestSource.SymLinkCreation,
                        (stream, blobLength) =>
                {
                    byte[] buffer = new byte[SymLinkTargetBufferSize];
                    uint bufferIndex = 0;

                    // TODO(#1361): Find a better solution than reading from the stream one byte at at time
                    int nextByte = stream.ReadByte();
                    while (nextByte != -1)
                    {
                        while (bufferIndex < buffer.Length && nextByte != -1)
                        {
                            buffer[bufferIndex] = (byte)nextByte;
                            nextByte = stream.ReadByte();
                            ++bufferIndex;
                        }

                        if (bufferIndex < buffer.Length)
                        {
                            buffer[bufferIndex] = 0;
                            symLinkBlobContents = Encoding.UTF8.GetString(buffer);
                        }
                        else
                        {
                            buffer[bufferIndex - 1] = 0;

                            EventMetadata metadata = this.CreateEventMetadata();
                            metadata.Add(nameof(sha), sha);
                            metadata.Add("bufferContents", Encoding.UTF8.GetString(buffer));
                            this.Context.Tracer.RelatedError(metadata, $"{nameof(this.TryGetSymLinkTarget)}: SymLink target exceeds buffer size");

                            throw new GetSymLinkTargetException("SymLink target exceeds buffer size");
                        }
                    }
                }))
                {
                    EventMetadata metadata = this.CreateEventMetadata();
                    metadata.Add(nameof(sha), sha);
                    this.Context.Tracer.RelatedError(metadata, $"{nameof(this.TryGetSymLinkTarget)}: TryCopyBlobContentStream failed");

                    return(false);
                }
            }
            catch (GetSymLinkTargetException e)
            {
                EventMetadata metadata = this.CreateEventMetadata(relativePath: null, exception: e);
                metadata.Add(nameof(sha), sha);
                this.Context.Tracer.RelatedError(metadata, $"{nameof(this.TryGetSymLinkTarget)}: TryCopyBlobContentStream caught GetSymLinkTargetException");

                return(false);
            }
            catch (DecoderFallbackException e)
            {
                EventMetadata metadata = this.CreateEventMetadata(relativePath: null, exception: e);
                metadata.Add(nameof(sha), sha);
                this.Context.Tracer.RelatedError(metadata, $"{nameof(this.TryGetSymLinkTarget)}: TryCopyBlobContentStream caught DecoderFallbackException");

                return(false);
            }

            symLinkTarget = symLinkBlobContents;

            return(true);
        }
Example #28
0
        public virtual void Initialize()
        {
            string folderPath = Path.GetDirectoryName(this.databasePath);

            this.fileSystem.CreateDirectory(folderPath);

            using (SqliteConnection connection = new SqliteConnection(this.sqliteConnectionString))
            {
                connection.Open();

                using (SqliteCommand pragmaWalCommand = connection.CreateCommand())
                {
                    // Advantages of using WAL ("Write-Ahead Log")
                    // 1. WAL is significantly faster in most scenarios.
                    // 2. WAL provides more concurrency as readers do not block writers and a writer does not block readers.
                    //    Reading and writing can proceed concurrently.
                    // 3. Disk I/O operations tends to be more sequential using WAL.
                    // 4. WAL uses many fewer fsync() operations and is thus less vulnerable to problems on systems
                    //    where the fsync() system call is broken.
                    // http://www.sqlite.org/wal.html
                    pragmaWalCommand.CommandText = $"PRAGMA journal_mode=WAL;";
                    pragmaWalCommand.ExecuteNonQuery();
                }

                using (SqliteCommand pragmaCacheSizeCommand = connection.CreateCommand())
                {
                    // If the argument N is negative, then the number of cache pages is adjusted to use approximately abs(N*1024) bytes of memory
                    // -40000 => 40,000 * 1024 bytes => ~39MB
                    pragmaCacheSizeCommand.CommandText = $"PRAGMA cache_size=-40000;";
                    pragmaCacheSizeCommand.ExecuteNonQuery();
                }

                EventMetadata databaseMetadata = this.CreateEventMetadata();

                using (SqliteCommand userVersionCommand = connection.CreateCommand())
                {
                    // The user_version pragma will to get or set the value of the user-version integer at offset 60 in the database header.
                    // The user-version is an integer that is available to applications to use however they want. SQLite makes no use of the user-version itself.
                    // https://sqlite.org/pragma.html#pragma_user_version
                    userVersionCommand.CommandText = $"PRAGMA user_version;";

                    object userVersion = userVersionCommand.ExecuteScalar();

                    if (userVersion == null || Convert.ToInt64(userVersion) < 1)
                    {
                        userVersionCommand.CommandText = $"PRAGMA user_version=1;";
                        userVersionCommand.ExecuteNonQuery();
                        this.tracer.RelatedInfo($"{nameof(BlobSize)}.{nameof(this.Initialize)}: setting user_version to 1");
                    }
                    else
                    {
                        databaseMetadata.Add("user_version", Convert.ToInt64(userVersion));
                    }
                }

                using (SqliteCommand pragmaSynchronousCommand = connection.CreateCommand())
                {
                    // GVFS uses the default value (FULL) to reduce the risks of corruption
                    // http://www.sqlite.org/pragma.html#pragma_synchronous
                    // (Note: This call is to retrieve the value of 'synchronous' and log it)
                    pragmaSynchronousCommand.CommandText = $"PRAGMA synchronous;";
                    object synchronous = pragmaSynchronousCommand.ExecuteScalar();
                    if (synchronous != null)
                    {
                        databaseMetadata.Add("synchronous", Convert.ToInt64(synchronous));
                    }
                }

                this.tracer.RelatedEvent(EventLevel.Informational, $"{nameof(BlobSize)}_{nameof(this.Initialize)}_db_settings", databaseMetadata);

                using (SqliteCommand createTableCommand = connection.CreateCommand())
                {
                    // Use a BLOB for sha rather than a string to reduce the size of the database
                    createTableCommand.CommandText = @"CREATE TABLE IF NOT EXISTS [BlobSizes] (sha BLOB, size INT, PRIMARY KEY (sha));";
                    createTableCommand.ExecuteNonQuery();
                }
            }

            this.flushDataThread = new Thread(this.FlushDbThreadMain);
            this.flushDataThread.IsBackground = true;
            this.flushDataThread.Start();
        }
        private Result CreatePlaceholders(string directoryRelativePath, IEnumerable <ProjectedFileInfo> projectedItems, string triggeringProcessName)
        {
            foreach (ProjectedFileInfo fileInfo in projectedItems)
            {
                string childRelativePath = Path.Combine(directoryRelativePath, fileInfo.Name);

                string           sha;
                FileSystemResult fileSystemResult;
                if (fileInfo.IsFolder)
                {
                    sha = string.Empty;
                    fileSystemResult = this.WritePlaceholderDirectory(childRelativePath);
                }
                else
                {
                    sha = fileInfo.Sha.ToString();

                    // Writing placeholders on Mac does not require a file size
                    fileSystemResult = this.WritePlaceholderFile(childRelativePath, DummyFileSize, sha);
                }

                Result result = (Result)fileSystemResult.RawResult;
                if (result != Result.Success)
                {
                    EventMetadata metadata = this.CreateEventMetadata(childRelativePath);
                    metadata.Add("fileInfo.Name", fileInfo.Name);
                    metadata.Add("fileInfo.Size", fileInfo.Size);
                    metadata.Add("fileInfo.IsFolder", fileInfo.IsFolder);
                    metadata.Add(nameof(result), result.ToString());
                    metadata.Add(nameof(sha), sha);
                    this.Context.Tracer.RelatedError(metadata, $"{nameof(this.CreatePlaceholders)}: Write placeholder failed");

                    if (result == Result.EIOError)
                    {
                        // If there is an IO error writing the placeholder then the file might already exist and it needs to
                        // be added to the modified paths so that git will show any differences or errors when interacting with the file
                        // This will happen in the include mode when the user creates a file that is already in the files that
                        // should be projected but we are trying to create the placeholder after it has already been created
                        this.FileSystemCallbacks.OnFileConvertedToFull(childRelativePath);
                    }
                    else
                    {
                        return(result);
                    }
                }
                else
                {
                    if (fileInfo.IsFolder)
                    {
                        this.FileSystemCallbacks.OnPlaceholderFolderCreated(childRelativePath, triggeringProcessName);
                    }
                    else
                    {
                        this.FileSystemCallbacks.OnPlaceholderFileCreated(childRelativePath, sha, triggeringProcessName);
                    }
                }
            }

            this.FileSystemCallbacks.OnPlaceholderFolderExpanded(directoryRelativePath);

            return(Result.Success);
        }
Example #30
0
        public void PerformDiff(string sourceTreeSha, string targetTreeSha)
        {
            EventMetadata metadata = new EventMetadata();

            metadata.Add("TargetTreeSha", targetTreeSha);
            metadata.Add("HeadTreeSha", sourceTreeSha);
            using (ITracer activity = this.tracer.StartActivity("PerformDiff", EventLevel.Informational, Keywords.Telemetry, metadata))
            {
                metadata = new EventMetadata();
                if (sourceTreeSha == null)
                {
                    this.UpdatedWholeTree = true;

                    // Nothing is checked out (fresh git init), so we must search the entire tree.
                    GitProcess.Result result = this.git.LsTree(
                        targetTreeSha,
                        line => this.EnqueueOperationsFromLsTreeLine(activity, line),
                        recursive: true,
                        showAllTrees: true);

                    if (result.ExitCodeIsFailure)
                    {
                        this.HasFailures = true;
                        metadata.Add("Errors", result.Errors);
                        metadata.Add("Output", result.Output.Length > 1024 ? result.Output.Substring(1024) : result.Output);
                    }

                    metadata.Add("Operation", "LsTree");
                }
                else
                {
                    // Diff head and target, determine what needs to be done.
                    GitProcess.Result result = this.git.DiffTree(
                        sourceTreeSha,
                        targetTreeSha,
                        line => this.EnqueueOperationsFromDiffTreeLine(this.tracer, line));

                    if (result.ExitCodeIsFailure)
                    {
                        this.HasFailures = true;
                        metadata.Add("Errors", result.Errors);
                        metadata.Add("Output", result.Output.Length > 1024 ? result.Output.Substring(1024) : result.Output);
                    }

                    metadata.Add("Operation", "DiffTree");
                }

                this.FlushStagedQueues();

                metadata.Add("Success", !this.HasFailures);
                metadata.Add("DirectoryOperationsCount", this.TotalDirectoryOperations);
                metadata.Add("FileDeleteOperationsCount", this.TotalFileDeletes);
                metadata.Add("RequiredBlobsCount", this.RequiredBlobs.Count);
                metadata.Add("FileAddOperationsCount", this.FileAddOperations.Sum(kvp => kvp.Value.Count));
                activity.Stop(metadata);
            }
        }