git-hbs2-http - very basic git http dumb algorithm

for fetching repos from hbs2-git for third-side
 programs (like nix).

 pushes are not supported (yet?).
This commit is contained in:
Dmitry Zuikov 2023-07-03 09:40:58 +03:00
parent 55cdf976da
commit 0af3056664
12 changed files with 491 additions and 70 deletions

27
docs/todo/hbs2-git-ux.txt Normal file
View File

@ -0,0 +1,27 @@
FIXME: hbs2-git-config-path-ux
1. Пользователю неочевидно, где этот конфиг искать ( ~/.local/config/hbs2-git/repo/config )
2. Если пользователь переименует репозиторий, то путь к конфигу станет неактуален
и это вызовет непонятную внезапную проблему.
Решение:
Пока непонятно. Если привязаться к inode, то при перемещении между FS будет ломаться,
и кроссплатформенность пострадает.
Кажется, при инициализации репо надо заводить cookie, и сохранять её в конфиге гита (буэ).
Тогда все операции ведутся через эту cookie, все развязки в стейте через эту cookie,
тогда importRefLogNew начинает работать с многими каталогами git, стейт с одной
стороны глобальный, с другой стороны зависит от cookie.
Если пользователь специально или случайно удалит/поменяет cookie -- то импорт для этого
репозитория пойдёт заново (ну и что, мы этого и хотели. глобальный у нас
только logobject и связаные с ним вью и таблицы, а таблицы, которые отмечают
обработанные данные --- имеют локальный смысл. )
Кроме того, надо задуматься о перемещении конфига в каталог гит локально,
что бы избежать проблем 1 и 2.
Введение конфига, привязанного к cookie, еще больше усугубит проблему (1), но решит
в принципе проблему 2.
От конфига нам, кстати, вообще ничего не нужно, кроме указания ключа записи в рефлог.
Кажется, можно его просто упразднить или писать в тот же gitconfig.

View File

@ -0,0 +1,249 @@
module Main where
import HBS2.Prelude.Plated
import HBS2.Clock
import HBS2Git.App
import HBS2Git.State
import HBS2Git.Import (getLogFlags, importRefLogNew)
import HBS2Git.GitRepoLog
import HBS2.Git.Types
import HBS2.Data.Types.Refs
import HBS2.Data.Detect (deepScan,ScanLevel(..))
import HBS2.OrDie
import HBS2.System.Logger.Simple
import Codec.Compression.Zlib (compress)
import Control.Concurrent
import Control.Exception
import Control.Monad
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Resource
import Data.ByteString.Lazy.Char8 qualified as LBS
import Data.Function
import Data.Functor
import Data.HashMap.Strict as HashMap
import Data.List (sortOn)
import Data.Text.Lazy qualified as Text
import Lens.Micro.Platform
import Network.HTTP.Types.Status
import Network.Wai.Handler.Warp (Port)
import Network.Wai.Middleware.RequestLogger
import System.Directory
import System.FilePath.Posix
import System.IO.Temp
import System.Timeout (timeout)
import Text.InterpolatedString.Perl6 (qc)
import UnliftIO.Async
import Streaming.ByteString qualified as SB
import Streaming.Zip qualified as SZip
import Web.Scotty
instance Parsable RepoRef where
parseParam txt = fromStringMay @RepoRef (Text.unpack txt)
& maybe (Left [qc|{txt}|]) Right
instance Parsable GitRef where
parseParam txt = Right $ fromString (Text.unpack txt)
getAppStatePath :: MonadIO m => RepoRef -> m FilePath
getAppStatePath repo = do
stateDir <- getAppStateDir
pure $ stateDir </> show (pretty repo)
blockSource :: MonadIO m => API -> HashRef -> SB.ByteStream m ()
blockSource api h = do
deepScan ScanDeep (const none) (fromHashRef h) (readBlockFrom api . HashRef) $ \ha -> do
sec <- lift $ readBlockFrom api (HashRef ha) `orDie` [qc|missed block {pretty ha}|]
when (h /= HashRef ha) do
SB.fromLazy sec
unpackObjects :: MonadIO m => API -> HashRef -> FilePath -> m ()
unpackObjects catApi lo path = do
let logPath = path </> show (pretty lo)
let logFile = logPath </> "data"
liftIO $ createDirectoryIfMissing True logPath
flags <- getLogFlags (readBlockFrom catApi) lo
let gzipped = or $ False : [ True | "gz" <- universeBi flags ]
let unzipped = if gzipped then SZip.gunzip else id
debug $ "GOT FLAGS:" <+> pretty lo <+> pretty flags
liftIO $ do
runResourceT $ SB.writeFile logFile $ unzipped $ blockSource catApi lo
gitRepoLogScan True logFile $ \entry mlbs -> do
let tp = case view gitLogEntryType entry of
GitLogEntryCommit -> Just Commit
GitLogEntryTree -> Just Tree
GitLogEntryBlob -> Just Blob
_ -> Nothing
let r = (,,) <$> tp
<*> view gitLogEntryHash entry
<*> mlbs
maybe1 r none $ \(t, eh, lbs) -> do
let fname = logPath </> show (pretty eh)
let pref = fromString (show (pretty t) <> " " <> show (LBS.length lbs) <> "\0")
-- debug $ "writing object" <+> pretty eh <+> pretty (LBS.unpack $ LBS.takeWhile (/= '\0') pref)
let co = compress (pref <> lbs)
liftIO $ LBS.writeFile fname co
retryFor :: RealFrac r => Int -> r -> Timeout 'Seconds -> IO a -> IO (Maybe a)
retryFor num waity sleep action = timeout (ceiling $ waity * 1000000) $ go num
where
go 0 = action
go n = ( (Just <$> action) `catch` handler ) >>= maybe (go (n-1)) pure
handler (_ :: SomeException) = pause @'Seconds sleep >> pure Nothing
dumbHttpServe :: MonadUnliftIO m => Port -> m ()
dumbHttpServe pnum = do
locks <- liftIO $ newMVar (HashMap.empty @HashRef @(MVar ()))
catApi <- liftIO (retryFor 100 30 0.5 detectHBS2PeerCatAPI) `orDie` [qc|Can't locate hbs2-peer API|]
notice $ "hbs2-peer API:" <+> pretty catApi
-- TODO: lru-like-cache-for-unpacked-logs
-- Деражть кэш, обновлять в нём таймстемпы
-- доступа к логам.
-- как только запись протухла - сносить каталог
-- с логом, тогда в следующий раз будет обратно
-- распакован
updater <- async $ forever do
pause @'Seconds 300
pure ()
runResourceT do
let myTempDir = "hbs-git-http"
temp <- liftIO getCanonicalTemporaryDirectory
(_,dir) <- allocate (createTempDirectory temp myTempDir) removeDirectoryRecursive
liftIO $ scotty pnum $ do
middleware logStdoutDev
get "/:repo/info/refs" $ do
repo <- param @RepoRef "repo"
res <- liftIO do
db <- makeDbPath repo >>= dbEnvReadOnly
refs <- withDB db stateGetActualRefs
let answ = Text.unlines $ Text.pack <$> [ show (pretty h) <> "\t" <> show (pretty r) | (r,h) <- refs ]
shutdownDB db
pure answ
text res
-- | REPO OBJECT REF
get (regex "^/(.+)/(refs/.+)$") $ do
repo <- fromString <$> param "1" -- reflog
ref <- param "2" -- refname
val <- liftIO do
db <- makeDbPath repo >>= dbEnvReadOnly
debug $ "QUERY: " <+> pretty ref
val <- withDB db $ stateGetActualRefValue ref
shutdownDB db
pure val
maybe1 val (status status404) $ \x -> do
text $ Text.pack $ show $ pretty x
get "/:repo/objects/:dd/:rest" $ do
repo <- param @RepoRef "repo"
dd <- param @String "dd"
rest <- param @String "rest"
let ha = fromString ( dd <> rest )
res <- runMaybeT do
db <- liftIO $ makeDbPath repo >>= dbEnvReadOnly
lo <- MaybeT $ liftIO $ withDB db $ stateGetGitLogObject ha
shutdownDB db
let logDir = dir </> show (pretty lo)
let fname = logDir </> show (pretty ha)
here <- liftIO $ doesFileExist fname
if here then do
pure fname
else do
lock <- liftIO $ getLock locks lo
MaybeT $ liftIO $ withMVar lock $ \_ -> do
unpackObjects catApi lo dir
here1 <- liftIO $ doesFileExist fname
if here1 then do
pure (Just fname)
else do
pure Nothing
maybe1 res (status status404) $ \r -> do
addHeader "content-type" "application/octet-stream"
file r
get "/:topic/HEAD" $ do
repo <- fromString <$> param "topic"
headRef <- liftIO $ do
db <- liftIO $ makeDbPath repo >>= dbEnvReadOnly
re <- withDB db stateGetActualRefs <&> headMay . sortOn guessHead . fmap (view _1)
shutdownDB db
pure re
case headRef of
Nothing -> status status404
Just hd -> do
text [qc|ref: {show $ pretty $ hd}|]
cancel updater
where
getLock locks k = modifyMVar locks $ \m -> do
case HashMap.lookup k m of
Just lock -> return (m, lock)
Nothing -> do
lock <- newMVar ()
pure (HashMap.insert k lock m, lock)
-- TODO: hbs2-peer-http-api-reflog-fetch
-- Ручка, что бы сделать reflog fetch
-- TODO: hbs2-peer-dyn-reflog-subscribe
-- Возможность динамически подписываться на рефлоги
-- TODO: hbs2-peer-hook-on-reflog-update
-- нужен хук, который даст возможность обрабатывать апдейты
-- по нужному рефлогу. нужно много где
main :: IO ()
main = do
setLogging @DEBUG debugPrefix
setLogging @INFO defLog
setLogging @ERROR errorPrefix
setLogging @WARN warnPrefix
setLogging @NOTICE noticePrefix
setLoggingOff @TRACE
-- TODO: hbs2-git-own-config
-- FIXME: port-number-to-config
dumbHttpServe 4017

View File

@ -72,11 +72,6 @@ capabilities :: BS.ByteString
capabilities = BS.unlines ["push","fetch"] capabilities = BS.unlines ["push","fetch"]
guessHead :: GitRef -> Integer
guessHead = \case
"refs/heads/master" -> 0
"refs/heads/main" -> 0
_ -> 1
loop :: forall m . ( MonadIO m loop :: forall m . ( MonadIO m
, MonadCatch m , MonadCatch m

View File

@ -8,12 +8,13 @@ import HBS2Git.State
import HBS2Git.Config import HBS2Git.Config
import HBS2Git.ListRefs import HBS2Git.ListRefs
import Control.Monad.Catch (MonadMask)
import Data.Foldable import Data.Foldable
import Prettyprinter.Render.Terminal import Prettyprinter.Render.Terminal
data ShowObject = ShowRef RepoRef | ShowConfig data ShowObject = ShowRef RepoRef | ShowConfig
showRef :: MonadIO m => RepoRef -> App m () showRef :: (MonadIO m, MonadMask m) => RepoRef -> App m ()
showRef h = do showRef h = do
db <- makeDbPath h >>= dbEnv db <- makeDbPath h >>= dbEnv
-- FIXME: re-implement-showRef -- FIXME: re-implement-showRef
@ -27,25 +28,25 @@ showRef h = do
-- print $ pretty "last operations:" -- print $ pretty "last operations:"
-- for_ imported (\(t,h1,h2) -> print $ pretty t <+> pretty h1 <+> pretty h2) -- for_ imported (\(t,h1,h2) -> print $ pretty t <+> pretty h1 <+> pretty h2)
showRefs :: MonadIO m => App m () showRefs :: (MonadIO m, MonadMask m) => App m ()
showRefs = do showRefs = do
liftIO $ putDoc $ line <> green "References:" <> section liftIO $ putDoc $ line <> green "References:" <> section
runListRefs runListRefs
showConfig :: MonadIO m => App m () showConfig :: (MonadIO m, MonadMask m) => App m ()
showConfig = liftIO do showConfig = liftIO do
ConfigPathInfo{..} <- getConfigPathInfo ConfigPathInfo{..} <- getConfigPathInfo
cfg <- readFile configFilePath cfg <- readFile configFilePath
putDoc $ green "Config file location:" <> section <> pretty configFilePath <> section putDoc $ green "Config file location:" <> section <> pretty configFilePath <> section
putDoc $ green "Config contents:" <> line <> pretty cfg putDoc $ green "Config contents:" <> line <> pretty cfg
showSummary :: MonadIO m => App m () showSummary :: (MonadIO m, MonadMask m) => App m ()
showSummary = do showSummary = do
showRefs showRefs
liftIO $ putDoc section liftIO $ putDoc section
showConfig showConfig
runShow :: MonadIO m => Maybe ShowObject -> App m () runShow :: (MonadIO m, MonadMask m) => Maybe ShowObject -> App m ()
runShow (Just (ShowRef h)) = showRef h runShow (Just (ShowRef h)) = showRef h
runShow (Just ShowConfig) = showConfig runShow (Just ShowConfig) = showConfig
runShow Nothing = showSummary runShow Nothing = showSummary

View File

@ -173,3 +173,28 @@ executable git-remote-hbs2
hs-source-dirs: git-hbs2 hs-source-dirs: git-hbs2
default-language: Haskell2010 default-language: Haskell2010
executable git-hbs2-http
import: shared-properties
main-is: GitHttpDumbMain.hs
ghc-options:
-threaded
-rtsopts
"-with-rtsopts=-N4 -A64m -AL256m -I0"
other-modules:
-- other-extensions:
build-depends:
base, hbs2-git
, http-types
, optparse-applicative
, scotty
, wai-extra
, warp
, zlib
hs-source-dirs: git-hbs2-http
default-language: Haskell2010

View File

@ -116,6 +116,11 @@ normalizeRef (GitRef x) = GitRef "refs/heads/" <> GitRef (fromMaybe x (Text.stri
where where
strip = Text.dropWhile (=='+') strip = Text.dropWhile (=='+')
guessHead :: GitRef -> Integer
guessHead = \case
"refs/heads/master" -> 0
"refs/heads/main" -> 0
_ -> 1
shutUp :: MonadIO m => m () shutUp :: MonadIO m => m ()
shutUp = do shutUp = do

View File

@ -241,7 +241,11 @@ writeBlockIO api bs = do
readBlock :: forall m . (HasCatAPI m, MonadIO m) => HashRef -> m (Maybe ByteString) readBlock :: forall m . (HasCatAPI m, MonadIO m) => HashRef -> m (Maybe ByteString)
readBlock h = do readBlock h = do
req1 <- getHttpCatAPI req1 <- getHttpCatAPI
let reqs = req1 <> "/" <> show (pretty h) readBlockFrom req1 h
readBlockFrom :: forall m . (MonadIO m) => API -> HashRef -> m (Maybe ByteString)
readBlockFrom api h = do
let reqs = api <> "/" <> show (pretty h)
req <- liftIO $ parseRequest reqs req <- liftIO $ parseRequest reqs
resp <- httpLBS req resp <- httpLBS req

View File

@ -69,7 +69,7 @@ makeLenses 'ExportEnv
exportRefDeleted :: forall o m . ( MonadIO m exportRefDeleted :: forall o m . ( MonadIO m
, MonadCatch m , MonadCatch m
-- , MonadMask m , MonadMask m
, MonadUnliftIO m , MonadUnliftIO m
, HasCatAPI m , HasCatAPI m
, HasConf m , HasConf m

View File

@ -72,14 +72,87 @@ blockSource h = do
liftIO $ readTVarIO tsize <&> fromIntegral liftIO $ readTVarIO tsize <&> fromIntegral
getLogFlags :: MonadIO m
=> (HashRef -> m (Maybe LBS.ByteString))
-> HashRef
-> m (Maybe [Text])
getLogFlags doRead h = do
runMaybeT do
treeBs <- MaybeT $ doRead h
let something = tryDetect (fromHashRef h) treeBs
let meta = mconcat $ rights [ parseTop (Text.unpack s) | ShortMetadata s <- universeBi something ]
-- TODO: check-if-it-is-hbs2-git-log
let tp = lastMay [ "hbs2-git-push-log"
| (ListVal (Key "type:" [SymbolVal "hbs2-git-push-log"]) ) <- meta
]
guard ( tp == Just "hbs2-git-push-log" )
pure $ mconcat [ Text.splitOn ":" (Text.pack (show $ pretty s))
| (ListVal (Key "flags:" [SymbolVal s]) ) <- meta
]
class HasImportOpts a where
importForce :: a -> Bool
importDontWriteGit :: a -> Bool
instance HasImportOpts Bool where
importForce f = f
importDontWriteGit = const False
instance HasImportOpts (Bool, Bool) where
importForce = fst
importDontWriteGit = snd
-- FIXME: ASAP-will-work-only-for-one-repo
-- сейчас все транзакции помечаются, как обработанные
-- в глобальном стейте для ссылки. таким образом,
-- если мы вызвали для одного репозитория,
-- то import не будет работать для остальных, т.к. решит,
-- что всё обработано.
--
-- Решение:
-- Вариант N1. Держать стейт локально в каждом
-- каталоге git.
-- Минусы:
-- - большой оверхед по данным
-- - мусор в каталоге git
-- - например, git-hbs2-http вообще работает без "репозитория",
-- как ему быть
--
-- Вариант N2. сделать развязку через какой-то ID
-- репозитория или путь к нему.
-- Минусы:
-- - выглядит хрупко
-- - например, git-hbs2-http вообще работает без "репозитория",
-- как ему быть
--
-- Вариант N3. БД обновлять отдельно, объекты git - отдельно
-- для каждого репозитория, запоминать (где?) проигранные для
-- него логи.
-- Минусы:
-- - двойное сканирование файлов логов - получение, распаковка,
-- сканирование и т.п. сначала для БД, потом для непосредственно
-- репозитория
--
importRefLogNew :: ( MonadIO m importRefLogNew :: ( MonadIO m
, MonadUnliftIO m , MonadUnliftIO m
, MonadCatch m , MonadCatch m
, MonadMask m
, HasCatAPI m , HasCatAPI m
, HasImportOpts opts
) )
=> Bool -> RepoRef -> m () => opts -> RepoRef -> m ()
importRefLogNew opts ref = runResourceT do
let force = importForce opts
importRefLogNew force ref = runResourceT do
let myTempDir = "hbs-git" let myTempDir = "hbs-git"
temp <- liftIO getCanonicalTemporaryDirectory temp <- liftIO getCanonicalTemporaryDirectory
(_,dir) <- allocate (createTempDirectory temp myTempDir) removeDirectoryRecursive (_,dir) <- allocate (createTempDirectory temp myTempDir) removeDirectoryRecursive
@ -232,6 +305,8 @@ importRefLogNew force ref = runResourceT do
_ -> pure () _ -> pure ()
-- otherwise we wan't process those logs next time.
unless (importDontWriteGit opts) do
statePutLogImported h statePutLogImported h
statePutTranImported e statePutTranImported e
@ -242,10 +317,10 @@ importRefLogNew force ref = runResourceT do
statePutRefImported logRoot statePutRefImported logRoot
savepointRelease sp0 savepointRelease sp0
where where
writeIfNew gitHandle dir h (GitObject tp s) = do writeIfNew gitHandle dir h (GitObject tp s) = do
unless (importDontWriteGit opts) do
let nf = dir </> show (pretty h) let nf = dir </> show (pretty h)
liftIO $ LBS.writeFile nf s liftIO $ LBS.writeFile nf s
hPutStrLn gitHandle nf hPutStrLn gitHandle nf

View File

@ -73,14 +73,14 @@ runListRefs = do
where where
isHbs2 (_, b) = Text.isPrefixOf hbs2Prefix b isHbs2 (_, b) = Text.isPrefixOf hbs2Prefix b
runToolsScan :: (MonadUnliftIO m,MonadCatch m) => RepoRef -> App m () runToolsScan :: (MonadUnliftIO m,MonadCatch m,MonadMask m) => RepoRef -> App m ()
runToolsScan ref = do runToolsScan ref = do
trace $ "runToolsScan" <+> pretty ref trace $ "runToolsScan" <+> pretty ref
importRefLogNew False ref importRefLogNew False ref
shutUp shutUp
pure () pure ()
runToolsGetRefs :: (MonadUnliftIO m,MonadCatch m) => RepoRef -> App m () runToolsGetRefs :: (MonadUnliftIO m,MonadCatch m,MonadMask m) => RepoRef -> App m ()
runToolsGetRefs ref = do runToolsGetRefs ref = do
db <- makeDbPath ref >>= dbEnv db <- makeDbPath ref >>= dbEnv
refs <- withDB db stateGetActualRefs refs <- withDB db stateGetActualRefs

View File

@ -1,5 +1,6 @@
module HBS2Git.State where module HBS2Git.State where
import HBS2.Prelude
import HBS2Git.Types import HBS2Git.Types
import HBS2.Data.Types.Refs import HBS2.Data.Types.Refs
import HBS2.Git.Types import HBS2.Git.Types
@ -7,6 +8,7 @@ import HBS2.Hash
import HBS2.System.Logger.Simple import HBS2.System.Logger.Simple
import Control.Monad.Trans.Resource
import Data.Functor import Data.Functor
import Data.Function import Data.Function
import Database.SQLite.Simple import Database.SQLite.Simple
@ -27,6 +29,7 @@ import Control.Concurrent.STM
import System.IO.Unsafe import System.IO.Unsafe
import Data.Graph (graphFromEdges, topSort) import Data.Graph (graphFromEdges, topSort)
import Data.Map qualified as Map import Data.Map qualified as Map
import Lens.Micro.Platform
-- FIXME: move-orphans-to-separate-module -- FIXME: move-orphans-to-separate-module
@ -61,7 +64,7 @@ newtype DB m a =
, Functor , Functor
, Monad , Monad
, MonadIO , MonadIO
, MonadReader Connection , MonadReader DBEnv
, MonadTrans , MonadTrans
, MonadThrow , MonadThrow
, MonadCatch , MonadCatch
@ -71,32 +74,54 @@ instance (HasRefCredentials m) => HasRefCredentials (DB m) where
getCredentials = lift . getCredentials getCredentials = lift . getCredentials
setCredentials r s = lift (setCredentials r s) setCredentials r s = lift (setCredentials r s)
dbConnTV :: TVar (Maybe DBEnv) stateConnection :: MonadIO m => DB m Connection
dbConnTV = unsafePerformIO $ newTVarIO Nothing stateConnection = do
{-# NOINLINE dbConnTV #-} env <- ask
initConnection env
dbEnv :: MonadIO m => FilePath -> m DBEnv initConnection :: MonadIO m => DBEnv -> m Connection
dbEnv fp = do initConnection env = do
mco <- liftIO $ readTVarIO (view dbConn env)
case mco of
Just co -> pure co
Nothing -> do
co <- liftIO $ open (view dbFilePath env)
liftIO $ atomically $ writeTVar (view dbConn env) (Just co)
pure co
dbEnv0 :: (MonadIO m, MonadMask m) => DB m () -> FilePath -> m DBEnv
dbEnv0 dbInit fp = do
trace "dbEnv called" trace "dbEnv called"
let dir = takeDirectory fp let dir = takeDirectory fp
liftIO $ createDirectoryIfMissing True dir liftIO $ createDirectoryIfMissing True dir
mbDb <- liftIO $ readTVarIO dbConnTV env <- DBEnv fp <$> liftIO (newTVarIO Nothing)
void $ withDB env dbInit
pure env
case mbDb of dbEnv :: (MonadIO m, MonadMask m) => FilePath -> m DBEnv
Nothing -> do dbEnv = dbEnv0 stateInit
co <- liftIO $ open fp
liftIO $ atomically $ writeTVar dbConnTV (Just co)
withDB co stateInit
pure co
Just db -> pure db dbEnvReadOnly :: (MonadIO m, MonadMask m) => FilePath -> m DBEnv
dbEnvReadOnly = dbEnv0 none
withDB :: DBEnv -> DB m a -> m a withDB :: (MonadIO m, MonadMask m) => DBEnv -> DB m a -> m a
withDB env action = runReaderT (fromDB action) env withDB env action = do
conn <- initConnection env
finally (runReaderT (fromDB action) env) $ do
-- NOTE: we could not close connection here.
pure ()
shutdownDB :: MonadIO m => DBEnv -> m ()
shutdownDB env = liftIO do
co <- atomically do
conn <- readTVar (view dbConn env)
writeTVar (view dbConn env) Nothing
pure conn
maybe1 co none close
stateInit :: MonadIO m => DB m () stateInit :: MonadIO m => DB m ()
stateInit = do stateInit = do
conn <- ask conn <- stateConnection
liftIO $ execute_ conn [qc| liftIO $ execute_ conn [qc|
create table if not exists logrefval create table if not exists logrefval
( loghash text not null ( loghash text not null
@ -203,17 +228,17 @@ savepointNew = do
savepointBegin :: forall m . MonadIO m => Savepoint -> DB m () savepointBegin :: forall m . MonadIO m => Savepoint -> DB m ()
savepointBegin (Savepoint sp) = do savepointBegin (Savepoint sp) = do
conn <- ask conn <- stateConnection
liftIO $ execute_ conn [qc|SAVEPOINT {sp}|] liftIO $ execute_ conn [qc|SAVEPOINT {sp}|]
savepointRelease:: forall m . MonadIO m => Savepoint -> DB m () savepointRelease:: forall m . MonadIO m => Savepoint -> DB m ()
savepointRelease (Savepoint sp) = do savepointRelease (Savepoint sp) = do
conn <- ask conn <- stateConnection
liftIO $ execute_ conn [qc|RELEASE SAVEPOINT {sp}|] liftIO $ execute_ conn [qc|RELEASE SAVEPOINT {sp}|]
savepointRollback :: forall m . MonadIO m => Savepoint -> DB m () savepointRollback :: forall m . MonadIO m => Savepoint -> DB m ()
savepointRollback (Savepoint sp) = do savepointRollback (Savepoint sp) = do
conn <- ask conn <- stateConnection
liftIO $ execute_ conn [qc|ROLLBACK TO SAVEPOINT {sp}|] liftIO $ execute_ conn [qc|ROLLBACK TO SAVEPOINT {sp}|]
transactional :: forall a m . (MonadCatch m, MonadIO m) => DB m a -> DB m a transactional :: forall a m . (MonadCatch m, MonadIO m) => DB m a -> DB m a
@ -242,7 +267,7 @@ transactional action = do
statePutLogRefVal :: MonadIO m => (HashRef, GitRef, GitHash) -> DB m () statePutLogRefVal :: MonadIO m => (HashRef, GitRef, GitHash) -> DB m ()
statePutLogRefVal row = do statePutLogRefVal row = do
conn <- ask conn <- stateConnection
liftIO $ execute conn [qc| liftIO $ execute conn [qc|
insert into logrefval (loghash,refname,refval) values(?,?,?) insert into logrefval (loghash,refname,refval) values(?,?,?)
on conflict (loghash,refname) do nothing on conflict (loghash,refname) do nothing
@ -251,7 +276,7 @@ statePutLogRefVal row = do
statePutLogObject :: MonadIO m => (HashRef, GitObjectType, GitHash) -> DB m () statePutLogObject :: MonadIO m => (HashRef, GitObjectType, GitHash) -> DB m ()
statePutLogObject row = do statePutLogObject row = do
conn <- ask conn <- stateConnection
liftIO $ execute conn [qc| liftIO $ execute conn [qc|
insert into logobject (loghash,type,githash) values(?,?,?) insert into logobject (loghash,type,githash) values(?,?,?)
on conflict (loghash,githash) do nothing on conflict (loghash,githash) do nothing
@ -259,14 +284,24 @@ statePutLogObject row = do
stateIsLogObjectExists :: MonadIO m => GitHash -> DB m Bool stateIsLogObjectExists :: MonadIO m => GitHash -> DB m Bool
stateIsLogObjectExists h = do stateIsLogObjectExists h = do
conn <- ask conn <- stateConnection
liftIO $ query conn [qc| liftIO $ query conn [qc|
SELECT NULL FROM logobject WHERE githash = ? LIMIT 1 SELECT NULL FROM logobject WHERE githash = ? LIMIT 1
|] (Only h) <&> isJust . listToMaybe . fmap (fromOnly @(Maybe Int)) |] (Only h) <&> isJust . listToMaybe . fmap (fromOnly @(Maybe Int))
stateGetGitLogObject :: MonadIO m => GitHash -> DB m (Maybe HashRef)
stateGetGitLogObject h = do
conn <- stateConnection
liftIO $ query conn [qc|
SELECT loghash FROM logobject
WHERE githash = ? and type in ('commit', 'tree', 'blob')
LIMIT 1
|] (Only h) <&> listToMaybe . fmap fromOnly
statePutLogContextCommit :: MonadIO m => HashRef -> GitHash -> DB m () statePutLogContextCommit :: MonadIO m => HashRef -> GitHash -> DB m ()
statePutLogContextCommit loghash ctx = do statePutLogContextCommit loghash ctx = do
conn <- ask conn <- stateConnection
liftIO $ execute conn [qc| liftIO $ execute conn [qc|
insert into logobject (loghash,type,githash) values(?,'context',?) insert into logobject (loghash,type,githash) values(?,'context',?)
on conflict (loghash,githash) do nothing on conflict (loghash,githash) do nothing
@ -274,7 +309,7 @@ statePutLogContextCommit loghash ctx = do
statePutLogCommitParent :: MonadIO m => (GitHash, GitHash) -> DB m () statePutLogCommitParent :: MonadIO m => (GitHash, GitHash) -> DB m ()
statePutLogCommitParent row = do statePutLogCommitParent row = do
conn <- ask conn <- stateConnection
liftIO $ execute conn [qc| liftIO $ execute conn [qc|
insert into logcommitparent (kommit,parent) values(?,?) insert into logcommitparent (kommit,parent) values(?,?)
on conflict (kommit,parent) do nothing on conflict (kommit,parent) do nothing
@ -283,7 +318,7 @@ statePutLogCommitParent row = do
statePutLogImported :: MonadIO m => HashRef -> DB m () statePutLogImported :: MonadIO m => HashRef -> DB m ()
statePutLogImported h = do statePutLogImported h = do
conn <- ask conn <- stateConnection
liftIO $ execute conn [qc| liftIO $ execute conn [qc|
insert into logimported (hash) values(?) insert into logimported (hash) values(?)
on conflict (hash) do nothing on conflict (hash) do nothing
@ -292,7 +327,7 @@ statePutLogImported h = do
stateGetLogImported :: MonadIO m => HashRef -> DB m Bool stateGetLogImported :: MonadIO m => HashRef -> DB m Bool
stateGetLogImported h = do stateGetLogImported h = do
conn <- ask conn <- stateConnection
r <- liftIO $ query @_ @(Only Int) conn [qc| r <- liftIO $ query @_ @(Only Int) conn [qc|
select 1 from logimported where hash = ? limit 1 select 1 from logimported where hash = ? limit 1
|] (Only h) |] (Only h)
@ -301,7 +336,7 @@ stateGetLogImported h = do
statePutRefImported :: MonadIO m => HashRef -> DB m () statePutRefImported :: MonadIO m => HashRef -> DB m ()
statePutRefImported h = do statePutRefImported h = do
conn <- ask conn <- stateConnection
liftIO $ execute conn [qc| liftIO $ execute conn [qc|
insert into refimported (hash) values(?) insert into refimported (hash) values(?)
on conflict (hash) do nothing on conflict (hash) do nothing
@ -309,7 +344,7 @@ statePutRefImported h = do
stateGetRefImported :: MonadIO m => HashRef -> DB m Bool stateGetRefImported :: MonadIO m => HashRef -> DB m Bool
stateGetRefImported h = do stateGetRefImported h = do
conn <- ask conn <- stateConnection
r <- liftIO $ query @_ @(Only Int) conn [qc| r <- liftIO $ query @_ @(Only Int) conn [qc|
select 1 from refimported where hash = ? limit 1 select 1 from refimported where hash = ? limit 1
|] (Only h) |] (Only h)
@ -317,7 +352,7 @@ stateGetRefImported h = do
statePutTranImported :: MonadIO m => HashRef -> DB m () statePutTranImported :: MonadIO m => HashRef -> DB m ()
statePutTranImported h = do statePutTranImported h = do
conn <- ask conn <- stateConnection
liftIO $ execute conn [qc| liftIO $ execute conn [qc|
insert into tranimported (hash) values(?) insert into tranimported (hash) values(?)
on conflict (hash) do nothing on conflict (hash) do nothing
@ -325,7 +360,7 @@ statePutTranImported h = do
stateGetTranImported :: MonadIO m => HashRef -> DB m Bool stateGetTranImported :: MonadIO m => HashRef -> DB m Bool
stateGetTranImported h = do stateGetTranImported h = do
conn <- ask conn <- stateConnection
r <- liftIO $ query @_ @(Only Int) conn [qc| r <- liftIO $ query @_ @(Only Int) conn [qc|
select 1 from tranimported where hash = ? limit 1 select 1 from tranimported where hash = ? limit 1
|] (Only h) |] (Only h)
@ -333,7 +368,7 @@ stateGetTranImported h = do
stateGetAllTranImported :: MonadIO m => DB m [HashRef] stateGetAllTranImported :: MonadIO m => DB m [HashRef]
stateGetAllTranImported = do stateGetAllTranImported = do
conn <- ask conn <- stateConnection
results <- liftIO $ query_ conn [qc| results <- liftIO $ query_ conn [qc|
select hash from tranimported select hash from tranimported
|] |]
@ -341,21 +376,21 @@ stateGetAllTranImported = do
stateGetImportedCommits :: MonadIO m => DB m [GitHash] stateGetImportedCommits :: MonadIO m => DB m [GitHash]
stateGetImportedCommits = do stateGetImportedCommits = do
conn <- ask conn <- stateConnection
liftIO $ query_ conn [qc| liftIO $ query_ conn [qc|
select distinct(githash) from logobject where type = 'commit' select distinct(githash) from logobject where type = 'commit'
|] <&> fmap fromOnly |] <&> fmap fromOnly
stateGetActualRefs :: MonadIO m => DB m [(GitRef, GitHash)] stateGetActualRefs :: MonadIO m => DB m [(GitRef, GitHash)]
stateGetActualRefs = do stateGetActualRefs = do
conn <- ask conn <- stateConnection
liftIO $ query_ conn [qc| liftIO $ query_ conn [qc|
select refname,refval from v_refval_actual select refname,refval from v_refval_actual
|] |]
stateGetActualRefValue :: MonadIO m => GitRef -> DB m (Maybe GitHash) stateGetActualRefValue :: MonadIO m => GitRef -> DB m (Maybe GitHash)
stateGetActualRefValue ref = do stateGetActualRefValue ref = do
conn <- ask conn <- stateConnection
liftIO $ query conn [qc| liftIO $ query conn [qc|
select refval from v_refval_actual select refval from v_refval_actual
where refname = ? where refname = ?
@ -363,14 +398,14 @@ stateGetActualRefValue ref = do
stateGetLastKnownCommits :: MonadIO m => Int -> DB m [GitHash] stateGetLastKnownCommits :: MonadIO m => Int -> DB m [GitHash]
stateGetLastKnownCommits n = do stateGetLastKnownCommits n = do
conn <- ask conn <- stateConnection
liftIO $ query conn [qc| liftIO $ query conn [qc|
select kommit from logcommitdepth order by depth asc limit ?; select kommit from logcommitdepth order by depth asc limit ?;
|] (Only n) <&> fmap fromOnly |] (Only n) <&> fmap fromOnly
stateUpdateCommitDepths :: MonadIO m => DB m () stateUpdateCommitDepths :: MonadIO m => DB m ()
stateUpdateCommitDepths = do stateUpdateCommitDepths = do
conn <- ask conn <- stateConnection
sp <- savepointNew sp <- savepointNew
rows <- liftIO $ query_ @(GitHash, GitHash) conn [qc|SELECT kommit, parent FROM logcommitparent|] rows <- liftIO $ query_ @(GitHash, GitHash) conn [qc|SELECT kommit, parent FROM logcommitparent|]

View File

@ -53,7 +53,12 @@ type HBS2L4Proto = L4Proto
-- FIXME: introduce-API-type -- FIXME: introduce-API-type
type API = String type API = String
type DBEnv = Connection data DBEnv =
DBEnv { _dbFilePath :: FilePath
, _dbConn :: TVar (Maybe Connection)
}
makeLenses 'DBEnv
type RepoRef = RefLogKey Schema type RepoRef = RefLogKey Schema