Пример #1
0
        protected void ValidateClientVersions(ITracer tracer, ScalarEnlistment enlistment, ServerScalarConfig scalarConfig, bool showWarnings)
        {
            this.CheckGitVersion(tracer, enlistment, out string gitVersion);
            enlistment.SetGitVersion(gitVersion);

            string errorMessage = null;
            bool   errorIsFatal = false;

            if (!this.TryValidateScalarVersion(enlistment, tracer, scalarConfig, out errorMessage, out errorIsFatal))
            {
                if (errorIsFatal)
                {
                    this.ReportErrorAndExit(tracer, errorMessage);
                }
                else if (showWarnings)
                {
                    this.Output.WriteLine();
                    this.Output.WriteLine(errorMessage);
                    this.Output.WriteLine();
                }
            }
        }
Пример #2
0
        private void TestGetRoot(
            HashSet <string> paths,
            bool expectedResult,
            string directory,
            string expectedEnlistmentRoot,
            string expectedWorkingDirectoryRoot)
        {
            bool actualResult = ScalarEnlistment.TryGetScalarEnlistmentRoot(
                directory,
                out string enlistmentRoot,
                out string workingDirectoryRoot,
                path => paths.Contains(path));

            actualResult.ShouldEqual(expectedResult);

            if (!expectedResult)
            {
                return;
            }

            enlistmentRoot.ShouldEqual(expectedEnlistmentRoot);
            workingDirectoryRoot.ShouldEqual(expectedWorkingDirectoryRoot);
        }
Пример #3
0
        protected override void OnStart(string[] args)
        {
            if (this.serviceThread != null)
            {
                throw new InvalidOperationException("Cannot start service twice in a row.");
            }

            // TODO: 865304 Used for functional tests and development only. Replace with a smarter appConfig-based solution
            string serviceName = args.FirstOrDefault(arg => arg.StartsWith(ServiceNameArgPrefix));

            if (serviceName != null)
            {
                this.serviceName = serviceName.Substring(ServiceNameArgPrefix.Length);
            }

            string serviceLogsDirectoryPath = ScalarPlatform.Instance.GetLogsDirectoryForGVFSComponent(this.serviceName);

            // Create the logs directory explicitly *before* creating a log file event listener to ensure that it
            // and its ancestor directories are created with the correct ACLs.
            this.CreateServiceLogsDirectory(serviceLogsDirectoryPath);
            this.tracer.AddLogFileEventListener(
                ScalarEnlistment.GetNewScalarLogFileName(serviceLogsDirectoryPath, ScalarConstants.LogFileTypes.Service),
                EventLevel.Verbose,
                Keywords.Any);

            try
            {
                this.serviceDataLocation  = ScalarPlatform.Instance.GetSecureDataRootForScalarComponent(this.serviceName);
                this.repoRegistryLocation = ScalarPlatform.Instance.GetCommonAppDataRootForScalarComponent(ScalarConstants.RepoRegistry.RegistryDirectoryName);
                this.CreateAndConfigureProgramDataDirectories();
                this.Start();
            }
            catch (Exception e)
            {
                this.LogExceptionAndExit(e, nameof(this.OnStart));
            }
        }
Пример #4
0
        protected override void OnSessionChange(SessionChangeDescription changeDescription)
        {
            try
            {
                base.OnSessionChange(changeDescription);

                if (!ScalarEnlistment.IsUnattended(tracer: null))
                {
                    if (changeDescription.Reason == SessionChangeReason.SessionLogon)
                    {
                        this.tracer.RelatedInfo("SessionLogon detected, sessionId: {0}", changeDescription.SessionId);
                        this.TryAssignUserFromSessionId(changeDescription.SessionId);
                    }
                    else if (changeDescription.Reason == SessionChangeReason.SessionLogoff)
                    {
                        this.tracer.RelatedInfo($"SessionLogoff detected {changeDescription.SessionId}");
                    }
                }
            }
            catch (Exception e)
            {
                this.LogExceptionAndExit(e, nameof(this.OnSessionChange));
            }
        }
Пример #5
0
        private void LogEnlistmentInfoAndSetConfigValues(ITracer tracer, GitProcess git, ScalarEnlistment enlistment)
        {
            string        enlistmentId = Guid.NewGuid().ToString("N");
            EventMetadata metadata     = this.CreateEventMetadata();

            metadata.Add("Enlistment", enlistment);
            metadata.Add("EnlistmentId", enlistmentId);
            metadata.Add("EnlistmentRoot", enlistment.EnlistmentRoot);
            metadata.Add("PhysicalDiskInfo", ScalarPlatform.Instance.GetPhysicalDiskInfo(enlistment.WorkingDirectoryRoot, sizeStatsOnly: false));
            tracer.RelatedEvent(EventLevel.Informational, "EnlistmentInfo", metadata, Keywords.Telemetry);

            GitProcess.Result configResult = git.SetInLocalConfig(ScalarConstants.GitConfig.EnlistmentId, enlistmentId, replaceAll: true);
            if (configResult.ExitCodeIsFailure)
            {
                string error = "Could not update config with enlistment id, error: " + configResult.Errors;
                tracer.RelatedWarning(error);
            }
        }
Пример #6
0
        private Result DoClone(string fullEnlistmentRootPathParameter, string normalizedEnlistmentRootPath)
        {
            Result cloneResult = null;

            cloneResult = this.TryCreateEnlistment(fullEnlistmentRootPathParameter, normalizedEnlistmentRootPath, out this.enlistment);

            if (!cloneResult.Success)
            {
                this.tracer.RelatedError($"Error while creating enlistment: {cloneResult.ErrorMessage}");
                return(cloneResult);
            }

            this.tracer.AddLogFileEventListener(
                ScalarEnlistment.GetNewScalarLogFileName(this.enlistment.ScalarLogsRoot, ScalarConstants.LogFileTypes.Clone),
                EventLevel.Informational,
                Keywords.Any);
            this.tracer.WriteStartEvent(
                this.enlistment.EnlistmentRoot,
                this.enlistment.RepoUrl,
                this.CacheServerUrl,
                this.AddVerbDataToMetadata(new EventMetadata
            {
                { nameof(this.Branch), this.Branch },
                { nameof(this.LocalCacheRoot), this.LocalCacheRoot },
                { nameof(this.SingleBranch), this.SingleBranch },
                { nameof(this.FullClone), this.FullClone },
                { nameof(this.NoFetchCommitsAndTrees), this.NoFetchCommitsAndTrees },
                { nameof(this.Unattended), this.Unattended },
                { nameof(ScalarPlatform.Instance.IsElevated), ScalarPlatform.Instance.IsElevated() },
                { "ProcessID", Process.GetCurrentProcess().Id },
                { nameof(this.EnlistmentRootPathParameter), this.EnlistmentRootPathParameter },
                { nameof(fullEnlistmentRootPathParameter), fullEnlistmentRootPathParameter },
            }));

            this.cacheServerResolver = new CacheServerResolver(this.tracer, this.enlistment);
            this.cacheServer         = this.cacheServerResolver.ParseUrlOrFriendlyName(this.CacheServerUrl);

            string resolvedLocalCacheRoot;

            if (string.IsNullOrWhiteSpace(this.LocalCacheRoot))
            {
                if (!LocalCacheResolver.TryGetDefaultLocalCacheRoot(this.enlistment, out resolvedLocalCacheRoot, out string localCacheRootError))
                {
                    this.ReportErrorAndExit(
                        this.tracer,
                        $"Failed to determine the default location for the local Scalar cache: `{localCacheRootError}`");
                }
            }
            else
            {
                resolvedLocalCacheRoot = Path.GetFullPath(this.LocalCacheRoot);
            }

            this.Output.WriteLine("Clone parameters:");
            this.Output.WriteLine("  Repo URL:     " + this.enlistment.RepoUrl);
            this.Output.WriteLine("  Branch:       " + (string.IsNullOrWhiteSpace(this.Branch) ? "Default" : this.Branch));
            this.Output.WriteLine("  Cache Server: " + this.cacheServer);
            this.Output.WriteLine("  Local Cache:  " + resolvedLocalCacheRoot);
            this.Output.WriteLine("  Destination:  " + this.enlistment.EnlistmentRoot);
            this.Output.WriteLine("  FullClone:     " + this.FullClone);

            string authErrorMessage;

            if (!this.TryAuthenticate(this.tracer, this.enlistment, out authErrorMessage))
            {
                this.ReportErrorAndExit(this.tracer, "Cannot clone because authentication failed: " + authErrorMessage);
            }

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

            this.cacheServer = this.ResolveCacheServer(this.tracer, this.cacheServer, this.cacheServerResolver, this.serverScalarConfig);

            this.ValidateClientVersions(this.tracer, this.enlistment, this.serverScalarConfig, showWarnings: true);

            using (this.objectRequestor = new GitObjectsHttpRequestor(this.tracer, this.enlistment, this.cacheServer, this.retryConfig))
            {
                cloneResult = this.CreateScalarDirctories(resolvedLocalCacheRoot);

                if (!cloneResult.Success)
                {
                    this.tracer.RelatedError(cloneResult.ErrorMessage);
                    return(cloneResult);
                }

                this.ShowStatusWhileRunning(
                    () =>
                {
                    cloneResult = this.CreateClone();
                    return(cloneResult.Success);
                },
                    "Cloning");

                if (!cloneResult.Success)
                {
                    this.tracer.RelatedError(cloneResult.ErrorMessage);
                    return(cloneResult);
                }

                if (!this.NoFetchCommitsAndTrees)
                {
                    ReturnCode result = this.Execute <RunVerb>(
                        this.enlistment,
                        verb =>
                    {
                        verb.MaintenanceTask     = ScalarConstants.VerbParameters.Maintenance.FetchTaskName;
                        verb.SkipVersionCheck    = true;
                        verb.ResolvedCacheServer = this.cacheServer;
                        verb.ServerScalarConfig  = this.serverScalarConfig;
                    });

                    if (result != ReturnCode.Success)
                    {
                        this.Output.WriteLine("\r\nError while fetching commits and trees @ {0}", fullEnlistmentRootPathParameter);
                        return(cloneResult);
                    }
                }

                this.ShowStatusWhileRunning(
                    () =>
                {
                    cloneResult = this.CheckoutRepo();
                    return(cloneResult.Success);
                },
                    "Populating working directory");
            }

            if (cloneResult.Success)
            {
                cloneResult = this.TryRegisterRepo();
            }

            return(cloneResult);
        }
 protected virtual bool IsUnattended()
 {
     return(ScalarEnlistment.IsUnattended(this.tracer));
 }
Пример #8
0
 protected abstract void Execute(ScalarEnlistment enlistment);
Пример #9
0
 private string GetAlternatesPath(ScalarEnlistment enlistment)
 {
     return(Path.Combine(enlistment.WorkingDirectoryRoot, ScalarConstants.DotGit.Objects.Info.Alternates));
 }
Пример #10
0
        protected override void Execute(ScalarEnlistment enlistment)
        {
            using (JsonTracer tracer = new JsonTracer(ScalarConstants.ScalarEtwProviderName, RunVerbName))
            {
                string cacheServerUrl = CacheServerResolver.GetUrlFromConfig(enlistment);

                string logFileName = ScalarEnlistment.GetNewScalarLogFileName(
                    enlistment.ScalarLogsRoot,
                    ScalarConstants.LogFileTypes.Maintenance,
                    logId: this.StartedByService ? "service" : null);

                List <GitMaintenanceStep> steps = new List <GitMaintenanceStep>();

                tracer.AddLogFileEventListener(
                    logFileName,
                    EventLevel.Informational,
                    Keywords.Any);
                tracer.WriteStartEvent(
                    enlistment.EnlistmentRoot,
                    enlistment.RepoUrl,
                    cacheServerUrl,
                    this.AddVerbDataToMetadata(
                        new EventMetadata
                {
                    { nameof(this.MaintenanceTask), this.MaintenanceTask },
                    { nameof(this.PackfileMaintenanceBatchSize), this.PackfileMaintenanceBatchSize },
                    { nameof(this.EnlistmentRootPathParameter), this.EnlistmentRootPathParameter },
                    { nameof(this.StartedByService), this.StartedByService },
                }));

                this.InitializeCachePaths(tracer, enlistment);
                PhysicalFileSystem fileSystem = new PhysicalFileSystem();
                using (ScalarContext context = new ScalarContext(tracer, fileSystem, enlistment))
                {
                    try
                    {
                        GitObjectsHttpRequestor objectRequestor = null;
                        CacheServerInfo         cacheServer;
                        GitObjects gitObjects;

                        switch (this.MaintenanceTask)
                        {
                        case ScalarConstants.VerbParameters.Maintenance.AllTasksName:
                            steps.Add(new ConfigStep(context));
                            this.InitializeServerConnection(tracer, enlistment, cacheServerUrl, out objectRequestor, out cacheServer);
                            gitObjects = new GitObjects(tracer, enlistment, objectRequestor, fileSystem);
                            steps.Add(new FetchStep(context, gitObjects, requireCacheLock: false, forceRun: !this.StartedByService));
                            steps.Add(new CommitGraphStep(context, requireObjectCacheLock: false));
                            steps.Add(new LooseObjectsStep(context, forceRun: !this.StartedByService));
                            steps.Add(new PackfileMaintenanceStep(
                                          context,
                                          forceRun: !this.StartedByService,
                                          batchSize: string.IsNullOrWhiteSpace(this.PackfileMaintenanceBatchSize) ?
                                          PackfileMaintenanceStep.DefaultBatchSizeBytes.ToString() :
                                          this.PackfileMaintenanceBatchSize));
                            break;

                        case ScalarConstants.VerbParameters.Maintenance.LooseObjectsTaskName:
                            this.FailIfBatchSizeSet(tracer);
                            steps.Add(new LooseObjectsStep(context, forceRun: !this.StartedByService));
                            break;

                        case ScalarConstants.VerbParameters.Maintenance.PackFilesTaskName:
                            steps.Add(new PackfileMaintenanceStep(
                                          context,
                                          forceRun: !this.StartedByService,
                                          batchSize: string.IsNullOrWhiteSpace(this.PackfileMaintenanceBatchSize) ?
                                          PackfileMaintenanceStep.DefaultBatchSizeBytes.ToString() :
                                          this.PackfileMaintenanceBatchSize));
                            break;

                        case ScalarConstants.VerbParameters.Maintenance.FetchTaskName:
                            this.FailIfBatchSizeSet(tracer);
                            this.InitializeServerConnection(tracer, enlistment, cacheServerUrl, out objectRequestor, out cacheServer);
                            gitObjects = new GitObjects(tracer, enlistment, objectRequestor, fileSystem);
                            steps.Add(new FetchStep(context, gitObjects, requireCacheLock: false, forceRun: !this.StartedByService));
                            break;

                        case ScalarConstants.VerbParameters.Maintenance.CommitGraphTaskName:
                            this.FailIfBatchSizeSet(tracer);
                            steps.Add(new CommitGraphStep(context, requireObjectCacheLock: false));
                            break;

                        case ScalarConstants.VerbParameters.Maintenance.ConfigTaskName:
                            this.FailIfBatchSizeSet(tracer);
                            steps.Add(new ConfigStep(context));
                            break;

                        default:
                            this.ReportErrorAndExit($"Unknown maintenance task requested: '{this.MaintenanceTask}'");
                            break;
                        }

                        foreach (GitMaintenanceStep step in steps)
                        {
                            this.ShowStatusWhileRunning(() => { step.Execute(); return(true); }, step.ProgressMessage);
                        }
                    }
                    catch (VerbAbortedException)
                    {
                        throw;
                    }
                    catch (AggregateException aggregateException)
                    {
                        string error = $"AggregateException thrown while running '{this.MaintenanceTask}' task: {aggregateException.Message}";
                        tracer.RelatedError(this.CreateEventMetadata(aggregateException), error);
                        foreach (Exception innerException in aggregateException.Flatten().InnerExceptions)
                        {
                            tracer.RelatedError(
                                this.CreateEventMetadata(innerException),
                                $"Unhandled {innerException.GetType().Name}: {innerException.Message}");
                        }

                        this.ReportErrorAndExit(tracer, ReturnCode.GenericError, error);
                    }
                    catch (Exception e)
                    {
                        string error = $"Exception thrown while running '{this.MaintenanceTask}' task: {e.Message}";
                        tracer.RelatedError(this.CreateEventMetadata(e), error);
                        this.ReportErrorAndExit(tracer, ReturnCode.GenericError, error);
                    }
                }
            }
        }
Пример #11
0
        protected override void Execute(ScalarEnlistment enlistment)
        {
            string diagnosticsRoot = Path.Combine(enlistment.EnlistmentRoot, ".scalarDiagnostics");

            if (!Directory.Exists(diagnosticsRoot))
            {
                Directory.CreateDirectory(diagnosticsRoot);
            }

            string archiveFolderPath = Path.Combine(diagnosticsRoot, "scalar_" + DateTime.Now.ToString("yyyyMMdd_HHmmss"));

            Directory.CreateDirectory(archiveFolderPath);

            using (FileStream diagnosticLogFile = new FileStream(Path.Combine(archiveFolderPath, "diagnostics.log"), FileMode.CreateNew))
                using (this.diagnosticLogFileWriter = new StreamWriter(diagnosticLogFile))
                {
                    this.WriteMessage("Collecting diagnostic info into temp folder " + archiveFolderPath);

                    this.WriteMessage(string.Empty);
                    this.WriteMessage("scalar version " + ProcessHelper.GetCurrentProcessVersion());

                    GitVersion gitVersion = null;
                    string     error      = null;
                    if (!string.IsNullOrEmpty(enlistment.GitBinPath) && GitProcess.TryGetVersion(enlistment.GitBinPath, out gitVersion, out error))
                    {
                        this.WriteMessage("git version " + gitVersion.ToString());
                    }
                    else
                    {
                        this.WriteMessage("Could not determine git version. " + error);
                    }

                    this.WriteMessage(enlistment.GitBinPath);
                    this.WriteMessage(string.Empty);
                    this.WriteMessage("Enlistment root: " + enlistment.EnlistmentRoot);
                    this.WriteMessage("Cache Server: " + CacheServerResolver.GetCacheServerFromConfig(enlistment));

                    string localCacheRoot;
                    string gitObjectsRoot;
                    this.GetLocalCachePaths(enlistment, out localCacheRoot, out gitObjectsRoot);
                    string actualLocalCacheRoot = !string.IsNullOrWhiteSpace(localCacheRoot) ? localCacheRoot : gitObjectsRoot;
                    this.WriteMessage("Local Cache: " + actualLocalCacheRoot);
                    this.WriteMessage(string.Empty);

                    this.PrintDiskSpaceInfo(actualLocalCacheRoot, this.EnlistmentRootPathParameter);

                    this.RecordVersionInformation();

                    this.ShowStatusWhileRunning(
                        () =>
                    {
                        // .git
                        this.CopyAllFiles(enlistment.WorkingDirectoryRoot, archiveFolderPath, ScalarConstants.DotGit.Root, copySubFolders: false);
                        this.CopyAllFiles(enlistment.WorkingDirectoryRoot, archiveFolderPath, ScalarConstants.DotGit.Hooks.Root, copySubFolders: false);
                        this.CopyAllFiles(enlistment.WorkingDirectoryRoot, archiveFolderPath, ScalarConstants.DotGit.Info.Root, copySubFolders: false);
                        this.CopyAllFiles(enlistment.WorkingDirectoryRoot, archiveFolderPath, ScalarConstants.DotGit.Logs.Root, copySubFolders: true);
                        this.CopyAllFiles(enlistment.WorkingDirectoryRoot, archiveFolderPath, ScalarConstants.DotGit.Refs.Root, copySubFolders: true);
                        this.CopyAllFiles(enlistment.WorkingDirectoryRoot, archiveFolderPath, ScalarConstants.DotGit.Objects.Info.Root, copySubFolders: false);
                        this.LogDirectoryEnumeration(enlistment.WorkingDirectoryRoot, Path.Combine(archiveFolderPath, ScalarConstants.DotGit.Objects.Root), ScalarConstants.DotGit.Objects.Pack.Root, "packs-local.txt");
                        this.LogLooseObjectCount(enlistment.WorkingDirectoryRoot, Path.Combine(archiveFolderPath, ScalarConstants.DotGit.Objects.Root), ScalarConstants.DotGit.Objects.Root, "objects-local.txt");

                        // local cache
                        this.CopyLocalCacheData(archiveFolderPath, gitObjectsRoot);

                        // service
                        string commonAppDataRoot = ScalarPlatform.Instance.GetCommonAppDataRootForScalar();
                        string secureDataRoot    = ScalarPlatform.Instance.GetSecureDataRootForScalar();

                        this.CopyAllFiles(
                            commonAppDataRoot,
                            archiveFolderPath,
                            this.ServiceName,
                            copySubFolders: true);

                        if (!commonAppDataRoot.Equals(secureDataRoot))
                        {
                            this.CopyAllFiles(
                                secureDataRoot,
                                archiveFolderPath,
                                this.ServiceName,
                                copySubFolders: true);
                        }

                        // service ui
                        this.CopyAllFiles(
                            ScalarPlatform.Instance.GetCommonAppDataRootForScalar(),
                            archiveFolderPath,
                            ScalarConstants.Service.UIName,
                            copySubFolders: true);

                        if (ScalarPlatform.Instance.UnderConstruction.SupportsScalarUpgrade)
                        {
                            // upgrader
                            this.CopyAllFiles(
                                ProductUpgraderInfo.GetParentLogDirectoryPath(),
                                archiveFolderPath,
                                DeprecatedUpgradeLogsDirectory,
                                copySubFolders: true,
                                targetFolderName: Path.Combine(ProductUpgraderInfo.UpgradeDirectoryName, DeprecatedUpgradeLogsDirectory));

                            this.CopyAllFiles(
                                ProductUpgraderInfo.GetParentLogDirectoryPath(),
                                archiveFolderPath,
                                ProductUpgraderInfo.LogDirectory,
                                copySubFolders: true,
                                targetFolderName: Path.Combine(ProductUpgraderInfo.UpgradeDirectoryName, ProductUpgraderInfo.LogDirectory));

                            this.LogDirectoryEnumeration(
                                ProductUpgraderInfo.GetUpgradeProtectedDataDirectory(),
                                Path.Combine(archiveFolderPath, ProductUpgraderInfo.UpgradeDirectoryName),
                                ProductUpgraderInfo.DownloadDirectory,
                                "downloaded-assets.txt");
                        }

                        if (ScalarPlatform.Instance.UnderConstruction.SupportsScalarConfig)
                        {
                            this.CopyFile(ScalarPlatform.Instance.GetSecureDataRootForScalar(), archiveFolderPath, LocalScalarConfig.FileName);
                        }

                        return(true);
                    },
                        "Copying logs");

                    this.CopyAllFiles(enlistment.DotGitRoot, archiveFolderPath, ScalarConstants.DotGit.Logs.Name, copySubFolders: false);
                }

            string zipFilePath = archiveFolderPath + ".zip";

            this.ShowStatusWhileRunning(
                () =>
            {
                ZipFile.CreateFromDirectory(archiveFolderPath, zipFilePath);
                this.fileSystem.DeleteDirectory(archiveFolderPath);

                return(true);
            },
                "Creating zip file");

            this.Output.WriteLine();
            this.Output.WriteLine("Diagnostics complete. All of the gathered info, as well as all of the output above, is captured in");
            this.Output.WriteLine(zipFilePath);
        }
Пример #12
0
        private Result DoClone(string fullEnlistmentRootPathParameter, string normalizedEnlistmentRootPath)
        {
            Result cloneResult = null;

            cloneResult = this.TryCreateEnlistment(fullEnlistmentRootPathParameter, normalizedEnlistmentRootPath, out this.enlistment);

            if (!cloneResult.Success)
            {
                this.tracer.RelatedError($"Error while creating enlistment: {cloneResult.ErrorMessage}");
                return(cloneResult);
            }

            this.tracer.AddLogFileEventListener(
                ScalarEnlistment.GetNewScalarLogFileName(this.enlistment.ScalarLogsRoot, ScalarConstants.LogFileTypes.Clone),
                EventLevel.Informational,
                Keywords.Any);
            this.tracer.WriteStartEvent(
                this.enlistment.EnlistmentRoot,
                this.enlistment.RepoUrl,
                this.CacheServerUrl,
                this.AddVerbDataToMetadata(new EventMetadata
            {
                { nameof(this.Branch), this.Branch },
                { nameof(this.LocalCacheRoot), this.LocalCacheRoot },
                { nameof(this.SingleBranch), this.SingleBranch },
                { nameof(this.FullClone), this.FullClone },
                { nameof(this.NoFetchCommitsAndTrees), this.NoFetchCommitsAndTrees },
                { nameof(this.Unattended), this.Unattended },
                { nameof(ScalarPlatform.Instance.IsElevated), ScalarPlatform.Instance.IsElevated() },
                { "ProcessID", Process.GetCurrentProcess().Id },
                { nameof(this.EnlistmentRootPathParameter), this.EnlistmentRootPathParameter },
                { nameof(fullEnlistmentRootPathParameter), fullEnlistmentRootPathParameter },
            }));

            this.cacheServerResolver = new CacheServerResolver(this.tracer, this.enlistment);
            this.cacheServer         = this.cacheServerResolver.ParseUrlOrFriendlyName(this.CacheServerUrl);

            string resolvedLocalCacheRoot;

            if (string.IsNullOrWhiteSpace(this.LocalCacheRoot))
            {
                if (!LocalCacheResolver.TryGetDefaultLocalCacheRoot(this.enlistment, out resolvedLocalCacheRoot, out string localCacheRootError))
                {
                    this.ReportErrorAndExit(
                        this.tracer,
                        $"Failed to determine the default location for the local Scalar cache: `{localCacheRootError}`");
                }
            }
            else
            {
                resolvedLocalCacheRoot = Path.GetFullPath(this.LocalCacheRoot);
            }

            // Determine what features of Git we have available to guide how we init/clone the repository
            var    gitFeatures = GitFeatureFlags.None;
            string gitBinPath  = ScalarPlatform.Instance.GitInstallation.GetInstalledGitBinPath();

            this.tracer.RelatedInfo("Attempting to determine Git version for installation '{0}'", gitBinPath);
            if (GitProcess.TryGetVersion(gitBinPath, out var gitVersion, out string gitVersionError))
            {
                this.tracer.RelatedInfo("Git installation '{0}' has version '{1}", gitBinPath, gitVersion);
                gitFeatures = gitVersion.GetFeatures();
            }