From ad25fb08a24607a5fe54f5b1e46b035f713001b8 Mon Sep 17 00:00:00 2001 From: "Scott B. Arbeit" Date: Sat, 27 Jul 2024 00:00:34 -0700 Subject: [PATCH] Updates to BranchDto to speed up `grace status`; changed default timestamp on record creation; NuGet updates. --- .../CosmosJsonSerializer.csproj | 2 +- src/Grace.Actors/Branch.Actor.fs | 158 ++-- src/Grace.Actors/Grace.Actors.fsproj | 8 +- src/Grace.Actors/Organization.Actor.fs | 10 +- src/Grace.Actors/Owner.Actor.fs | 8 +- src/Grace.Actors/Reference.Actor.fs | 13 +- src/Grace.Actors/Repository.Actor.fs | 6 +- src/Grace.Actors/Services.Actor.fs | 2 +- .../Grace.Aspire.AppHost.csproj | 12 +- .../Grace.Aspire.ServiceDefaults.csproj | 2 +- src/Grace.CLI/Command/Branch.CLI.fs | 772 ++++++++---------- src/Grace.CLI/Command/Diff.CLI.fs | 35 +- src/Grace.CLI/Command/Reference.CLI.fs | 2 +- src/Grace.CLI/Command/Repository.CLI.fs | 2 +- src/Grace.SDK/Grace.SDK.fsproj | 2 +- .../Grace.Server.Tests.fsproj | 2 +- src/Grace.Server/Branch.Server.fs | 2 +- src/Grace.Server/Grace.Server.fsproj | 6 +- src/Grace.Shared/Constants.Shared.fs | 4 + src/Grace.Shared/Dto/Dto.Shared.fs | 18 +- src/Grace.Shared/Types.Shared.fs | 4 +- 21 files changed, 514 insertions(+), 556 deletions(-) diff --git a/src/CosmosSerializer/CosmosJsonSerializer.csproj b/src/CosmosSerializer/CosmosJsonSerializer.csproj index 165d0a2..d2a7d37 100644 --- a/src/CosmosSerializer/CosmosJsonSerializer.csproj +++ b/src/CosmosSerializer/CosmosJsonSerializer.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/Grace.Actors/Branch.Actor.fs b/src/Grace.Actors/Branch.Actor.fs index d169541..0fa87a7 100644 --- a/src/Grace.Actors/Branch.Actor.fs +++ b/src/Grace.Actors/Branch.Actor.fs @@ -50,55 +50,76 @@ module Branch = /// Indicates that the actor is in an undefined state, and should be reset. let mutable isDisposed = false - let updateDto branchEventType currentBranchDto = - let newBranchDto = - match branchEventType with - | Created(branchId, branchName, parentBranchId, basedOn, repositoryId, initialPermissions) -> - let mutable branchDto = - { BranchDto.Default with - BranchId = branchId - BranchName = branchName - ParentBranchId = parentBranchId - BasedOn = basedOn - RepositoryId = repositoryId } - - for referenceType in initialPermissions do - branchDto <- - match referenceType with - | Promotion -> { branchDto with PromotionEnabled = true } - | Commit -> { branchDto with CommitEnabled = true } - | Checkpoint -> { branchDto with CheckpointEnabled = true } - | Save -> { branchDto with SaveEnabled = true } - | Tag -> { branchDto with TagEnabled = true } - | External -> { branchDto with ExternalEnabled = true } - | Rebase -> branchDto // Rebase is always allowed. (Auto-rebase is optional, but rebase itself is always allowed.) - - branchDto - | Rebased referenceId ->{ currentBranchDto with BasedOn = referenceId } - | NameSet branchName -> { currentBranchDto with BranchName = branchName } - | Assigned(referenceDto, directoryVersion, sha256Hash, referenceText) -> - { currentBranchDto with LatestPromotion = referenceDto; BasedOn = referenceDto.ReferenceId } - | Promoted(referenceDto, directoryVersion, sha256Hash, referenceText) -> - { currentBranchDto with LatestPromotion = referenceDto; BasedOn = referenceDto.ReferenceId } - | Committed(referenceDto, directoryVersion, sha256Hash, referenceText) -> { currentBranchDto with LatestCommit = referenceDto } - | Checkpointed(referenceDto, directoryVersion, sha256Hash, referenceText) -> { currentBranchDto with LatestCheckpoint = referenceDto } - | Saved(referenceDto, directoryVersion, sha256Hash, referenceText) -> { currentBranchDto with LatestSave = referenceDto } - | Tagged(referenceDto, directoryVersion, sha256Hash, referenceText) -> currentBranchDto // No changes to currentBranchDto. - | ExternalCreated(referenceDto, directoryVersion, sha256Hash, referenceText) -> currentBranchDto // No changes to currentBranchDto. - | EnabledAssign enabled -> { currentBranchDto with AssignEnabled = enabled } - | EnabledPromotion enabled -> { currentBranchDto with PromotionEnabled = enabled } - | EnabledCommit enabled -> { currentBranchDto with CommitEnabled = enabled } - | EnabledCheckpoint enabled -> { currentBranchDto with CheckpointEnabled = enabled } - | EnabledSave enabled -> { currentBranchDto with SaveEnabled = enabled } - | EnabledTag enabled -> { currentBranchDto with TagEnabled = enabled } - | EnabledExternal enabled -> { currentBranchDto with ExternalEnabled = enabled } - | EnabledAutoRebase enabled -> { currentBranchDto with AutoRebaseEnabled = enabled } - | ReferenceRemoved _ -> currentBranchDto - | LogicalDeleted(force, deleteReason) -> { currentBranchDto with DeletedAt = Some(getCurrentInstant ()); DeleteReason = deleteReason } - | PhysicalDeleted -> currentBranchDto // Do nothing because it's about to be deleted anyway. - | Undeleted -> { currentBranchDto with DeletedAt = None; DeleteReason = String.Empty } - - { newBranchDto with UpdatedAt = Some(getCurrentInstant ()) } + let updateDto branchEvent currentBranchDto = + task { + let branchEventType = branchEvent.Event + let! newBranchDto = + match branchEventType with + | Created(branchId, branchName, parentBranchId, basedOn, repositoryId, initialPermissions) -> + task { + let! referenceDto = + if basedOn <> ReferenceId.Empty then + task { + let referenceActorProxy = actorProxyFactory.CreateActorProxy(Reference.GetActorId basedOn, ActorName.Reference) + return! referenceActorProxy.Get (branchEvent.Metadata.CorrelationId + $" Creating branch {branchName}") + } + else + ReferenceDto.Default |> returnTask + + let mutable branchDto = + { BranchDto.Default with + BranchId = branchId + BranchName = branchName + ParentBranchId = parentBranchId + BasedOn = referenceDto + RepositoryId = repositoryId + CreatedAt = branchEvent.Metadata.Timestamp + } + + for referenceType in initialPermissions do + branchDto <- + match referenceType with + | Promotion -> { branchDto with PromotionEnabled = true } + | Commit -> { branchDto with CommitEnabled = true } + | Checkpoint -> { branchDto with CheckpointEnabled = true } + | Save -> { branchDto with SaveEnabled = true } + | Tag -> { branchDto with TagEnabled = true } + | External -> { branchDto with ExternalEnabled = true } + | Rebase -> branchDto // Rebase is always allowed. (Auto-rebase is optional, but rebase itself is always allowed.) + + return branchDto + } + | Rebased referenceId -> + task { + let referenceActorProxy = actorProxyFactory.CreateActorProxy(Reference.GetActorId referenceId, ActorName.Reference) + let! referenceDto = referenceActorProxy.Get (branchEvent.Metadata.CorrelationId + "Rebasing branch") + return { currentBranchDto with BasedOn = referenceDto } + } + | NameSet branchName -> { currentBranchDto with BranchName = branchName } |> returnTask + | Assigned(referenceDto, directoryVersion, sha256Hash, referenceText) -> + { currentBranchDto with LatestPromotion = referenceDto; BasedOn = referenceDto } |> returnTask + | Promoted(referenceDto, directoryVersion, sha256Hash, referenceText) -> + { currentBranchDto with LatestPromotion = referenceDto; BasedOn = referenceDto } |> returnTask + | Committed(referenceDto, directoryVersion, sha256Hash, referenceText) -> { currentBranchDto with LatestCommit = referenceDto } |> returnTask + | Checkpointed(referenceDto, directoryVersion, sha256Hash, referenceText) -> { currentBranchDto with LatestCheckpoint = referenceDto } |> returnTask + | Saved(referenceDto, directoryVersion, sha256Hash, referenceText) -> { currentBranchDto with LatestSave = referenceDto } |> returnTask + | Tagged(referenceDto, directoryVersion, sha256Hash, referenceText) -> currentBranchDto |> returnTask // No changes to currentBranchDto. + | ExternalCreated(referenceDto, directoryVersion, sha256Hash, referenceText) -> currentBranchDto |> returnTask // No changes to currentBranchDto. + | EnabledAssign enabled -> { currentBranchDto with AssignEnabled = enabled } |> returnTask + | EnabledPromotion enabled -> { currentBranchDto with PromotionEnabled = enabled } |> returnTask + | EnabledCommit enabled -> { currentBranchDto with CommitEnabled = enabled } |> returnTask + | EnabledCheckpoint enabled -> { currentBranchDto with CheckpointEnabled = enabled } |> returnTask + | EnabledSave enabled -> { currentBranchDto with SaveEnabled = enabled } |> returnTask + | EnabledTag enabled -> { currentBranchDto with TagEnabled = enabled } |> returnTask + | EnabledExternal enabled -> { currentBranchDto with ExternalEnabled = enabled } |> returnTask + | EnabledAutoRebase enabled -> { currentBranchDto with AutoRebaseEnabled = enabled } |> returnTask + | ReferenceRemoved _ -> currentBranchDto |> returnTask + | LogicalDeleted(force, deleteReason) -> { currentBranchDto with DeletedAt = Some(getCurrentInstant ()); DeleteReason = deleteReason } |> returnTask + | PhysicalDeleted -> currentBranchDto |> returnTask // Do nothing because it's about to be deleted anyway. + | Undeleted -> { currentBranchDto with DeletedAt = None; DeleteReason = String.Empty } |> returnTask + + return { newBranchDto with UpdatedAt = Some branchEvent.Metadata.Timestamp } + } member val private correlationId: CorrelationId = String.Empty with get, set @@ -117,31 +138,34 @@ module Branch = branchEvents.AddRange(retrievedEvents) // Apply all events to the branchDto. - branchDto <- - retrievedEvents - |> Seq.fold (fun branchDto branchEvent -> branchDto |> updateDto branchEvent.Event) BranchDto.Default + for branchEvent in retrievedEvents do + let! updatedBranchDto = branchDto |> updateDto branchEvent + branchDto <- updatedBranchDto // Get the latest references and update the dto. let referenceTypes = [| Save; Checkpoint; Commit; Promotion; Rebase |] //logToConsole $"In Branch.Actor.OnActivateAsync: About to call getLatestReferenceByReferenceTypes()." let! latestReferences = getLatestReferenceByReferenceTypes referenceTypes branchDto.BranchId - latestReferences |> Seq.iter (fun kvp -> + for kvp in latestReferences do let referenceDto = kvp.Value match kvp.Key with | Save -> branchDto <- { branchDto with LatestSave = referenceDto } | Checkpoint -> branchDto <- { branchDto with LatestCheckpoint = referenceDto } | Commit -> branchDto <- { branchDto with LatestCommit = referenceDto } - | Promotion -> branchDto <- { branchDto with LatestPromotion = referenceDto; BasedOn = referenceDto.ReferenceId } + | Promotion -> branchDto <- { branchDto with LatestPromotion = referenceDto; BasedOn = referenceDto } | Rebase -> let basedOnLink = kvp.Value.Links |> Array.find (fun link -> match link with | ReferenceLinkType.BasedOn _ -> true ) let basedOnReferenceId = match basedOnLink with | ReferenceLinkType.BasedOn referenceId -> referenceId - branchDto <- { branchDto with BasedOn = basedOnReferenceId } + logToConsole $"In Branch.Actor.OnActivateAsync: basedOnReferenceId: {basedOnReferenceId}." + let basedOnReferenceActorProxy = actorProxyFactory.CreateActorProxy(Reference.GetActorId basedOnReferenceId, ActorName.Reference) + let! basedOnReferenceDto = basedOnReferenceActorProxy.Get "Activating branch" + + branchDto <- { branchDto with BasedOn = basedOnReferenceDto } | External -> () | Tag -> () - ) message <- "Retrieved from database" | None -> message <- "Not found in database" @@ -239,7 +263,8 @@ module Branch = if branchEvents.Count = 0 then do! this.OnFirstWrite() // Update the branchDto with the event. - branchDto <- branchDto |> updateDto branchEvent.Event + let! updatedBranchDto = branchDto |> updateDto branchEvent + branchDto <- updatedBranchDto match branchEvent.Event with // Don't save these reference creation events, and don't send them as events; that was done by the Reference actor when the reference was created. @@ -351,11 +376,22 @@ module Branch = let referenceActor = actorProxyFactory.CreateActorProxy(actorId, Constants.ActorName.Reference) - let referenceDto = {ReferenceDto.Default with ReferenceId = referenceId; RepositoryId = repositoryId; BranchId = branchId; DirectoryId = directoryId; Sha256Hash = sha256Hash; ReferenceText = referenceText; ReferenceType = referenceType; Links = links} + let referenceDto = + {ReferenceDto.Default with + ReferenceId = referenceId + RepositoryId = repositoryId + BranchId = branchId + DirectoryId = directoryId + Sha256Hash = sha256Hash + ReferenceText = referenceText + ReferenceType = referenceType + Links = links + CreatedAt = getCurrentInstant () + } let referenceCommand = Reference.ReferenceCommand.Create referenceDto match! referenceActor.Handle referenceCommand metadata with | Ok _ -> - let! referenceDto = referenceActor.Get metadata.CorrelationId + let! referenceDto = referenceActor.Get (metadata.CorrelationId + "Adding reference") return Ok referenceDto | Error error -> return Error error } @@ -373,7 +409,7 @@ module Branch = if branchName <> InitialBranchName then // We need to get the reference that we're rebasing on, so we can get the directoryId and sha256Hash. let referenceActorProxy = actorProxyFactory.CreateActorProxy(Reference.GetActorId basedOn, ActorName.Reference) - let! promotionDto = referenceActorProxy.Get metadata.CorrelationId + let! promotionDto = referenceActorProxy.Get (metadata.CorrelationId + "processCommand1") match! addReference repositoryId branchId promotionDto.DirectoryId promotionDto.Sha256Hash promotionDto.ReferenceText ReferenceType.Rebase [| ReferenceLinkType.BasedOn promotionDto.ReferenceId |] with | Ok rebaseReferenceId -> @@ -398,7 +434,7 @@ module Branch = // We need to get the reference that we're rebasing on, so we can get the directoryId and sha256Hash. let referenceActorProxy = actorProxyFactory.CreateActorProxy(ActorId($"{referenceId}"), ActorName.Reference) - let! promotionDto = referenceActorProxy.Get metadata.CorrelationId + let! promotionDto = referenceActorProxy.Get (metadata.CorrelationId + "processCommand2") // Add the Rebase reference to this branch. match! addReferenceToCurrentBranch promotionDto.DirectoryId promotionDto.Sha256Hash promotionDto.ReferenceText ReferenceType.Rebase [| ReferenceLinkType.BasedOn promotionDto.ReferenceId |] with diff --git a/src/Grace.Actors/Grace.Actors.fsproj b/src/Grace.Actors/Grace.Actors.fsproj index ce0484c..65d6e77 100644 --- a/src/Grace.Actors/Grace.Actors.fsproj +++ b/src/Grace.Actors/Grace.Actors.fsproj @@ -42,13 +42,13 @@ - - - + + + - + diff --git a/src/Grace.Actors/Organization.Actor.fs b/src/Grace.Actors/Organization.Actor.fs index e8ecd43..2cfb95d 100644 --- a/src/Grace.Actors/Organization.Actor.fs +++ b/src/Grace.Actors/Organization.Actor.fs @@ -47,11 +47,11 @@ module Organization = /// Indicates that the actor is in an undefined state, and should be reset. let mutable isDisposed = false - let updateDto (organizationEventType: OrganizationEventType) currentOrganizationDto = + let updateDto organizationEvent currentOrganizationDto = let newOrganizationDto = - match organizationEventType with + match organizationEvent.Event with | Created(organizationId, organizationName, ownerId) -> - { OrganizationDto.Default with OrganizationId = organizationId; OrganizationName = organizationName; OwnerId = ownerId } + { OrganizationDto.Default with OrganizationId = organizationId; OrganizationName = organizationName; OwnerId = ownerId; CreatedAt = organizationEvent.Metadata.Timestamp } | NameSet(organizationName) -> { currentOrganizationDto with OrganizationName = organizationName } | TypeSet(organizationType) -> { currentOrganizationDto with OrganizationType = organizationType } | SearchVisibilitySet(searchVisibility) -> { currentOrganizationDto with SearchVisibility = searchVisibility } @@ -60,7 +60,7 @@ module Organization = | PhysicalDeleted -> currentOrganizationDto // Do nothing because it's about to be deleted anyway. | Undeleted -> { currentOrganizationDto with DeletedAt = None; DeleteReason = String.Empty } - { newOrganizationDto with UpdatedAt = Some(getCurrentInstant ()) } + { newOrganizationDto with UpdatedAt = Some organizationEvent.Metadata.Timestamp } member val private correlationId: CorrelationId = String.Empty with get, set @@ -193,7 +193,7 @@ module Organization = do! daprClient.PublishEventAsync(GracePubSubService, GraceEventStreamTopic, graceEvent) // Update the Dto based on the current event. - organizationDto <- organizationDto |> updateDto organizationEvent.Event + organizationDto <- organizationDto |> updateDto organizationEvent do! DefaultAsyncRetryPolicy.ExecuteAsync(fun () -> stateManager.SetStateAsync(dtoStateName, organizationDto)) diff --git a/src/Grace.Actors/Owner.Actor.fs b/src/Grace.Actors/Owner.Actor.fs index 53410b4..3003269 100644 --- a/src/Grace.Actors/Owner.Actor.fs +++ b/src/Grace.Actors/Owner.Actor.fs @@ -50,8 +50,8 @@ module Owner = let updateDto ownerEvent currentOwnerDto = let newOwnerDto = - match ownerEvent with - | Created(ownerId, ownerName) -> { OwnerDto.Default with OwnerId = ownerId; OwnerName = ownerName } + match ownerEvent.Event with + | Created(ownerId, ownerName) -> { OwnerDto.Default with OwnerId = ownerId; OwnerName = ownerName; CreatedAt = ownerEvent.Metadata.Timestamp } | NameSet(ownerName) -> { currentOwnerDto with OwnerName = ownerName } | TypeSet(ownerType) -> { currentOwnerDto with OwnerType = ownerType } | SearchVisibilitySet(searchVisibility) -> { currentOwnerDto with SearchVisibility = searchVisibility } @@ -60,7 +60,7 @@ module Owner = | PhysicalDeleted -> currentOwnerDto // Do nothing because it's about to be deleted anyway. | Undeleted -> { currentOwnerDto with DeletedAt = None; DeleteReason = String.Empty } - { newOwnerDto with UpdatedAt = Some(getCurrentInstant ()) } + { newOwnerDto with UpdatedAt = Some ownerEvent.Metadata.Timestamp } member val private correlationId: CorrelationId = String.Empty with get, set @@ -196,7 +196,7 @@ module Owner = do! DefaultAsyncRetryPolicy.ExecuteAsync(fun () -> stateManager.SetStateAsync(eventsStateName, ownerEvents)) - ownerDto <- ownerDto |> updateDto ownerEvent.Event + ownerDto <- ownerDto |> updateDto ownerEvent do! DefaultAsyncRetryPolicy.ExecuteAsync(fun () -> stateManager.SetStateAsync(dtoStateName, ownerDto)) diff --git a/src/Grace.Actors/Reference.Actor.fs b/src/Grace.Actors/Reference.Actor.fs index 08f39c8..35a2fad 100644 --- a/src/Grace.Actors/Reference.Actor.fs +++ b/src/Grace.Actors/Reference.Actor.fs @@ -42,9 +42,9 @@ module Reference = /// Indicates that the actor is in an undefined state, and should be reset. let mutable isDisposed = false - let updateDto referenceEventType currentReferenceDto = + let updateDto referenceEvent currentReferenceDto = let newReferenceDto = - match referenceEventType with + match referenceEvent.Event with | Created createdDto -> { currentReferenceDto with ReferenceId = createdDto.ReferenceId @@ -55,6 +55,7 @@ module Reference = ReferenceType = createdDto.ReferenceType ReferenceText = createdDto.ReferenceText Links = createdDto.Links + CreatedAt = referenceEvent.Metadata.Timestamp } | LinkAdded link -> {currentReferenceDto with Links = currentReferenceDto.Links |> Array.append (Array.singleton link) |> Array.distinct } | LinkRemoved link -> {currentReferenceDto with Links = currentReferenceDto.Links |> Array.except (Array.singleton link) } @@ -62,7 +63,7 @@ module Reference = | PhysicalDeleted -> currentReferenceDto // Do nothing because it's about to be deleted anyway. | Undeleted -> {currentReferenceDto with DeletedAt = None; DeleteReason = String.Empty} - {newReferenceDto with UpdatedAt = Some(getCurrentInstant())} + {newReferenceDto with UpdatedAt = Some referenceEvent.Metadata.Timestamp} member val private correlationId: CorrelationId = String.Empty with get, set @@ -79,7 +80,7 @@ module Reference = referenceEvents.AddRange(retrievedEvents) // Apply all events to the state. - referenceDto <- retrievedEvents |> Seq.fold (fun referenceDto referenceEvent -> referenceDto |> updateDto referenceEvent.Event) ReferenceDto.Default + referenceDto <- retrievedEvents |> Seq.fold (fun referenceDto referenceEvent -> referenceDto |> updateDto referenceEvent) ReferenceDto.Default message <- "Retrieved from database" | None -> message <- "Not found in database" @@ -166,7 +167,7 @@ module Reference = do! Storage.SaveState stateManager eventsStateName referenceEvents // Update the referenceDto with the event. - referenceDto <- referenceDto |> updateDto referenceEvent.Event + referenceDto <- referenceDto |> updateDto referenceEvent // Publish the event to the rest of the world. let graceEvent = Events.GraceEvent.ReferenceEvent referenceEvent @@ -268,7 +269,7 @@ module Reference = do! Storage.SaveState stateManager eventsStateName referenceEvents // Update the referenceDto with the event. - referenceDto <- referenceDto |> updateDto referenceEvent.Event + referenceDto <- referenceDto |> updateDto referenceEvent // Publish the event to the rest of the world. let graceEvent = Events.GraceEvent.ReferenceEvent referenceEvent diff --git a/src/Grace.Actors/Repository.Actor.fs b/src/Grace.Actors/Repository.Actor.fs index 461b17b..d7d1619 100644 --- a/src/Grace.Actors/Repository.Actor.fs +++ b/src/Grace.Actors/Repository.Actor.fs @@ -158,7 +158,9 @@ module Repository = OrganizationId = organizationId ObjectStorageProvider = Constants.DefaultObjectStorageProvider StorageAccountName = Constants.DefaultObjectStorageAccount - StorageContainerName = StorageContainerName Constants.DefaultObjectStorageContainerName } + StorageContainerName = StorageContainerName Constants.DefaultObjectStorageContainerName + CreatedAt = repositoryEvent.Metadata.Timestamp + } | Initialized -> { currentRepositoryDto with InitializedAt = Some(getCurrentInstant ()) } | ObjectStorageProviderSet objectStorageProvider -> { currentRepositoryDto with ObjectStorageProvider = objectStorageProvider } | StorageAccountNameSet storageAccountName -> { currentRepositoryDto with StorageAccountName = storageAccountName } @@ -179,7 +181,7 @@ module Repository = | PhysicalDeleted -> currentRepositoryDto // Do nothing because it's about to be deleted anyway. | Undeleted -> { currentRepositoryDto with DeletedAt = None; DeleteReason = String.Empty } - { newRepositoryDto with UpdatedAt = Some(getCurrentInstant ()) } + { newRepositoryDto with UpdatedAt = Some repositoryEvent.Metadata.Timestamp } // This is essentially an object-oriented implementation of the Lazy pattern. I was having issues with Lazy, // and after a solid day wrestling with it, I dropped it and did this. Works a treat. diff --git a/src/Grace.Actors/Services.Actor.fs b/src/Grace.Actors/Services.Actor.fs index ca16b02..ab0ef31 100644 --- a/src/Grace.Actors/Services.Actor.fs +++ b/src/Grace.Actors/Services.Actor.fs @@ -1501,7 +1501,7 @@ module Services = let referenceActorProxy = actorProxyFactory.CreateActorProxy(referenceActorId, ActorName.Reference) - let! referenceDto = referenceActorProxy.Get correlationId + let! referenceDto = referenceActorProxy.Get (correlationId + " (getRootDirectoryByReferenceId)") return! getRootDirectoryBySha256Hash repositoryId referenceDto.Sha256Hash correlationId } diff --git a/src/Grace.Aspire.AppHost/Grace.Aspire.AppHost.csproj b/src/Grace.Aspire.AppHost/Grace.Aspire.AppHost.csproj index 15725c3..3f75dc4 100644 --- a/src/Grace.Aspire.AppHost/Grace.Aspire.AppHost.csproj +++ b/src/Grace.Aspire.AppHost/Grace.Aspire.AppHost.csproj @@ -9,13 +9,13 @@ - - + + - - - - + + + + diff --git a/src/Grace.Aspire.ServiceDefaults/Grace.Aspire.ServiceDefaults.csproj b/src/Grace.Aspire.ServiceDefaults/Grace.Aspire.ServiceDefaults.csproj index 16cceca..1ac0430 100644 --- a/src/Grace.Aspire.ServiceDefaults/Grace.Aspire.ServiceDefaults.csproj +++ b/src/Grace.Aspire.ServiceDefaults/Grace.Aspire.ServiceDefaults.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/Grace.CLI/Command/Branch.CLI.fs b/src/Grace.CLI/Command/Branch.CLI.fs index 909bfee..018d81e 100644 --- a/src/Grace.CLI/Command/Branch.CLI.fs +++ b/src/Grace.CLI/Command/Branch.CLI.fs @@ -1107,7 +1107,7 @@ module Branch = t1.Value <- 100.0 // If the current branch is based on the parent's latest promotion, then we can proceed with the promotion. - if branchDto.BasedOn = parentBranchDto.LatestPromotion.ReferenceId then + if branchDto.BasedOn.ReferenceId = parentBranchDto.LatestPromotion.ReferenceId then t2.StartTask() let promotionParameters = @@ -2897,359 +2897,338 @@ module Branch = | Ok returnValue -> let parentBranchDto = returnValue.ReturnValue - if branchDto.BasedOn = parentBranchDto.LatestPromotion.ReferenceId then + if branchDto.BasedOn.ReferenceId = parentBranchDto.LatestPromotion.ReferenceId then AnsiConsole.MarkupLine("The current branch is already based on the latest promotion in the parent branch.") AnsiConsole.MarkupLine("Run `grace status` to see more.") return 0 else // Now, get ReferenceDtos for current.BasedOn and parent.LatestPromotion so we have their DirectoryId's. - let getReferencesByReferenceIdParameters = - Parameters.Repository.GetReferencesByReferenceIdParameters( + let latestCommit = branchDto.LatestCommit + let parentLatestPromotion = parentBranchDto.LatestPromotion + let basedOn = branchDto.BasedOn + + // 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 = $"{branchDto.RepositoryId}", - CorrelationId = parameters.CorrelationId, - ReferenceIds = [| branchDto.BasedOn; branchDto.LatestCommit.ReferenceId; parentBranchDto.LatestPromotion.ReferenceId |] + BranchId = $"{branchDto.BranchId}", + MaxCount = 1, + CorrelationId = parameters.CorrelationId ) - - match! Repository.GetReferencesByReferenceId(getReferencesByReferenceIdParameters) with + //logToAnsiConsole Colors.Verbose $"getReferencesParameters: {getReferencesParameters |> serialize)}" + match! Branch.GetReferences(getReferencesParameters) with | Ok returnValue -> - let referenceDtos = returnValue.ReturnValue - - let latestCommit = referenceDtos.FirstOrDefault((fun ref -> ref.ReferenceId = branchDto.LatestCommit.ReferenceId), ReferenceDto.Default) + let latestReference = + if returnValue.ReturnValue.Count() > 0 then + returnValue.ReturnValue.First() + else + ReferenceDto.Default + //logToAnsiConsole Colors.Verbose $"latestReference: {serialize latestReference}" + // Now we have all of the references we need, so we have DirectoryId's to do diffs with. - let parentLatestPromotion = - referenceDtos.FirstOrDefault((fun ref -> ref.ReferenceId = parentBranchDto.LatestPromotion.ReferenceId), ReferenceDto.Default) + let! (diffs, errors) = + task { + if basedOn.DirectoryId <> DirectoryVersionId.Empty then + // First diff: parent promotion that current branch is based on vs. parent's latest promotion. + 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) - let basedOn = referenceDtos.FirstOrDefault((fun ref -> ref.ReferenceId = branchDto.BasedOn), ReferenceDto.Default) + // Second diff: latest reference on current branch vs. parent promotion that current branch is based on. + 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) - // 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 = $"{branchDto.RepositoryId}", - BranchId = $"{branchDto.BranchId}", - MaxCount = 1, - CorrelationId = parameters.CorrelationId - ) - //logToAnsiConsole Colors.Verbose $"getReferencesParameters: {getReferencesParameters |> serialize)}" - match! Branch.GetReferences(getReferencesParameters) with - | Ok returnValue -> - let latestReference = - if returnValue.ReturnValue.Count() > 0 then - returnValue.ReturnValue.First() + let returnValue = Result.partition [ firstDiff; secondDiff ] + return returnValue else - ReferenceDto.Default - //logToAnsiConsole Colors.Verbose $"latestReference: {serialize latestReference}" - // Now we have all of the references we need, so we have DirectoryId's to do diffs with. + // 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( + 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 ] + return returnValue + } + + // So, right now, if repo just created, and BasedOn is empty, we'll have a single diff. + // That fails a few lines below here. + // Have to decide what to do in this case. + + if errors.Count() = 0 then + // Yay! We have our two diffs. + let diff1 = diffs[0].ReturnValue + let diff2 = diffs[1].ReturnValue + + let filesToDownload = List() + + // Identify which files have been changed in the first diff, but not in the second diff. + // We can just download and copy these files into place in the working directory. + for fileDifference in diff1.Differences do + if + not + <| diff2.Differences.Any(fun d -> d.RelativePath = fileDifference.RelativePath) + then + // Copy different file version into place - similar to how we do it for switch + filesToDownload.Add(fileDifference) + + let getParentLatestPromotionDirectoryParameters = + Parameters.Directory.GetParameters( + RepositoryId = $"{branchDto.RepositoryId}", + DirectoryId = $"{parentLatestPromotion.DirectoryId}", + CorrelationId = parameters.CorrelationId + ) - let! (diffs, errors) = - task { - if basedOn.DirectoryId <> DirectoryVersionId.Empty then - // First diff: parent promotion that current branch is based on vs. parent's latest promotion. - 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) + let getLatestReferenceDirectoryParameters = + Parameters.Directory.GetParameters( + RepositoryId = $"{branchDto.RepositoryId}", + DirectoryId = $"{latestReference.DirectoryId}", + CorrelationId = parameters.CorrelationId + ) - // Second diff: latest reference on current branch vs. parent promotion that current branch is based on. - 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) - - let returnValue = Result.partition [ firstDiff; secondDiff ] - return returnValue - 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( - 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 ] - return returnValue - } + // Get the directory versions for the parent promotion that we're rebasing on, and the latest reference. + let! d1 = Directory.GetDirectoryVersionsRecursive(getParentLatestPromotionDirectoryParameters) - // So, right now, if repo just created, and BasedOn is empty, we'll have a single diff. - // That fails a few lines below here. - // Have to decide what to do in this case. + let! d2 = Directory.GetDirectoryVersionsRecursive(getLatestReferenceDirectoryParameters) - if errors.Count() = 0 then - // Yay! We have our two diffs. - let diff1 = diffs[0].ReturnValue - let diff2 = diffs[1].ReturnValue - - let filesToDownload = List() - - // Identify which files have been changed in the first diff, but not in the second diff. - // We can just download and copy these files into place in the working directory. - for fileDifference in diff1.Differences do - if - not - <| diff2.Differences.Any(fun d -> d.RelativePath = fileDifference.RelativePath) - then - // Copy different file version into place - similar to how we do it for switch - filesToDownload.Add(fileDifference) - - let getParentLatestPromotionDirectoryParameters = - Parameters.Directory.GetParameters( - RepositoryId = $"{branchDto.RepositoryId}", - DirectoryId = $"{parentLatestPromotion.DirectoryId}", - CorrelationId = parameters.CorrelationId - ) + let createFileVersionLookupDictionary (directoryVersions: IEnumerable) = + let lookup = Dictionary() - let getLatestReferenceDirectoryParameters = - Parameters.Directory.GetParameters( - RepositoryId = $"{branchDto.RepositoryId}", - DirectoryId = $"{latestReference.DirectoryId}", - CorrelationId = parameters.CorrelationId - ) + directoryVersions + |> Seq.map (fun dv -> dv.ToLocalDirectoryVersion(dv.CreatedAt.ToDateTimeUtc())) + |> Seq.map (fun dv -> dv.Files) + |> Seq.concat + |> Seq.iter (fun file -> lookup.Add(file.RelativePath, file)) - // Get the directory versions for the parent promotion that we're rebasing on, and the latest reference. - let! d1 = Directory.GetDirectoryVersionsRecursive(getParentLatestPromotionDirectoryParameters) - - let! d2 = Directory.GetDirectoryVersionsRecursive(getLatestReferenceDirectoryParameters) - - let createFileVersionLookupDictionary (directoryVersions: IEnumerable) = - let lookup = Dictionary() - - directoryVersions - |> Seq.map (fun dv -> dv.ToLocalDirectoryVersion(dv.CreatedAt.ToDateTimeUtc())) - |> Seq.map (fun dv -> dv.Files) - |> Seq.concat - |> Seq.iter (fun file -> lookup.Add(file.RelativePath, file)) - - lookup - - let (directories, errors) = Result.partition [ d1; d2 ] - - if errors.Count() = 0 then - let parentLatestPromotionDirectoryVersions = directories[0].ReturnValue - let latestReferenceDirectoryVersions = directories[1].ReturnValue - - let parentLatestPromotionLookup = createFileVersionLookupDictionary parentLatestPromotionDirectoryVersions - - let latestReferenceLookup = createFileVersionLookupDictionary latestReferenceDirectoryVersions - - // Get the specific FileVersions for those files from the contents of the parent's latest promotion. - let fileVersionsToDownload = - filesToDownload - |> Seq.where (fun fileToDownload -> parentLatestPromotionLookup.ContainsKey($"{fileToDownload.RelativePath}")) - |> Seq.map (fun fileToDownload -> parentLatestPromotionLookup[$"{fileToDownload.RelativePath}"]) - //logToAnsiConsole Colors.Verbose $"fileVersionsToDownload: {fileVersionsToDownload.Count()}" - //for f in fileVersionsToDownload do - // logToAnsiConsole Colors.Verbose $"relativePath: {f.RelativePath}" - - // Download those FileVersions from object storage, and copy them into the working directory. - match! downloadFilesFromObjectStorage fileVersionsToDownload parameters.CorrelationId with - | Ok _ -> - //logToAnsiConsole Colors.Verbose $"Succeeded in downloadFilesFromObjectStorage." - fileVersionsToDownload - |> Seq.iter (fun file -> - logToAnsiConsole Colors.Verbose $"Copying {file.RelativePath} from {file.FullObjectPath} to {file.FullName}." - // Delete the existing file in the working directory. - File.Delete(file.FullName) - // Copy the version from the object cache to the working directory. - File.Copy(file.FullObjectPath, file.FullName)) - - logToAnsiConsole Colors.Verbose $"Copied files into place." - | Error error -> AnsiConsole.WriteLine($"[{Colors.Error}]{Markup.Escape(error)}[/]") - - // If a file has changed in the second diff, but not in the first diff, cool, we can keep those changes, nothing to be done. - - // If a file has changed in both, we have to check the two diffs at the line-level to see if there are any conflicts. - let mutable potentialPromotionConflicts = false - - for diff1Difference in diff1.Differences do - let diff2DifferenceQuery = - diff2.Differences.Where(fun d -> - d.RelativePath = diff1Difference.RelativePath - && d.FileSystemEntryType = FileSystemEntryType.File - && d.DifferenceType = DifferenceType.Change) - - if diff2DifferenceQuery.Count() = 1 then - // We have a file that's changed in both diffs. - let diff2Difference = diff2DifferenceQuery.First() - - // Check the Sha256Hash values; if they're identical, ignore the file. - //let fileVersion1 = parentLatestPromotionLookup[$"{diff1Difference.RelativePath}"] - let fileVersion1 = - parentLatestPromotionLookup.FirstOrDefault(fun kvp -> kvp.Key = $"{diff1Difference.RelativePath}") - //let fileVersion2 = latestReferenceLookup[$"{diff2Difference.RelativePath}"] - let fileVersion2 = latestReferenceLookup.FirstOrDefault(fun kvp -> kvp.Key = $"{diff2Difference.RelativePath}") - //if (not <| isNull(fileVersion1) && not <| isNull(fileVersion2)) && (fileVersion1.Value.Sha256Hash <> fileVersion2.Value.Sha256Hash) then - if (fileVersion1.Value.Sha256Hash <> fileVersion2.Value.Sha256Hash) then - // Compare them at a line level; if there are no overlapping lines, we can just modify the working-directory version. - // ... - // For now, we're just going to show a message. - AnsiConsole.MarkupLine( - $"[{Colors.Important}]Potential promotion conflict: file {diff1Difference.RelativePath} has been changed in both the latest promotion, and in the current branch.[/]" - ) + lookup - AnsiConsole.MarkupLine( - $"[{Colors.Important}]fileVersion1.Sha256Hash: {fileVersion1.Value.Sha256Hash}; fileVersion1.LastWriteTimeUTC: {fileVersion1.Value.LastWriteTimeUtc}.[/]" - ) + let (directories, errors) = Result.partition [ d1; d2 ] - AnsiConsole.MarkupLine( - $"[{Colors.Important}]fileVersion2.Sha256Hash: {fileVersion2.Value.Sha256Hash}; fileVersion2.LastWriteTimeUTC: {fileVersion2.Value.LastWriteTimeUtc}.[/]" - ) + if errors.Count() = 0 then + let parentLatestPromotionDirectoryVersions = directories[0].ReturnValue + let latestReferenceDirectoryVersions = directories[1].ReturnValue + + let parentLatestPromotionLookup = createFileVersionLookupDictionary parentLatestPromotionDirectoryVersions + + let latestReferenceLookup = createFileVersionLookupDictionary latestReferenceDirectoryVersions + + // Get the specific FileVersions for those files from the contents of the parent's latest promotion. + let fileVersionsToDownload = + filesToDownload + |> Seq.where (fun fileToDownload -> parentLatestPromotionLookup.ContainsKey($"{fileToDownload.RelativePath}")) + |> Seq.map (fun fileToDownload -> parentLatestPromotionLookup[$"{fileToDownload.RelativePath}"]) + //logToAnsiConsole Colors.Verbose $"fileVersionsToDownload: {fileVersionsToDownload.Count()}" + //for f in fileVersionsToDownload do + // logToAnsiConsole Colors.Verbose $"relativePath: {f.RelativePath}" + + // Download those FileVersions from object storage, and copy them into the working directory. + match! downloadFilesFromObjectStorage fileVersionsToDownload parameters.CorrelationId with + | Ok _ -> + //logToAnsiConsole Colors.Verbose $"Succeeded in downloadFilesFromObjectStorage." + fileVersionsToDownload + |> Seq.iter (fun file -> + logToAnsiConsole Colors.Verbose $"Copying {file.RelativePath} from {file.FullObjectPath} to {file.FullName}." + // Delete the existing file in the working directory. + File.Delete(file.FullName) + // Copy the version from the object cache to the working directory. + File.Copy(file.FullObjectPath, file.FullName)) + + logToAnsiConsole Colors.Verbose $"Copied files into place." + | Error error -> AnsiConsole.WriteLine($"[{Colors.Error}]{Markup.Escape(error)}[/]") + + // If a file has changed in the second diff, but not in the first diff, cool, we can keep those changes, nothing to be done. + + // If a file has changed in both, we have to check the two diffs at the line-level to see if there are any conflicts. + let mutable potentialPromotionConflicts = false + + for diff1Difference in diff1.Differences do + let diff2DifferenceQuery = + diff2.Differences.Where(fun d -> + d.RelativePath = diff1Difference.RelativePath + && d.FileSystemEntryType = FileSystemEntryType.File + && d.DifferenceType = DifferenceType.Change) + + if diff2DifferenceQuery.Count() = 1 then + // We have a file that's changed in both diffs. + let diff2Difference = diff2DifferenceQuery.First() + + // Check the Sha256Hash values; if they're identical, ignore the file. + //let fileVersion1 = parentLatestPromotionLookup[$"{diff1Difference.RelativePath}"] + let fileVersion1 = + parentLatestPromotionLookup.FirstOrDefault(fun kvp -> kvp.Key = $"{diff1Difference.RelativePath}") + //let fileVersion2 = latestReferenceLookup[$"{diff2Difference.RelativePath}"] + let fileVersion2 = latestReferenceLookup.FirstOrDefault(fun kvp -> kvp.Key = $"{diff2Difference.RelativePath}") + //if (not <| isNull(fileVersion1) && not <| isNull(fileVersion2)) && (fileVersion1.Value.Sha256Hash <> fileVersion2.Value.Sha256Hash) then + if (fileVersion1.Value.Sha256Hash <> fileVersion2.Value.Sha256Hash) then + // Compare them at a line level; if there are no overlapping lines, we can just modify the working-directory version. + // ... + // For now, we're just going to show a message. + AnsiConsole.MarkupLine( + $"[{Colors.Important}]Potential promotion conflict: file {diff1Difference.RelativePath} has been changed in both the latest promotion, and in the current branch.[/]" + ) - potentialPromotionConflicts <- true + AnsiConsole.MarkupLine( + $"[{Colors.Important}]fileVersion1.Sha256Hash: {fileVersion1.Value.Sha256Hash}; fileVersion1.LastWriteTimeUTC: {fileVersion1.Value.LastWriteTimeUtc}.[/]" + ) - /// 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 + AnsiConsole.MarkupLine( + $"[{Colors.Important}]fileVersion2.Sha256Hash: {fileVersion2.Value.Sha256Hash}; fileVersion2.LastWriteTimeUTC: {fileVersion2.Value.LastWriteTimeUtc}.[/]" + ) - return Ok(updatedGraceStatus, newDirectoryVersions) - else - return Ok(graceStatus, List()) - } + potentialPromotionConflicts <- true - /// Upload new DirectoryVersion records to the server. - let uploadNewDirectoryVersions (currentBranch: BranchDto) (newDirectoryVersions: List) = - task { - if currentBranch.SaveEnabled && newDirectoryVersions.Any() then - let saveParameters = SaveDirectoryVersionsParameters() + /// 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 - saveParameters.DirectoryVersions <- newDirectoryVersions.Select(fun dv -> dv.ToDirectoryVersion).ToList() + return Ok(updatedGraceStatus, newDirectoryVersions) + else + return Ok(graceStatus, List()) + } - 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 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.DirectoryVersionId <> localDirectoryVersion.DirectoryVersionId) - |> Seq.iter (fun dv -> - let mutable localDirectoryVersion = LocalDirectoryVersion.Default - - updatedGraceStatus.Index.Remove(dv.DirectoryVersionId, &localDirectoryVersion) - |> ignore)) - - let! result = uploadNewDirectoryVersions branchDto newDirectoryVersions - do! writeGraceStatusFile updatedGraceStatus - do! updateGraceWatchInterprocessFile updatedGraceStatus - newGraceStatus <- updatedGraceStatus + /// 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 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.DirectoryVersionId <> localDirectoryVersion.DirectoryVersionId) + |> Seq.iter (fun dv -> + let mutable localDirectoryVersion = LocalDirectoryVersion.Default + + updatedGraceStatus.Index.Remove(dv.DirectoryVersionId, &localDirectoryVersion) + |> ignore)) + + let! result = uploadNewDirectoryVersions branchDto newDirectoryVersions + do! writeGraceStatusFile updatedGraceStatus + do! updateGraceWatchInterprocessFile updatedGraceStatus + newGraceStatus <- updatedGraceStatus + + | Error error -> logToAnsiConsole Colors.Error (Markup.Escape($"{error}")) - | 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 - // 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, + Sha256Hash = rootDirectoryVersion.Sha256Hash, + DirectoryVersionId = rootDirectoryVersion.DirectoryVersionId, + Message = + $"Save after rebase from {parentBranchDto.BranchName}; {getShortSha256Hash parentLatestPromotion.Sha256Hash} - {parentLatestPromotion.ReferenceText}." + ) - let saveReferenceParameters = - Parameters.Branch.CreateReferenceParameters( + match! Branch.Save(saveReferenceParameters) with + | Ok returnValue -> + // 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, - Sha256Hash = rootDirectoryVersion.Sha256Hash, - DirectoryVersionId = rootDirectoryVersion.DirectoryVersionId, - Message = - $"Save after rebase from {parentBranchDto.BranchName}; {getShortSha256Hash parentLatestPromotion.Sha256Hash} - {parentLatestPromotion.ReferenceText}." + BasedOn = parentLatestPromotion.ReferenceId ) - match! Branch.Save(saveReferenceParameters) with + match! Branch.Rebase(rebaseParameters) with | Ok returnValue -> - // 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 - ) + AnsiConsole.MarkupLine($"[{Colors.Important}]Rebase succeeded.[/]") - match! Branch.Rebase(rebaseParameters) with - | Ok returnValue -> - AnsiConsole.MarkupLine($"[{Colors.Important}]Rebase succeeded.[/]") + AnsiConsole.MarkupLine($"[{Colors.Verbose}]({serialize returnValue})[/]") - AnsiConsole.MarkupLine($"[{Colors.Verbose}]({serialize returnValue})[/]") - - return 0 - | Error error -> - logToAnsiConsole Colors.Error (Markup.Escape($"{error}")) - return -1 + return 0 | Error error -> logToAnsiConsole Colors.Error (Markup.Escape($"{error}")) return -1 - else - AnsiConsole.MarkupLine( - $"[{Colors.Highlighted}]A potential promotion conflict was detected. Rebase not successful.[/]" - ) - + | Error error -> + logToAnsiConsole Colors.Error (Markup.Escape($"{error}")) return -1 else - logToAnsiConsole Colors.Error (Markup.Escape($"{errors.First()}")) + AnsiConsole.MarkupLine( + $"[{Colors.Highlighted}]A potential promotion conflict was detected. Rebase not successful.[/]" + ) + return -1 else logToAnsiConsole Colors.Error (Markup.Escape($"{errors.First()}")) return -1 - | Error error -> - logToAnsiConsole Colors.Error (Markup.Escape($"{error}")) + else + logToAnsiConsole Colors.Error (Markup.Escape($"{errors.First()}")) return -1 | Error error -> logToAnsiConsole Colors.Error (Markup.Escape($"{error}")) @@ -3317,147 +3296,116 @@ module Branch = let parentBranchDto = returnValue.ReturnValue // Now that I have the current and parent branch, I can get the details for the latest promotion, latest commit, latest checkpoint, and latest save. - let referenceIds = - [| parentBranchDto.LatestPromotion.ReferenceId - branchDto.LatestCommit.ReferenceId - branchDto.LatestCheckpoint.ReferenceId - branchDto.LatestSave.ReferenceId - branchDto.BasedOn |] - - let getReferencesByIdParameters = - Parameters.Repository.GetReferencesByReferenceIdParameters( + let latestSave = branchDto.LatestSave + let latestCheckpoint = branchDto.LatestCheckpoint + let latestCommit = branchDto.LatestCommit + let latestParentBranchPromotion = parentBranchDto.LatestPromotion + let basedOn = branchDto.BasedOn + + let getReferencesParameters = + Parameters.Branch.GetReferencesParameters( OwnerId = parameters.OwnerId, OwnerName = parameters.OwnerName, OrganizationId = parameters.OrganizationId, OrganizationName = parameters.OrganizationName, RepositoryId = parameters.RepositoryId, RepositoryName = parameters.RepositoryName, - ReferenceIds = referenceIds, + BranchId = $"{branchDto.BranchId}", + MaxCount = 1, CorrelationId = parameters.CorrelationId ) - match! Repository.GetReferencesByReferenceId(getReferencesByIdParameters) with + match! Branch.GetReferences(getReferencesParameters) with | Ok returnValue -> - let referenceDtos = returnValue.ReturnValue - - let latestSave = referenceDtos.FirstOrDefault((fun ref -> ref.ReferenceId = branchDto.LatestSave.ReferenceId ), ReferenceDto.Default) - - let latestCheckpoint = referenceDtos.FirstOrDefault((fun ref -> ref.ReferenceId = branchDto.LatestCheckpoint.ReferenceId), ReferenceDto.Default) - - let latestCommit = referenceDtos.FirstOrDefault((fun ref -> ref.ReferenceId = branchDto.LatestCommit.ReferenceId), ReferenceDto.Default) - - let latestParentBranchPromotion = - referenceDtos.FirstOrDefault((fun ref -> ref.ReferenceId = parentBranchDto.LatestPromotion.ReferenceId), ReferenceDto.Default) + let latestReference = + if returnValue.ReturnValue.Count() > 0 then + returnValue.ReturnValue.First() + else + ReferenceDto.Default - let basedOn = referenceDtos.FirstOrDefault((fun ref -> ref.ReferenceId = branchDto.BasedOn), ReferenceDto.Default) + let getReferenceRowValue referenceDto = + if referenceDto.ReferenceId = ReferenceId.Empty then + $" None" + else if parseResult |> verbose then + $" {getShortSha256Hash referenceDto.Sha256Hash} - {ago referenceDto.CreatedAt} - {instantToLocalTime referenceDto.CreatedAt} [{Colors.Deemphasized}]- {referenceDto.ReferenceId} - {referenceDto.DirectoryId}[/]" + else + $" {getShortSha256Hash referenceDto.Sha256Hash} - {ago referenceDto.CreatedAt} - {instantToLocalTime referenceDto.CreatedAt} [{Colors.Deemphasized}]- {referenceDto.ReferenceId}[/]" - let getReferencesParameters = - Parameters.Branch.GetReferencesParameters( - OwnerId = parameters.OwnerId, - OwnerName = parameters.OwnerName, - OrganizationId = parameters.OrganizationId, - OrganizationName = parameters.OrganizationName, - RepositoryId = parameters.RepositoryId, - RepositoryName = parameters.RepositoryName, - BranchId = $"{branchDto.BranchId}", - MaxCount = 1, - CorrelationId = parameters.CorrelationId - ) + let permissions (branchDto: Dto.Branch.BranchDto) = + let sb = StringBuilder() - match! Branch.GetReferences(getReferencesParameters) with - | Ok returnValue -> - let latestReference = - if returnValue.ReturnValue.Count() > 0 then - returnValue.ReturnValue.First() - else - ReferenceDto.Default + if branchDto.PromotionEnabled then sb.Append("Promotion/") |> ignore - let getReferenceRowValue referenceDto = - if referenceDto.ReferenceId = ReferenceId.Empty then - $" None" - else if parseResult |> verbose then - $" {getShortSha256Hash referenceDto.Sha256Hash} - {ago referenceDto.CreatedAt} - {instantToLocalTime referenceDto.CreatedAt} [{Colors.Deemphasized}]- {referenceDto.ReferenceId} - {referenceDto.DirectoryId}[/]" - else - $" {getShortSha256Hash referenceDto.Sha256Hash} - {ago referenceDto.CreatedAt} - {instantToLocalTime referenceDto.CreatedAt} [{Colors.Deemphasized}]- {referenceDto.ReferenceId}[/]" + if branchDto.CommitEnabled then sb.Append("Commit/") |> ignore - let permissions (branchDto: Dto.Branch.BranchDto) = - let sb = StringBuilder() + if branchDto.CheckpointEnabled then sb.Append("Checkpoint/") |> ignore - if branchDto.PromotionEnabled then sb.Append("Promotion/") |> ignore + if branchDto.SaveEnabled then sb.Append("Save/") |> ignore - if branchDto.CommitEnabled then sb.Append("Commit/") |> ignore + if branchDto.TagEnabled then sb.Append("Tag") |> ignore - if branchDto.CheckpointEnabled then sb.Append("Checkpoint/") |> ignore + if sb[sb.Length - 1] = '/' then sb.Remove(sb.Length - 1, 1) |> ignore - if branchDto.SaveEnabled then sb.Append("Save/") |> ignore + sb.ToString() - if branchDto.TagEnabled then sb.Append("Tag") |> ignore + let table = Table(Border = TableBorder.DoubleEdge) + table.ShowHeaders <- false - if sb[sb.Length - 1] = '/' then sb.Remove(sb.Length - 1, 1) |> ignore + table + .AddColumns(String.replicate (Current().OwnerName.Length) "_", String.replicate (Current().OwnerName.Length) "_") // Using Current().OwnerName.Length is aesthetically pleasing, there's no deeper reason for it. + .AddRow( + $"[{Colors.Important}]Owner[/]", + $"[{Colors.Important}]{Current().OwnerName}[/] [{Colors.Deemphasized}]- {Current().OwnerId}[/]" + ) + .AddRow( + $"[{Colors.Important}]Organization[/]", + $"[{Colors.Important}]{Current().OrganizationName}[/] [{Colors.Deemphasized}]- {Current().OrganizationId}[/]" + ) + .AddRow( + $"[{Colors.Important}]Repository[/]", + $"[{Colors.Important}]{Current().RepositoryName}[/] [{Colors.Deemphasized}]- {Current().RepositoryId}[/]" + ) + .AddRow(String.Empty, String.Empty) + .AddRow( + $"[{Colors.Important}]Branch[/]", + $"[{Colors.Important}]{branchDto.BranchName}[/] - Allows {permissions branchDto} [{Colors.Deemphasized}]- {branchDto.BranchId}[/]" + ) + //.AddRow(String.Empty, $"Permissions: {permissions}.") + .AddRow($" - latest save ", getReferenceRowValue latestSave) + .AddRow($" - latest checkpoint ", getReferenceRowValue latestCheckpoint) + .AddRow($" - latest commit", getReferenceRowValue latestCommit) + .AddRow($" - based on", getReferenceRowValue basedOn) + |> ignore - sb.ToString() + if + branchDto.BasedOn.ReferenceId = parentBranchDto.LatestPromotion.ReferenceId + || branchDto.ParentBranchId = Constants.DefaultParentBranchId + then + table.AddRow($"", $"[{Colors.Added}] Based on latest promotion.[/]") |> ignore + else + table.AddRow($"", $"[{Colors.Important}] Not based on latest promotion.[/]") + |> ignore - let table = Table(Border = TableBorder.DoubleEdge) - table.ShowHeaders <- false + table.AddRow(String.Empty, String.Empty) |> ignore + if branchDto.ParentBranchId <> Constants.DefaultParentBranchId then table - .AddColumns(String.replicate (Current().OwnerName.Length) "_", String.replicate (Current().OwnerName.Length) "_") // Using Current().OwnerName.Length is aesthetically pleasing, there's no deeper reason for it. - .AddRow( - $"[{Colors.Important}]Owner[/]", - $"[{Colors.Important}]{Current().OwnerName}[/] [{Colors.Deemphasized}]- {Current().OwnerId}[/]" - ) - .AddRow( - $"[{Colors.Important}]Organization[/]", - $"[{Colors.Important}]{Current().OrganizationName}[/] [{Colors.Deemphasized}]- {Current().OrganizationId}[/]" - ) .AddRow( - $"[{Colors.Important}]Repository[/]", - $"[{Colors.Important}]{Current().RepositoryName}[/] [{Colors.Deemphasized}]- {Current().RepositoryId}[/]" + $"[{Colors.Important}]Parent branch[/]", + $"[{Colors.Important}]{parentBranchDto.BranchName}[/] - Allows {permissions parentBranchDto} [{Colors.Deemphasized}]- {parentBranchDto.BranchId}[/]" ) - .AddRow(String.Empty, String.Empty) - .AddRow( - $"[{Colors.Important}]Branch[/]", - $"[{Colors.Important}]{branchDto.BranchName}[/] - Allows {permissions branchDto} [{Colors.Deemphasized}]- {branchDto.BranchId}[/]" - ) - //.AddRow(String.Empty, $"Permissions: {permissions}.") - .AddRow($" - latest save ", getReferenceRowValue latestSave) - .AddRow($" - latest checkpoint ", getReferenceRowValue latestCheckpoint) - .AddRow($" - latest commit", getReferenceRowValue latestCommit) - .AddRow($" - based on", getReferenceRowValue basedOn) + .AddRow($" - latest promotion", getReferenceRowValue latestParentBranchPromotion) + .AddRow($"", $" {latestParentBranchPromotion.ReferenceText}") + |> ignore + else + table.AddRow($"[{Colors.Important}]Parent branch[/]", $"[{Colors.Important}] None[/]") |> ignore - if - branchDto.BasedOn = parentBranchDto.LatestPromotion.ReferenceId - || branchDto.ParentBranchId = Constants.DefaultParentBranchId - then - table.AddRow($"", $"[{Colors.Added}] Based on latest promotion.[/]") |> ignore - else - table.AddRow($"", $"[{Colors.Important}] Not based on latest promotion.[/]") - |> ignore - - table.AddRow(String.Empty, String.Empty) |> ignore - - if branchDto.ParentBranchId <> Constants.DefaultParentBranchId then - table - .AddRow( - $"[{Colors.Important}]Parent branch[/]", - $"[{Colors.Important}]{parentBranchDto.BranchName}[/] - Allows {permissions parentBranchDto} [{Colors.Deemphasized}]- {parentBranchDto.BranchId}[/]" - ) - .AddRow($" - latest promotion", getReferenceRowValue latestParentBranchPromotion) - .AddRow($"", $" {latestParentBranchPromotion.ReferenceText}") - |> ignore - else - table.AddRow($"[{Colors.Important}]Parent branch[/]", $"[{Colors.Important}] None[/]") - |> ignore - - // Need to add this portion of the header after everything else is rendered so we know the width. - //let headerWidth = if table.Columns[0].Width.HasValue then table.Columns[0].Width.Value else 27 // 27 = longest current text - //table.Columns[0].Header <- Markup(String.replicate headerWidth "_") - AnsiConsole.Write(table) - return 0 - | Error error -> - logToAnsiConsole Colors.Error (Markup.Escape($"{error}")) - return -1 + // Need to add this portion of the header after everything else is rendered so we know the width. + //let headerWidth = if table.Columns[0].Width.HasValue then table.Columns[0].Width.Value else 27 // 27 = longest current text + //table.Columns[0].Header <- Markup(String.replicate headerWidth "_") + AnsiConsole.Write(table) + return 0 | Error error -> logToAnsiConsole Colors.Error (Markup.Escape($"{error}")) return -1 diff --git a/src/Grace.CLI/Command/Diff.CLI.fs b/src/Grace.CLI/Command/Diff.CLI.fs index 532695d..9b5f101 100644 --- a/src/Grace.CLI/Command/Diff.CLI.fs +++ b/src/Grace.CLI/Command/Diff.CLI.fs @@ -467,40 +467,7 @@ module Diff = match! Branch.Get(branchParameters) with | Ok returnValue -> let branchDto = returnValue.ReturnValue - - let getReferencesByIdParameters = - Parameters.Repository.GetReferencesByReferenceIdParameters( - OwnerId = parameters.OwnerId, - OwnerName = parameters.OwnerName, - OrganizationId = parameters.OrganizationId, - OrganizationName = parameters.OrganizationName, - RepositoryId = parameters.RepositoryId, - RepositoryName = parameters.RepositoryName, - ReferenceIds = [| branchDto.BasedOn |], - MaxCount = 1, - CorrelationId = parameters.CorrelationId - ) - - match! Repository.GetReferencesByReferenceId(getReferencesByIdParameters) with - | Ok returnValue -> - if returnValue.ReturnValue.Count() > 0 then - // We're only taking the first one because we've only asked for one in the parameters. - let referenceDto = returnValue.ReturnValue.First() - promotions.Add(referenceDto) - //logToAnsiConsole Colors.Verbose $"In diffToReference / Promotion: Got promotion reference from branchDto.BasedOn. ReferenceId: {referenceDto.ReferenceId}; Sha256Hash: {referenceDto.Sha256Hash}." - else - logToAnsiConsole - Colors.Verbose - $"In diffToReference / Promotion: Did not find the basedOn reference." - - () - | Error error -> - logToAnsiConsole Colors.Error "Error in GetReferencesByReferenceId." - - logToAnsiConsole Colors.Error (Markup.Escape($"{error}")) - - if parseResult |> json || parseResult |> verbose then - logToAnsiConsole Colors.Verbose (serialize error) + promotions.Add(branchDto.BasedOn) | Error error -> logToAnsiConsole Colors.Error (Markup.Escape($"Error in Branch.Get: {error}")) diff --git a/src/Grace.CLI/Command/Reference.CLI.fs b/src/Grace.CLI/Command/Reference.CLI.fs index dadfac2..f3a5efd 100644 --- a/src/Grace.CLI/Command/Reference.CLI.fs +++ b/src/Grace.CLI/Command/Reference.CLI.fs @@ -940,7 +940,7 @@ module Reference = t1.Value <- 100.0 // If the current branch is based on the parent's latest promotion, then we can proceed with the promotion. - if branchDto.BasedOn = parentBranchDto.LatestPromotion.ReferenceId then + if branchDto.BasedOn.ReferenceId = parentBranchDto.LatestPromotion.ReferenceId then t2.StartTask() let promotionParameters = diff --git a/src/Grace.CLI/Command/Repository.CLI.fs b/src/Grace.CLI/Command/Repository.CLI.fs index 57c6042..47afaa9 100644 --- a/src/Grace.CLI/Command/Repository.CLI.fs +++ b/src/Grace.CLI/Command/Repository.CLI.fs @@ -927,7 +927,7 @@ module Repository = branchName = branch.BranchName updatedAt = branch.UpdatedAt parentBranchName = parent.branchName - basedOnLatestPromotion = (branch.BasedOn = parent.latestPromotion.ReferenceId) |}) + basedOnLatestPromotion = (branch.BasedOn.ReferenceId = parent.latestPromotion.ReferenceId) |}) ) .OrderBy(fun branch -> branch.parentBranchName, branch.branchName) diff --git a/src/Grace.SDK/Grace.SDK.fsproj b/src/Grace.SDK/Grace.SDK.fsproj index cf4be1f..7d9fad6 100644 --- a/src/Grace.SDK/Grace.SDK.fsproj +++ b/src/Grace.SDK/Grace.SDK.fsproj @@ -27,7 +27,7 @@ - + diff --git a/src/Grace.Server.Tests/Grace.Server.Tests.fsproj b/src/Grace.Server.Tests/Grace.Server.Tests.fsproj index 1b2fdbe..a29ae86 100644 --- a/src/Grace.Server.Tests/Grace.Server.Tests.fsproj +++ b/src/Grace.Server.Tests/Grace.Server.Tests.fsproj @@ -24,7 +24,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Grace.Server/Branch.Server.fs b/src/Grace.Server/Branch.Server.fs index 40373be..93e34c7 100644 --- a/src/Grace.Server/Branch.Server.fs +++ b/src/Grace.Server/Branch.Server.fs @@ -237,7 +237,7 @@ module Branch = (Guid.Parse(parameters.BranchId)), (BranchName parameters.BranchName), (Guid.Parse(parentBranchId)), - parentBranch.BasedOn, + parentBranch.BasedOn.ReferenceId, Guid.Parse(parameters.RepositoryId), parameters.InitialPermissions ) diff --git a/src/Grace.Server/Grace.Server.fsproj b/src/Grace.Server/Grace.Server.fsproj index fc6c387..1768022 100644 --- a/src/Grace.Server/Grace.Server.fsproj +++ b/src/Grace.Server/Grace.Server.fsproj @@ -65,8 +65,8 @@ - - + + @@ -74,7 +74,7 @@ - +