Merge pull request #4977 from unisonweb/24-05-20-upgrade-commit

This commit is contained in:
Arya Irani 2024-05-31 14:19:20 -04:00 committed by GitHub
commit 32900b13ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 519 additions and 338 deletions

View File

@ -25,6 +25,7 @@ module Unison.Cli.ProjectUtils
getProjectAndBranchByTheseNames,
expectProjectAndBranchByTheseNames,
expectLooseCodeOrProjectBranch,
getProjectBranchCausalHash,
-- * Loading remote project info
expectRemoteProjectById,
@ -36,9 +37,17 @@ module Unison.Cli.ProjectUtils
expectRemoteProjectBranchByNames,
expectRemoteProjectBranchByTheseNames,
-- * Projecting out common things
justTheIds,
justTheIds',
justTheNames,
-- * Other helpers
findTemporaryBranchName,
expectLatestReleaseBranchName,
-- * Upgrade branch utils
getUpgradeBranchParent,
)
where
@ -46,7 +55,10 @@ import Control.Lens
import Data.List qualified as List
import Data.Maybe (fromJust)
import Data.Set qualified as Set
import Data.Text qualified as Text
import Data.These (These (..))
import U.Codebase.Causal qualified
import U.Codebase.HashTags (CausalHash)
import U.Codebase.Sqlite.DbId
import U.Codebase.Sqlite.Project qualified as Sqlite
import U.Codebase.Sqlite.ProjectBranch qualified as Sqlite
@ -56,6 +68,7 @@ import Unison.Cli.Monad qualified as Cli
import Unison.Cli.MonadUtils qualified as Cli
import Unison.Cli.Share.Projects (IncludeSquashedHead)
import Unison.Cli.Share.Projects qualified as Share
import Unison.Codebase qualified as Codebase
import Unison.Codebase.Editor.Input (LooseCodeOrProject)
import Unison.Codebase.Editor.Output (Output (LocalProjectBranchDoesntExist))
import Unison.Codebase.Editor.Output qualified as Output
@ -104,6 +117,18 @@ resolveBranchRelativePath = \case
Left branchName -> That branchName
Right (projectName, branchName) -> These projectName branchName
justTheIds :: ProjectAndBranch Sqlite.Project Sqlite.ProjectBranch -> ProjectAndBranch ProjectId ProjectBranchId
justTheIds x =
ProjectAndBranch x.project.projectId x.branch.branchId
justTheIds' :: Sqlite.ProjectBranch -> ProjectAndBranch ProjectId ProjectBranchId
justTheIds' x =
ProjectAndBranch x.projectId x.branchId
justTheNames :: ProjectAndBranch Sqlite.Project Sqlite.ProjectBranch -> ProjectAndBranch ProjectName ProjectBranchName
justTheNames x =
ProjectAndBranch x.project.name x.branch.name
-- @findTemporaryBranchName projectId preferred@ finds some unused branch name in @projectId@ with a name
-- like @preferred@.
findTemporaryBranchName :: ProjectId -> ProjectBranchName -> Transaction ProjectBranchName
@ -264,6 +289,13 @@ expectLooseCodeOrProjectBranch =
That (ProjectAndBranch (Just project) branch) -> Right (These project branch)
These path _ -> Left path -- (3) above
-- | Get the causal hash of a project branch.
getProjectBranchCausalHash :: ProjectAndBranch ProjectId ProjectBranchId -> Transaction CausalHash
getProjectBranchCausalHash branch = do
let path = projectBranchPath branch
causal <- Codebase.getShallowCausalFromRoot Nothing (Path.unabsolute path)
pure causal.causalHash
------------------------------------------------------------------------------------------------------------------------
-- Remote project utils
@ -374,3 +406,14 @@ expectLatestReleaseBranchName remoteProject =
case remoteProject.latestRelease of
Nothing -> Cli.returnEarly (Output.ProjectHasNoReleases remoteProject.projectName)
Just semver -> pure (UnsafeProjectBranchName ("releases/" <> into @Text semver))
-- | @getUpgradeBranchParent branch@ returns the parent branch of an "upgrade" branch.
--
-- When an upgrade fails, we put you on a branch called `upgrade-<old>-to-<new>`. That's an "upgrade" branch. It's not
-- currently distinguished in the database, so we first just switch on whether its name begins with "upgrade-". If it
-- does, then we get the branch's parent, which should exist, but perhaps wouldn't if the user had manually made a
-- parentless branch called "upgrade-whatever" for whatever reason.
getUpgradeBranchParent :: Sqlite.ProjectBranch -> Maybe ProjectBranchId
getUpgradeBranchParent branch = do
guard ("upgrade-" `Text.isPrefixOf` into @Text branch.name)
branch.parentBranchId

View File

@ -57,6 +57,7 @@ import Unison.Codebase.Editor.HandleInput.AuthLogin (authLogin)
import Unison.Codebase.Editor.HandleInput.Branch (handleBranch)
import Unison.Codebase.Editor.HandleInput.BranchRename (handleBranchRename)
import Unison.Codebase.Editor.HandleInput.Branches (handleBranches)
import Unison.Codebase.Editor.HandleInput.CommitUpgrade (handleCommitUpgrade)
import Unison.Codebase.Editor.HandleInput.DebugDefinition qualified as DebugDefinition
import Unison.Codebase.Editor.HandleInput.DebugFoldRanges qualified as DebugFoldRanges
import Unison.Codebase.Editor.HandleInput.DeleteBranch (handleDeleteBranch)
@ -963,6 +964,7 @@ loop e = do
CloneI remoteNames localNames -> handleClone remoteNames localNames
ReleaseDraftI semver -> handleReleaseDraft semver
UpgradeI old new -> handleUpgrade old new
UpgradeCommitI -> handleCommitUpgrade
LibInstallI libdep -> handleInstallLib libdep
inputDescription :: Input -> Cli Text
@ -1138,6 +1140,7 @@ inputDescription input =
UiI {} -> wat
UpI {} -> wat
UpgradeI {} -> wat
UpgradeCommitI {} -> wat
VersionI -> wat
where
hp' :: Either SCH.ShortCausalHash Path' -> Cli Text

View File

@ -120,11 +120,10 @@ doCreateBranch createFrom project newBranchName description = do
Cli.getBranchAt (ProjectUtils.projectBranchPath (ProjectAndBranch sourceProjectId sourceBranchId))
CreateFrom'LooseCode sourcePath -> Cli.getBranchAt sourcePath
CreateFrom'Nothingness -> pure Branch.empty
let projectId = project ^. #projectId
let parentBranchId =
case createFrom of
CreateFrom'Branch (ProjectAndBranch _ sourceBranch)
| (sourceBranch ^. #projectId) == projectId -> Just (sourceBranch ^. #branchId)
| sourceBranch.projectId == project.projectId -> Just sourceBranch.branchId
_ -> Nothing
doCreateBranch' sourceNamespaceObject parentBranchId project (pure newBranchName) description

View File

@ -0,0 +1,50 @@
-- | @upgrade.commit@ handler.
module Unison.Codebase.Editor.HandleInput.CommitUpgrade
( handleCommitUpgrade,
)
where
import U.Codebase.Sqlite.Project qualified
import U.Codebase.Sqlite.Queries qualified as Queries
import Unison.Cli.Monad (Cli)
import Unison.Cli.Monad qualified as Cli
import Unison.Cli.ProjectUtils qualified as ProjectUtils
import Unison.Codebase.Editor.HandleInput.DeleteBranch qualified as DeleteBranch
import Unison.Codebase.Editor.HandleInput.Merge2 qualified as Merge
import Unison.Codebase.Editor.HandleInput.ProjectSwitch qualified as ProjectSwitch
import Unison.Codebase.Editor.Output qualified as Output
import Unison.Merge.TwoWay (TwoWay (..))
import Unison.Prelude
import Unison.Project (ProjectAndBranch (..))
handleCommitUpgrade :: Cli ()
handleCommitUpgrade = do
(upgradeProjectAndBranch, _path) <- ProjectUtils.expectCurrentProjectBranch
-- Assert that this is an "upgrade" branch and get its parent, which is the branch we were on when we ran `upgrade`.
parentBranchId <-
ProjectUtils.getUpgradeBranchParent upgradeProjectAndBranch.branch
& onNothing (Cli.returnEarly Output.NoUpgradeInProgress)
parentBranch <-
Cli.runTransaction do
Queries.expectProjectBranch upgradeProjectAndBranch.project.projectId parentBranchId
let parentProjectAndBranch =
ProjectAndBranch upgradeProjectAndBranch.project parentBranch
-- Switch to the parent
ProjectSwitch.switchToProjectBranch (ProjectUtils.justTheIds parentProjectAndBranch)
-- Merge the upgrade branch into the parent
Merge.doMergeLocalBranch
TwoWay
{ alice = parentProjectAndBranch,
bob = upgradeProjectAndBranch
}
-- Delete the upgrade branch
DeleteBranch.doDeleteProjectBranch upgradeProjectAndBranch

View File

@ -1,19 +1,21 @@
-- | @delete.branch@ input handler
module Unison.Codebase.Editor.HandleInput.DeleteBranch
( handleDeleteBranch,
doDeleteProjectBranch,
)
where
import Control.Lens (over, (^.))
import Control.Lens (over)
import Data.Map.Strict qualified as Map
import Data.These (These (..))
import U.Codebase.Sqlite.Project qualified as Sqlite
import U.Codebase.Sqlite.ProjectBranch qualified as Sqlite
import U.Codebase.Sqlite.Queries qualified as Queries
import Unison.Cli.Monad (Cli)
import Unison.Cli.Monad qualified as Cli
import Unison.Cli.MonadUtils qualified as Cli
import Unison.Cli.ProjectUtils qualified as ProjectUtils
import Unison.Codebase.Branch qualified as Branch
import Unison.Codebase.Editor.Output qualified as Output
import Unison.Codebase.Path qualified as Path
import Unison.Prelude
import Unison.Project (ProjectAndBranch (..), ProjectBranchName, ProjectName)
@ -25,47 +27,45 @@ import Witch (unsafeFrom)
-- Its children branches, if any, are reparented to their grandparent, if any. You may delete the only branch in a
-- project.
handleDeleteBranch :: ProjectAndBranch (Maybe ProjectName) ProjectBranchName -> Cli ()
handleDeleteBranch projectAndBranchNames0 = do
projectAndBranchNames <-
ProjectUtils.hydrateNames
case projectAndBranchNames0 of
handleDeleteBranch projectAndBranchNamesToDelete = do
projectAndBranchToDelete <-
ProjectUtils.expectProjectAndBranchByTheseNames
case projectAndBranchNamesToDelete of
ProjectAndBranch Nothing branch -> That branch
ProjectAndBranch (Just project) branch -> These project branch
maybeCurrentBranch <- ProjectUtils.getCurrentProjectBranch
deletedBranch <-
Cli.runTransactionWithRollback \rollback -> do
branch <-
Queries.loadProjectBranchByNames (projectAndBranchNames ^. #project) (projectAndBranchNames ^. #branch)
& onNothingM (rollback (Output.LocalProjectBranchDoesntExist projectAndBranchNames))
Queries.deleteProjectBranch (branch ^. #projectId) (branch ^. #branchId)
pure branch
let projectId = deletedBranch ^. #projectId
Cli.stepAt
("delete.branch " <> into @Text projectAndBranchNames)
( Path.unabsolute (ProjectUtils.projectBranchesPath projectId),
\branchObject ->
branchObject
& over
Branch.children
(Map.delete (ProjectUtils.projectBranchSegment (deletedBranch ^. #branchId)))
)
doDeleteProjectBranch projectAndBranchToDelete
-- If the user is on the branch that they're deleting, we have to cd somewhere; try these in order:
--
-- 1. cd to parent branch, if it exists
-- 2. cd to "main", if it exists
-- 3. cd to loose code path `.`
whenJust maybeCurrentBranch \(ProjectAndBranch _currentProject currentBranch, _restPath) ->
when (deletedBranch == currentBranch) do
whenJust maybeCurrentBranch \(currentProjectAndBranch, _restPath) ->
when (ProjectUtils.justTheIds currentProjectAndBranch == ProjectUtils.justTheIds projectAndBranchToDelete) do
newPath <-
case deletedBranch ^. #parentBranchId of
case projectAndBranchToDelete.branch.parentBranchId of
Nothing ->
Cli.runTransaction (Queries.loadProjectBranchByName projectId (unsafeFrom @Text "main")) <&> \case
Nothing -> Path.Absolute Path.empty
Just mainBranch -> ProjectUtils.projectBranchPath (ProjectAndBranch projectId (mainBranch ^. #branchId))
Just parentBranchId -> pure (ProjectUtils.projectBranchPath (ProjectAndBranch projectId parentBranchId))
let loadMain =
Queries.loadProjectBranchByName projectAndBranchToDelete.project.projectId (unsafeFrom @Text "main")
in Cli.runTransaction loadMain <&> \case
Nothing -> Path.Absolute Path.empty
Just mainBranch -> ProjectUtils.projectBranchPath (ProjectUtils.justTheIds' mainBranch)
Just parentBranchId ->
pure $
ProjectUtils.projectBranchPath
(ProjectAndBranch projectAndBranchToDelete.project.projectId parentBranchId)
Cli.cd newPath
-- | Delete a project branch and record an entry in the reflog.
doDeleteProjectBranch :: ProjectAndBranch Sqlite.Project Sqlite.ProjectBranch -> Cli ()
doDeleteProjectBranch projectAndBranch = do
Cli.runTransaction do
Queries.deleteProjectBranch projectAndBranch.project.projectId projectAndBranch.branch.branchId
Cli.stepAt
("delete.branch " <> into @Text (ProjectUtils.justTheNames projectAndBranch))
( Path.unabsolute (ProjectUtils.projectBranchesPath projectAndBranch.project.projectId),
over Branch.children (Map.delete (ProjectUtils.projectBranchSegment projectAndBranch.branch.branchId))
)

View File

@ -7,6 +7,7 @@ module Unison.Codebase.Editor.HandleInput.Merge2
BobMergeInfo (..),
LcaMergeInfo (..),
doMerge,
doMergeLocalBranch,
)
where
@ -47,7 +48,7 @@ import Unison.Cli.MergeTypes (MergeSource (..), MergeSourceAndTarget (..), Merge
import Unison.Cli.Monad (Cli)
import Unison.Cli.Monad qualified as Cli
import Unison.Cli.MonadUtils qualified as Cli
import Unison.Cli.ProjectUtils qualified as Cli
import Unison.Cli.ProjectUtils qualified as ProjectUtils
import Unison.Codebase (Codebase)
import Unison.Codebase qualified as Codebase
import Unison.Codebase.Branch (Branch0)
@ -140,55 +141,30 @@ import Prelude hiding (unzip, zip, zipWith)
handleMerge :: ProjectAndBranch (Maybe ProjectName) ProjectBranchName -> Cli ()
handleMerge (ProjectAndBranch maybeBobProjectName bobBranchName) = do
-- Assert that Alice (us) is on a project branch, and grab the causal hash.
(ProjectAndBranch aliceProject aliceProjectBranch, _path) <- Cli.expectCurrentProjectBranch
aliceCausalHash <- Cli.runTransaction (projectBranchToCausalHash aliceProjectBranch)
(aliceProjectAndBranch, _path) <- ProjectUtils.expectCurrentProjectBranch
-- Resolve Bob's maybe-project-name + branch-name to the info the merge algorithm needs: the project name, branch
-- name, and causal hash.
bobProject <-
case maybeBobProjectName of
Nothing -> pure aliceProject
Nothing -> pure aliceProjectAndBranch.project
Just bobProjectName
| bobProjectName == aliceProject.name -> pure aliceProject
| bobProjectName == aliceProjectAndBranch.project.name -> pure aliceProjectAndBranch.project
| otherwise -> do
Cli.runTransaction (Queries.loadProjectByName bobProjectName)
& onNothingM (Cli.returnEarly (Output.LocalProjectDoesntExist bobProjectName))
bobProjectBranch <- Cli.expectProjectBranchByName bobProject bobBranchName
bobCausalHash <- Cli.runTransaction (projectBranchToCausalHash bobProjectBranch)
bobProjectBranch <- ProjectUtils.expectProjectBranchByName bobProject bobBranchName
let bobProjectAndBranch = ProjectAndBranch bobProject bobProjectBranch
-- Using Alice and Bob's causal hashes, find the LCA (if it exists)
lcaCausalHash <- Cli.runTransaction (Operations.lca aliceCausalHash bobCausalHash)
-- Do the merge!
doMerge
MergeInfo
{ alice =
AliceMergeInfo
{ causalHash = aliceCausalHash,
project = aliceProject,
projectBranch = aliceProjectBranch
},
bob =
BobMergeInfo
{ causalHash = bobCausalHash,
source = MergeSource'LocalProjectBranch (ProjectAndBranch bobProject.name bobBranchName)
},
lca =
LcaMergeInfo
{ causalHash = lcaCausalHash
},
description = "merge " <> into @Text (ProjectAndBranch bobProject.name bobBranchName)
doMergeLocalBranch
TwoWay
{ alice = aliceProjectAndBranch,
bob = bobProjectAndBranch
}
where
projectBranchToCausalHash :: ProjectBranch -> Transaction CausalHash
projectBranchToCausalHash branch = do
let path = Cli.projectBranchPath (ProjectAndBranch branch.projectId branch.branchId)
causal <- Codebase.getShallowCausalFromRoot Nothing (Path.unabsolute path)
pure causal.causalHash
data MergeInfo = MergeInfo
{ alice :: !AliceMergeInfo,
bob :: BobMergeInfo,
bob :: !BobMergeInfo,
lca :: !LcaMergeInfo,
-- | How should we describe this merge in the reflog?
description :: !Text
@ -196,8 +172,7 @@ data MergeInfo = MergeInfo
data AliceMergeInfo = AliceMergeInfo
{ causalHash :: !CausalHash,
project :: !Project,
projectBranch :: !ProjectBranch
projectAndBranch :: !(ProjectAndBranch Project ProjectBranch)
}
data BobMergeInfo = BobMergeInfo
@ -216,240 +191,273 @@ doMerge info = do
then realDebugFunctions
else fakeDebugFunctions
let alicePath = Cli.projectBranchPath (ProjectAndBranch info.alice.project.projectId info.alice.projectBranch.branchId)
let aliceBranchNames = ProjectAndBranch info.alice.project.name info.alice.projectBranch.name
let alicePath = ProjectUtils.projectBranchPath (ProjectUtils.justTheIds info.alice.projectAndBranch)
let aliceBranchNames = ProjectUtils.justTheNames info.alice.projectAndBranch
let mergeSource = MergeSourceOrTarget'Source info.bob.source
let mergeTarget = MergeSourceOrTarget'Target aliceBranchNames
let mergeSourceAndTarget = MergeSourceAndTarget {alice = aliceBranchNames, bob = info.bob.source}
Cli.Env {codebase} <- ask
-- If alice == bob, or LCA == bob (so alice is ahead of bob), then we are done.
when (info.alice.causalHash == info.bob.causalHash || info.lca.causalHash == Just info.bob.causalHash) do
Cli.returnEarly (Output.MergeAlreadyUpToDate2 mergeSourceAndTarget)
Cli.label \done -> do
-- If alice == bob, or LCA == bob (so alice is ahead of bob), then we are done.
when (info.alice.causalHash == info.bob.causalHash || info.lca.causalHash == Just info.bob.causalHash) do
Cli.respond (Output.MergeAlreadyUpToDate2 mergeSourceAndTarget)
done ()
-- Otherwise, if LCA == alice (so alice is behind bob), then we could fast forward to bob, so we're done.
when (info.lca.causalHash == Just info.alice.causalHash) do
bobBranch <- liftIO (Codebase.expectBranchForHash codebase info.bob.causalHash)
_ <- Cli.updateAt info.description alicePath (\_aliceBranch -> bobBranch)
Cli.returnEarly (Output.MergeSuccessFastForward mergeSourceAndTarget)
-- Otherwise, if LCA == alice (so alice is behind bob), then we could fast forward to bob, so we're done.
when (info.lca.causalHash == Just info.alice.causalHash) do
bobBranch <- liftIO (Codebase.expectBranchForHash codebase info.bob.causalHash)
_ <- Cli.updateAt info.description alicePath (\_aliceBranch -> bobBranch)
Cli.respond (Output.MergeSuccessFastForward mergeSourceAndTarget)
done ()
-- Create a bunch of cached database lookup functions
db <- makeMergeDatabase codebase
-- Create a bunch of cached database lookup functions
db <- makeMergeDatabase codebase
-- Load Alice/Bob/LCA causals
causals <- Cli.runTransaction do
traverse
Operations.expectCausalBranchByCausalHash
TwoOrThreeWay
{ alice = info.alice.causalHash,
bob = info.bob.causalHash,
lca = info.lca.causalHash
}
-- Load Alice/Bob/LCA causals
causals <- Cli.runTransaction do
traverse
Operations.expectCausalBranchByCausalHash
TwoOrThreeWay
{ alice = info.alice.causalHash,
bob = info.bob.causalHash,
lca = info.lca.causalHash
}
liftIO (debugFunctions.debugCausals causals)
liftIO (debugFunctions.debugCausals causals)
-- Load Alice/Bob/LCA branches
branches <-
-- Load Alice/Bob/LCA branches
branches <-
Cli.runTransaction do
alice <- causals.alice.value
bob <- causals.bob.value
lca <- for causals.lca \causal -> causal.value
pure TwoOrThreeWay {lca, alice, bob}
-- Assert that neither Alice nor Bob have defns in lib
for_ [(mergeTarget, branches.alice), (mergeSource, branches.bob)] \(who, branch) -> do
libdeps <-
case Map.lookup NameSegment.libSegment branch.children of
Nothing -> pure V2.Branch.empty
Just libdeps -> Cli.runTransaction libdeps.value
when (not (Map.null libdeps.terms) || not (Map.null libdeps.types)) do
Cli.returnEarly (Output.MergeDefnsInLib who)
-- Load Alice/Bob/LCA definitions and decl name lookups
(defns3, declNameLookups, lcaDeclToConstructors) <- do
let emptyNametree = Nametree {value = Defns Map.empty Map.empty, children = Map.empty}
let loadDefns branch =
Cli.runTransaction (loadNamespaceDefinitions (referent2to1 db) branch) & onLeftM \conflictedName ->
Cli.returnEarly case conflictedName of
ConflictedName'Term name refs -> Output.MergeConflictedTermName name refs
ConflictedName'Type name refs -> Output.MergeConflictedTypeName name refs
let load = \case
Nothing -> pure (emptyNametree, DeclNameLookup Map.empty Map.empty)
Just (who, branch) -> do
defns <- loadDefns branch
declNameLookup <-
Cli.runTransaction (checkDeclCoherency db.loadDeclNumConstructors defns) & onLeftM \err ->
Cli.returnEarly case err of
IncoherentDeclReason'ConstructorAlias name1 name2 ->
Output.MergeConstructorAlias who name1 name2
IncoherentDeclReason'MissingConstructorName name -> Output.MergeMissingConstructorName who name
IncoherentDeclReason'NestedDeclAlias shorterName longerName ->
Output.MergeNestedDeclAlias who shorterName longerName
IncoherentDeclReason'StrayConstructor name -> Output.MergeStrayConstructor who name
pure (defns, declNameLookup)
(aliceDefns0, aliceDeclNameLookup) <- load (Just (mergeTarget, branches.alice))
(bobDefns0, bobDeclNameLookup) <- load (Just (mergeSource, branches.bob))
lcaDefns0 <- maybe (pure emptyNametree) loadDefns branches.lca
lcaDeclToConstructors <- Cli.runTransaction (lenientCheckDeclCoherency db.loadDeclNumConstructors lcaDefns0)
let flatten defns = Defns (flattenNametree (view #terms) defns) (flattenNametree (view #types) defns)
let defns3 = flatten <$> ThreeWay {alice = aliceDefns0, bob = bobDefns0, lca = lcaDefns0}
let declNameLookups = TwoWay {alice = aliceDeclNameLookup, bob = bobDeclNameLookup}
pure (defns3, declNameLookups, lcaDeclToConstructors)
let defns = ThreeWay.forgetLca defns3
liftIO (debugFunctions.debugDefns defns3 declNameLookups lcaDeclToConstructors)
-- Diff LCA->Alice and LCA->Bob
diffs <- Cli.runTransaction (Merge.nameBasedNamespaceDiff db declNameLookups lcaDeclToConstructors defns3)
liftIO (debugFunctions.debugDiffs diffs)
-- Bail early if it looks like we can't proceed with the merge, because Alice or Bob has one or more conflicted alias
for_ ((,) <$> TwoWay mergeTarget mergeSource <*> diffs) \(who, diff) ->
whenJust (findConflictedAlias defns3.lca diff) \(name1, name2) ->
Cli.returnEarly (Output.MergeConflictedAliases who name1 name2)
-- Combine the LCA->Alice and LCA->Bob diffs together
let diff = combineDiffs diffs
liftIO (debugFunctions.debugCombinedDiff diff)
-- Partition the combined diff into the conflicted things and the unconflicted things
(conflicts, unconflicts) <-
partitionCombinedDiffs defns declNameLookups diff & onLeft \name ->
Cli.returnEarly (Output.MergeConflictInvolvingBuiltin name)
liftIO (debugFunctions.debugPartitionedDiff conflicts unconflicts)
-- Identify the unconflicted dependents we need to pull into the Unison file (either first for typechecking, if there
-- aren't conflicts, or else for manual conflict resolution without a typechecking step, if there are)
dependents <- Cli.runTransaction (identifyDependents defns conflicts unconflicts)
liftIO (debugFunctions.debugDependents dependents)
let stageOne :: DefnsF (Map Name) Referent TypeReference
stageOne =
makeStageOne
declNameLookups
conflicts
unconflicts
dependents
(bimap BiMultimap.range BiMultimap.range defns3.lca)
liftIO (debugFunctions.debugStageOne stageOne)
-- Load and merge Alice's and Bob's libdeps
mergedLibdeps <-
Cli.runTransaction do
libdeps <- loadLibdeps branches
libdepsToBranch0 db (Merge.mergeLibdeps getTwoFreshNames libdeps)
-- Make PPE for Alice that contains all of Alice's names, but suffixified against her names + Bob's names
let mkPpes :: TwoWay Names -> Names -> TwoWay PrettyPrintEnvDecl
mkPpes defnsNames libdepsNames =
defnsNames <&> \names -> PPED.makePPED (PPE.namer (names <> libdepsNames)) suffixifier
where
suffixifier = PPE.suffixifyByName (fold defnsNames <> libdepsNames)
let ppes = mkPpes (defnsToNames <$> defns) (Branch.toNames mergedLibdeps)
hydratedThings <- do
Cli.runTransaction do
for ((,) <$> conflicts <*> dependents) \(conflicts1, dependents1) ->
let hydrate = hydrateDefns (Codebase.unsafeGetTermComponent codebase) Operations.expectDeclComponent
in (,) <$> hydrate conflicts1 <*> hydrate dependents1
let (renderedConflicts, renderedDependents) =
let honk declNameLookup ppe defns =
let (types, accessorNames) =
Writer.runWriter $
defns.types & Map.traverseWithKey \name (ref, typ) ->
renderTypeBinding
-- Sort of a hack; since the decl printer looks in the PPE for names of constructors,
-- we just delete all term names out and add back the constructors...
-- probably no need to wipe out the suffixified side but we do it anyway
(setPpedToConstructorNames declNameLookup name ref ppe)
name
ref
typ
terms =
defns.terms & Map.mapMaybeWithKey \name (term, typ) ->
if Set.member name accessorNames
then Nothing
else Just (renderTermBinding ppe.suffixifiedPPE name term typ)
in Defns {terms, types}
in unzip $
( \declNameLookup (conflicts, dependents) ppe ->
let honk1 = honk declNameLookup ppe
in (honk1 conflicts, honk1 dependents)
)
<$> declNameLookups
<*> hydratedThings
<*> ppes
let prettyUnisonFile =
makePrettyUnisonFile
TwoWay
{ alice = into @Text aliceBranchNames,
bob =
case info.bob.source of
MergeSource'LocalProjectBranch bobBranchNames -> into @Text bobBranchNames
MergeSource'RemoteProjectBranch bobBranchNames
| aliceBranchNames == bobBranchNames -> "remote " <> into @Text bobBranchNames
| otherwise -> into @Text bobBranchNames
MergeSource'RemoteLooseCode info ->
case Path.toName info.path of
Nothing -> "<root>"
Just name -> Name.toText name
}
renderedConflicts
renderedDependents
let stageOneBranch = defnsAndLibdepsToBranch0 codebase stageOne mergedLibdeps
maybeTypecheckedUnisonFile <-
let thisMergeHasConflicts =
-- Eh, they'd either both be null, or neither, but just check both maps anyway
not (defnsAreEmpty conflicts.alice) || not (defnsAreEmpty conflicts.bob)
in if thisMergeHasConflicts
then pure Nothing
else do
currentPath <- Cli.getCurrentPath
parsingEnv <- makeParsingEnv currentPath (Branch.toNames stageOneBranch)
prettyParseTypecheck2 prettyUnisonFile parsingEnv <&> eitherToMaybe
let parents =
(\causal -> (causal.causalHash, Codebase.expectBranchForHash codebase causal.causalHash)) <$> causals
case maybeTypecheckedUnisonFile of
Nothing -> do
Cli.Env {writeSource} <- ask
_temporaryBranchId <-
HandleInput.Branch.doCreateBranch'
(Branch.mergeNode stageOneBranch parents.alice parents.bob)
Nothing
info.alice.projectAndBranch.project
(findTemporaryBranchName info.alice.projectAndBranch.project.projectId mergeSourceAndTarget)
info.description
scratchFilePath <-
Cli.getLatestFile <&> \case
Nothing -> "scratch.u"
Just (file, _) -> file
liftIO $ writeSource (Text.pack scratchFilePath) (Text.pack $ Pretty.toPlain 80 prettyUnisonFile)
Cli.respond (Output.MergeFailure scratchFilePath mergeSourceAndTarget)
Just tuf -> do
Cli.runTransaction (Codebase.addDefsToCodebase codebase tuf)
let stageTwoBranch = Branch.batchUpdates (typecheckedUnisonFileToBranchAdds tuf) stageOneBranch
_ <-
Cli.updateAt
info.description
alicePath
(\_aliceBranch -> Branch.mergeNode stageTwoBranch parents.alice parents.bob)
Cli.respond (Output.MergeSuccess mergeSourceAndTarget)
doMergeLocalBranch :: TwoWay (ProjectAndBranch Project ProjectBranch) -> Cli ()
doMergeLocalBranch branches = do
(aliceCausalHash, bobCausalHash, lcaCausalHash) <-
Cli.runTransaction do
alice <- causals.alice.value
bob <- causals.bob.value
lca <- for causals.lca \causal -> causal.value
pure TwoOrThreeWay {lca, alice, bob}
aliceCausalHash <- ProjectUtils.getProjectBranchCausalHash (ProjectUtils.justTheIds branches.alice)
bobCausalHash <- ProjectUtils.getProjectBranchCausalHash (ProjectUtils.justTheIds branches.bob)
-- Using Alice and Bob's causal hashes, find the LCA (if it exists)
lcaCausalHash <- Operations.lca aliceCausalHash bobCausalHash
pure (aliceCausalHash, bobCausalHash, lcaCausalHash)
-- Assert that neither Alice nor Bob have defns in lib
for_ [(mergeTarget, branches.alice), (mergeSource, branches.bob)] \(who, branch) -> do
libdeps <-
case Map.lookup NameSegment.libSegment branch.children of
Nothing -> pure V2.Branch.empty
Just libdeps -> Cli.runTransaction libdeps.value
when (not (Map.null libdeps.terms) || not (Map.null libdeps.types)) do
Cli.returnEarly (Output.MergeDefnsInLib who)
-- Load Alice/Bob/LCA definitions and decl name lookups
(defns3, declNameLookups, lcaDeclToConstructors) <- do
let emptyNametree = Nametree {value = Defns Map.empty Map.empty, children = Map.empty}
let loadDefns branch =
Cli.runTransaction (loadNamespaceDefinitions (referent2to1 db) branch) & onLeftM \conflictedName ->
Cli.returnEarly case conflictedName of
ConflictedName'Term name refs -> Output.MergeConflictedTermName name refs
ConflictedName'Type name refs -> Output.MergeConflictedTypeName name refs
let load = \case
Nothing -> pure (emptyNametree, DeclNameLookup Map.empty Map.empty)
Just (who, branch) -> do
defns <- loadDefns branch
declNameLookup <-
Cli.runTransaction (checkDeclCoherency db.loadDeclNumConstructors defns) & onLeftM \err ->
Cli.returnEarly case err of
IncoherentDeclReason'ConstructorAlias name1 name2 ->
Output.MergeConstructorAlias who name1 name2
IncoherentDeclReason'MissingConstructorName name -> Output.MergeMissingConstructorName who name
IncoherentDeclReason'NestedDeclAlias shorterName longerName ->
Output.MergeNestedDeclAlias who shorterName longerName
IncoherentDeclReason'StrayConstructor name -> Output.MergeStrayConstructor who name
pure (defns, declNameLookup)
(aliceDefns0, aliceDeclNameLookup) <- load (Just (mergeTarget, branches.alice))
(bobDefns0, bobDeclNameLookup) <- load (Just (mergeSource, branches.bob))
lcaDefns0 <- maybe (pure emptyNametree) loadDefns branches.lca
lcaDeclToConstructors <- Cli.runTransaction (lenientCheckDeclCoherency db.loadDeclNumConstructors lcaDefns0)
let flatten defns = Defns (flattenNametree (view #terms) defns) (flattenNametree (view #types) defns)
let defns3 = flatten <$> ThreeWay {alice = aliceDefns0, bob = bobDefns0, lca = lcaDefns0}
let declNameLookups = TwoWay {alice = aliceDeclNameLookup, bob = bobDeclNameLookup}
pure (defns3, declNameLookups, lcaDeclToConstructors)
let defns = ThreeWay.forgetLca defns3
liftIO (debugFunctions.debugDefns defns3 declNameLookups lcaDeclToConstructors)
-- Diff LCA->Alice and LCA->Bob
diffs <- Cli.runTransaction (Merge.nameBasedNamespaceDiff db declNameLookups lcaDeclToConstructors defns3)
liftIO (debugFunctions.debugDiffs diffs)
-- Bail early if it looks like we can't proceed with the merge, because Alice or Bob has one or more conflicted alias
for_ ((,) <$> TwoWay mergeTarget mergeSource <*> diffs) \(who, diff) ->
whenJust (findConflictedAlias defns3.lca diff) \(name1, name2) ->
Cli.returnEarly (Output.MergeConflictedAliases who name1 name2)
-- Combine the LCA->Alice and LCA->Bob diffs together
let diff = combineDiffs diffs
liftIO (debugFunctions.debugCombinedDiff diff)
-- Partition the combined diff into the conflicted things and the unconflicted things
(conflicts, unconflicts) <-
partitionCombinedDiffs defns declNameLookups diff & onLeft \name ->
Cli.returnEarly (Output.MergeConflictInvolvingBuiltin name)
liftIO (debugFunctions.debugPartitionedDiff conflicts unconflicts)
-- Identify the unconflicted dependents we need to pull into the Unison file (either first for typechecking, if there
-- aren't conflicts, or else for manual conflict resolution without a typechecking step, if there are)
dependents <- Cli.runTransaction (identifyDependents defns conflicts unconflicts)
liftIO (debugFunctions.debugDependents dependents)
let stageOne :: DefnsF (Map Name) Referent TypeReference
stageOne =
makeStageOne
declNameLookups
conflicts
unconflicts
dependents
(bimap BiMultimap.range BiMultimap.range defns3.lca)
liftIO (debugFunctions.debugStageOne stageOne)
-- Load and merge Alice's and Bob's libdeps
mergedLibdeps <-
Cli.runTransaction do
libdeps <- loadLibdeps branches
libdepsToBranch0 db (Merge.mergeLibdeps getTwoFreshNames libdeps)
-- Make PPE for Alice that contains all of Alice's names, but suffixified against her names + Bob's names
let mkPpes :: TwoWay Names -> Names -> TwoWay PrettyPrintEnvDecl
mkPpes defnsNames libdepsNames =
defnsNames <&> \names -> PPED.makePPED (PPE.namer (names <> libdepsNames)) suffixifier
where
suffixifier = PPE.suffixifyByName (fold defnsNames <> libdepsNames)
let ppes = mkPpes (defnsToNames <$> defns) (Branch.toNames mergedLibdeps)
hydratedThings <- do
Cli.runTransaction do
for ((,) <$> conflicts <*> dependents) \(conflicts1, dependents1) ->
let hydrate = hydrateDefns (Codebase.unsafeGetTermComponent codebase) Operations.expectDeclComponent
in (,) <$> hydrate conflicts1 <*> hydrate dependents1
let (renderedConflicts, renderedDependents) =
let honk declNameLookup ppe defns =
let (types, accessorNames) =
Writer.runWriter $
defns.types & Map.traverseWithKey \name (ref, typ) ->
renderTypeBinding
-- Sort of a hack; since the decl printer looks in the PPE for names of constructors,
-- we just delete all term names out and add back the constructors...
-- probably no need to wipe out the suffixified side but we do it anyway
(setPpedToConstructorNames declNameLookup name ref ppe)
name
ref
typ
terms =
defns.terms & Map.mapMaybeWithKey \name (term, typ) ->
if Set.member name accessorNames
then Nothing
else Just (renderTermBinding ppe.suffixifiedPPE name term typ)
in Defns {terms, types}
in unzip $
( \declNameLookup (conflicts, dependents) ppe ->
let honk1 = honk declNameLookup ppe
in (honk1 conflicts, honk1 dependents)
)
<$> declNameLookups
<*> hydratedThings
<*> ppes
let prettyUnisonFile =
makePrettyUnisonFile
TwoWay
{ alice = into @Text aliceBranchNames,
bob =
case info.bob.source of
MergeSource'LocalProjectBranch bobBranchNames -> into @Text bobBranchNames
MergeSource'RemoteProjectBranch bobBranchNames
| aliceBranchNames == bobBranchNames -> "remote " <> into @Text bobBranchNames
| otherwise -> into @Text bobBranchNames
MergeSource'RemoteLooseCode info ->
case Path.toName info.path of
Nothing -> "<root>"
Just name -> Name.toText name
}
renderedConflicts
renderedDependents
let stageOneBranch = defnsAndLibdepsToBranch0 codebase stageOne mergedLibdeps
maybeTypecheckedUnisonFile <-
let thisMergeHasConflicts =
-- Eh, they'd either both be null, or neither, but just check both maps anyway
not (defnsAreEmpty conflicts.alice) || not (defnsAreEmpty conflicts.bob)
in if thisMergeHasConflicts
then pure Nothing
else do
currentPath <- Cli.getCurrentPath
parsingEnv <- makeParsingEnv currentPath (Branch.toNames stageOneBranch)
prettyParseTypecheck2 prettyUnisonFile parsingEnv <&> eitherToMaybe
let parents =
(\causal -> (causal.causalHash, Codebase.expectBranchForHash codebase causal.causalHash)) <$> causals
case maybeTypecheckedUnisonFile of
Nothing -> do
Cli.Env {writeSource} <- ask
_temporaryBranchId <-
HandleInput.Branch.doCreateBranch'
(Branch.mergeNode stageOneBranch parents.alice parents.bob)
Nothing
info.alice.project
(findTemporaryBranchName info.alice.project.projectId mergeSourceAndTarget)
info.description
scratchFilePath <-
Cli.getLatestFile <&> \case
Nothing -> "scratch.u"
Just (file, _) -> file
liftIO $ writeSource (Text.pack scratchFilePath) (Text.pack $ Pretty.toPlain 80 prettyUnisonFile)
Cli.respond (Output.MergeFailure scratchFilePath mergeSourceAndTarget)
Just tuf -> do
Cli.runTransaction (Codebase.addDefsToCodebase codebase tuf)
let stageTwoBranch = Branch.batchUpdates (typecheckedUnisonFileToBranchAdds tuf) stageOneBranch
_ <-
Cli.updateAt
info.description
alicePath
(\_aliceBranch -> Branch.mergeNode stageTwoBranch parents.alice parents.bob)
Cli.respond (Output.MergeSuccess mergeSourceAndTarget)
-- Do the merge!
doMerge
MergeInfo
{ alice =
AliceMergeInfo
{ causalHash = aliceCausalHash,
projectAndBranch = branches.alice
},
bob =
BobMergeInfo
{ causalHash = bobCausalHash,
source = MergeSource'LocalProjectBranch (ProjectUtils.justTheNames branches.bob)
},
lca =
LcaMergeInfo
{ causalHash = lcaCausalHash
},
description = "merge " <> into @Text (ProjectUtils.justTheNames branches.bob)
}
------------------------------------------------------------------------------------------------------------------------
-- Loading basic info out of the database
@ -832,7 +840,7 @@ defnsToNames defns =
findTemporaryBranchName :: ProjectId -> MergeSourceAndTarget -> Transaction ProjectBranchName
findTemporaryBranchName projectId mergeSourceAndTarget = do
Cli.findTemporaryBranchName projectId preferred
ProjectUtils.findTemporaryBranchName projectId preferred
where
preferred :: ProjectBranchName
preferred =

View File

@ -1,11 +1,13 @@
-- | @switch@ input handler
module Unison.Codebase.Editor.HandleInput.ProjectSwitch
( projectSwitch,
switchToProjectBranch,
)
where
import Control.Lens ((^.))
import Data.These (These (..))
import U.Codebase.Sqlite.DbId (ProjectBranchId, ProjectId)
import U.Codebase.Sqlite.Project qualified
import U.Codebase.Sqlite.Queries qualified as Queries
import Unison.Cli.Monad (Cli)
import Unison.Cli.Monad qualified as Cli
@ -31,52 +33,47 @@ projectSwitch projectNames = do
ProjectUtils.getCurrentProjectBranch >>= \case
Nothing -> switchToProjectAndBranchByTheseNames (This projectName)
Just (ProjectAndBranch currentProject _currentBranch, _restPath) -> do
let currentProjectName = currentProject ^. #name
(projectExists, branchExists) <-
Cli.runTransaction do
(,)
<$> Queries.projectExistsByName projectName
<*> Queries.projectBranchExistsByName (currentProject ^. #projectId) branchName
<*> Queries.projectBranchExistsByName currentProject.projectId branchName
case (projectExists, branchExists) of
(False, False) -> Cli.respond (Output.LocalProjectNorProjectBranchExist projectName branchName)
(False, True) -> switchToProjectAndBranchByTheseNames (These currentProjectName branchName)
(False, True) -> switchToProjectAndBranchByTheseNames (These currentProject.name branchName)
(True, False) -> switchToProjectAndBranchByTheseNames (This projectName)
(True, True) ->
Cli.respondNumbered $
Output.AmbiguousSwitch
projectName
(ProjectAndBranch currentProjectName branchName)
(ProjectAndBranch currentProject.name branchName)
ProjectAndBranchNames'Unambiguous projectAndBranchNames0 ->
switchToProjectAndBranchByTheseNames projectAndBranchNames0
switchToProjectAndBranchByTheseNames :: These ProjectName ProjectBranchName -> Cli ()
switchToProjectAndBranchByTheseNames projectAndBranchNames0 = do
branch <- case projectAndBranchNames0 of
This projectName ->
Cli.runTransactionWithRollback \rollback -> do
project <-
Queries.loadProjectByName projectName & onNothingM do
rollback (Output.LocalProjectDoesntExist projectName)
Queries.loadMostRecentBranch (project ^. #projectId) >>= \case
Nothing -> do
let branchName = unsafeFrom @Text "main"
branch <-
Queries.loadProjectBranchByName (project ^. #projectId) branchName & onNothingM do
rollback (Output.LocalProjectBranchDoesntExist (ProjectAndBranch projectName branchName))
setMostRecentBranch branch
Just branchId ->
Queries.loadProjectBranch (project ^. #projectId) branchId >>= \case
Nothing -> error "impossible"
Just branch -> pure branch
_ -> do
projectAndBranchNames@(ProjectAndBranch projectName branchName) <- ProjectUtils.hydrateNames projectAndBranchNames0
Cli.runTransactionWithRollback \rollback -> do
branch <-
branch <-
case projectAndBranchNames0 of
This projectName ->
Cli.runTransactionWithRollback \rollback -> do
project <-
Queries.loadProjectByName projectName & onNothingM do
rollback (Output.LocalProjectDoesntExist projectName)
let branchName = unsafeFrom @Text "main"
Queries.loadProjectBranchByName project.projectId branchName & onNothingM do
rollback (Output.LocalProjectBranchDoesntExist (ProjectAndBranch projectName branchName))
_ -> do
projectAndBranchNames@(ProjectAndBranch projectName branchName) <- ProjectUtils.hydrateNames projectAndBranchNames0
Cli.runTransactionWithRollback \rollback -> do
Queries.loadProjectBranchByNames projectName branchName & onNothingM do
rollback (Output.LocalProjectBranchDoesntExist projectAndBranchNames)
setMostRecentBranch branch
Cli.cd (ProjectUtils.projectBranchPath (ProjectAndBranch (branch ^. #projectId) (branch ^. #branchId)))
where
setMostRecentBranch branch = do
Queries.setMostRecentBranch (branch ^. #projectId) (branch ^. #branchId)
pure branch
switchToProjectBranch (ProjectUtils.justTheIds' branch)
-- | Switch to a branch:
--
-- * Record it as the most-recent branch (so it's restored when ucm starts).
-- * Change the current path in the in-memory loop state.
switchToProjectBranch :: ProjectAndBranch ProjectId ProjectBranchId -> Cli ()
switchToProjectBranch x = do
Cli.runTransaction (Queries.setMostRecentBranch x.project x.branch)
Cli.cd (ProjectUtils.projectBranchPath x)

View File

@ -115,8 +115,7 @@ handlePull unresolvedSourceAndTarget pullMode = do
{ alice =
AliceMergeInfo
{ causalHash = aliceCausalHash,
project = target.project,
projectBranch = target.branch
projectAndBranch = target
},
bob =
BobMergeInfo
@ -209,9 +208,9 @@ resolveExplicitSource includeSquashed = \case
(ProjectAndBranch (remoteProjectId, remoteProjectName) remoteBranchName)
pure (ReadShare'ProjectBranch remoteProjectBranch)
ReadShare'ProjectBranch (That branchNameOrLatestRelease) -> do
(ProjectAndBranch localProject localBranch, _restPath) <- ProjectUtils.expectCurrentProjectBranch
let localProjectId = localProject.projectId
let localBranchId = localBranch.branchId
(localProjectAndBranch, _restPath) <- ProjectUtils.expectCurrentProjectBranch
let localProjectId = localProjectAndBranch.project.projectId
let localBranchId = localProjectAndBranch.branch.branchId
Cli.runTransaction (Queries.loadRemoteProjectBranch localProjectId Share.hardCodedUri localBranchId) >>= \case
Just (remoteProjectId, _maybeProjectBranchId) -> do
remoteProjectName <- Cli.runTransaction (Queries.expectRemoteProjectName remoteProjectId Share.hardCodedUri)
@ -228,9 +227,7 @@ resolveExplicitSource includeSquashed = \case
pure (ReadShare'ProjectBranch remoteProjectBranch)
Nothing -> do
Cli.returnEarly $
Output.NoAssociatedRemoteProject
Share.hardCodedUri
(ProjectAndBranch localProject.name localBranch.name)
Output.NoAssociatedRemoteProject Share.hardCodedUri (ProjectUtils.justTheNames localProjectAndBranch)
ReadShare'ProjectBranch (These projectName branchNameOrLatestRelease) -> do
remoteProject <- ProjectUtils.expectRemoteProjectByName projectName
let remoteProjectId = remoteProject.projectId

View File

@ -225,6 +225,7 @@ data Input
| -- New merge algorithm: merge the given project branch into the current one.
MergeI (ProjectAndBranch (Maybe ProjectName) ProjectBranchName)
| LibInstallI !(ProjectAndBranch ProjectName (Maybe ProjectBranchNameOrLatestRelease))
| UpgradeCommitI
deriving (Eq, Show)
-- | The source of a `branch` command: what to make the new branch from.

View File

@ -405,6 +405,7 @@ data Output
| MergeNestedDeclAlias !MergeSourceOrTarget !Name !Name
| MergeStrayConstructor !MergeSourceOrTarget !Name
| InstalledLibdep !(ProjectAndBranch ProjectName ProjectBranchName) !NameSegment
| NoUpgradeInProgress
data UpdateOrUpgrade = UOUUpdate | UOUUpgrade
@ -641,6 +642,7 @@ isFailure o = case o of
MergeNestedDeclAlias {} -> True
MergeStrayConstructor {} -> True
InstalledLibdep {} -> False
NoUpgradeInProgress {} -> True
isNumberedFailure :: NumberedOutput -> Bool
isNumberedFailure = \case

View File

@ -117,6 +117,7 @@ module Unison.CommandLine.InputPatterns
updateOld,
updateOldNoPatch,
upgrade,
upgradeCommitInputPattern,
view,
viewGlobal,
viewReflog,
@ -3140,6 +3141,19 @@ upgrade =
_ -> Left $ I.help upgrade
}
upgradeCommitInputPattern :: InputPattern
upgradeCommitInputPattern =
InputPattern
{ patternName = "upgrade.commit",
aliases = ["commit.upgrade"],
visibility = I.Visible,
args = [],
help = P.wrap $ makeExample' upgradeCommitInputPattern <> "commits the current upgrade.",
parse = \case
[] -> Right Input.UpgradeCommitI
_ -> Left (I.help upgradeCommitInputPattern)
}
validInputs :: [InputPattern]
validInputs =
sortOn
@ -3258,6 +3272,7 @@ validInputs =
updateOld,
updateOldNoPatch,
upgrade,
upgradeCommitInputPattern,
view,
viewGlobal,
viewReflog

View File

@ -1038,8 +1038,6 @@ notifyUser dir = \case
LoadingFile sourceName -> do
fileName <- renderFileName $ Text.unpack sourceName
pure $ P.wrap $ "Loading changes detected in " <> P.group (fileName <> ".")
-- TODO: Present conflicting TermEdits and TypeEdits
-- if we ever allow users to edit hashes directly.
Typechecked sourceName ppe slurpResult uf -> do
let fileStatusMsg = SlurpResult.pretty False ppe slurpResult
let containsWatchExpressions = notNull $ UF.watchComponents uf
@ -1072,8 +1070,7 @@ notifyUser dir = \case
<> IP.makeExample' IP.add
<> " or "
<> P.group (IP.makeExample' IP.update <> ",")
<> "here's how your codebase would"
<> "change:",
<> "here's how your codebase would change:",
P.indentN 2 $ SlurpResult.pretty False ppe slurpResult
]
]
@ -2015,6 +2012,8 @@ notifyUser dir = \case
<> prettyProjectAndBranchName libdep
<> "as"
<> P.group (P.text (NameSegment.toEscapedText segment) <> ".")
NoUpgradeInProgress ->
pure . P.wrap $ "It doesn't look like there's an upgrade in progress."
expectedEmptyPushDest :: WriteRemoteNamespace Void -> Pretty
expectedEmptyPushDest namespace =

View File

@ -55,6 +55,7 @@ library
Unison.Codebase.Editor.HandleInput.Branch
Unison.Codebase.Editor.HandleInput.Branches
Unison.Codebase.Editor.HandleInput.BranchRename
Unison.Codebase.Editor.HandleInput.CommitUpgrade
Unison.Codebase.Editor.HandleInput.DebugDefinition
Unison.Codebase.Editor.HandleInput.DebugFoldRanges
Unison.Codebase.Editor.HandleInput.DeleteBranch

View File

@ -15,6 +15,7 @@ proj/main> add
```
Test tab completion and fzf options of upgrade command.
```ucm
proj/main> debug.tab-complete upgrade ol
proj/main> debug.fuzzy-options upgrade _

View File

@ -30,6 +30,7 @@ proj/main> add
```
Test tab completion and fzf options of upgrade command.
```ucm
proj/main> debug.tab-complete upgrade ol

View File

@ -16,3 +16,17 @@ proj/main> add
```ucm:error
proj/main> upgrade old new
```
Resolve the error and commit the upgrade.
```unison
thingy = foo + +10
```
```ucm
proj/upgrade-old-to-new> update
proj/upgrade-old-to-new> upgrade.commit
proj/main> view thingy
proj/main> ls lib
proj/main> branches
```

View File

@ -44,3 +44,53 @@ thingy =
foo + 10
```
Resolve the error and commit the upgrade.
```unison
thingy = foo + +10
```
```ucm
Loading changes detected in scratch.u.
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These names already exist. You can `update` them to your
new definition:
thingy : Int
```
```ucm
proj/upgrade-old-to-new> update
Okay, I'm searching the branch for code that needs to be
updated...
Done.
proj/upgrade-old-to-new> upgrade.commit
I fast-forward merged proj/upgrade-old-to-new into proj/main.
proj/main> view thingy
thingy : Int
thingy =
use Int +
foo + +10
proj/main> ls lib
1. builtin/ (469 terms, 74 types)
2. new/ (1 term)
proj/main> branches
Branch Remote branch
1. main
```