{- git ls-tree interface
-
- - Copyright 2011-2019 Joey Hess <id@joeyh.name>
+ - Copyright 2011-2020 Joey Hess <id@joeyh.name>
-
- Licensed under the GNU AGPL version 3 or higher.
-}
-{-# LANGUAGE BangPatterns #-}
-
module Git.LsTree (
TreeItem(..),
LsTreeMode(..),
lsTree,
lsTree',
+ lsTreeStrict,
+ lsTreeStrict',
lsTreeParams,
lsTreeFiles,
parseLsTree,
import System.Posix.Types
import qualified Data.ByteString as S
import qualified Data.ByteString.Lazy as L
+import qualified Data.Attoparsec.ByteString as AS
import qualified Data.Attoparsec.ByteString.Lazy as A
import qualified Data.Attoparsec.ByteString.Char8 as A8
, typeobj :: S.ByteString
, sha :: Ref
, file :: TopFilePath
- } deriving Show
+ } deriving (Show)
data LsTreeMode = LsTreeRecursive | LsTreeNonRecursive
(l, cleanup) <- pipeNullSplit (lsTreeParams lsmode t ps) repo
return (rights (map parseLsTree l), cleanup)
+lsTreeStrict :: LsTreeMode -> Ref -> Repo -> IO [TreeItem]
+lsTreeStrict = lsTreeStrict' []
+
+lsTreeStrict' :: [CommandParam] -> LsTreeMode -> Ref -> Repo -> IO [TreeItem]
+lsTreeStrict' ps lsmode t repo = rights . map parseLsTreeStrict
+ <$> pipeNullSplitStrict (lsTreeParams lsmode t ps) repo
+
lsTreeParams :: LsTreeMode -> Ref -> [CommandParam] -> [CommandParam]
lsTreeParams lsmode r ps =
[ Param "ls-tree"
A.Done _ r -> Right r
A.Fail _ _ err -> Left err
+parseLsTreeStrict :: S.ByteString -> Either String TreeItem
+parseLsTreeStrict b = go (AS.parse parserLsTree b)
+ where
+ go (AS.Done _ r) = Right r
+ go (AS.Fail _ _ err) = Left err
+ go (AS.Partial c) = go (c mempty)
+
{- Parses a line of ls-tree output, in format:
- mode SP type SP sha TAB file
-
fmtObjectType TreeObject = "tree"
{- Types of items in a tree. -}
-data TreeItemType = TreeFile | TreeExecutable | TreeSymlink | TreeSubmodule
+data TreeItemType
+ = TreeFile
+ | TreeExecutable
+ | TreeSymlink
+ | TreeSubmodule
+ | TreeSubtree
deriving (Eq, Show)
{- Git uses magic numbers to denote the type of a tree item. -}
readTreeItemType "100755" = Just TreeExecutable
readTreeItemType "120000" = Just TreeSymlink
readTreeItemType "160000" = Just TreeSubmodule
+readTreeItemType "040000" = Just TreeSubtree
readTreeItemType _ = Nothing
fmtTreeItemType :: TreeItemType -> S.ByteString
fmtTreeItemType TreeExecutable = "100755"
fmtTreeItemType TreeSymlink = "120000"
fmtTreeItemType TreeSubmodule = "160000"
+fmtTreeItemType TreeSubtree = "040000"
toTreeItemType :: FileMode -> Maybe TreeItemType
toTreeItemType 0o100644 = Just TreeFile
toTreeItemType 0o100755 = Just TreeExecutable
toTreeItemType 0o120000 = Just TreeSymlink
toTreeItemType 0o160000 = Just TreeSubmodule
+toTreeItemType 0o040000 = Just TreeSubtree
toTreeItemType _ = Nothing
fromTreeItemType :: TreeItemType -> FileMode
fromTreeItemType TreeExecutable = 0o100755
fromTreeItemType TreeSymlink = 0o120000
fromTreeItemType TreeSubmodule = 0o160000
+fromTreeItemType TreeSubtree = 0o040000
data Commit = Commit
{ commitTree :: Sha
import Types.Creds
import Types.Import
import qualified Git
+import qualified Git.LsTree as LsTree
+import Git.Types (toTreeItemType, TreeItemType(..))
+import Git.FilePath
import Config
import Config.Cost
import Annex.SpecialRemote.Config
import Types.ProposedAccepted
import Utility.Metered
import qualified Remote.Helper.ThirdPartyPopulated as ThirdPartyPopulated
+import Logs.Export
import Text.Read
import Control.Exception (evaluate)
, checkPresentCheap = borgLocal borgrepo
, exportActions = exportUnsupported
, importActions = ImportActions
- { listImportableContents = listImportableContentsM borgrepo
+ { listImportableContents = listImportableContentsM u borgrepo
, importKey = Just ThirdPartyPopulated.importKey
, retrieveExportWithContentIdentifier = retrieveExportWithContentIdentifierM borgrepo
, checkPresentExportWithContentIdentifier = checkPresentExportWithContentIdentifierM borgrepo
borgLocal :: BorgRepo -> Bool
borgLocal = notElem ':'
--- TODO avoid rescanning archives that have already been scanned
---
-- XXX the tree generated by using this does not seem to get grafted into
-- the git-annex branch, so would be subject to being lost to GC.
-- Is this a general problem affecting importtree too?
-listImportableContentsM :: BorgRepo -> Annex (Maybe (ImportableContents (ContentIdentifier, ByteSize)))
-listImportableContentsM borgrepo = prompt $ do
+listImportableContentsM :: UUID -> BorgRepo -> Annex (Maybe (ImportableContents (ContentIdentifier, ByteSize)))
+listImportableContentsM u borgrepo = prompt $ do
+ imported <- getImported u
ls <- withborglist borgrepo "{barchive}{NUL}" $ \as ->
- forM as $ \archive ->
- let archive' = borgrepo ++ "::" ++ decodeBS' archive
- in withborglist archive' "{size}{NUL}{path}{NUL}" $
- liftIO . evaluate . force . parsefilelist archive
- return (Just (mkimportablecontents ls))
+ forM as $ \archivename ->
+ case M.lookup archivename imported of
+ Just getfast -> getfast
+ Nothing ->
+ let archive = borgrepo ++ "::" ++ decodeBS' archivename
+ in withborglist archive "{size}{NUL}{path}{NUL}" $
+ liftIO . evaluate . force . parsefilelist archivename
+ return $ Just $ mkimportablecontents ls
where
withborglist what format a = do
let p = (proc "borg" ["list", what, "--format", format])
forceSuccessProcess p pid
a l `finally` cleanup
- parsefilelist archive (bsz:f:rest) = case readMaybe (fromRawFilePath bsz) of
- Nothing -> parsefilelist archive rest
+ parsefilelist archivename (bsz:f:rest) = case readMaybe (fromRawFilePath bsz) of
+ Nothing -> parsefilelist archivename rest
Just sz ->
- let loc = genImportLocation archive f
+ let loc = genImportLocation archivename f
-- This does a little unncessary work to parse the
-- key, which is then thrown away. But, it lets the
-- file list be shrank down to only the ones that are
-- the rest of the files in memory.
in case ThirdPartyPopulated.importKey' loc sz of
Just k -> (loc, (borgContentIdentifier, sz))
- : parsefilelist archive rest
- Nothing -> parsefilelist archive rest
+ : parsefilelist archivename rest
+ Nothing -> parsefilelist archivename rest
parsefilelist _ _ = []
-- importableHistory is not used for retrieval, so is not
-- populated with old archives. Instead, a tree of archives
-- is constructed, by genImportLocation including the archive
-- name in the ImportLocation.
- mkimportablecontents (l) = ImportableContents
+ mkimportablecontents l = ImportableContents
{ importableContents = concat l
, importableHistory = []
}
-
+
+-- We do not need a ContentIdentifier in order to retrieve a file from
+-- borg; the ImportLocation contains all that's needed. So, this is left
+-- empty.
+borgContentIdentifier :: ContentIdentifier
+borgContentIdentifier = ContentIdentifier mempty
+
-- Borg does not allow / in the name of an archive, so the archive
-- name will always be the first directory in the ImportLocation.
--
go (archivename:rest) = (archivename, P.joinPath rest)
go _ = giveup $ "Unable to parse import location " ++ fromRawFilePath (fromImportLocation loc)
--- We do not need a ContentIdentifier in order to retrieve a file from
--- borg; the ImportLocation contains all that's needed. So, this is left
--- empty.
-borgContentIdentifier :: ContentIdentifier
-borgContentIdentifier = ContentIdentifier mempty
+-- Since the ImportLocation starts with the archive name, a list of all
+-- archive names we've already imported can be found by just listing the
+-- last imported tree. And the contents of those archives can be retrieved
+-- by listing the subtree recursively, which will likely be quite a lot
+-- faster than running borg.
+getImported :: UUID -> Annex (M.Map BorgArchiveName (Annex [(ImportLocation, (ContentIdentifier, ByteSize))]))
+getImported u = M.unions <$> (mapM go . exportedTreeishes =<< getExport u)
+ where
+ go t = M.fromList . mapMaybe mk
+ <$> inRepo (LsTree.lsTreeStrict LsTree.LsTreeNonRecursive t)
+
+ mk ti
+ | toTreeItemType (LsTree.mode ti) == Just TreeSubtree = Just
+ ( getTopFilePath (LsTree.file ti)
+ , getcontents
+ (getTopFilePath (LsTree.file ti))
+ (LsTree.sha ti)
+ )
+ | otherwise = Nothing
+
+ getcontents archivename t = mapMaybe (mkcontents archivename)
+ <$> inRepo (LsTree.lsTreeStrict LsTree.LsTreeRecursive t)
+
+ mkcontents archivename ti = do
+ let f = ThirdPartyPopulated.fromThirdPartyImportLocation $
+ mkImportLocation $ getTopFilePath $ LsTree.file ti
+ k <- deserializeKey' (P.takeFileName f)
+ return
+ ( genImportLocation archivename f
+ ,
+ ( borgContentIdentifier
+ -- defaulting to 0 size is ok, this size
+ -- only gets used by
+ -- ThirdPartyPopulated.importKey,
+ -- which ignores the size when the key
+ -- does not have a size.
+ , fromMaybe 0 (fromKey keySize k)
+ )
+ )
retrieveExportWithContentIdentifierM :: BorgRepo -> ImportLocation -> ContentIdentifier -> FilePath -> Annex Key -> MeterUpdate -> Annex Key
retrieveExportWithContentIdentifierM borgrepo loc cid dest k p = error "TODO"