2019-02-21 21:32:59 +00:00
|
|
|
{- git-annex import from remotes
|
|
|
|
-
|
|
|
|
- Copyright 2019 Joey Hess <id@joeyh.name>
|
|
|
|
-
|
|
|
|
- Licensed under the GNU AGPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
2019-02-23 19:47:55 +00:00
|
|
|
module Annex.Import (
|
|
|
|
ImportTreeConfig(..),
|
|
|
|
ImportCommitConfig(..),
|
|
|
|
buildImportCommit,
|
|
|
|
buildImportTrees
|
|
|
|
) where
|
2019-02-21 21:32:59 +00:00
|
|
|
|
|
|
|
import Annex.Common
|
|
|
|
import Types.Import
|
2019-02-22 16:41:17 +00:00
|
|
|
import Types.Remote (uuid)
|
2019-02-21 21:32:59 +00:00
|
|
|
import Git.Types
|
|
|
|
import Git.Tree
|
2019-02-22 16:41:17 +00:00
|
|
|
import Git.Sha
|
2019-02-21 21:32:59 +00:00
|
|
|
import Git.FilePath
|
2019-02-22 16:41:17 +00:00
|
|
|
import qualified Git.Ref
|
|
|
|
import qualified Git.Branch
|
|
|
|
import qualified Annex
|
2019-02-21 21:32:59 +00:00
|
|
|
import Annex.Link
|
2019-02-22 16:41:17 +00:00
|
|
|
import Annex.LockFile
|
|
|
|
import Logs.Export
|
|
|
|
import Database.Export
|
2019-02-21 21:32:59 +00:00
|
|
|
|
2019-02-23 19:47:55 +00:00
|
|
|
{- Configures how to build an import tree. -}
|
|
|
|
data ImportTreeConfig
|
|
|
|
= ImportTree
|
|
|
|
-- ^ Import the tree as-is from the remote.
|
|
|
|
| ImportSubTree TopFilePath Sha
|
|
|
|
-- ^ Import a tree from the remote and graft it into a subdirectory
|
|
|
|
-- of the existing tree whose Sha is provided, replacing anything
|
|
|
|
-- that was there before.
|
|
|
|
deriving (Show)
|
|
|
|
|
|
|
|
{- Configures how to build an import commit. -}
|
|
|
|
data ImportCommitConfig = ImportCommitConfig
|
|
|
|
{ importCommitParentRef :: Maybe Ref
|
|
|
|
-- ^ Use the commit that the Ref points to as the parent of the
|
|
|
|
-- commit. The Ref may be a branch name.
|
|
|
|
, importCommitMode :: Git.Branch.CommitMode
|
|
|
|
, importCommitMessage :: String
|
|
|
|
}
|
|
|
|
|
|
|
|
{- Builds a commit for an import from a special remote.
|
2019-02-21 21:32:59 +00:00
|
|
|
-
|
|
|
|
- When a remote provided a history of versions of files,
|
|
|
|
- builds a corresponding tree of git commits.
|
|
|
|
-
|
2019-02-23 19:47:55 +00:00
|
|
|
- When there are no changes to commit (ie, the imported tree is the same
|
|
|
|
- as the tree in the importCommitParent), returns Nothing.
|
|
|
|
-
|
2019-02-21 21:32:59 +00:00
|
|
|
- After importing from a remote, exporting the same thing back to the
|
2019-02-22 16:41:17 +00:00
|
|
|
- remote should be a no-op. So, the export log and database are
|
|
|
|
- updated to reflect the imported tree.
|
2019-02-21 21:32:59 +00:00
|
|
|
-
|
2019-02-23 19:47:55 +00:00
|
|
|
- This does not download any content from a remote. But since it needs the
|
2019-02-22 16:41:17 +00:00
|
|
|
- Key of imported files to be known, its caller will have to first download
|
2019-02-21 21:32:59 +00:00
|
|
|
- new files in order to generate keys for them.
|
|
|
|
-}
|
|
|
|
buildImportCommit
|
2019-02-22 16:41:17 +00:00
|
|
|
:: Remote
|
2019-02-23 19:47:55 +00:00
|
|
|
-> ImportTreeConfig
|
|
|
|
-> ImportCommitConfig
|
2019-02-21 21:32:59 +00:00
|
|
|
-> ImportableContents Key
|
2019-02-22 16:41:17 +00:00
|
|
|
-> Annex (Either String (Maybe Ref))
|
2019-02-23 19:47:55 +00:00
|
|
|
buildImportCommit remote importtreeconfig importcommitconfig importable =
|
|
|
|
case importCommitParentRef importcommitconfig of
|
|
|
|
Nothing -> go emptyTree Nothing
|
|
|
|
Just parentref -> inRepo (Git.Ref.sha parentref) >>= \case
|
|
|
|
Nothing -> return $
|
|
|
|
Left $ "Cannot find ref " ++ fromRef parentref
|
|
|
|
Just basecommit -> inRepo (Git.Ref.tree basecommit) >>= \case
|
|
|
|
Nothing -> go emptyTree Nothing
|
|
|
|
Just origtree -> go origtree (Just basecommit)
|
2019-02-21 21:32:59 +00:00
|
|
|
where
|
2019-02-23 19:47:55 +00:00
|
|
|
basetree = case importtreeconfig of
|
|
|
|
ImportTree -> emptyTree
|
|
|
|
ImportSubTree _ sha -> sha
|
|
|
|
subdir = case importtreeconfig of
|
|
|
|
ImportTree -> Nothing
|
|
|
|
ImportSubTree dir _ -> Just dir
|
|
|
|
|
|
|
|
go origtree basecommit = do
|
|
|
|
imported@(History finaltree _) <-
|
|
|
|
buildImportTrees basetree subdir importable
|
|
|
|
mkcommits origtree basecommit imported >>= \case
|
|
|
|
Nothing -> return (Right Nothing)
|
|
|
|
Just finalcommit -> do
|
|
|
|
updateexportdb finaltree
|
|
|
|
updateexportlog finaltree
|
|
|
|
return (Right (Just finalcommit))
|
|
|
|
|
|
|
|
mkcommits origtree basecommit (History importedtree hs) = do
|
|
|
|
parents <- catMaybes <$> mapM (mkcommits origtree basecommit) hs
|
|
|
|
if importedtree == origtree && null parents
|
|
|
|
then return Nothing -- no changes to commit
|
2019-02-22 16:41:17 +00:00
|
|
|
else do
|
2019-02-22 16:44:22 +00:00
|
|
|
let commitparents = if null parents
|
2019-02-23 19:47:55 +00:00
|
|
|
then catMaybes [basecommit]
|
2019-02-22 16:44:22 +00:00
|
|
|
else parents
|
2019-02-23 19:47:55 +00:00
|
|
|
commit <- inRepo $ Git.Branch.commitTree
|
|
|
|
(importCommitMode importcommitconfig)
|
|
|
|
(importCommitMessage importcommitconfig)
|
|
|
|
commitparents
|
|
|
|
importedtree
|
2019-02-22 16:41:17 +00:00
|
|
|
return (Just commit)
|
2019-02-23 19:47:55 +00:00
|
|
|
|
2019-02-22 16:41:17 +00:00
|
|
|
updateexportdb importedtree =
|
|
|
|
withExclusiveLock (gitAnnexExportLock (uuid remote)) $ do
|
|
|
|
db <- openDb (uuid remote)
|
|
|
|
prevtree <- liftIO $ fromMaybe emptyTree
|
|
|
|
<$> getExportTreeCurrent db
|
|
|
|
when (importedtree /= prevtree) $ do
|
|
|
|
updateExportTree db prevtree importedtree
|
|
|
|
liftIO $ recordExportTreeCurrent db importedtree
|
|
|
|
-- TODO: addExportedLocation etc
|
|
|
|
liftIO $ flushDbQueue db
|
2019-02-23 19:47:55 +00:00
|
|
|
|
2019-02-22 16:41:17 +00:00
|
|
|
updateexportlog importedtree = do
|
|
|
|
old <- getExport (uuid remote)
|
|
|
|
recordExport (uuid remote) $ ExportChange
|
|
|
|
{ oldTreeish = exportedTreeishes old
|
|
|
|
, newTreeish = importedtree
|
|
|
|
}
|
2019-02-21 21:32:59 +00:00
|
|
|
|
|
|
|
data History t = History t [History t]
|
2019-02-22 16:41:17 +00:00
|
|
|
deriving (Show)
|
2019-02-21 21:32:59 +00:00
|
|
|
|
2019-02-22 16:41:17 +00:00
|
|
|
{- Builds a history of git trees reflecting the ImportableContents.
|
|
|
|
-
|
|
|
|
- When a subdir is provided, imported tree is grafted into the basetree at
|
|
|
|
- that location, replacing any object that was there.
|
|
|
|
-}
|
2019-02-21 21:32:59 +00:00
|
|
|
buildImportTrees
|
2019-02-22 16:41:17 +00:00
|
|
|
:: Ref
|
|
|
|
-> Maybe TopFilePath
|
2019-02-21 21:32:59 +00:00
|
|
|
-> ImportableContents Key
|
|
|
|
-> Annex (History Sha)
|
2019-02-22 16:41:17 +00:00
|
|
|
buildImportTrees basetree msubdir importable = History
|
|
|
|
<$> (go (importableContents importable) =<< Annex.gitRepo)
|
|
|
|
<*> mapM (buildImportTrees basetree msubdir) (importableHistory importable)
|
2019-02-21 21:32:59 +00:00
|
|
|
where
|
2019-02-22 16:41:17 +00:00
|
|
|
go ls repo = withMkTreeHandle repo $ \hdl -> do
|
|
|
|
importtree <- liftIO . recordTree' hdl
|
|
|
|
. treeItemsToTree
|
|
|
|
=<< mapM mktreeitem ls
|
|
|
|
case msubdir of
|
|
|
|
Nothing -> return importtree
|
|
|
|
Just subdir -> liftIO $
|
|
|
|
graftTree' importtree subdir basetree repo hdl
|
2019-02-21 21:32:59 +00:00
|
|
|
mktreeitem (loc, k) = do
|
|
|
|
let lf = fromImportLocation loc
|
2019-02-22 16:41:17 +00:00
|
|
|
let treepath = asTopFilePath lf
|
|
|
|
let topf = asTopFilePath $
|
|
|
|
maybe lf (\sd -> getTopFilePath sd </> lf) msubdir
|
2019-02-21 21:32:59 +00:00
|
|
|
relf <- fromRepo $ fromTopFilePath topf
|
|
|
|
symlink <- calcRepo $ gitAnnexLink relf k
|
|
|
|
linksha <- hashSymlink symlink
|
2019-02-22 16:41:17 +00:00
|
|
|
return $ TreeItem treepath (fromTreeItemType TreeSymlink) linksha
|