mirror of
https://github.com/unisonweb/unison.git
synced 2024-09-11 02:25:39 +03:00
Merge pull request #4986 from unisonweb/24-05-20-merge-commit
This commit is contained in:
commit
2e38bf860d
@ -47,7 +47,8 @@ module Unison.Cli.ProjectUtils
|
||||
findTemporaryBranchName,
|
||||
expectLatestReleaseBranchName,
|
||||
|
||||
-- * Upgrade branch utils
|
||||
-- * Merge/upgrade branch utils
|
||||
getMergeBranchParent,
|
||||
getUpgradeBranchParent,
|
||||
)
|
||||
where
|
||||
@ -411,6 +412,17 @@ expectLatestReleaseBranchName remoteProject =
|
||||
Nothing -> Cli.returnEarly (Output.ProjectHasNoReleases remoteProject.projectName)
|
||||
Just semver -> pure (UnsafeProjectBranchName ("releases/" <> into @Text semver))
|
||||
|
||||
-- | @getMergeBranchParent branch@ returns the parent branch of a "merge" branch.
|
||||
--
|
||||
-- When a merge fails, we put you on a branch called `merge-<source>-into-<target>`. That's a "merge" branch. It's not
|
||||
-- currently distinguished in the database, so we first just switch on whether its name begins with "merge-". 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 "merge-whatever" for whatever reason.
|
||||
getMergeBranchParent :: Sqlite.ProjectBranch -> Maybe ProjectBranchId
|
||||
getMergeBranchParent branch = do
|
||||
guard ("merge-" `Text.isPrefixOf` into @Text branch.name)
|
||||
branch.parentBranchId
|
||||
|
||||
-- | @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
|
||||
|
@ -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.CommitMerge (handleCommitMerge)
|
||||
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
|
||||
@ -362,6 +363,7 @@ loop e = do
|
||||
then Success
|
||||
else BranchEmpty branchEmpty
|
||||
MergeI branch -> handleMerge branch
|
||||
MergeCommitI -> handleCommitMerge
|
||||
MergeLocalBranchI src0 dest0 mergeMode -> do
|
||||
description <- inputDescription input
|
||||
src0 <- ProjectUtils.expectLooseCodeOrProjectBranch src0
|
||||
@ -1115,6 +1117,7 @@ inputDescription input =
|
||||
ListDependentsI {} -> wat
|
||||
LoadI {} -> wat
|
||||
MergeI {} -> wat
|
||||
MergeCommitI {} -> wat
|
||||
NamesI {} -> wat
|
||||
NamespaceDependenciesI {} -> wat
|
||||
PopBranchI {} -> wat
|
||||
|
@ -124,7 +124,8 @@ doCreateBranch createFrom project newBranchName description = do
|
||||
CreateFrom'Branch (ProjectAndBranch _ sourceBranch)
|
||||
| sourceBranch.projectId == project.projectId -> Just sourceBranch.branchId
|
||||
_ -> Nothing
|
||||
doCreateBranch' sourceNamespaceObject parentBranchId project (pure newBranchName) description
|
||||
(newBranchId, _) <- doCreateBranch' sourceNamespaceObject parentBranchId project (pure newBranchName) description
|
||||
pure newBranchId
|
||||
|
||||
doCreateBranch' ::
|
||||
Branch IO ->
|
||||
@ -132,10 +133,10 @@ doCreateBranch' ::
|
||||
Sqlite.Project ->
|
||||
Sqlite.Transaction ProjectBranchName ->
|
||||
Text ->
|
||||
Cli ProjectBranchId
|
||||
Cli (ProjectBranchId, ProjectBranchName)
|
||||
doCreateBranch' sourceNamespaceObject parentBranchId project getNewBranchName description = do
|
||||
let projectId = project ^. #projectId
|
||||
newBranchId <-
|
||||
(newBranchId, newBranchName) <-
|
||||
Cli.runTransactionWithRollback \rollback -> do
|
||||
newBranchName <- getNewBranchName
|
||||
Queries.projectBranchExistsByName projectId newBranchName >>= \case
|
||||
@ -152,9 +153,9 @@ doCreateBranch' sourceNamespaceObject parentBranchId project getNewBranchName de
|
||||
parentBranchId = parentBranchId
|
||||
}
|
||||
Queries.setMostRecentBranch projectId newBranchId
|
||||
pure newBranchId
|
||||
pure (newBranchId, newBranchName)
|
||||
|
||||
let newBranchPath = ProjectUtils.projectBranchPath (ProjectAndBranch projectId newBranchId)
|
||||
_ <- Cli.updateAt description newBranchPath (const sourceNamespaceObject)
|
||||
Cli.cd newBranchPath
|
||||
pure newBranchId
|
||||
pure (newBranchId, newBranchName)
|
||||
|
@ -0,0 +1,52 @@
|
||||
-- | @merge.commit@ handler.
|
||||
module Unison.Codebase.Editor.HandleInput.CommitMerge
|
||||
( handleCommitMerge,
|
||||
)
|
||||
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 (..))
|
||||
|
||||
-- Note: this implementation is similar to `upgrade.commit`.
|
||||
|
||||
handleCommitMerge :: Cli ()
|
||||
handleCommitMerge = do
|
||||
(mergeProjectAndBranch, _path) <- ProjectUtils.expectCurrentProjectBranch
|
||||
|
||||
-- Assert that this is a "merge" branch and get its parent, which is the branch we were on when we ran `merge`.
|
||||
|
||||
parentBranchId <-
|
||||
ProjectUtils.getMergeBranchParent mergeProjectAndBranch.branch
|
||||
& onNothing (Cli.returnEarly Output.NoMergeInProgress)
|
||||
parentBranch <-
|
||||
Cli.runTransaction do
|
||||
Queries.expectProjectBranch mergeProjectAndBranch.project.projectId parentBranchId
|
||||
|
||||
let parentProjectAndBranch =
|
||||
ProjectAndBranch mergeProjectAndBranch.project parentBranch
|
||||
|
||||
-- Switch to the parent
|
||||
|
||||
ProjectSwitch.switchToProjectBranch (ProjectUtils.justTheIds parentProjectAndBranch)
|
||||
|
||||
-- Merge the merge branch into the parent
|
||||
|
||||
Merge.doMergeLocalBranch
|
||||
TwoWay
|
||||
{ alice = parentProjectAndBranch,
|
||||
bob = mergeProjectAndBranch
|
||||
}
|
||||
|
||||
-- Delete the merge branch
|
||||
|
||||
DeleteBranch.doDeleteProjectBranch mergeProjectAndBranch
|
@ -17,6 +17,8 @@ import Unison.Merge.TwoWay (TwoWay (..))
|
||||
import Unison.Prelude
|
||||
import Unison.Project (ProjectAndBranch (..))
|
||||
|
||||
-- Note: this implementation is similar to `merge.commit`.
|
||||
|
||||
handleCommitUpgrade :: Cli ()
|
||||
handleCommitUpgrade = do
|
||||
(upgradeProjectAndBranch, _path) <- ProjectUtils.expectCurrentProjectBranch
|
||||
|
@ -200,235 +200,236 @@ doMerge info = do
|
||||
|
||||
Cli.Env {codebase} <- ask
|
||||
|
||||
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 ()
|
||||
finalOutput <-
|
||||
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
|
||||
done (Output.MergeAlreadyUpToDate2 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 ()
|
||||
-- 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)
|
||||
done (Output.MergeSuccessFastForward mergeSourceAndTarget)
|
||||
|
||||
-- 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 <-
|
||||
Cli.runTransaction do
|
||||
alice <- causals.alice.value
|
||||
bob <- causals.bob.value
|
||||
lca <- for causals.lca \causal -> causal.value
|
||||
pure TwoOrThreeWay {lca, alice, bob}
|
||||
-- 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)
|
||||
-- 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
|
||||
done (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 typeName conName1 conName2 ->
|
||||
Output.MergeConstructorAlias who typeName conName1 conName2
|
||||
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)
|
||||
-- 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 ->
|
||||
done 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 ->
|
||||
done case err of
|
||||
IncoherentDeclReason'ConstructorAlias typeName conName1 conName2 ->
|
||||
Output.MergeConstructorAlias who typeName conName1 conName2
|
||||
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)
|
||||
(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}
|
||||
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)
|
||||
pure (defns3, declNameLookups, lcaDeclToConstructors)
|
||||
|
||||
let defns = ThreeWay.forgetLca defns3
|
||||
let defns = ThreeWay.forgetLca defns3
|
||||
|
||||
liftIO (debugFunctions.debugDefns defns3 declNameLookups lcaDeclToConstructors)
|
||||
liftIO (debugFunctions.debugDefns defns3 declNameLookups lcaDeclToConstructors)
|
||||
|
||||
-- Diff LCA->Alice and LCA->Bob
|
||||
diffs <- Cli.runTransaction (Merge.nameBasedNamespaceDiff db declNameLookups lcaDeclToConstructors defns3)
|
||||
-- Diff LCA->Alice and LCA->Bob
|
||||
diffs <- Cli.runTransaction (Merge.nameBasedNamespaceDiff db declNameLookups lcaDeclToConstructors defns3)
|
||||
|
||||
liftIO (debugFunctions.debugDiffs diffs)
|
||||
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)
|
||||
-- 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) ->
|
||||
done (Output.MergeConflictedAliases who name1 name2)
|
||||
|
||||
-- Combine the LCA->Alice and LCA->Bob diffs together
|
||||
let diff = combineDiffs diffs
|
||||
-- Combine the LCA->Alice and LCA->Bob diffs together
|
||||
let diff = combineDiffs diffs
|
||||
|
||||
liftIO (debugFunctions.debugCombinedDiff diff)
|
||||
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)
|
||||
-- Partition the combined diff into the conflicted things and the unconflicted things
|
||||
(conflicts, unconflicts) <-
|
||||
partitionCombinedDiffs defns declNameLookups diff & onLeft \name ->
|
||||
done (Output.MergeConflictInvolvingBuiltin name)
|
||||
|
||||
liftIO (debugFunctions.debugPartitionedDiff conflicts unconflicts)
|
||||
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)
|
||||
-- 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)
|
||||
liftIO (debugFunctions.debugDependents dependents)
|
||||
|
||||
let stageOne :: DefnsF (Map Name) Referent TypeReference
|
||||
stageOne =
|
||||
makeStageOne
|
||||
declNameLookups
|
||||
conflicts
|
||||
unconflicts
|
||||
dependents
|
||||
(bimap BiMultimap.range BiMultimap.range defns3.lca)
|
||||
let stageOne :: DefnsF (Map Name) Referent TypeReference
|
||||
stageOne =
|
||||
makeStageOne
|
||||
declNameLookups
|
||||
conflicts
|
||||
unconflicts
|
||||
dependents
|
||||
(bimap BiMultimap.range BiMultimap.range defns3.lca)
|
||||
|
||||
liftIO (debugFunctions.debugStageOne stageOne)
|
||||
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)
|
||||
-- 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)
|
||||
-- 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
|
||||
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 (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 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
|
||||
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
|
||||
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
|
||||
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)
|
||||
case maybeTypecheckedUnisonFile of
|
||||
Nothing -> do
|
||||
Cli.Env {writeSource} <- ask
|
||||
(_temporaryBranchId, temporaryBranchName) <-
|
||||
HandleInput.Branch.doCreateBranch'
|
||||
(Branch.mergeNode stageOneBranch parents.alice parents.bob)
|
||||
(Just info.alice.projectAndBranch.branch.branchId)
|
||||
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)
|
||||
pure (Output.MergeFailure scratchFilePath mergeSourceAndTarget temporaryBranchName)
|
||||
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)
|
||||
pure (Output.MergeSuccess mergeSourceAndTarget)
|
||||
|
||||
Cli.respond finalOutput
|
||||
|
||||
doMergeLocalBranch :: TwoWay (ProjectAndBranch Project ProjectBranch) -> Cli ()
|
||||
doMergeLocalBranch branches = do
|
||||
@ -886,7 +887,7 @@ findTemporaryBranchName projectId mergeSourceAndTarget = do
|
||||
-- Fails if there is a conflicted name.
|
||||
loadNamespaceDefinitions ::
|
||||
forall m.
|
||||
Monad m =>
|
||||
(Monad m) =>
|
||||
(V2.Referent -> m Referent) ->
|
||||
V2.Branch m ->
|
||||
m (Either ConflictedName (Nametree (DefnsF (Map NameSegment) Referent TypeReference)))
|
||||
|
@ -228,6 +228,7 @@ data Input
|
||||
!Bool -- Remind the user to use `lib.install` next time, not `pull`?
|
||||
!(ProjectAndBranch ProjectName (Maybe ProjectBranchNameOrLatestRelease))
|
||||
| UpgradeCommitI
|
||||
| MergeCommitI
|
||||
deriving (Eq, Show)
|
||||
|
||||
-- | The source of a `branch` command: what to make the new branch from.
|
||||
|
@ -392,7 +392,7 @@ data Output
|
||||
| UpgradeFailure !ProjectBranchName !ProjectBranchName !FilePath !NameSegment !NameSegment
|
||||
| UpgradeSuccess !NameSegment !NameSegment
|
||||
| LooseCodePushDeprecated
|
||||
| MergeFailure !FilePath !MergeSourceAndTarget
|
||||
| MergeFailure !FilePath !MergeSourceAndTarget !ProjectBranchName
|
||||
| MergeSuccess !MergeSourceAndTarget
|
||||
| MergeSuccessFastForward !MergeSourceAndTarget
|
||||
| MergeConflictedAliases !MergeSourceOrTarget !Name !Name
|
||||
@ -408,6 +408,7 @@ data Output
|
||||
| NoUpgradeInProgress
|
||||
| UseLibInstallNotPull !(ProjectAndBranch ProjectName ProjectBranchName)
|
||||
| PullIntoMissingBranch !(ReadRemoteNamespace Share.RemoteProjectBranch) !(ProjectAndBranch (Maybe ProjectName) ProjectBranchName)
|
||||
| NoMergeInProgress
|
||||
|
||||
data UpdateOrUpgrade = UOUUpdate | UOUUpgrade
|
||||
|
||||
@ -647,6 +648,7 @@ isFailure o = case o of
|
||||
NoUpgradeInProgress {} -> True
|
||||
UseLibInstallNotPull {} -> False
|
||||
PullIntoMissingBranch {} -> True
|
||||
NoMergeInProgress {} -> True
|
||||
|
||||
isNumberedFailure :: NumberedOutput -> Bool
|
||||
isNumberedFailure = \case
|
||||
|
@ -73,6 +73,7 @@ module Unison.CommandLine.InputPatterns
|
||||
load,
|
||||
makeStandalone,
|
||||
mergeBuiltins,
|
||||
mergeCommitInputPattern,
|
||||
mergeIOBuiltins,
|
||||
mergeInputPattern,
|
||||
mergeOldInputPattern,
|
||||
@ -2126,6 +2127,48 @@ mergeInputPattern =
|
||||
_ -> Left $ I.help mergeInputPattern
|
||||
}
|
||||
|
||||
mergeCommitInputPattern :: InputPattern
|
||||
mergeCommitInputPattern =
|
||||
InputPattern
|
||||
{ patternName = "merge.commit",
|
||||
aliases = ["commit.merge"],
|
||||
visibility = I.Visible,
|
||||
args = [],
|
||||
help =
|
||||
let mainBranch = UnsafeProjectBranchName "main"
|
||||
tempBranch = UnsafeProjectBranchName "merge-topic-into-main"
|
||||
in P.wrap
|
||||
( makeExample' mergeCommitInputPattern
|
||||
<> "merges a temporary branch created by the"
|
||||
<> makeExample' mergeInputPattern
|
||||
<> "command back into its parent branch, and removes the temporary branch."
|
||||
)
|
||||
<> P.newline
|
||||
<> P.newline
|
||||
<> P.wrap
|
||||
( "For example, if you've done"
|
||||
<> makeExample mergeInputPattern ["topic"]
|
||||
<> "from"
|
||||
<> P.group (prettyProjectBranchName mainBranch <> ",")
|
||||
<> "then"
|
||||
<> makeExample' mergeCommitInputPattern
|
||||
<> "is equivalent to doing"
|
||||
)
|
||||
<> P.newline
|
||||
<> P.newline
|
||||
<> P.indentN
|
||||
2
|
||||
( P.bulleted
|
||||
[ makeExampleNoBackticks projectSwitch [prettySlashProjectBranchName mainBranch],
|
||||
makeExampleNoBackticks mergeInputPattern [prettySlashProjectBranchName tempBranch],
|
||||
makeExampleNoBackticks deleteBranch [prettySlashProjectBranchName tempBranch]
|
||||
]
|
||||
),
|
||||
parse = \case
|
||||
[] -> Right Input.MergeCommitI
|
||||
_ -> Left (I.help mergeCommitInputPattern)
|
||||
}
|
||||
|
||||
parseLooseCodeOrProject :: String -> Maybe Input.LooseCodeOrProject
|
||||
parseLooseCodeOrProject inputString =
|
||||
case (asLooseCode, asBranch) of
|
||||
@ -3326,6 +3369,7 @@ validInputs =
|
||||
mergeOldPreviewInputPattern,
|
||||
mergeOldSquashInputPattern,
|
||||
mergeInputPattern,
|
||||
mergeCommitInputPattern,
|
||||
names False, -- names
|
||||
names True, -- names.global
|
||||
namespaceDependencies,
|
||||
|
@ -2082,14 +2082,32 @@ notifyUser dir = \case
|
||||
"",
|
||||
"Your non-project code is still available to pull from Share, and you can pull it into a local namespace using `pull myhandle.public`"
|
||||
]
|
||||
MergeFailure path aliceAndBob ->
|
||||
pure . P.wrap $
|
||||
"I couldn't automatically merge"
|
||||
<> prettyMergeSource aliceAndBob.bob
|
||||
<> "into"
|
||||
<> P.group (prettyProjectAndBranchName aliceAndBob.alice <> ".")
|
||||
<> "However, I've added the definitions that need attention to the top of"
|
||||
<> P.group (prettyFilePath path <> ".")
|
||||
MergeFailure path aliceAndBob temp ->
|
||||
pure $
|
||||
P.lines $
|
||||
[ P.wrap $
|
||||
"I couldn't automatically merge"
|
||||
<> prettyMergeSource aliceAndBob.bob
|
||||
<> "into"
|
||||
<> P.group (prettyProjectAndBranchName aliceAndBob.alice <> ".")
|
||||
<> "However, I've added the definitions that need attention to the top of"
|
||||
<> P.group (prettyFilePath path <> "."),
|
||||
"",
|
||||
P.wrap "When you're done, you can run",
|
||||
"",
|
||||
P.indentN 2 (IP.makeExampleNoBackticks IP.mergeCommitInputPattern []),
|
||||
"",
|
||||
P.wrap $
|
||||
"to merge your changes back into"
|
||||
<> prettyProjectBranchName aliceAndBob.alice.branch
|
||||
<> "and delete the temporary branch. Or, if you decide to cancel the merge instead, you can run",
|
||||
"",
|
||||
P.indentN 2 (IP.makeExampleNoBackticks IP.deleteBranch [prettySlashProjectBranchName temp]),
|
||||
"",
|
||||
P.wrap $
|
||||
"to delete the temporary branch and switch back to"
|
||||
<> P.group (prettyProjectBranchName aliceAndBob.alice.branch <> ".")
|
||||
]
|
||||
MergeSuccess aliceAndBob ->
|
||||
pure . P.wrap $
|
||||
"I merged"
|
||||
@ -2133,6 +2151,8 @@ notifyUser dir = \case
|
||||
case maybeTargetProject of
|
||||
Nothing -> prettyProjectBranchName targetBranch
|
||||
Just targetProject -> prettyProjectAndBranchName (ProjectAndBranch targetProject targetBranch)
|
||||
NoMergeInProgress ->
|
||||
pure . P.wrap $ "It doesn't look like there's a merge 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.CommitMerge
|
||||
Unison.Codebase.Editor.HandleInput.CommitUpgrade
|
||||
Unison.Codebase.Editor.HandleInput.DebugDefinition
|
||||
Unison.Codebase.Editor.HandleInput.DebugFoldRanges
|
||||
|
@ -1,7 +1,12 @@
|
||||
# The `merge` command
|
||||
|
||||
The `merge` command merges together two branches in the same project: the current branch (unspecificed), and the target
|
||||
branch. For example, to merge `topic` into `main`, switch to `main` and run `merge topic`.
|
||||
branch. For example, to merge `topic` into `main`, switch to `main` and run `merge topic`:
|
||||
|
||||
```ucm:error
|
||||
.> help merge
|
||||
.> help merge.commit
|
||||
```
|
||||
|
||||
Let's see a simple unconflicted merge in action: Alice (us) and Bob (them) add different terms. The merged result
|
||||
contains both additions.
|
||||
@ -949,6 +954,94 @@ project/alice> merge bob
|
||||
.> project.delete project
|
||||
```
|
||||
|
||||
## `merge.commit` example (success)
|
||||
|
||||
After merge conflicts are resolved, you can use `merge.commit` rather than `switch` + `merge` + `branch.delete` to
|
||||
"commit" your changes.
|
||||
|
||||
```ucm:hide
|
||||
.> project.create-empty project
|
||||
project/main> builtins.mergeio
|
||||
```
|
||||
|
||||
Original branch:
|
||||
```unison:hide
|
||||
foo : Text
|
||||
foo = "old foo"
|
||||
```
|
||||
|
||||
```ucm:hide
|
||||
project/main> add
|
||||
project/main> branch alice
|
||||
```
|
||||
|
||||
Alice's changes:
|
||||
```unison:hide
|
||||
foo : Text
|
||||
foo = "alices foo"
|
||||
```
|
||||
|
||||
```ucm:hide
|
||||
project/alice> update
|
||||
project/main> branch bob
|
||||
```
|
||||
|
||||
Bob's changes:
|
||||
|
||||
```unison:hide
|
||||
foo : Text
|
||||
foo = "bobs foo"
|
||||
```
|
||||
|
||||
Attempt to merge:
|
||||
|
||||
```ucm:hide
|
||||
project/bob> update
|
||||
```
|
||||
```ucm:error
|
||||
project/alice> merge /bob
|
||||
```
|
||||
|
||||
Resolve conflicts and commit:
|
||||
|
||||
```unison
|
||||
foo : Text
|
||||
foo = "alice and bobs foo"
|
||||
```
|
||||
|
||||
```ucm
|
||||
project/merge-bob-into-alice> update
|
||||
project/merge-bob-into-alice> merge.commit
|
||||
project/alice> view foo
|
||||
project/alice> branches
|
||||
```
|
||||
|
||||
```ucm:hide
|
||||
.> project.delete project
|
||||
```
|
||||
|
||||
## `merge.commit` example (failure)
|
||||
|
||||
`merge.commit` can only be run on a "merge branch".
|
||||
|
||||
```ucm:hide
|
||||
.> project.create-empty project
|
||||
project/main> builtins.mergeio
|
||||
```
|
||||
|
||||
```ucm
|
||||
project/main> branch topic
|
||||
```
|
||||
|
||||
```ucm:error
|
||||
project/topic> merge.commit
|
||||
```
|
||||
|
||||
```ucm:hide
|
||||
.> project.delete project
|
||||
```
|
||||
|
||||
|
||||
## Precondition violations
|
||||
|
||||
There are a number of conditions under which we can't perform a merge, and the user will have to fix up the namespace(s) manually before attempting to merge again.
|
||||
|
@ -1,8 +1,29 @@
|
||||
# The `merge` command
|
||||
|
||||
The `merge` command merges together two branches in the same project: the current branch (unspecificed), and the target
|
||||
branch. For example, to merge `topic` into `main`, switch to `main` and run `merge topic`.
|
||||
branch. For example, to merge `topic` into `main`, switch to `main` and run `merge topic`:
|
||||
|
||||
```ucm
|
||||
.> help merge
|
||||
|
||||
merge
|
||||
`merge /branch` merges `branch` into the current branch
|
||||
|
||||
.> help merge.commit
|
||||
|
||||
merge.commit (or commit.merge)
|
||||
`merge.commit` merges a temporary branch created by the `merge`
|
||||
command back into its parent branch, and removes the temporary
|
||||
branch.
|
||||
|
||||
For example, if you've done `merge topic` from main, then
|
||||
`merge.commit` is equivalent to doing
|
||||
|
||||
* switch /main
|
||||
* merge /merge-topic-into-main
|
||||
* delete.branch /merge-topic-into-main
|
||||
|
||||
```
|
||||
Let's see a simple unconflicted merge in action: Alice (us) and Bob (them) add different terms. The merged result
|
||||
contains both additions.
|
||||
|
||||
@ -488,6 +509,18 @@ project/alice> merge /bob
|
||||
I couldn't automatically merge project/bob into project/alice.
|
||||
However, I've added the definitions that need attention to the
|
||||
top of scratch.u.
|
||||
|
||||
When you're done, you can run
|
||||
|
||||
merge.commit
|
||||
|
||||
to merge your changes back into alice and delete the temporary
|
||||
branch. Or, if you decide to cancel the merge instead, you can
|
||||
run
|
||||
|
||||
delete.branch /merge-bob-into-alice
|
||||
|
||||
to delete the temporary branch and switch back to alice.
|
||||
|
||||
```
|
||||
```unison:added-by-ucm scratch.u
|
||||
@ -529,6 +562,18 @@ project/alice> merge /bob
|
||||
I couldn't automatically merge project/bob into project/alice.
|
||||
However, I've added the definitions that need attention to the
|
||||
top of scratch.u.
|
||||
|
||||
When you're done, you can run
|
||||
|
||||
merge.commit
|
||||
|
||||
to merge your changes back into alice and delete the temporary
|
||||
branch. Or, if you decide to cancel the merge instead, you can
|
||||
run
|
||||
|
||||
delete.branch /merge-bob-into-alice
|
||||
|
||||
to delete the temporary branch and switch back to alice.
|
||||
|
||||
```
|
||||
```unison:added-by-ucm scratch.u
|
||||
@ -582,6 +627,18 @@ project/alice> merge /bob
|
||||
I couldn't automatically merge project/bob into project/alice.
|
||||
However, I've added the definitions that need attention to the
|
||||
top of scratch.u.
|
||||
|
||||
When you're done, you can run
|
||||
|
||||
merge.commit
|
||||
|
||||
to merge your changes back into alice and delete the temporary
|
||||
branch. Or, if you decide to cancel the merge instead, you can
|
||||
run
|
||||
|
||||
delete.branch /merge-bob-into-alice
|
||||
|
||||
to delete the temporary branch and switch back to alice.
|
||||
|
||||
```
|
||||
```unison:added-by-ucm scratch.u
|
||||
@ -639,6 +696,18 @@ project/alice> merge /bob
|
||||
I couldn't automatically merge project/bob into project/alice.
|
||||
However, I've added the definitions that need attention to the
|
||||
top of scratch.u.
|
||||
|
||||
When you're done, you can run
|
||||
|
||||
merge.commit
|
||||
|
||||
to merge your changes back into alice and delete the temporary
|
||||
branch. Or, if you decide to cancel the merge instead, you can
|
||||
run
|
||||
|
||||
delete.branch /merge-bob-into-alice
|
||||
|
||||
to delete the temporary branch and switch back to alice.
|
||||
|
||||
```
|
||||
```unison:added-by-ucm scratch.u
|
||||
@ -676,6 +745,18 @@ project/alice> merge /bob
|
||||
I couldn't automatically merge project/bob into project/alice.
|
||||
However, I've added the definitions that need attention to the
|
||||
top of scratch.u.
|
||||
|
||||
When you're done, you can run
|
||||
|
||||
merge.commit
|
||||
|
||||
to merge your changes back into alice and delete the temporary
|
||||
branch. Or, if you decide to cancel the merge instead, you can
|
||||
run
|
||||
|
||||
delete.branch /merge-bob-into-alice
|
||||
|
||||
to delete the temporary branch and switch back to alice.
|
||||
|
||||
```
|
||||
```unison:added-by-ucm scratch.u
|
||||
@ -717,6 +798,18 @@ project/alice> merge bob
|
||||
I couldn't automatically merge project/bob into project/alice.
|
||||
However, I've added the definitions that need attention to the
|
||||
top of scratch.u.
|
||||
|
||||
When you're done, you can run
|
||||
|
||||
merge.commit
|
||||
|
||||
to merge your changes back into alice and delete the temporary
|
||||
branch. Or, if you decide to cancel the merge instead, you can
|
||||
run
|
||||
|
||||
delete.branch /merge-bob-into-alice
|
||||
|
||||
to delete the temporary branch and switch back to alice.
|
||||
|
||||
```
|
||||
```unison:added-by-ucm scratch.u
|
||||
@ -751,6 +844,18 @@ project/alice> merge bob
|
||||
I couldn't automatically merge project/bob into project/alice.
|
||||
However, I've added the definitions that need attention to the
|
||||
top of scratch.u.
|
||||
|
||||
When you're done, you can run
|
||||
|
||||
merge.commit
|
||||
|
||||
to merge your changes back into alice and delete the temporary
|
||||
branch. Or, if you decide to cancel the merge instead, you can
|
||||
run
|
||||
|
||||
delete.branch /merge-bob-into-alice
|
||||
|
||||
to delete the temporary branch and switch back to alice.
|
||||
|
||||
```
|
||||
```unison:added-by-ucm scratch.u
|
||||
@ -797,6 +902,18 @@ project/alice> merge bob
|
||||
I couldn't automatically merge project/bob into project/alice.
|
||||
However, I've added the definitions that need attention to the
|
||||
top of scratch.u.
|
||||
|
||||
When you're done, you can run
|
||||
|
||||
merge.commit
|
||||
|
||||
to merge your changes back into alice and delete the temporary
|
||||
branch. Or, if you decide to cancel the merge instead, you can
|
||||
run
|
||||
|
||||
delete.branch /merge-bob-into-alice
|
||||
|
||||
to delete the temporary branch and switch back to alice.
|
||||
|
||||
```
|
||||
```unison:added-by-ucm scratch.u
|
||||
@ -859,6 +976,18 @@ project/alice> merge bob
|
||||
I couldn't automatically merge project/bob into project/alice.
|
||||
However, I've added the definitions that need attention to the
|
||||
top of scratch.u.
|
||||
|
||||
When you're done, you can run
|
||||
|
||||
merge.commit
|
||||
|
||||
to merge your changes back into alice and delete the temporary
|
||||
branch. Or, if you decide to cancel the merge instead, you can
|
||||
run
|
||||
|
||||
delete.branch /merge-bob-into-alice
|
||||
|
||||
to delete the temporary branch and switch back to alice.
|
||||
|
||||
```
|
||||
```unison:added-by-ucm scratch.u
|
||||
@ -906,6 +1035,18 @@ project/alice> merge bob
|
||||
I couldn't automatically merge project/bob into project/alice.
|
||||
However, I've added the definitions that need attention to the
|
||||
top of scratch.u.
|
||||
|
||||
When you're done, you can run
|
||||
|
||||
merge.commit
|
||||
|
||||
to merge your changes back into alice and delete the temporary
|
||||
branch. Or, if you decide to cancel the merge instead, you can
|
||||
run
|
||||
|
||||
delete.branch /merge-bob-into-alice
|
||||
|
||||
to delete the temporary branch and switch back to alice.
|
||||
|
||||
```
|
||||
```unison:added-by-ucm scratch.u
|
||||
@ -929,6 +1070,129 @@ bob _ = 19
|
||||
|
||||
```
|
||||
|
||||
## `merge.commit` example (success)
|
||||
|
||||
After merge conflicts are resolved, you can use `merge.commit` rather than `switch` + `merge` + `branch.delete` to
|
||||
"commit" your changes.
|
||||
|
||||
Original branch:
|
||||
```unison
|
||||
foo : Text
|
||||
foo = "old foo"
|
||||
```
|
||||
|
||||
Alice's changes:
|
||||
```unison
|
||||
foo : Text
|
||||
foo = "alices foo"
|
||||
```
|
||||
|
||||
Bob's changes:
|
||||
|
||||
```unison
|
||||
foo : Text
|
||||
foo = "bobs foo"
|
||||
```
|
||||
|
||||
Attempt to merge:
|
||||
|
||||
```ucm
|
||||
project/alice> merge /bob
|
||||
|
||||
I couldn't automatically merge project/bob into project/alice.
|
||||
However, I've added the definitions that need attention to the
|
||||
top of scratch.u.
|
||||
|
||||
When you're done, you can run
|
||||
|
||||
merge.commit
|
||||
|
||||
to merge your changes back into alice and delete the temporary
|
||||
branch. Or, if you decide to cancel the merge instead, you can
|
||||
run
|
||||
|
||||
delete.branch /merge-bob-into-alice
|
||||
|
||||
to delete the temporary branch and switch back to alice.
|
||||
|
||||
```
|
||||
```unison:added-by-ucm scratch.u
|
||||
-- project/alice
|
||||
foo : Text
|
||||
foo = "alices foo"
|
||||
|
||||
-- project/bob
|
||||
foo : Text
|
||||
foo = "bobs foo"
|
||||
|
||||
|
||||
```
|
||||
|
||||
Resolve conflicts and commit:
|
||||
|
||||
```unison
|
||||
foo : Text
|
||||
foo = "alice and bobs foo"
|
||||
```
|
||||
|
||||
```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 new definitions are ok to `add`:
|
||||
|
||||
foo : Text
|
||||
|
||||
```
|
||||
```ucm
|
||||
project/merge-bob-into-alice> update
|
||||
|
||||
Okay, I'm searching the branch for code that needs to be
|
||||
updated...
|
||||
|
||||
Done.
|
||||
|
||||
project/merge-bob-into-alice> merge.commit
|
||||
|
||||
I fast-forward merged project/merge-bob-into-alice into
|
||||
project/alice.
|
||||
|
||||
project/alice> view foo
|
||||
|
||||
foo : Text
|
||||
foo = "alice and bobs foo"
|
||||
|
||||
project/alice> branches
|
||||
|
||||
Branch Remote branch
|
||||
1. alice
|
||||
2. bob
|
||||
3. main
|
||||
|
||||
```
|
||||
## `merge.commit` example (failure)
|
||||
|
||||
`merge.commit` can only be run on a "merge branch".
|
||||
|
||||
```ucm
|
||||
project/main> branch topic
|
||||
|
||||
Done. I've created the topic branch based off of main.
|
||||
|
||||
Tip: To merge your work back into the main branch, first
|
||||
`switch /main` then `merge /topic`.
|
||||
|
||||
```
|
||||
```ucm
|
||||
project/topic> merge.commit
|
||||
|
||||
It doesn't look like there's a merge in progress.
|
||||
|
||||
```
|
||||
## Precondition violations
|
||||
|
||||
There are a number of conditions under which we can't perform a merge, and the user will have to fix up the namespace(s) manually before attempting to merge again.
|
||||
|
Loading…
Reference in New Issue
Block a user