Merge pull request #2910 from unisonweb/cp/allow-pushing-v2-2

Allow pushing to v2 codebases
This commit is contained in:
Chris Penner 2022-02-17 15:30:47 -06:00 committed by GitHub
commit f713eb0e9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 79 additions and 69 deletions

View File

@ -112,7 +112,6 @@ withOpenOrCreateCodebase cbInit debugName initOptions action = do
CreateWhenMissing dir ->
createCodebaseWithResult cbInit debugName dir (\codebase -> action (CreatedCodebase, dir, codebase))
Left err@OpenCodebaseUnknownSchemaVersion{} -> pure (Left (resolvedPath, InitErrorOpen err))
Left err@OpenCodebaseOther{} -> pure (Left (resolvedPath, InitErrorOpen err))
createCodebase :: MonadIO m => Init m v a -> DebugName -> CodebasePath -> (Codebase m v a -> m r) -> m (Either Pretty r)
createCodebase cbInit debugName path action = do
@ -122,13 +121,6 @@ createCodebase cbInit debugName path action = do
P.wrap $
"It looks like there's already a codebase in: "
<> prettyDir
CreateCodebaseOther message ->
P.wrap ("I ran into an error when creating the codebase in: " <> prettyDir)
<> P.newline
<> P.newline
<> "The error was:"
<> P.newline
<> P.indentN 2 message
-- * compatibility stuff

View File

@ -10,4 +10,3 @@ type Pretty = P.Pretty P.ColorText
data CreateCodebaseError
= CreateCodebaseAlreadyExists
| CreateCodebaseOther Pretty

View File

@ -5,13 +5,11 @@ module Unison.Codebase.Init.OpenCodebaseError
where
import Unison.Prelude
import Unison.Util.Pretty (ColorText, Pretty)
-- | An error that can occur when attempting to open a codebase.
data OpenCodebaseError
= -- | The codebase doesn't exist.
OpenCodebaseDoesntExist
-- | The codebase exists, but its schema version is unknown to this application.
| OpenCodebaseUnknownSchemaVersion Word64
| OpenCodebaseOther (Pretty ColorText)
| -- | The codebase exists, but its schema version is unknown to this application.
OpenCodebaseUnknownSchemaVersion Word64
deriving stock (Show)

View File

@ -126,15 +126,40 @@ makeCodebaseDirPath root = root </> ".unison" </> "v2"
init :: HasCallStack => (MonadUnliftIO m) => Codebase.Init m Symbol Ann
init = Codebase.Init
{ withOpenCodebase=withCodebaseOrError
, withCreatedCodebase=createCodebaseOrError
, withCreatedCodebase=withCreatedCodebase'
, codebasePath=makeCodebaseDirPath
}
where
withCreatedCodebase' debugName path action =
createCodebaseOrError debugName path (action . fst)
data CodebaseStatus =
ExistingCodebase
| CreatedCodebase
deriving (Eq)
-- | Open the codebase at the given location, or create it if one doesn't already exist.
withOpenOrCreateCodebase ::
MonadUnliftIO m =>
Codebase.DebugName ->
CodebasePath ->
LocalOrRemote ->
((CodebaseStatus, Codebase m Symbol Ann, Connection) -> m r) ->
m (Either Codebase1.OpenCodebaseError r)
withOpenOrCreateCodebase debugName codebasePath localOrRemote action = do
createCodebaseOrError debugName codebasePath (action' CreatedCodebase) >>= \case
Left (Codebase1.CreateCodebaseAlreadyExists) -> do
sqliteCodebase debugName codebasePath localOrRemote (action' ExistingCodebase)
Right r -> pure (Right r)
where
action' openOrCreate (codebase, conn) = action (openOrCreate, codebase, conn)
-- | Create a codebase at the given location.
createCodebaseOrError ::
(MonadUnliftIO m) =>
Codebase.DebugName ->
CodebasePath ->
(Codebase m Symbol Ann -> m r) ->
((Codebase m Symbol Ann, Connection) -> m r) ->
m (Either Codebase1.CreateCodebaseError r)
createCodebaseOrError debugName path action = do
ifM
@ -153,18 +178,6 @@ createCodebaseOrError debugName path action = do
Left schemaVersion -> error ("Failed to open codebase with schema version: " ++ show schemaVersion ++ ", which is unexpected because I just created this codebase.")
Right result -> pure (Right result)
withOpenOrCreateCodebaseConnection ::
(MonadUnliftIO m) =>
Codebase.DebugName ->
FilePath ->
(Connection -> m r) ->
m r
withOpenOrCreateCodebaseConnection debugName path action = do
unlessM
(doesFileExist $ makeCodebasePath path)
(initSchemaIfNotExist path)
withConnection debugName path action
-- | Use the codebase in the provided path.
-- The codebase is automatically closed when the action completes or throws an exception.
withCodebaseOrError ::
@ -178,7 +191,7 @@ withCodebaseOrError debugName dir action = do
doesFileExist (makeCodebasePath dir) >>= \case
False -> pure (Left Codebase1.OpenCodebaseDoesntExist)
True ->
sqliteCodebase debugName dir Local action <&> mapLeft \(SchemaVersion n) -> Codebase1.OpenCodebaseUnknownSchemaVersion n
sqliteCodebase debugName dir Local (action . fst)
initSchemaIfNotExist :: MonadIO m => FilePath -> m ()
initSchemaIfNotExist path = liftIO do
@ -285,8 +298,8 @@ sqliteCodebase ::
CodebasePath ->
-- | When local, back up the existing codebase before migrating, in case there's a catastrophic bug in the migration.
LocalOrRemote ->
(Codebase m Symbol Ann -> m r) ->
m (Either SchemaVersion r)
((Codebase m Symbol Ann, Connection) -> m r) ->
m (Either Codebase1.OpenCodebaseError r)
sqliteCodebase debugName root localOrRemote action = do
Monad.when debug $ traceM $ "sqliteCodebase " ++ debugName ++ " " ++ root
withConnection debugName root $ \conn -> do
@ -835,7 +848,7 @@ sqliteCodebase debugName root localOrRemote action = do
-- Migrate if necessary.
(`finally` finalizer) $ runReaderT Q.schemaVersion conn >>= \case
SchemaVersion 2 -> Right <$> action codebase
SchemaVersion 2 -> Right <$> action (codebase, conn)
SchemaVersion 1 -> do
liftIO $ putStrLn ("Migrating from schema version 1 -> 2.")
case localOrRemote of
@ -851,8 +864,8 @@ sqliteCodebase debugName root localOrRemote action = do
Remote -> pure ()
migrateSchema12 conn codebase
-- it's ok to pass codebase along; whatever it cached during the migration won't break anything
Right <$> action codebase
v -> pure $ Left v
Right <$> action (codebase, conn)
v -> pure . Left $ Codebase1.OpenCodebaseUnknownSchemaVersion (fromIntegral v)
-- well one or the other. :zany_face: the thinking being that they wouldn't hash-collide
termExists', declExists' :: MonadIO m => Hash -> ReaderT Connection (ExceptT Ops.Error m) Bool
@ -1100,7 +1113,7 @@ viewRemoteBranch' (repo, sbh, path) gitBranchBehavior action = UnliftIO.try $ do
-- Unexpected error from sqlite
_ -> throwIO sqlError
result <- sqliteCodebase "viewRemoteBranch.gitCache" remotePath Remote \codebase -> do
result <- sqliteCodebase "viewRemoteBranch.gitCache" remotePath Remote \(codebase, _conn) -> do
-- try to load the requested branch from it
branch <- time "Git fetch (sbh)" $ case sbh of
-- no sub-branch was specified, so use the root.
@ -1127,7 +1140,7 @@ viewRemoteBranch' (repo, sbh, path) gitBranchBehavior action = UnliftIO.try $ do
Just b -> action (b, remotePath)
Nothing -> throwIO . C.GitCodebaseError $ GitError.CouldntFindRemoteBranch repo path
case result of
Left schemaVersion -> throwIO . C.GitSqliteCodebaseError $ GitError.UnrecognizedSchemaVersion repo remotePath schemaVersion
Left err -> throwIO . C.GitSqliteCodebaseError $ C.gitErrorFromOpenCodebaseError remotePath repo err
Right inner -> pure inner
-- Push a branch to a repo. Optionally attempt to set the branch as the new root, which fails if the branch is not after
@ -1140,7 +1153,7 @@ pushGitBranch ::
WriteRepo ->
PushGitBranchOpts ->
m (Either C.GitError ())
pushGitBranch srcConn branch repo (PushGitBranchOpts setRoot _syncMode) = mapLeft C.GitProtocolError <$> do
pushGitBranch srcConn branch repo (PushGitBranchOpts setRoot _syncMode) = UnliftIO.try do
-- Pull the latest remote into our git cache
-- Use a local git clone to copy this git repo into a temp-dir
-- Delete the codebase in our temp-dir
@ -1155,41 +1168,45 @@ pushGitBranch srcConn branch repo (PushGitBranchOpts setRoot _syncMode) = mapLef
-- Delete the temp-dir.
--
-- set up the cache dir
withRepo readRepo Git.CreateBranchIfMissing $ \pushStaging -> do
withOpenOrCreateCodebaseConnection @m "push.dest" (Git.gitDirToPath pushStaging) $ \destConn -> do
flip runReaderT destConn $ Q.withSavepoint_ @(ReaderT _ m) "push" $ do
throwExceptT $ doSync (Git.gitDirToPath pushStaging) srcConn destConn
throwEitherMWith C.GitProtocolError . withRepo readRepo Git.CreateBranchIfMissing $ \pushStaging -> do
throwEitherMWith (C.GitSqliteCodebaseError . C.gitErrorFromOpenCodebaseError (Git.gitDirToPath pushStaging) readRepo)
. withOpenOrCreateCodebase "push.dest" (Git.gitDirToPath pushStaging) Remote $ \(codebaseStatus, _destCodebase, destConn) -> do
flip runReaderT destConn $ Q.withSavepoint_ @(ReaderT _ m) "push" $ do
throwExceptT $ doSync codebaseStatus (Git.gitDirToPath pushStaging) srcConn destConn
void $ push pushStaging repo
where
readRepo :: ReadRepo
readRepo = writeToRead repo
doSync :: FilePath -> Connection -> Connection -> ExceptT C.GitError (ReaderT Connection m) ()
doSync remotePath srcConn destConn = do
doSync :: CodebaseStatus -> FilePath -> Connection -> Connection -> ExceptT C.GitError (ReaderT Connection m) ()
doSync codebaseStatus remotePath srcConn destConn = do
_ <- flip State.execStateT emptySyncProgressState $
syncInternal syncProgress srcConn destConn (Branch.transform (lift . lift . lift) branch)
when setRoot $ overwriteRoot remotePath destConn
overwriteRoot :: forall m. MonadIO m => FilePath -> Connection -> ExceptT C.GitError m ()
overwriteRoot remotePath destConn = do
when setRoot $ overwriteRoot codebaseStatus remotePath destConn
overwriteRoot :: forall m. MonadIO m => CodebaseStatus -> FilePath -> Connection -> ExceptT C.GitError m ()
overwriteRoot codebaseStatus remotePath destConn = do
let newRootHash = Branch.headHash branch
-- the call to runDB "handles" the possible DB error by bombing
maybeOldRootHash <- fmap Cv.branchHash2to1 <$> runDB destConn Ops.loadMaybeRootCausalHash
case maybeOldRootHash of
Nothing -> runDB destConn $ do
setRepoRoot newRootHash
(Just oldRootHash) -> runDB destConn $ do
before oldRootHash newRootHash >>= \case
Nothing ->
error $
"I couldn't find the hash " ++ show newRootHash
++ " that I just synced to the cached copy of "
++ repoString
++ " in "
++ show remotePath
++ "."
Just False -> do
lift . lift . throwError . C.GitProtocolError $ GitError.PushDestinationHasNewStuff repo
Just True -> do
case codebaseStatus of
ExistingCodebase -> do
-- the call to runDB "handles" the possible DB error by bombing
maybeOldRootHash <- fmap Cv.branchHash2to1 <$> runDB destConn Ops.loadMaybeRootCausalHash
case maybeOldRootHash of
Nothing -> runDB destConn $ do
setRepoRoot newRootHash
(Just oldRootHash) -> runDB destConn $ do
before oldRootHash newRootHash >>= \case
Nothing ->
error $
"I couldn't find the hash " ++ show newRootHash
++ " that I just synced to the cached copy of "
++ repoString
++ " in "
++ show remotePath
++ "."
Just False -> do
lift . lift . throwError . C.GitProtocolError $ GitError.PushDestinationHasNewStuff repo
Just True -> pure ()
CreatedCodebase -> pure ()
runDB destConn $ setRepoRoot newRootHash
repoString = Text.unpack $ printWriteRepo repo
setRepoRoot :: forall m. Q.DB m => Branch.Hash -> m ()

View File

@ -9,17 +9,18 @@ module Unison.Codebase.Type
GitError (..),
GetRootBranchError (..),
SyncToDir,
gitErrorFromOpenCodebaseError,
)
where
import Unison.Codebase.Branch (Branch)
import qualified Unison.Codebase.Branch as Branch
import Unison.Codebase.Editor.RemoteRepo (ReadRemoteNamespace, WriteRepo)
import Unison.Codebase.Editor.RemoteRepo (ReadRemoteNamespace, WriteRepo, ReadRepo)
import Unison.Codebase.GitError (GitCodebaseError, GitProtocolError)
import Unison.Codebase.Patch (Patch)
import qualified Unison.Codebase.Reflog as Reflog
import Unison.Codebase.ShortBranchHash (ShortBranchHash)
import Unison.Codebase.SqliteCodebase.GitError (GitSqliteCodebaseError)
import Unison.Codebase.SqliteCodebase.GitError (GitSqliteCodebaseError (..))
import Unison.Codebase.SyncMode (SyncMode)
import Unison.CodebasePath (CodebasePath)
import Unison.DataDeclaration (Decl)
@ -33,6 +34,7 @@ import Unison.Term (Term)
import Unison.Type (Type)
import qualified Unison.WatchKind as WK
import qualified Unison.Codebase.Editor.Git as Git
import Unison.Codebase.Init.OpenCodebaseError (OpenCodebaseError(..))
type SyncToDir m =
CodebasePath -> -- dest codebase
@ -177,3 +179,9 @@ data GitError
deriving (Show)
instance Exception GitError
gitErrorFromOpenCodebaseError :: CodebasePath -> ReadRepo -> OpenCodebaseError -> GitSqliteCodebaseError
gitErrorFromOpenCodebaseError path repo = \case
OpenCodebaseDoesntExist -> NoDatabaseFile repo path
OpenCodebaseUnknownSchemaVersion v ->
UnrecognizedSchemaVersion repo path (fromIntegral v)

View File

@ -57,7 +57,6 @@ initCodebase fmt = do
result <- Codebase.Init.withCreatedCodebase cbInit "ucm-test" tmp (const $ pure ())
case result of
Left CreateCodebaseAlreadyExists -> fail $ P.toANSI 80 "Codebase already exists"
Left (CreateCodebaseOther p) -> fail $ P.toANSI 80 p
Right _ -> pure $ Codebase tmp fmt
deleteCodebase :: Codebase -> IO ()

View File

@ -386,9 +386,6 @@ getCodebaseOrExit codebasePathOption action = do
, "Please upgrade your version of UCM."
])
InitErrorOpen (OpenCodebaseOther errMessage) ->
pure errMessage
FoundV1Codebase ->
pure (P.lines
[ "Found a v1 codebase at " <> pDir <> ".",