implement upgrade.commit command

This commit is contained in:
Mitchell Rosen 2024-05-20 12:09:00 -04:00
parent dbe4892437
commit 75795e61e4
14 changed files with 195 additions and 51 deletions

View File

@ -25,6 +25,7 @@ module Unison.Cli.ProjectUtils
getProjectAndBranchByTheseNames,
expectProjectAndBranchByTheseNames,
expectLooseCodeOrProjectBranch,
getProjectBranchCausalHash,
-- * Loading remote project info
expectRemoteProjectById,
@ -36,6 +37,11 @@ module Unison.Cli.ProjectUtils
expectRemoteProjectBranchByNames,
expectRemoteProjectBranchByTheseNames,
-- * Projecting out common things
justTheIds,
justTheIds',
justTheNames,
-- * Other helpers
findTemporaryBranchName,
expectLatestReleaseBranchName,
@ -49,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
@ -59,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
@ -73,7 +83,6 @@ import Unison.Project.Util
import Unison.Sqlite (Transaction)
import Unison.Sqlite qualified as Sqlite
import Witch (unsafeFrom)
import qualified Data.Text as Text
branchRelativePathToAbsolute :: BranchRelativePath -> Cli Path.Absolute
branchRelativePathToAbsolute brp =
@ -108,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
@ -268,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

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)
@ -1192,6 +1193,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
@ -1407,6 +1409,7 @@ inputDescription input =
UiI {} -> wat
UpI {} -> wat
UpgradeI {} -> wat
UpgradeCommitI {} -> wat
VersionI -> wat
where
hp' :: Either SCH.ShortCausalHash Path' -> Cli Text

View File

@ -4,25 +4,66 @@ module Unison.Codebase.Editor.HandleInput.CommitUpgrade
)
where
import Data.Text qualified as Text
import U.Codebase.Sqlite.Operations qualified as Operations
import U.Codebase.Sqlite.Project qualified
import U.Codebase.Sqlite.Queries qualified as Queries
import Unison.Cli.MergeTypes (MergeSource (..))
import Unison.Cli.Monad (Cli)
import Unison.Cli.Monad qualified as Cli
import Unison.Cli.ProjectUtils qualified as ProjectUtils
import Unison.Codebase.Editor.HandleInput.Merge2 qualified as Merge
import Unison.Codebase.Editor.HandleInput.ProjectSwitch qualified as ProjectSwitch
import Unison.NameSegment (NameSegment)
import Unison.Codebase.Editor.Output qualified as Output
import Unison.CommandLine.InputPattern qualified as InputPattern
import Unison.CommandLine.InputPatterns qualified as InputPatterns
import Unison.Prelude
import Unison.Project (ProjectAndBranch (..))
handleCommitUpgrade :: Cli ()
handleCommitUpgrade = do
(projectAndBranch, _path) <- ProjectUtils.expectCurrentProjectBranch
(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 projectAndBranch.branch
& onNothing wundefined
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 projectAndBranch.project.projectId parentBranchId
-- Merge in the upgrade branch
wundefined
ProjectSwitch.switchToProjectBranch (ProjectUtils.justTheIds parentProjectAndBranch)
-- Merge the upgrade branch into the parent
(parentCausalHash, upgradeCausalHash, lcaCausalHash) <-
Cli.runTransaction do
parentCausalHash <- ProjectUtils.getProjectBranchCausalHash (ProjectUtils.justTheIds parentProjectAndBranch)
upgradeCausalHash <- ProjectUtils.getProjectBranchCausalHash (ProjectUtils.justTheIds upgradeProjectAndBranch)
lcaCausalHash <- Operations.lca parentCausalHash upgradeCausalHash
pure (parentCausalHash, upgradeCausalHash, lcaCausalHash)
Merge.doMerge
Merge.MergeInfo
{ alice =
Merge.AliceMergeInfo
{ causalHash = parentCausalHash,
projectAndBranch = parentProjectAndBranch
},
bob =
Merge.BobMergeInfo
{ causalHash = upgradeCausalHash,
source = MergeSource'LocalProjectBranch (ProjectUtils.justTheNames upgradeProjectAndBranch)
},
lca =
Merge.LcaMergeInfo
{ causalHash = lcaCausalHash
},
description = Text.pack (InputPattern.patternName InputPatterns.upgradeCommitInputPattern)
}

View File

@ -43,11 +43,11 @@ import U.Codebase.Sqlite.Project (Project (..))
import U.Codebase.Sqlite.ProjectBranch (ProjectBranch (..))
import U.Codebase.Sqlite.Queries qualified as Queries
import Unison.Builtin.Decls qualified as Builtin.Decls
import Unison.Cli.MergeTypes (MergeSource (..), MergeSourceOrTarget (..), MergeSourceAndTarget (..))
import Unison.Cli.MergeTypes (MergeSource (..), MergeSourceAndTarget (..), MergeSourceOrTarget (..))
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,24 +140,28 @@ 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)
(aliceCausalHash, bobCausalHash, lcaCausalHash) <-
Cli.runTransaction do
aliceCausalHash <- ProjectUtils.getProjectBranchCausalHash (ProjectUtils.justTheIds aliceProjectAndBranch)
bobCausalHash <- ProjectUtils.getProjectBranchCausalHash (ProjectUtils.justTheIds bobProjectAndBranch)
-- Using Alice and Bob's causal hashes, find the LCA (if it exists)
lcaCausalHash <- Operations.lca aliceCausalHash bobCausalHash
pure (aliceCausalHash, bobCausalHash, lcaCausalHash)
-- Do the merge!
doMerge
@ -165,30 +169,23 @@ handleMerge (ProjectAndBranch maybeBobProjectName bobBranchName) = do
{ alice =
AliceMergeInfo
{ causalHash = aliceCausalHash,
project = aliceProject,
projectBranch = aliceProjectBranch
projectAndBranch = aliceProjectAndBranch
},
bob =
BobMergeInfo
{ causalHash = bobCausalHash,
source = MergeSource'LocalProjectBranch (ProjectAndBranch bobProject.name bobBranchName)
source = MergeSource'LocalProjectBranch (ProjectUtils.justTheNames bobProjectAndBranch)
},
lca =
LcaMergeInfo
{ causalHash = lcaCausalHash
},
description = "merge " <> into @Text (ProjectAndBranch bobProject.name bobBranchName)
description = "merge " <> into @Text (ProjectUtils.justTheNames 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 +193,7 @@ data MergeInfo = MergeInfo
data AliceMergeInfo = AliceMergeInfo
{ causalHash :: !CausalHash,
project :: !Project,
projectBranch :: !ProjectBranch
projectAndBranch :: !(ProjectAndBranch Project ProjectBranch)
}
data BobMergeInfo = BobMergeInfo
@ -216,11 +212,11 @@ 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 }
let mergeSourceAndTarget = MergeSourceAndTarget {alice = aliceBranchNames, bob = info.bob.source}
Cli.Env {codebase} <- ask
@ -438,8 +434,8 @@ doMerge info = do
HandleInput.Branch.doCreateBranch'
(Branch.mergeNode stageOneBranch parents.alice parents.bob)
Nothing
info.alice.project
(findTemporaryBranchName info.alice.project.projectId mergeSourceAndTarget)
info.alice.projectAndBranch.project
(findTemporaryBranchName info.alice.projectAndBranch.project.projectId mergeSourceAndTarget)
info.description
scratchFilePath <-
Cli.getLatestFile <&> \case
@ -838,7 +834,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

@ -8,7 +8,6 @@ where
import Data.These (These (..))
import U.Codebase.Sqlite.DbId (ProjectBranchId, ProjectId)
import U.Codebase.Sqlite.Project qualified
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
@ -68,13 +67,13 @@ switchToProjectAndBranchByTheseNames projectAndBranchNames0 = do
Cli.runTransactionWithRollback \rollback -> do
Queries.loadProjectBranchByNames projectName branchName & onNothingM do
rollback (Output.LocalProjectBranchDoesntExist projectAndBranchNames)
switchToProjectBranch branch.projectId branch.branchId
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 :: ProjectId -> ProjectBranchId -> Cli ()
switchToProjectBranch projectId branchId = do
Cli.runTransaction (Queries.setMostRecentBranch projectId branchId)
Cli.cd (ProjectUtils.projectBranchPath (ProjectAndBranch projectId branchId))
switchToProjectBranch :: ProjectAndBranch ProjectId ProjectBranchId -> Cli ()
switchToProjectBranch x = do
Cli.runTransaction (Queries.setMostRecentBranch x.project x.branch)
Cli.cd (ProjectUtils.projectBranchPath x)

View File

@ -125,8 +125,7 @@ handlePull unresolvedSourceAndTarget pullMode = do
{ alice =
AliceMergeInfo
{ causalHash = aliceCausalHash,
project = target.project,
projectBranch = target.branch
projectAndBranch = target
},
bob =
BobMergeInfo
@ -221,9 +220,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)
@ -240,9 +239,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

@ -242,6 +242,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

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

View File

@ -127,6 +127,7 @@ module Unison.CommandLine.InputPatterns
updateOld,
updateOldNoPatch,
upgrade,
upgradeCommitInputPattern,
view,
viewGlobal,
viewPatch,
@ -3142,6 +3143,19 @@ upgrade =
segment NE.:| [] <- Just (Name.reverseSegments name)
Just segment
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
@ -3270,6 +3284,7 @@ validInputs =
updateOld,
updateOldNoPatch,
upgrade,
upgradeCommitInputPattern,
view,
viewGlobal,
viewPatch,

View File

@ -2311,6 +2311,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

@ -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,16 @@ 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
```

View File

@ -44,3 +44,48 @@ 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)
```