mirror of
https://github.com/unisonweb/unison.git
synced 2024-09-11 10:35:57 +03:00
Merge pull request #4977 from unisonweb/24-05-20-upgrade-commit
This commit is contained in:
commit
32900b13ef
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
@ -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))
|
||||
)
|
||||
|
@ -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 =
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 =
|
||||
|
@ -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
|
||||
|
@ -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 _
|
||||
|
@ -30,6 +30,7 @@ proj/main> add
|
||||
|
||||
```
|
||||
Test tab completion and fzf options of upgrade command.
|
||||
|
||||
```ucm
|
||||
proj/main> debug.tab-complete upgrade ol
|
||||
|
||||
|
@ -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
|
||||
```
|
||||
|
@ -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
|
||||
|
||||
```
|
||||
|
Loading…
Reference in New Issue
Block a user