From 1a9b66ae2df571d8b316ee157ec1ebb16e2a4d52 Mon Sep 17 00:00:00 2001 From: Scott Arbeit Date: Tue, 26 Dec 2023 13:35:17 -0800 Subject: [PATCH] Tons of bug fixes for `grace watch` and `grace rebase`; added checking for Sha256Hash when creating a reference --- src/Grace.Actors/Branch.Actor.fs | 4 +- src/Grace.Actors/Services.Actor.fs | 3 +- src/Grace.CLI/Command/Branch.CLI.fs | 761 ++++++++++-------- src/Grace.CLI/Command/Services.CLI.fs | 14 +- src/Grace.CLI/Command/Watch.CLI.fs | 50 +- src/Grace.Server/Branch.Server.fs | 10 +- src/Grace.Shared/Constants.Shared.fs | 3 + .../Validation/Errors.Validation.fs | 2 + 8 files changed, 477 insertions(+), 370 deletions(-) diff --git a/src/Grace.Actors/Branch.Actor.fs b/src/Grace.Actors/Branch.Actor.fs index bc72498..2e375d9 100644 --- a/src/Grace.Actors/Branch.Actor.fs +++ b/src/Grace.Actors/Branch.Actor.fs @@ -131,9 +131,9 @@ module Branch = override this.OnPostActorMethodAsync(context) = let duration_ms = (getCurrentInstant().Minus(actorStartTime).TotalMilliseconds).ToString("F3") if String.IsNullOrEmpty(currentCommand) then - log.LogInformation("{CurrentInstant}: Finished {ActorName}.{MethodName}; Id: {Id}; Duration: {duration_ms}ms.", getCurrentInstantExtended(), actorName, context.MethodName, this.Id, duration_ms) + log.LogInformation("{CurrentInstant}: Finished {ActorName}.{MethodName}; Id: {Id}; Name: {Name}; Duration: {duration_ms}ms.", getCurrentInstantExtended(), actorName, context.MethodName, this.Id, branchDto.BranchName, duration_ms) else - log.LogInformation("{CurrentInstant}: Finished {ActorName}.{MethodName}; Command: {Command}; Id: {Id}; Duration: {duration_ms}ms.", getCurrentInstantExtended(), actorName, context.MethodName, currentCommand, this.Id, duration_ms) + log.LogInformation("{CurrentInstant}: Finished {ActorName}.{MethodName}; Command: {Command}; Id: {Id}; Name: {Name}; Duration: {duration_ms}ms.", getCurrentInstantExtended(), actorName, context.MethodName, currentCommand, this.Id, branchDto.BranchName, duration_ms) logScope.Dispose() Task.CompletedTask diff --git a/src/Grace.Actors/Services.Actor.fs b/src/Grace.Actors/Services.Actor.fs index 35bd0c4..c22b7e7 100644 --- a/src/Grace.Actors/Services.Actor.fs +++ b/src/Grace.Actors/Services.Actor.fs @@ -989,7 +989,8 @@ module Services = // Add the results to the list in the same order as the supplied referenceIds. referenceIds |> Seq.iter (fun referenceId -> if referenceId <> ReferenceId.Empty then - referenceDtos.Add(queryResults[referenceId]) + if queryResults.ContainsKey(referenceId) then + referenceDtos.Add(queryResults[referenceId]) else // In case the caller supplied an empty referenceId, add a default ReferenceDto. referenceDtos.Add(ReferenceDto.Default)) diff --git a/src/Grace.CLI/Command/Branch.CLI.fs b/src/Grace.CLI/Command/Branch.CLI.fs index 496684c..5c1c18f 100644 --- a/src/Grace.CLI/Command/Branch.CLI.fs +++ b/src/Grace.CLI/Command/Branch.CLI.fs @@ -795,7 +795,7 @@ module Branch = let table = Table(Border = TableBorder.DoubleEdge) table.AddColumns([| TableColumn($"[{Colors.Important}]Type[/]"); TableColumn($"[{Colors.Important}]Message[/]"); TableColumn($"[{Colors.Important}]SHA-256[/]"); TableColumn($"[{Colors.Important}]When[/]", Alignment = Justify.Right); TableColumn($"[{Colors.Important}][/]") |]) |> ignore for row in sortedResults do - //printfn "%A" row + //logToAnsiConsole Colors.Verbose $"{serialize row}" let sha256Hash = if parseResult.HasOption(Options.fullSha) then $"{row.Sha256Hash}" else @@ -1143,7 +1143,6 @@ module Branch = //logToAnsiConsole Colors.Verbose $"Succeeded calling updateGraceStatusWithNewDirectoryVersions." let mutable isError = false - let updatesInProgressFileName = getUpdateInProgressFileName() // Identify files that we don't already have in object cache and download them. for directoryVersion in graceStatusWithNewDirectoryVersionsFromServer.Index.Values do @@ -1154,7 +1153,7 @@ module Branch = // Write the UpdatesInProgress file to let grace watch know to ignore these changes. // This file is deleted in the finally clause. - do! File.WriteAllTextAsync(updatesInProgressFileName, "This file won't exist for long.") + do! File.WriteAllTextAsync(updateInProgressFileName, "This file won't exist for long.") // Update working directory based on new GraceStatus.Index updateWorkingDirectory newGraceStatus graceStatusWithNewDirectoryVersionsFromServer newDirectoryVersions @@ -1171,7 +1170,7 @@ module Branch = t |> setProgressTaskValue showOutput 100.0 finally // Delete the UpdatesInProgress file. - File.Delete(updatesInProgressFileName) + File.Delete(updateInProgressFileName) | Error error -> logToAnsiConsole Colors.Verbose $"Failed downloading files from object storage for {directoryVersion.RelativePath}." @@ -1239,351 +1238,347 @@ module Branch = let private switchHandler parseResult (parameters: SwitchParameters) = task { try - try - if parseResult |> verbose then printParseResult parseResult - let validateIncomingParameters = CommonValidations parseResult parameters - match validateIncomingParameters with - | Ok _ -> - let getParameters = Parameters.Branch.GetBranchParameters( - OwnerId = parameters.OwnerId, OwnerName = parameters.OwnerName, - OrganizationId = parameters.OrganizationId, OrganizationName = parameters.OrganizationName, - RepositoryId = parameters.RepositoryId, RepositoryName = parameters.RepositoryName, - BranchId = $"{Current().BranchId}", CorrelationId = parameters.CorrelationId) - match! Branch.Get(getParameters) with - | Ok currentBranch -> - let mutable rootDirectoryId = DirectoryId.Empty - let mutable rootDirectorySha256Hash = Sha256Hash String.Empty - let mutable previousDirectoryIds: HashSet = null - - if parseResult |> hasOutput then - return! progress.Columns(progressColumns) - .StartAsync(fun progressContext -> - task { - let t0 = progressContext.AddTask($"[{Color.DodgerBlue1}]Reading Grace index file.[/]") - let t1 = progressContext.AddTask($"[{Color.DodgerBlue1}]Scanning working directory for changes.[/]", autoStart = false) - let t2 = progressContext.AddTask($"[{Color.DodgerBlue1}]Creating new directory verions.[/]", autoStart = false) - let t3 = progressContext.AddTask($"[{Color.DodgerBlue1}]Uploading changed files to object storage.[/]", autoStart = false) - let t4 = progressContext.AddTask($"[{Color.DodgerBlue1}]Uploading new directory versions.[/]", autoStart = false) - let t5 = progressContext.AddTask($"[{Color.DodgerBlue1}]Create a save reference.[/]", autoStart = false) - let t6 = progressContext.AddTask($"[{Color.DodgerBlue1}]Getting new branch version from the server.[/]", autoStart = false) - let t7 = progressContext.AddTask($"[{Color.DodgerBlue1}]Updating object cache and working directory.[/]", autoStart = false) - - t0.Increment(0.0) - let! previousGraceStatus = readGraceStatusFile() - let mutable newGraceStatus = previousGraceStatus - t0.Value <- 100.0 - - if currentBranch.ReturnValue.SaveEnabled then - match! getGraceWatchStatus() with - | Some graceWatchStatus -> - t1.Value <- 100.0 - t2.Value <- 100.0 - t3.Value <- 100.0 - t4.Value <- 100.0 - t5.Value <- 100.0 - rootDirectoryId <- graceWatchStatus.RootDirectoryId - rootDirectorySha256Hash <- graceWatchStatus.RootDirectorySha256Hash - previousDirectoryIds <- graceWatchStatus.DirectoryIds - | None -> - t1.StartTask() - let! differences = scanForDifferences previousGraceStatus - t1.Value <- 100.0 - - t2.StartTask() - let! (updatedGraceStatus, newDirectoryVersions) = getNewGraceStatusAndDirectoryVersions previousGraceStatus differences - newGraceStatus <- updatedGraceStatus - rootDirectoryId <- newGraceStatus.RootDirectoryId - rootDirectorySha256Hash <- newGraceStatus.RootDirectorySha256Hash - previousDirectoryIds <- newGraceStatus.Index.Keys.ToHashSet() - t2.Value <- 100.0 - - t3.StartTask() - let updatedRelativePaths = - differences.Select(fun difference -> - match difference.DifferenceType with - | Add -> match difference.FileSystemEntryType with | FileSystemEntryType.File -> Some difference.RelativePath | FileSystemEntryType.Directory -> None - | Change -> match difference.FileSystemEntryType with | FileSystemEntryType.File -> Some difference.RelativePath | FileSystemEntryType.Directory -> None - | Delete -> None) - .Where(fun relativePathOption -> relativePathOption.IsSome) - .Select(fun relativePath -> relativePath.Value) - - let newFileVersions = updatedRelativePaths.Select(fun relativePath -> - newDirectoryVersions.First(fun dv -> dv.Files.Exists(fun file -> file.RelativePath = relativePath)).Files.First(fun file -> file.RelativePath = relativePath)) - - let! uploadResult = uploadFilesToObjectStorage newFileVersions (getCorrelationId parseResult) - t3.Value <- 100.0 - - t4.StartTask() - let saveParameters = SaveDirectoryVersionsParameters() - saveParameters.DirectoryVersions <- newDirectoryVersions.Select(fun dv -> dv.ToDirectoryVersion).ToList() - let! uploadDirectoryVersions = Directory.SaveDirectoryVersions saveParameters - t4.Value <- 100.0 + if parseResult |> verbose then printParseResult parseResult + let validateIncomingParameters = CommonValidations parseResult parameters + match validateIncomingParameters with + | Ok _ -> + let getParameters = Parameters.Branch.GetBranchParameters( + OwnerId = parameters.OwnerId, OwnerName = parameters.OwnerName, + OrganizationId = parameters.OrganizationId, OrganizationName = parameters.OrganizationName, + RepositoryId = parameters.RepositoryId, RepositoryName = parameters.RepositoryName, + BranchId = $"{Current().BranchId}", CorrelationId = parameters.CorrelationId) + match! Branch.Get(getParameters) with + | Ok currentBranch -> + let mutable rootDirectoryId = DirectoryId.Empty + let mutable rootDirectorySha256Hash = Sha256Hash String.Empty + let mutable previousDirectoryIds: HashSet = null + + if parseResult |> hasOutput then + return! progress.Columns(progressColumns) + .StartAsync(fun progressContext -> + task { + let t0 = progressContext.AddTask($"[{Color.DodgerBlue1}]Reading Grace index file.[/]") + let t1 = progressContext.AddTask($"[{Color.DodgerBlue1}]Scanning working directory for changes.[/]", autoStart = false) + let t2 = progressContext.AddTask($"[{Color.DodgerBlue1}]Creating new directory verions.[/]", autoStart = false) + let t3 = progressContext.AddTask($"[{Color.DodgerBlue1}]Uploading changed files to object storage.[/]", autoStart = false) + let t4 = progressContext.AddTask($"[{Color.DodgerBlue1}]Uploading new directory versions.[/]", autoStart = false) + let t5 = progressContext.AddTask($"[{Color.DodgerBlue1}]Create a save reference.[/]", autoStart = false) + let t6 = progressContext.AddTask($"[{Color.DodgerBlue1}]Getting new branch version from the server.[/]", autoStart = false) + let t7 = progressContext.AddTask($"[{Color.DodgerBlue1}]Updating object cache and working directory.[/]", autoStart = false) + + t0.Increment(0.0) + let! previousGraceStatus = readGraceStatusFile() + let mutable newGraceStatus = previousGraceStatus + t0.Value <- 100.0 - t5.StartTask() - let! saveResult = createSaveReference newGraceStatus.Index[rootDirectoryId] $"Save created during branch switch." (getCorrelationId parseResult) - match saveResult with - | Ok returnValue -> () - | Error error -> - logToAnsiConsole Colors.Error $"{error}" - t5.Value <- 100.0 - else - // Save is not enabled on this branch, so we can skip all of the above. - t1.Value <- 100.0 - t2.Value <- 100.0 - t3.Value <- 100.0 - t4.Value <- 100.0 - t5.Value <- 100.0 - // Get current values from the current branch based on finding no differences, because we're not creating a save or uploading anything. - let! (updatedGraceStatus, newDirectoryVersions) = getNewGraceStatusAndDirectoryVersions previousGraceStatus (List()) - newGraceStatus <- updatedGraceStatus - rootDirectoryId <- newGraceStatus.RootDirectoryId - rootDirectorySha256Hash <- newGraceStatus.RootDirectorySha256Hash - previousDirectoryIds <- newGraceStatus.Index.Keys.ToHashSet() - t6.StartTask() - // If we have a branch name or ID to switch to, cool, let's do it. - //logToAnsiConsole Colors.Verbose $"parameters.BranchName: {parameters.BranchName}; parameters.BranchId: {parameters.BranchId}." + if currentBranch.ReturnValue.SaveEnabled then + match! getGraceWatchStatus() with + | Some graceWatchStatus -> + t1.Value <- 100.0 + t2.Value <- 100.0 + t3.Value <- 100.0 + t4.Value <- 100.0 + t5.Value <- 100.0 + rootDirectoryId <- graceWatchStatus.RootDirectoryId + rootDirectorySha256Hash <- graceWatchStatus.RootDirectorySha256Hash + previousDirectoryIds <- graceWatchStatus.DirectoryIds + | None -> + t1.StartTask() + let! differences = scanForDifferences previousGraceStatus + t1.Value <- 100.0 + + t2.StartTask() + let! (updatedGraceStatus, newDirectoryVersions) = getNewGraceStatusAndDirectoryVersions previousGraceStatus differences + newGraceStatus <- updatedGraceStatus + rootDirectoryId <- newGraceStatus.RootDirectoryId + rootDirectorySha256Hash <- newGraceStatus.RootDirectorySha256Hash + previousDirectoryIds <- newGraceStatus.Index.Keys.ToHashSet() + t2.Value <- 100.0 + + t3.StartTask() + let updatedRelativePaths = + differences.Select(fun difference -> + match difference.DifferenceType with + | Add -> match difference.FileSystemEntryType with | FileSystemEntryType.File -> Some difference.RelativePath | FileSystemEntryType.Directory -> None + | Change -> match difference.FileSystemEntryType with | FileSystemEntryType.File -> Some difference.RelativePath | FileSystemEntryType.Directory -> None + | Delete -> None) + .Where(fun relativePathOption -> relativePathOption.IsSome) + .Select(fun relativePath -> relativePath.Value) + + let newFileVersions = updatedRelativePaths.Select(fun relativePath -> + newDirectoryVersions.First(fun dv -> dv.Files.Exists(fun file -> file.RelativePath = relativePath)).Files.First(fun file -> file.RelativePath = relativePath)) + + let! uploadResult = uploadFilesToObjectStorage newFileVersions (getCorrelationId parseResult) + t3.Value <- 100.0 + + t4.StartTask() + let saveParameters = SaveDirectoryVersionsParameters() + saveParameters.DirectoryVersions <- newDirectoryVersions.Select(fun dv -> dv.ToDirectoryVersion).ToList() + let! uploadDirectoryVersions = Directory.SaveDirectoryVersions saveParameters + t4.Value <- 100.0 + + t5.StartTask() + let! saveResult = createSaveReference newGraceStatus.Index[rootDirectoryId] $"Save created during branch switch." (getCorrelationId parseResult) + match saveResult with + | Ok returnValue -> () + | Error error -> + logToAnsiConsole Colors.Error $"{error}" + t5.Value <- 100.0 + else + // Save is not enabled on this branch, so we can skip all of the above. + t1.Value <- 100.0 + t2.Value <- 100.0 + t3.Value <- 100.0 + t4.Value <- 100.0 + t5.Value <- 100.0 + // Get current values from the current branch based on finding no differences, because we're not creating a save or uploading anything. + let! (updatedGraceStatus, newDirectoryVersions) = getNewGraceStatusAndDirectoryVersions previousGraceStatus (List()) + newGraceStatus <- updatedGraceStatus + rootDirectoryId <- newGraceStatus.RootDirectoryId + rootDirectorySha256Hash <- newGraceStatus.RootDirectorySha256Hash + previousDirectoryIds <- newGraceStatus.Index.Keys.ToHashSet() + t6.StartTask() + // If we have a branch name or ID to switch to, cool, let's do it. + //logToAnsiConsole Colors.Verbose $"parameters.BranchName: {parameters.BranchName}; parameters.BranchId: {parameters.BranchId}." - if (not <| String.IsNullOrEmpty(parameters.BranchId) && parseResult.FindResultFor(Options.branchId).IsImplicit = false) - || (not <| String.IsNullOrEmpty(parameters.BranchName) && parseResult.FindResultFor(Options.branchName).IsImplicit = false) then - // First, let's make sure we're not switching to the current branch, which is a no-op. - //if not <| ((parseResult.FindResultFor(Options.branchId).IsImplicit = false && parameters.BranchId = Current().BranchId.ToString()) - // || (parseResult.FindResultFor(Options.branchName).IsImplicit = false && parameters.BranchName = Current().BranchName.ToString())) then - //if not <| parseResult.FindResultFor(Options.branchId).IsImplicit - // Get branch information based on Id and name. - //logToAnsiConsole Colors.Verbose "Get branch information based on Id and name." - let getParameters = Parameters.Branch.GetBranchParameters( - OwnerId = parameters.OwnerId, OwnerName = parameters.OwnerName, - OrganizationId = parameters.OrganizationId, OrganizationName = parameters.OrganizationName, - RepositoryId = parameters.RepositoryId, RepositoryName = parameters.RepositoryName, - CorrelationId = parameters.CorrelationId) - if not <| parseResult.FindResultFor(Options.branchId).IsImplicit then getParameters.BranchId <- parameters.BranchId - if not <| parseResult.FindResultFor(Options.branchName).IsImplicit then getParameters.BranchName <- parameters.BranchName - let! branchGetResult = Branch.Get(getParameters) - match branchGetResult with + if (not <| String.IsNullOrEmpty(parameters.BranchId) && parseResult.FindResultFor(Options.branchId).IsImplicit = false) + || (not <| String.IsNullOrEmpty(parameters.BranchName) && parseResult.FindResultFor(Options.branchName).IsImplicit = false) then + // First, let's make sure we're not switching to the current branch, which is a no-op. + //if not <| ((parseResult.FindResultFor(Options.branchId).IsImplicit = false && parameters.BranchId = Current().BranchId.ToString()) + // || (parseResult.FindResultFor(Options.branchName).IsImplicit = false && parameters.BranchName = Current().BranchName.ToString())) then + //if not <| parseResult.FindResultFor(Options.branchId).IsImplicit + // Get branch information based on Id and name. + //logToAnsiConsole Colors.Verbose "Get branch information based on Id and name." + let getParameters = Parameters.Branch.GetBranchParameters( + OwnerId = parameters.OwnerId, OwnerName = parameters.OwnerName, + OrganizationId = parameters.OrganizationId, OrganizationName = parameters.OrganizationName, + RepositoryId = parameters.RepositoryId, RepositoryName = parameters.RepositoryName, + CorrelationId = parameters.CorrelationId) + if not <| parseResult.FindResultFor(Options.branchId).IsImplicit then getParameters.BranchId <- parameters.BranchId + if not <| parseResult.FindResultFor(Options.branchName).IsImplicit then getParameters.BranchName <- parameters.BranchName + let! branchGetResult = Branch.Get(getParameters) + match branchGetResult with + | Ok returnValue -> + //logToAnsiConsole Colors.Verbose "Found branch information." + let branchDto = returnValue.ReturnValue + let getVersionParameters = + Parameters.Branch.GetBranchVersionParameters(BranchId = $"{branchDto.BranchId}", + OwnerId = parameters.OwnerId, OwnerName = parameters.OwnerName, + OrganizationId = parameters.OrganizationId, OrganizationName = parameters.OrganizationName, + RepositoryId = $"{branchDto.RepositoryId}", + ReferenceId = parameters.ReferenceId, Sha256Hash = parameters.Sha256Hash, CorrelationId = parameters.CorrelationId) + //logToAnsiConsole Colors.Verbose "Calling GetVersion." + let! result = Branch.GetVersion getVersionParameters + t6.Value <- 100.0 + + t7.StartTask() + match result with | Ok returnValue -> - //logToAnsiConsole Colors.Verbose "Found branch information." - let branchDto = returnValue.ReturnValue - let getVersionParameters = - Parameters.Branch.GetBranchVersionParameters(BranchId = $"{branchDto.BranchId}", - OwnerId = parameters.OwnerId, OwnerName = parameters.OwnerName, - OrganizationId = parameters.OrganizationId, OrganizationName = parameters.OrganizationName, - RepositoryId = $"{branchDto.RepositoryId}", - ReferenceId = parameters.ReferenceId, Sha256Hash = parameters.Sha256Hash, CorrelationId = parameters.CorrelationId) - //logToAnsiConsole Colors.Verbose "Calling GetVersion." - let! result = Branch.GetVersion getVersionParameters - t6.Value <- 100.0 - - t7.StartTask() - match result with - | Ok returnValue -> - //logToAnsiConsole Colors.Verbose "Succeeded calling Branch.GetVersion." - let directoryIds = returnValue.ReturnValue - let missingDirectoryIds = directoryIds.Where(fun directoryId -> not <| previousDirectoryIds.Contains(directoryId)).ToList() + //logToAnsiConsole Colors.Verbose "Succeeded calling Branch.GetVersion." + let directoryIds = returnValue.ReturnValue + let missingDirectoryIds = directoryIds.Where(fun directoryId -> not <| previousDirectoryIds.Contains(directoryId)).ToList() - // Get missing directory versions from server. - let getByDirectoryIdParameters = Parameters.Directory.GetByDirectoryIdsParameters(RepositoryId = parameters.RepositoryId, DirectoryId = $"{rootDirectoryId}", DirectoryIds = missingDirectoryIds) - match! Directory.GetByDirectoryIds getByDirectoryIdParameters with - | Ok returnValue -> - //logToAnsiConsole Colors.Verbose $"Succeeded calling Directory.GetByDirectoryIds." - // Create a new version of GraceStatus that includes the new DirectoryVersions. - let newDirectoryVersions = returnValue.ReturnValue - let newerGraceStatus = updateGraceStatusWithNewDirectoryVersionsFromServer newGraceStatus newDirectoryVersions - //logToAnsiConsole Colors.Verbose $"Succeeded calling updateGraceStatusWithNewDirectoryVersions." - - // Identify files that we don't already have in object cache and download them. - for directoryVersion in newerGraceStatus.Index.Values do - match! downloadFilesFromObjectStorage directoryVersion.Files (getCorrelationId parseResult) with - | Ok _ -> - //logToAnsiConsole Colors.Verbose $"Succeeded downloading files from object storage for {directoryVersion.RelativePath}." - () - | Error error -> - logToAnsiConsole Colors.Verbose $"Failed downloading files from object storage for {directoryVersion.RelativePath}." - logToAnsiConsole Colors.Error $"{error}" - - // Write the UpdatesInProgress file to let grace watch know to ignore these changes. - // This file is deleted in the finally clause. - do! File.WriteAllTextAsync(getUpdateInProgressFileName(), "This file won't exist for long.") - - // Update working directory based on new GraceStatus.Index - updateWorkingDirectory newGraceStatus newerGraceStatus newDirectoryVersions - //logToAnsiConsole Colors.Verbose $"Succeeded calling updateWorkingDirectory." - - // Save the new Grace Status. - do! writeGraceStatusFile newerGraceStatus - - // Update graceconfig.json. - let configuration = Current() - configuration.BranchId <- branchDto.BranchId - configuration.BranchName <- branchDto.BranchName - updateConfiguration configuration - - File.Delete(getUpdateInProgressFileName()) - | Error error -> - logToAnsiConsole Colors.Verbose $"Failed calling Directory.GetByDirectoryIds." - logToAnsiConsole Colors.Error $"{error}" - | Error error -> - logToAnsiConsole Colors.Verbose "Failed calling GetVersion." + // Get missing directory versions from server. + let getByDirectoryIdParameters = Parameters.Directory.GetByDirectoryIdsParameters(RepositoryId = parameters.RepositoryId, DirectoryId = $"{rootDirectoryId}", DirectoryIds = missingDirectoryIds) + match! Directory.GetByDirectoryIds getByDirectoryIdParameters with + | Ok returnValue -> + //logToAnsiConsole Colors.Verbose $"Succeeded calling Directory.GetByDirectoryIds." + // Create a new version of GraceStatus that includes the new DirectoryVersions. + let newDirectoryVersions = returnValue.ReturnValue + let newerGraceStatus = updateGraceStatusWithNewDirectoryVersionsFromServer newGraceStatus newDirectoryVersions + //logToAnsiConsole Colors.Verbose $"Succeeded calling updateGraceStatusWithNewDirectoryVersions." + + // Identify files that we don't already have in object cache and download them. + for directoryVersion in newerGraceStatus.Index.Values do + match! downloadFilesFromObjectStorage directoryVersion.Files (getCorrelationId parseResult) with + | Ok _ -> + //logToAnsiConsole Colors.Verbose $"Succeeded downloading files from object storage for {directoryVersion.RelativePath}." + () + | Error error -> + logToAnsiConsole Colors.Verbose $"Failed downloading files from object storage for {directoryVersion.RelativePath}." + logToAnsiConsole Colors.Error $"{error}" + + // Update working directory based on new GraceStatus.Index + updateWorkingDirectory newGraceStatus newerGraceStatus newDirectoryVersions + //logToAnsiConsole Colors.Verbose $"Succeeded calling updateWorkingDirectory." + + // Save the new Grace Status. + do! writeGraceStatusFile newerGraceStatus + + // Update graceconfig.json. + let configuration = Current() + configuration.BranchId <- branchDto.BranchId + configuration.BranchName <- branchDto.BranchName + updateConfiguration configuration + | Error error -> + logToAnsiConsole Colors.Verbose $"Failed calling Directory.GetByDirectoryIds." logToAnsiConsole Colors.Error $"{error}" - t7.Value <- 100 - | Error error -> logToAnsiConsole Colors.Error $"{error}" + | Error error -> + logToAnsiConsole Colors.Verbose "Failed calling GetVersion." + logToAnsiConsole Colors.Error $"{error}" + t7.Value <- 100 + | Error error -> logToAnsiConsole Colors.Error $"{error}" - return 0 - //else - // logToAnsiConsole Colors.Highlighted $"The specified branch is already the current branch." - // t5.StopTask() - // t6.StopTask() - // return -1 - else - logToAnsiConsole Colors.Error (BranchError.getErrorMessage BranchError.EitherBranchIdOrBranchNameRequired) - t6.StopTask() - t7.StopTask() - return -1 - }) - else - let! previousGraceStatus = readGraceStatusFile() - let mutable newGraceStatus = previousGraceStatus - - if currentBranch.ReturnValue.SaveEnabled then - match! getGraceWatchStatus() with - | Some graceWatchStatus -> - rootDirectoryId <- graceWatchStatus.RootDirectoryId - rootDirectorySha256Hash <- graceWatchStatus.RootDirectorySha256Hash - previousDirectoryIds <- graceWatchStatus.DirectoryIds - | None -> - let! differences = scanForDifferences previousGraceStatus - - let! (updatedGraceStatus, newDirectoryVersions) = getNewGraceStatusAndDirectoryVersions previousGraceStatus differences - newGraceStatus <- updatedGraceStatus - rootDirectoryId <- newGraceStatus.RootDirectoryId - rootDirectorySha256Hash <- newGraceStatus.RootDirectorySha256Hash - previousDirectoryIds <- newGraceStatus.Index.Keys.ToHashSet() - - let updatedRelativePaths = - differences.Select(fun difference -> - match difference.DifferenceType with - | Add -> match difference.FileSystemEntryType with | FileSystemEntryType.File -> Some difference.RelativePath | FileSystemEntryType.Directory -> None - | Change -> match difference.FileSystemEntryType with | FileSystemEntryType.File -> Some difference.RelativePath | FileSystemEntryType.Directory -> None - | Delete -> None) - .Where(fun relativePathOption -> relativePathOption.IsSome) - .Select(fun relativePath -> relativePath.Value) - - let newFileVersions = updatedRelativePaths.Select(fun relativePath -> - newDirectoryVersions.First(fun dv -> dv.Files.Exists(fun file -> file.RelativePath = relativePath)).Files.First(fun file -> file.RelativePath = relativePath)) - - let! uploadResult = uploadFilesToObjectStorage newFileVersions (getCorrelationId parseResult) - - let saveParameters = SaveDirectoryVersionsParameters() - saveParameters.DirectoryVersions <- newDirectoryVersions.Select(fun dv -> dv.ToDirectoryVersion).ToList() - let! uploadDirectoryVersions = Directory.SaveDirectoryVersions saveParameters - - let! saveResult = createSaveReference newGraceStatus.Index[rootDirectoryId] $"Save created during branch switch." (getCorrelationId parseResult) - match saveResult with - | Ok returnValue -> () - | Error error -> - logToAnsiConsole Colors.Error $"{error}" - else - // Save is not enabled on this branch, so we can skip all of the above. - // Get current values from the current branch based on finding no differences, because we're not creating a save or uploading anything. - let! (updatedGraceStatus, newDirectoryVersions) = getNewGraceStatusAndDirectoryVersions previousGraceStatus (List()) + return 0 + //else + // logToAnsiConsole Colors.Highlighted $"The specified branch is already the current branch." + // t5.StopTask() + // t6.StopTask() + // return -1 + else + logToAnsiConsole Colors.Error (BranchError.getErrorMessage BranchError.EitherBranchIdOrBranchNameRequired) + t6.StopTask() + t7.StopTask() + return -1 + }) + else + let! previousGraceStatus = readGraceStatusFile() + let mutable newGraceStatus = previousGraceStatus + + if currentBranch.ReturnValue.SaveEnabled then + match! getGraceWatchStatus() with + | Some graceWatchStatus -> + rootDirectoryId <- graceWatchStatus.RootDirectoryId + rootDirectorySha256Hash <- graceWatchStatus.RootDirectorySha256Hash + previousDirectoryIds <- graceWatchStatus.DirectoryIds + | None -> + let! differences = scanForDifferences previousGraceStatus + + let! (updatedGraceStatus, newDirectoryVersions) = getNewGraceStatusAndDirectoryVersions previousGraceStatus differences newGraceStatus <- updatedGraceStatus rootDirectoryId <- newGraceStatus.RootDirectoryId rootDirectorySha256Hash <- newGraceStatus.RootDirectorySha256Hash previousDirectoryIds <- newGraceStatus.Index.Keys.ToHashSet() + + let updatedRelativePaths = + differences.Select(fun difference -> + match difference.DifferenceType with + | Add -> match difference.FileSystemEntryType with | FileSystemEntryType.File -> Some difference.RelativePath | FileSystemEntryType.Directory -> None + | Change -> match difference.FileSystemEntryType with | FileSystemEntryType.File -> Some difference.RelativePath | FileSystemEntryType.Directory -> None + | Delete -> None) + .Where(fun relativePathOption -> relativePathOption.IsSome) + .Select(fun relativePath -> relativePath.Value) + + let newFileVersions = updatedRelativePaths.Select(fun relativePath -> + newDirectoryVersions.First(fun dv -> dv.Files.Exists(fun file -> file.RelativePath = relativePath)).Files.First(fun file -> file.RelativePath = relativePath)) + + let! uploadResult = uploadFilesToObjectStorage newFileVersions (getCorrelationId parseResult) + + let saveParameters = SaveDirectoryVersionsParameters() + saveParameters.DirectoryVersions <- newDirectoryVersions.Select(fun dv -> dv.ToDirectoryVersion).ToList() + let! uploadDirectoryVersions = Directory.SaveDirectoryVersions saveParameters + + let! saveResult = createSaveReference newGraceStatus.Index[rootDirectoryId] $"Save created during branch switch." (getCorrelationId parseResult) + match saveResult with + | Ok returnValue -> () + | Error error -> + logToAnsiConsole Colors.Error $"{error}" + else + // Save is not enabled on this branch, so we can skip all of the above. + // Get current values from the current branch based on finding no differences, because we're not creating a save or uploading anything. + let! (updatedGraceStatus, newDirectoryVersions) = getNewGraceStatusAndDirectoryVersions previousGraceStatus (List()) + newGraceStatus <- updatedGraceStatus + rootDirectoryId <- newGraceStatus.RootDirectoryId + rootDirectorySha256Hash <- newGraceStatus.RootDirectorySha256Hash + previousDirectoryIds <- newGraceStatus.Index.Keys.ToHashSet() - // If we have a branch name or ID to switch to, cool, let's do it. - //logToAnsiConsole Colors.Verbose $"parameters.BranchName: {parameters.BranchName}; parameters.BranchId: {parameters.BranchId}." + // If we have a branch name or ID to switch to, cool, let's do it. + //logToAnsiConsole Colors.Verbose $"parameters.BranchName: {parameters.BranchName}; parameters.BranchId: {parameters.BranchId}." - if (not <| String.IsNullOrEmpty(parameters.BranchId) && parseResult.FindResultFor(Options.branchId).IsImplicit = false) - || (not <| String.IsNullOrEmpty(parameters.BranchName) && parseResult.FindResultFor(Options.branchName).IsImplicit = false) then - // First, let's make sure we're not switching to the current branch, which is a no-op. - //if not <| ((parseResult.FindResultFor(Options.branchId).IsImplicit = false && parameters.BranchId = Current().BranchId.ToString()) - // || (parseResult.FindResultFor(Options.branchName).IsImplicit = false && parameters.BranchName = Current().BranchName.ToString())) then - //if not <| parseResult.FindResultFor(Options.branchId).IsImplicit - // Get branch information based on Id and name. - //logToAnsiConsole Colors.Verbose "Get branch information based on Id and name." - let getParameters = Parameters.Branch.GetBranchParameters( - OwnerId = parameters.OwnerId, OwnerName = parameters.OwnerName, - OrganizationId = parameters.OrganizationId, OrganizationName = parameters.OrganizationName, - RepositoryId = parameters.RepositoryId, RepositoryName = parameters.RepositoryName, - CorrelationId = parameters.CorrelationId) - if not <| parseResult.FindResultFor(Options.branchId).IsImplicit then getParameters.BranchId <- parameters.BranchId - if not <| parseResult.FindResultFor(Options.branchName).IsImplicit then getParameters.BranchName <- parameters.BranchName - let! branchGetResult = Branch.Get(getParameters) - match branchGetResult with - | Ok returnValue -> - //logToAnsiConsole Colors.Verbose "Found branch information." - let branchDto = returnValue.ReturnValue - let getVersionParameters = - Parameters.Branch.GetBranchVersionParameters(BranchId = $"{branchDto.BranchId}", - OwnerId = parameters.OwnerId, OwnerName = parameters.OwnerName, - OrganizationId = parameters.OrganizationId, OrganizationName = parameters.OrganizationName, - RepositoryId = $"{branchDto.RepositoryId}", - ReferenceId = parameters.ReferenceId, Sha256Hash = parameters.Sha256Hash, CorrelationId = parameters.CorrelationId) - //logToAnsiConsole Colors.Verbose "Calling GetVersion." - let! result = Branch.GetVersion getVersionParameters + if (not <| String.IsNullOrEmpty(parameters.BranchId) && parseResult.FindResultFor(Options.branchId).IsImplicit = false) + || (not <| String.IsNullOrEmpty(parameters.BranchName) && parseResult.FindResultFor(Options.branchName).IsImplicit = false) then + // First, let's make sure we're not switching to the current branch, which is a no-op. + //if not <| ((parseResult.FindResultFor(Options.branchId).IsImplicit = false && parameters.BranchId = Current().BranchId.ToString()) + // || (parseResult.FindResultFor(Options.branchName).IsImplicit = false && parameters.BranchName = Current().BranchName.ToString())) then + //if not <| parseResult.FindResultFor(Options.branchId).IsImplicit + // Get branch information based on Id and name. + //logToAnsiConsole Colors.Verbose "Get branch information based on Id and name." + let getParameters = Parameters.Branch.GetBranchParameters( + OwnerId = parameters.OwnerId, OwnerName = parameters.OwnerName, + OrganizationId = parameters.OrganizationId, OrganizationName = parameters.OrganizationName, + RepositoryId = parameters.RepositoryId, RepositoryName = parameters.RepositoryName, + CorrelationId = parameters.CorrelationId) + if not <| parseResult.FindResultFor(Options.branchId).IsImplicit then getParameters.BranchId <- parameters.BranchId + if not <| parseResult.FindResultFor(Options.branchName).IsImplicit then getParameters.BranchName <- parameters.BranchName + let! branchGetResult = Branch.Get(getParameters) + match branchGetResult with + | Ok returnValue -> + //logToAnsiConsole Colors.Verbose "Found branch information." + let branchDto = returnValue.ReturnValue + let getVersionParameters = + Parameters.Branch.GetBranchVersionParameters(BranchId = $"{branchDto.BranchId}", + OwnerId = parameters.OwnerId, OwnerName = parameters.OwnerName, + OrganizationId = parameters.OrganizationId, OrganizationName = parameters.OrganizationName, + RepositoryId = $"{branchDto.RepositoryId}", + ReferenceId = parameters.ReferenceId, Sha256Hash = parameters.Sha256Hash, CorrelationId = parameters.CorrelationId) + //logToAnsiConsole Colors.Verbose "Calling GetVersion." + let! result = Branch.GetVersion getVersionParameters - match result with - | Ok returnValue -> - //logToAnsiConsole Colors.Verbose "Succeeded calling Branch.GetVersion." - let directoryIds = returnValue.ReturnValue - let missingDirectoryIds = directoryIds.Where(fun directoryId -> not <| previousDirectoryIds.Contains(directoryId)).ToList() + match result with + | Ok returnValue -> + //logToAnsiConsole Colors.Verbose "Succeeded calling Branch.GetVersion." + let directoryIds = returnValue.ReturnValue + let missingDirectoryIds = directoryIds.Where(fun directoryId -> not <| previousDirectoryIds.Contains(directoryId)).ToList() - // Get missing directory versions from server. - let getByDirectoryIdParameters = Parameters.Directory.GetByDirectoryIdsParameters(RepositoryId = parameters.RepositoryId, DirectoryId = $"{rootDirectoryId}", DirectoryIds = missingDirectoryIds) - match! Directory.GetByDirectoryIds getByDirectoryIdParameters with - | Ok returnValue -> - //logToAnsiConsole Colors.Verbose $"Succeeded calling Directory.GetByDirectoryIds." - // Create a new version of GraceStatus that includes the new DirectoryVersions. - let newDirectoryVersions = returnValue.ReturnValue - let newerGraceStatus = updateGraceStatusWithNewDirectoryVersionsFromServer newGraceStatus newDirectoryVersions - //logToAnsiConsole Colors.Verbose $"Succeeded calling updateGraceStatusWithNewDirectoryVersions." - - // Identify files that we don't already have in object cache and download them. - for directoryVersion in newerGraceStatus.Index.Values do - match! downloadFilesFromObjectStorage directoryVersion.Files (getCorrelationId parseResult) with - | Ok _ -> - //logToAnsiConsole Colors.Verbose $"Succeeded downloading files from object storage for {directoryVersion.RelativePath}." - () - | Error error -> - logToAnsiConsole Colors.Verbose $"Failed downloading files from object storage for {directoryVersion.RelativePath}." - logToAnsiConsole Colors.Error $"{error}" + // Get missing directory versions from server. + let getByDirectoryIdParameters = Parameters.Directory.GetByDirectoryIdsParameters(RepositoryId = parameters.RepositoryId, DirectoryId = $"{rootDirectoryId}", DirectoryIds = missingDirectoryIds) + match! Directory.GetByDirectoryIds getByDirectoryIdParameters with + | Ok returnValue -> + //logToAnsiConsole Colors.Verbose $"Succeeded calling Directory.GetByDirectoryIds." + // Create a new version of GraceStatus that includes the new DirectoryVersions. + let newDirectoryVersions = returnValue.ReturnValue + let newerGraceStatus = updateGraceStatusWithNewDirectoryVersionsFromServer newGraceStatus newDirectoryVersions + //logToAnsiConsole Colors.Verbose $"Succeeded calling updateGraceStatusWithNewDirectoryVersions." + + // Identify files that we don't already have in object cache and download them. + for directoryVersion in newerGraceStatus.Index.Values do + match! downloadFilesFromObjectStorage directoryVersion.Files (getCorrelationId parseResult) with + | Ok _ -> + //logToAnsiConsole Colors.Verbose $"Succeeded downloading files from object storage for {directoryVersion.RelativePath}." + () + | Error error -> + logToAnsiConsole Colors.Verbose $"Failed downloading files from object storage for {directoryVersion.RelativePath}." + logToAnsiConsole Colors.Error $"{error}" - // Update working directory based on new GraceStatus.Index - updateWorkingDirectory newGraceStatus newerGraceStatus newDirectoryVersions - //logToAnsiConsole Colors.Verbose $"Succeeded calling updateWorkingDirectory." + // Update working directory based on new GraceStatus.Index + updateWorkingDirectory newGraceStatus newerGraceStatus newDirectoryVersions + //logToAnsiConsole Colors.Verbose $"Succeeded calling updateWorkingDirectory." - // Save the new Grace Status. - do! writeGraceStatusFile newerGraceStatus + // Save the new Grace Status. + do! writeGraceStatusFile newerGraceStatus - // Update graceconfig.json. - let configuration = Current() - configuration.BranchId <- branchDto.BranchId - configuration.BranchName <- branchDto.BranchName - updateConfiguration configuration - | Error error -> - logToAnsiConsole Colors.Verbose $"Failed calling Directory.GetByDirectoryIds." - logToAnsiConsole Colors.Error (Markup.Escape($"{error}")) - | Error error -> - logToAnsiConsole Colors.Verbose "Failed calling GetVersion." + // Update graceconfig.json. + let configuration = Current() + configuration.BranchId <- branchDto.BranchId + configuration.BranchName <- branchDto.BranchName + updateConfiguration configuration + | Error error -> + logToAnsiConsole Colors.Verbose $"Failed calling Directory.GetByDirectoryIds." logToAnsiConsole Colors.Error (Markup.Escape($"{error}")) - | Error error -> logToAnsiConsole Colors.Error (Markup.Escape($"{error}")) + | Error error -> + logToAnsiConsole Colors.Verbose "Failed calling GetVersion." + logToAnsiConsole Colors.Error (Markup.Escape($"{error}")) + | Error error -> logToAnsiConsole Colors.Error (Markup.Escape($"{error}")) - return 0 - else - logToAnsiConsole Colors.Highlighted $"Not implemented yet." - return -1 - | Error error -> return -1 + return 0 + else + logToAnsiConsole Colors.Highlighted $"Not implemented yet." + return -1 | Error error -> return -1 - with - | ex -> return -1 - finally - File.Delete(getUpdateInProgressFileName()) + | Error error -> return -1 + with + | ex -> return -1 } let private Switch = CommandHandler.Create(fun (parseResult: ParseResult) (switchParameters: SwitchParameters) -> task { - let! result = switchHandler2 parseResult switchParameters - return result + try + // Write the UpdatesInProgress file to let grace watch know to ignore these changes. + do! File.WriteAllTextAsync(updateInProgressFileName, "`grace switch` is in progress.") + let! result = switchHandler2 parseResult switchParameters + return result + finally + File.Delete(updateInProgressFileName) }) type RebaseParameters() = @@ -1606,9 +1601,10 @@ module Branch = // -------------------------------------------------------------------------------------------------------------------------------------- // First, get the current branchDto so we have the latest promotion that it's based on. - let branchGetParameters = Parameters.Branch.GetBranchParameters(OwnerId = $"{Current().OwnerId}", - OrganizationId = $"{Current().OrganizationId}", RepositoryId = $"{Current().RepositoryId}", BranchId = $"{Current().BranchId}", - CorrelationId = parameters.CorrelationId) + let branchGetParameters = Parameters.Branch.GetBranchParameters(OwnerId = parameters.OwnerId, OwnerName = parameters.OwnerName, + OrganizationId = parameters.OrganizationId, OrganizationName = parameters.OrganizationName, + RepositoryId = parameters.RepositoryId, RepositoryName = parameters.RepositoryName, + BranchId = parameters.BranchId, BranchName = parameters.BranchName, CorrelationId = parameters.CorrelationId) match! Branch.Get(branchGetParameters) with | Ok returnValue -> let branchDto = returnValue.ReturnValue @@ -1623,9 +1619,10 @@ module Branch = return 0 else // Now, get ReferenceDtos for current.BasedOn and parent.LatestPromotion so we have their DirectoryId's. - let getReferencesByReferenceIdParameters = Parameters.Repository.GetReferencesByReferenceIdParameters(OwnerId = $"{Current().OwnerId}", - OrganizationId = $"{Current().OrganizationId}", RepositoryId = $"{Current().RepositoryId}", CorrelationId = parameters.CorrelationId, - ReferenceIds = [| branchDto.BasedOn; branchDto.LatestCommit; parentBranchDto.LatestPromotion |]) + let getReferencesByReferenceIdParameters = Parameters.Repository.GetReferencesByReferenceIdParameters(OwnerId = parameters.OwnerId, OwnerName = parameters.OwnerName, + OrganizationId = parameters.OrganizationId, OrganizationName = parameters.OrganizationName, + RepositoryId = $"{branchDto.RepositoryId}", CorrelationId = parameters.CorrelationId, + ReferenceIds = [| branchDto.BasedOn; branchDto.LatestCommit; parentBranchDto.LatestPromotion |]) match! Repository.GetReferencesByReferenceId(getReferencesByReferenceIdParameters) with | Ok returnValue -> let referenceDtos = returnValue.ReturnValue @@ -1636,7 +1633,7 @@ module Branch = // Get the latest reference from the current branch. let getReferencesParameters = Parameters.Branch.GetReferencesParameters(OwnerId = parameters.OwnerId, OwnerName = parameters.OwnerName, OrganizationId = parameters.OrganizationId, OrganizationName = parameters.OrganizationName, - RepositoryId = parameters.RepositoryId, RepositoryName = parameters.RepositoryName, + RepositoryId = $"{branchDto.RepositoryId}", BranchId = $"{branchDto.BranchId}", MaxCount = 1, CorrelationId = parameters.CorrelationId) //logToAnsiConsole Colors.Verbose $"getReferencesParameters: {getReferencesParameters |> serialize)}" match! Branch.GetReferences(getReferencesParameters) with @@ -1649,7 +1646,9 @@ module Branch = task { if basedOn.DirectoryId <> DirectoryId.Empty then // First diff: parent promotion that current branch is based on vs. parent's latest promotion. - let diffParameters = Parameters.Diff.GetDiffParameters(DirectoryId1 = basedOn.DirectoryId, DirectoryId2 = parentLatestPromotion.DirectoryId, CorrelationId = parameters.CorrelationId) + let diffParameters = Parameters.Diff.GetDiffParameters(OwnerId = parameters.OwnerId, OwnerName = parameters.OwnerName, + OrganizationId = parameters.OrganizationId, OrganizationName = parameters.OrganizationName, + RepositoryId = $"{branchDto.RepositoryId}", DirectoryId1 = basedOn.DirectoryId, DirectoryId2 = parentLatestPromotion.DirectoryId, CorrelationId = parameters.CorrelationId) logToAnsiConsole Colors.Verbose $"First diff: {Markup.Escape(serialize diffParameters)}" let! firstDiff = Diff.GetDiff(diffParameters) @@ -1660,7 +1659,9 @@ module Branch = | Error error -> logToAnsiConsole Colors.Error (Markup.Escape($"{error}")) // Second diff: latest reference on current branch vs. parent promotion that current branch is based on. - let diffParameters = Parameters.Diff.GetDiffParameters(DirectoryId1 = latestReference.DirectoryId, DirectoryId2 = basedOn.DirectoryId, CorrelationId = parameters.CorrelationId) + let diffParameters = Parameters.Diff.GetDiffParameters(OwnerId = parameters.OwnerId, OwnerName = parameters.OwnerName, + OrganizationId = parameters.OrganizationId, OrganizationName = parameters.OrganizationName, + RepositoryId = $"{branchDto.RepositoryId}", DirectoryId1 = latestReference.DirectoryId, DirectoryId2 = basedOn.DirectoryId, CorrelationId = parameters.CorrelationId) logToAnsiConsole Colors.Verbose $"Second diff: {Markup.Escape(serialize diffParameters)}" let! secondDiff = Diff.GetDiff(diffParameters) @@ -1669,7 +1670,9 @@ module Branch = else // This should only happen when first creating a repository, when main has no promotions. // Only one diff possible: latest reference on current branch vs. parent's latest promotion. - let diffParameters = Parameters.Diff.GetDiffParameters(DirectoryId1 = latestReference.DirectoryId, DirectoryId2 = parentLatestPromotion.DirectoryId, CorrelationId = parameters.CorrelationId) + let diffParameters = Parameters.Diff.GetDiffParameters(OwnerId = parameters.OwnerId, OwnerName = parameters.OwnerName, + OrganizationId = parameters.OrganizationId, OrganizationName = parameters.OrganizationName, + RepositoryId = $"{branchDto.RepositoryId}", DirectoryId1 = latestReference.DirectoryId, DirectoryId2 = parentLatestPromotion.DirectoryId, CorrelationId = parameters.CorrelationId) logToAnsiConsole Colors.Verbose $"Initial diff: {Markup.Escape(serialize diffParameters)}" let! diff = Diff.GetDiff(diffParameters) let returnValue = Result.partition [diff] @@ -1771,19 +1774,80 @@ module Branch = AnsiConsole.MarkupLine($"[{Colors.Important}]fileVersion2.Sha256Hash: {fileVersion2.Value.Sha256Hash}; fileVersion2.LastWriteTimeUTC: {fileVersion2.Value.LastWriteTimeUtc}.[/]") potentialPromotionConflicts <- true + /// Create new directory versions and updates Grace Status with them. + let getNewGraceStatusAndDirectoryVersions (showOutput, graceStatus, currentBranch: BranchDto, differences: IEnumerable) = + task { + if differences.Count() > 0 then + let! (updatedGraceStatus, newDirectoryVersions) = getNewGraceStatusAndDirectoryVersions graceStatus differences + return Ok (updatedGraceStatus, newDirectoryVersions) + else + return Ok (graceStatus, List()) + } + + /// Upload new DirectoryVersion records to the server. + let uploadNewDirectoryVersions (currentBranch: BranchDto) (newDirectoryVersions: List) = + task { + if currentBranch.SaveEnabled && newDirectoryVersions.Any() then + let saveParameters = SaveDirectoryVersionsParameters() + saveParameters.DirectoryVersions <- newDirectoryVersions.Select(fun dv -> dv.ToDirectoryVersion).ToList() + match! Directory.SaveDirectoryVersions saveParameters with + | Ok returnValue -> + return Ok () + | Error error -> + return Error error + else + return Ok () + } + if not <| potentialPromotionConflicts then // Yay! No promotion conflicts. - let rebaseParameters = Parameters.Branch.RebaseParameters(BranchId = $"{branchDto.BranchId}", RepositoryId = $"{branchDto.RepositoryId}", + let mutable newGraceStatus = graceStatus + + // Update the GraceStatus file with the new file versions (and therefore new LocalDirectoryVersion's) we just put in place. + // filesToDownload is, conveniently, the list of files we're changing in the rebase. + match! getNewGraceStatusAndDirectoryVersions (parseResult |> hasOutput, graceStatus, branchDto, filesToDownload) with + | Ok (updatedGraceStatus, newDirectoryVersions) -> + // Ensure that previous DirectoryVersions for a given path are deleted from GraceStatus. + newDirectoryVersions |> Seq.iter (fun localDirectoryVersion -> + let directoryVersionsWithSameRelativePath = updatedGraceStatus.Index.Values.Where(fun dv -> dv.RelativePath = localDirectoryVersion.RelativePath) + if directoryVersionsWithSameRelativePath.Count() > 1 then + // Delete all but the most recent DirectoryVersion for this path. + directoryVersionsWithSameRelativePath |> Seq.where (fun dv -> dv.DirectoryId <> localDirectoryVersion.DirectoryId) |> Seq.iter (fun dv -> + let mutable localDirectoryVersion = LocalDirectoryVersion.Default + updatedGraceStatus.Index.Remove(dv.DirectoryId, &localDirectoryVersion) |> ignore + ) + ) + let! result = uploadNewDirectoryVersions branchDto newDirectoryVersions + do! writeGraceStatusFile updatedGraceStatus + do! updateGraceWatchInterprocessFile updatedGraceStatus + newGraceStatus <- updatedGraceStatus + + | Error error -> logToAnsiConsole Colors.Error (Markup.Escape($"{error}")) + + // Create a save reference to mark the state of the branch after rebase. + let rootDirectoryVersion = getRootDirectoryVersion newGraceStatus + let saveReferenceParameters = Parameters.Branch.CreateReferenceParameters(BranchId = $"{branchDto.BranchId}", RepositoryId = $"{branchDto.RepositoryId}", OwnerId = parameters.OwnerId, OwnerName = parameters.OwnerName, OrganizationId = parameters.OrganizationId, OrganizationName = parameters.OrganizationName, - BasedOn = parentLatestPromotion.ReferenceId) - match! Branch.Rebase(rebaseParameters) with + Sha256Hash = rootDirectoryVersion.Sha256Hash, DirectoryId = rootDirectoryVersion.DirectoryId, + Message = $"Save after rebase from {parentBranchDto.BranchName}; {getShortSha256Hash parentLatestPromotion.Sha256Hash} - {parentLatestPromotion.ReferenceText}.") + match! Branch.Save(saveReferenceParameters) with | Ok returnValue -> - AnsiConsole.MarkupLine($"[{Colors.Important}]Rebase succeeded.[/]") - AnsiConsole.MarkupLine($"[{Colors.Verbose}]({serialize returnValue})[/]") - return 0 - | Error error -> - logToAnsiConsole Colors.Error (Markup.Escape($"{error}")) + // Add a rebase event to the branch. + let rebaseParameters = Parameters.Branch.RebaseParameters(BranchId = $"{branchDto.BranchId}", RepositoryId = $"{branchDto.RepositoryId}", + OwnerId = parameters.OwnerId, OwnerName = parameters.OwnerName, + OrganizationId = parameters.OrganizationId, OrganizationName = parameters.OrganizationName, + BasedOn = parentLatestPromotion.ReferenceId) + match! Branch.Rebase(rebaseParameters) with + | Ok returnValue -> + AnsiConsole.MarkupLine($"[{Colors.Important}]Rebase succeeded.[/]") + AnsiConsole.MarkupLine($"[{Colors.Verbose}]({serialize returnValue})[/]") + return 0 + | Error error -> + logToAnsiConsole Colors.Error (Markup.Escape($"{error}")) + return -1 + | Error error -> + logToAnsiConsole Colors.Error (Markup.Escape($"{error}")) return -1 else AnsiConsole.MarkupLine($"[{Colors.Highlighted}]A potential promotion conflict was detected. Rebase not successful.[/]") @@ -1811,9 +1875,14 @@ module Branch = let private Rebase = CommandHandler.Create(fun (parseResult: ParseResult) (rebaseParameters: RebaseParameters) -> task { - let! graceStatus = readGraceStatusFile() - let! result = rebaseHandler parseResult (rebaseParameters |> normalizeIdsAndNames parseResult) graceStatus - return result + try + Directory.CreateDirectory(Path.GetDirectoryName(updateInProgressFileName)) |> ignore + do! File.WriteAllTextAsync(updateInProgressFileName, "`grace rebase` is in progress.") + let! graceStatus = readGraceStatusFile() + let! result = rebaseHandler parseResult (rebaseParameters |> normalizeIdsAndNames parseResult) graceStatus + return result + finally + File.Delete(updateInProgressFileName) }) type StatusParameters() = diff --git a/src/Grace.CLI/Command/Services.CLI.fs b/src/Grace.CLI/Command/Services.CLI.fs index 84bbc20..7403f7b 100644 --- a/src/Grace.CLI/Command/Services.CLI.fs +++ b/src/Grace.CLI/Command/Services.CLI.fs @@ -428,15 +428,17 @@ module Services = | ObjectStorageProvider.Unknown -> return Ok () | AzureBlobStorage -> - let anyErrors = files.ToArray() + let results = files.ToArray() |> Array.where (fun f -> not <| File.Exists(f.FullObjectPath)) |> Array.Parallel.map (fun f -> (task { return! Storage.GetFileFromObjectStorage f.ToFileVersion correlationId }).Result) - |> Array.exists (fun result -> match result with | Ok _ -> false | Error _ -> true) - if anyErrors then - return Error "Some files could not be downloaded from object storage." + let (results, errors) = results |> Array.partition (fun result -> match result with | Ok _ -> true | Error _ -> false) + if errors.Count() > 0 then + let sb = StringBuilder($"Some files could not be downloaded from object storage.{Environment.NewLine}") + errors |> Seq.iter(fun e -> match e with | Ok _ -> () | Error e -> sb.AppendLine(e.Error) |> ignore) + return Error (sb.ToString()) else return Ok () | AWSS3 -> @@ -540,7 +542,7 @@ module Services = /// Gets a list of new or updated LocalDirectoryVersions that reflect changes in the working directory. /// /// If an empty list of differences is passed in, returns the same GraceStatus that was passed in, and an empty list of LocalDirectoryVersions. - let getNewGraceStatusAndDirectoryVersions (previousGraceStatus: GraceStatus) (differences: List) = + let getNewGraceStatusAndDirectoryVersions (previousGraceStatus: GraceStatus) (differences: IEnumerable) = task { /// Holds DirectoryVersions that have already been changed, so we can make more changes to them if needed. let changedDirectoryVersions = ConcurrentDictionary() @@ -862,7 +864,7 @@ module Services = newGraceStatus /// Gets the file name used to indicate to `grace watch` that updates are in progress from another Grace command, and that it should ignore them. - let getUpdateInProgressFileName() = getNativeFilePath (Path.Combine(Environment.GetEnvironmentVariable("temp"), $"grace-UpdatesInProgress.txt")) + let updateInProgressFileName = getNativeFilePath (Path.Combine(Path.GetTempPath(), "Grace", Constants.UpdateInProgressFileName)) /// Updates the working directory to match the contents of new DirectoryVersions. /// diff --git a/src/Grace.CLI/Command/Watch.CLI.fs b/src/Grace.CLI/Command/Watch.CLI.fs index b2612e8..ba7e303 100644 --- a/src/Grace.CLI/Command/Watch.CLI.fs +++ b/src/Grace.CLI/Command/Watch.CLI.fs @@ -62,7 +62,7 @@ module Watch = logToConsole $"In Delete: filePath: {filePath}" let isNotDirectory path = not <| Directory.Exists(path) - let updateInProgress() = File.Exists(getUpdateInProgressFileName()) + let updateInProgress() = File.Exists(updateInProgressFileName) let updateNotInProgress() = not <| updateInProgress() let OnCreated (args: FileSystemEventArgs) = @@ -118,9 +118,30 @@ module Watch = let correlationId = Guid.NewGuid().ToString() logToAnsiConsole Colors.Error $"I saw that the FileSystemWatcher threw an exception: {args.GetException().Message}. grace watch should be restarted." - /// Creates a FileSystemWatcher for the given root directory. - let createFileSystemWatcher rootDirectory = - let fileSystemWatcher = new FileSystemWatcher(rootDirectory) + let OnGraceUpdateInProgressCreated (args: FileSystemEventArgs) = + if args.FullPath = updateInProgressFileName then + if updateInProgress() then + logToAnsiConsole Colors.Important $"Update is in progress from another Grace instance." + else + logToAnsiConsole Colors.Important $"{updateInProgressFileName} should already exist, but it doesn't." + + let OnGraceUpdateInProgressChanged (args: FileSystemEventArgs) = + if args.FullPath = updateInProgressFileName then + if updateInProgress() then + logToAnsiConsole Colors.Important $"Update is in progress from another Grace instance." + else + logToAnsiConsole Colors.Important $"{updateInProgressFileName} should already exist, but it doesn't." + + let OnGraceUpdateInProgressDeleted (args: FileSystemEventArgs) = + if args.FullPath = updateInProgressFileName then + if updateNotInProgress() then + logToAnsiConsole Colors.Important $"Update has finished in another Grace instance." + else + logToAnsiConsole Colors.Important $"{updateInProgressFileName} should have been deleted, but it hasn't yet." + + /// Creates a FileSystemWatcher for the given path. + let createFileSystemWatcher path = + let fileSystemWatcher = new FileSystemWatcher(path) fileSystemWatcher.InternalBufferSize <- (64 * 1024) // Default is 4K, choosing maximum of 64K for safety. fileSystemWatcher.IncludeSubdirectories <- true fileSystemWatcher.NotifyFilter <- NotifyFilters.DirectoryName ||| NotifyFilters.FileName ||| NotifyFilters.LastWrite ||| NotifyFilters.Security @@ -288,13 +309,19 @@ module Watch = task { try // Create the FileSystemWatcher, but don't enable it yet. - use fileSystemWatcher = createFileSystemWatcher (Current().RootDirectory) - use created = Observable.FromEventPattern(fileSystemWatcher, "Created").Select(fun e -> e.EventArgs).Subscribe(OnCreated) - use changed = Observable.FromEventPattern(fileSystemWatcher, "Changed").Select(fun e -> e.EventArgs).Subscribe(OnChanged) - use deleted = Observable.FromEventPattern(fileSystemWatcher, "Deleted").Select(fun e -> e.EventArgs).Subscribe(OnDeleted) - use renamed = Observable.FromEventPattern(fileSystemWatcher, "Renamed") .Select(fun e -> e.EventArgs).Subscribe(OnRenamed) - use errored = Observable.FromEventPattern(fileSystemWatcher, "Error") .Select(fun e -> e.EventArgs).Subscribe(OnError) // I want all of the errors. + use rootDirectoryFileSystemWatcher = createFileSystemWatcher (Current().RootDirectory) + use created = Observable.FromEventPattern(rootDirectoryFileSystemWatcher, "Created").Select(fun e -> e.EventArgs).Subscribe(OnCreated) + use changed = Observable.FromEventPattern(rootDirectoryFileSystemWatcher, "Changed").Select(fun e -> e.EventArgs).Subscribe(OnChanged) + use deleted = Observable.FromEventPattern(rootDirectoryFileSystemWatcher, "Deleted").Select(fun e -> e.EventArgs).Subscribe(OnDeleted) + use renamed = Observable.FromEventPattern(rootDirectoryFileSystemWatcher, "Renamed") .Select(fun e -> e.EventArgs).Subscribe(OnRenamed) + use errored = Observable.FromEventPattern(rootDirectoryFileSystemWatcher, "Error") .Select(fun e -> e.EventArgs).Subscribe(OnError) // I want all of the errors. + Directory.CreateDirectory(Path.GetDirectoryName(updateInProgressFileName)) |> ignore + use updateInProgressFileSystemWatcher = createFileSystemWatcher (Path.GetDirectoryName(updateInProgressFileName)) + use updateInProgressChanged = Observable.FromEventPattern(updateInProgressFileSystemWatcher, "Created").Select(fun e -> e.EventArgs).Subscribe(OnGraceUpdateInProgressCreated) + use updateInProgressChanged = Observable.FromEventPattern(updateInProgressFileSystemWatcher, "Changed").Select(fun e -> e.EventArgs).Subscribe(OnGraceUpdateInProgressChanged) + use updateInProgressDeleted = Observable.FromEventPattern(updateInProgressFileSystemWatcher, "Deleted").Select(fun e -> e.EventArgs).Subscribe(OnGraceUpdateInProgressDeleted) + // Load the Grace Index file. let! status = readGraceStatusFile() graceStatus <- status @@ -303,7 +330,8 @@ module Watch = do! updateGraceWatchInterprocessFile graceStatus // Enable the FileSystemWatcher. - fileSystemWatcher.EnableRaisingEvents <- true + rootDirectoryFileSystemWatcher.EnableRaisingEvents <- true + updateInProgressFileSystemWatcher.EnableRaisingEvents <- true let timerTimeSpan = TimeSpan.FromSeconds(1.0) logToAnsiConsole Colors.Verbose $"The change processor timer will tick every {timerTimeSpan.TotalSeconds:F1} seconds." diff --git a/src/Grace.Server/Branch.Server.fs b/src/Grace.Server/Branch.Server.fs index f147534..1fcded3 100644 --- a/src/Grace.Server/Branch.Server.fs +++ b/src/Grace.Server/Branch.Server.fs @@ -209,6 +209,7 @@ module Branch = String.isValidGraceName parameters.BranchName InvalidBranchName String.isNotEmpty parameters.Message MessageIsRequired String.maxLength parameters.Message 2048 StringIsTooLong + String.isValidSha256Hash parameters.Sha256Hash Sha256HashIsRequired Owner.ownerExists parameters.OwnerId parameters.OwnerName OwnerDoesNotExist Organization.organizationExists parameters.OwnerId parameters.OwnerName parameters.OrganizationId parameters.OrganizationName OrganizationDoesNotExist Repository.repositoryExists parameters.OwnerId parameters.OwnerName parameters.OrganizationId parameters.OrganizationName parameters.RepositoryId parameters.RepositoryName RepositoryDoesNotExist @@ -231,6 +232,7 @@ module Branch = String.isValidGraceName parameters.BranchName InvalidBranchName String.isNotEmpty parameters.Message MessageIsRequired String.maxLength parameters.Message 2048 StringIsTooLong + String.isValidSha256Hash parameters.Sha256Hash Sha256HashIsRequired Owner.ownerExists parameters.OwnerId parameters.OwnerName OwnerDoesNotExist Organization.organizationExists parameters.OwnerId parameters.OwnerName parameters.OrganizationId parameters.OrganizationName OrganizationDoesNotExist Repository.repositoryExists parameters.OwnerId parameters.OwnerName parameters.OrganizationId parameters.OrganizationName parameters.RepositoryId parameters.RepositoryName RepositoryDoesNotExist @@ -253,6 +255,7 @@ module Branch = String.isValidGraceName parameters.BranchName InvalidBranchName Input.eitherIdOrNameMustBeProvided parameters.BranchId parameters.BranchName EitherBranchIdOrBranchNameRequired String.maxLength parameters.Message 2048 StringIsTooLong + String.isValidSha256Hash parameters.Sha256Hash Sha256HashIsRequired Owner.ownerExists parameters.OwnerId parameters.OwnerName OwnerDoesNotExist Organization.organizationExists parameters.OwnerId parameters.OwnerName parameters.OrganizationId parameters.OrganizationName OrganizationDoesNotExist Repository.repositoryExists parameters.OwnerId parameters.OwnerName parameters.OrganizationId parameters.OrganizationName parameters.RepositoryId parameters.RepositoryName RepositoryDoesNotExist @@ -275,6 +278,7 @@ module Branch = String.isValidGraceName parameters.BranchName InvalidBranchName Input.eitherIdOrNameMustBeProvided parameters.BranchId parameters.BranchName EitherBranchIdOrBranchNameRequired String.maxLength parameters.Message 4096 StringIsTooLong + String.isValidSha256Hash parameters.Sha256Hash Sha256HashIsRequired Owner.ownerExists parameters.OwnerId parameters.OwnerName OwnerDoesNotExist Organization.organizationExists parameters.OwnerId parameters.OwnerName parameters.OrganizationId parameters.OrganizationName OrganizationDoesNotExist Repository.repositoryExists parameters.OwnerId parameters.OwnerName parameters.OrganizationId parameters.OrganizationName parameters.RepositoryId parameters.RepositoryName RepositoryDoesNotExist @@ -297,6 +301,7 @@ module Branch = String.isValidGraceName parameters.BranchName InvalidBranchName Input.eitherIdOrNameMustBeProvided parameters.BranchId parameters.BranchName EitherBranchIdOrBranchNameRequired String.maxLength parameters.Message 2048 StringIsTooLong + String.isValidSha256Hash parameters.Sha256Hash Sha256HashIsRequired Owner.ownerExists parameters.OwnerId parameters.OwnerName OwnerDoesNotExist Organization.organizationExists parameters.OwnerId parameters.OwnerName parameters.OrganizationId parameters.OrganizationName OrganizationDoesNotExist Repository.repositoryExists parameters.OwnerId parameters.OwnerName parameters.OrganizationId parameters.OrganizationName parameters.RepositoryId parameters.RepositoryName RepositoryDoesNotExist @@ -832,10 +837,7 @@ module Branch = let! latestReference = getLatestReference branchDto.BranchId match latestReference with | Some latestReference -> - let referenceActorId = Reference.GetActorId branchDto.LatestSave - let referenceActorProxy = actorProxyFactory.CreateActorProxy(referenceActorId, ActorName.Reference) - let! referenceDto = referenceActorProxy.Get() - let directoryActorId = DirectoryVersion.GetActorId referenceDto.DirectoryId + let directoryActorId = DirectoryVersion.GetActorId latestReference.DirectoryId let directoryActorProxy = actorProxyFactory.CreateActorProxy(directoryActorId, ActorName.DirectoryVersion) let! contents = directoryActorProxy.GetDirectoryVersionsRecursive(false) return contents diff --git a/src/Grace.Shared/Constants.Shared.fs b/src/Grace.Shared/Constants.Shared.fs index 3b5ca25..b4a4ef6 100644 --- a/src/Grace.Shared/Constants.Shared.fs +++ b/src/Grace.Shared/Constants.Shared.fs @@ -221,6 +221,9 @@ module Constants = /// The name of the inter-process communication file used by grace watch to share status with other invocations of Grace. let IpcFileName = "graceWatchStatus.json" + /// The name of the file to let `grace watch` know that `grace rebase` or `grace switch` is underway. + let UpdateInProgressFileName = "graceUpdateInProgress.txt" + /// The default expiration time for a cache entry. let DefaultExpirationTime = TimeSpan.FromMinutes(5.0) diff --git a/src/Grace.Shared/Validation/Errors.Validation.fs b/src/Grace.Shared/Validation/Errors.Validation.fs index 8714206..82a4985 100644 --- a/src/Grace.Shared/Validation/Errors.Validation.fs +++ b/src/Grace.Shared/Validation/Errors.Validation.fs @@ -45,6 +45,7 @@ module Errors = | RepositoryDoesNotExist | SaveIsDisabled | Sha256HashDoesNotExist + | Sha256HashIsRequired | StringIsTooLong | TagIsDisabled | ValueMustBePositive @@ -88,6 +89,7 @@ module Errors = | RepositoryDoesNotExist -> getLocalizedString StringResourceName.RepositoryDoesNotExist | SaveIsDisabled -> getLocalizedString StringResourceName.SaveIsDisabled | Sha256HashDoesNotExist -> getLocalizedString StringResourceName.Sha256HashDoesNotExist + | Sha256HashIsRequired -> getLocalizedString StringResourceName.Sha256HashIsRequired | StringIsTooLong -> getLocalizedString StringResourceName.StringIsTooLong | TagIsDisabled -> getLocalizedString StringResourceName.TagIsDisabled | ValueMustBePositive -> getLocalizedString StringResourceName.ValueMustBePositive