2017-08-29 18:58:38 +00:00
|
|
|
{- git-annex command
|
|
|
|
-
|
|
|
|
- Copyright 2017 Joey Hess <id@joeyh.name>
|
|
|
|
-
|
|
|
|
- Licensed under the GNU GPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
2017-09-19 18:20:47 +00:00
|
|
|
{-# LANGUAGE TupleSections, BangPatterns #-}
|
2017-09-07 18:32:47 +00:00
|
|
|
|
2017-08-29 18:58:38 +00:00
|
|
|
module Command.Export where
|
|
|
|
|
|
|
|
import Command
|
2017-09-12 18:19:26 +00:00
|
|
|
import qualified Annex
|
2017-08-29 18:58:38 +00:00
|
|
|
import qualified Git
|
|
|
|
import qualified Git.DiffTree
|
2017-08-31 17:29:54 +00:00
|
|
|
import qualified Git.LsTree
|
2017-08-31 19:41:48 +00:00
|
|
|
import qualified Git.Ref
|
2017-08-31 17:29:54 +00:00
|
|
|
import Git.Types
|
2017-08-29 18:58:38 +00:00
|
|
|
import Git.FilePath
|
2017-08-31 19:41:48 +00:00
|
|
|
import Git.Sha
|
2017-08-29 18:58:38 +00:00
|
|
|
import Types.Remote
|
2017-09-15 20:34:45 +00:00
|
|
|
import Types.Export
|
2017-09-18 17:57:25 +00:00
|
|
|
import Annex.Export
|
2017-08-29 18:58:38 +00:00
|
|
|
import Annex.Content
|
2017-09-20 16:56:17 +00:00
|
|
|
import Annex.Transfer
|
2017-08-29 18:58:38 +00:00
|
|
|
import Annex.CatFile
|
2017-09-18 16:12:11 +00:00
|
|
|
import Annex.LockFile
|
2017-08-31 16:37:25 +00:00
|
|
|
import Logs.Location
|
2017-08-31 19:41:48 +00:00
|
|
|
import Logs.Export
|
2017-09-04 17:52:22 +00:00
|
|
|
import Database.Export
|
2017-08-29 18:58:38 +00:00
|
|
|
import Messages.Progress
|
2017-09-19 17:05:43 +00:00
|
|
|
import Config
|
2017-08-29 18:58:38 +00:00
|
|
|
import Utility.Tmp
|
2017-09-20 16:56:17 +00:00
|
|
|
import Utility.Metered
|
2017-08-29 18:58:38 +00:00
|
|
|
|
|
|
|
import qualified Data.ByteString.Lazy as L
|
2017-09-07 18:32:47 +00:00
|
|
|
import qualified Data.Map as M
|
2017-09-19 18:20:47 +00:00
|
|
|
import Control.Concurrent
|
2017-08-29 18:58:38 +00:00
|
|
|
|
|
|
|
cmd :: Command
|
|
|
|
cmd = command "export" SectionCommon
|
|
|
|
"export content to a remote"
|
|
|
|
paramTreeish (seek <$$> optParser)
|
|
|
|
|
|
|
|
data ExportOptions = ExportOptions
|
|
|
|
{ exportTreeish :: Git.Ref
|
|
|
|
, exportRemote :: DeferredParse Remote
|
2017-09-19 17:05:43 +00:00
|
|
|
, exportTracking :: Bool
|
2017-08-29 18:58:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
optParser :: CmdParamsDesc -> Parser ExportOptions
|
|
|
|
optParser _ = ExportOptions
|
|
|
|
<$> (Git.Ref <$> parsetreeish)
|
|
|
|
<*> (parseRemoteOption <$> parseToOption)
|
2017-09-19 17:05:43 +00:00
|
|
|
<*> parsetracking
|
2017-08-29 18:58:38 +00:00
|
|
|
where
|
|
|
|
parsetreeish = argument str
|
|
|
|
( metavar paramTreeish
|
|
|
|
)
|
2017-09-19 17:05:43 +00:00
|
|
|
parsetracking = switch
|
|
|
|
( long "tracking"
|
|
|
|
<> help ("track changes to the " ++ paramTreeish)
|
|
|
|
)
|
2017-08-29 18:58:38 +00:00
|
|
|
|
2017-09-06 19:33:40 +00:00
|
|
|
-- To handle renames which swap files, the exported file is first renamed
|
|
|
|
-- to a stable temporary name based on the key.
|
|
|
|
exportTempName :: ExportKey -> ExportLocation
|
2017-09-18 18:24:42 +00:00
|
|
|
exportTempName ek = mkExportLocation $
|
2017-09-06 19:33:40 +00:00
|
|
|
".git-annex-tmp-content-" ++ key2file (asKey (ek))
|
|
|
|
|
2017-08-29 18:58:38 +00:00
|
|
|
seek :: ExportOptions -> CommandSeek
|
|
|
|
seek o = do
|
|
|
|
r <- getParsed (exportRemote o)
|
2017-09-07 17:45:31 +00:00
|
|
|
unlessM (isExportSupported r) $
|
2017-09-04 17:55:49 +00:00
|
|
|
giveup "That remote does not support exports."
|
2017-09-19 18:20:47 +00:00
|
|
|
when (exportTracking o) $
|
|
|
|
setConfig (remoteConfig r "export-tracking")
|
|
|
|
(fromRef $ exportTreeish o)
|
2017-09-04 17:55:49 +00:00
|
|
|
new <- fromMaybe (giveup "unknown tree") <$>
|
2017-08-31 22:06:49 +00:00
|
|
|
-- Dereference the tree pointed to by the branch, commit,
|
|
|
|
-- or tag.
|
|
|
|
inRepo (Git.Ref.tree (exportTreeish o))
|
2017-09-19 18:20:47 +00:00
|
|
|
withExclusiveLock (gitAnnexExportLock (uuid r)) $ do
|
|
|
|
db <- openDb (uuid r)
|
|
|
|
ea <- exportActions r
|
|
|
|
changeExport r ea db new
|
2017-09-19 18:26:03 +00:00
|
|
|
unlessM (Annex.getState Annex.fast) $
|
|
|
|
void $ fillExport r ea db new
|
2017-09-19 18:20:47 +00:00
|
|
|
closeDb db
|
|
|
|
|
|
|
|
-- | Changes what's exported to the remote. Does not upload any new
|
|
|
|
-- files, but does delete and rename files already exported to the remote.
|
|
|
|
changeExport :: Remote -> ExportActions Annex -> ExportHandle -> Git.Ref -> CommandSeek
|
|
|
|
changeExport r ea db new = do
|
2017-08-31 19:41:48 +00:00
|
|
|
old <- getExport (uuid r)
|
2017-09-12 20:59:04 +00:00
|
|
|
recordExportBeginning (uuid r) new
|
2017-09-19 17:05:43 +00:00
|
|
|
|
2017-09-06 19:33:40 +00:00
|
|
|
-- Clean up after incomplete export of a tree, in which
|
|
|
|
-- the next block of code below may have renamed some files to
|
|
|
|
-- temp files. Diff from the incomplete tree to the new tree,
|
|
|
|
-- and delete any temp files that the new tree can't use.
|
|
|
|
forM_ (concatMap incompleteExportedTreeish old) $ \incomplete ->
|
2017-09-12 20:59:04 +00:00
|
|
|
mapdiff (\diff -> startRecoverIncomplete r ea db (Git.DiffTree.srcsha diff) (Git.DiffTree.file diff))
|
2017-09-07 19:37:49 +00:00
|
|
|
incomplete
|
|
|
|
new
|
2017-09-06 19:33:40 +00:00
|
|
|
|
|
|
|
-- Diff the old and new trees, and delete or rename to new name all
|
|
|
|
-- changed files in the export. After this, every file that remains
|
|
|
|
-- in the export will have the content from the new treeish.
|
2017-08-31 19:41:48 +00:00
|
|
|
--
|
2017-09-18 18:24:42 +00:00
|
|
|
-- When there was an export conflict, this resolves it.
|
|
|
|
--
|
|
|
|
-- The ExportTree is also updated here to reflect the new tree.
|
2017-09-06 19:33:40 +00:00
|
|
|
case map exportedTreeish old of
|
2017-09-18 18:24:42 +00:00
|
|
|
[] -> updateExportTree db emptyTree new
|
2017-09-06 19:33:40 +00:00
|
|
|
[oldtreesha] -> do
|
2017-09-18 18:24:42 +00:00
|
|
|
diffmap <- mkDiffMap oldtreesha new db
|
2017-09-07 18:32:47 +00:00
|
|
|
let seekdiffmap a = seekActions $ pure $ map a (M.toList diffmap)
|
|
|
|
-- Rename old files to temp, or delete.
|
2017-09-18 18:24:42 +00:00
|
|
|
seekdiffmap $ \(ek, (moldf, mnewf)) -> do
|
2017-09-07 18:32:47 +00:00
|
|
|
case (moldf, mnewf) of
|
|
|
|
(Just oldf, Just _newf) ->
|
2017-09-12 20:59:04 +00:00
|
|
|
startMoveToTempName r ea db oldf ek
|
2017-09-07 18:32:47 +00:00
|
|
|
(Just oldf, Nothing) ->
|
2017-09-12 20:59:04 +00:00
|
|
|
startUnexport' r ea db oldf ek
|
2017-09-07 18:32:47 +00:00
|
|
|
_ -> stop
|
2017-09-06 19:33:40 +00:00
|
|
|
-- Rename from temp to new files.
|
2017-09-07 18:32:47 +00:00
|
|
|
seekdiffmap $ \(ek, (moldf, mnewf)) ->
|
|
|
|
case (moldf, mnewf) of
|
|
|
|
(Just _oldf, Just newf) ->
|
2017-09-12 20:59:04 +00:00
|
|
|
startMoveFromTempName r ea db ek newf
|
2017-09-07 18:32:47 +00:00
|
|
|
_ -> stop
|
2017-09-06 19:33:40 +00:00
|
|
|
ts -> do
|
|
|
|
warning "Export conflict detected. Different trees have been exported to the same special remote. Resolving.."
|
|
|
|
forM_ ts $ \oldtreesha -> do
|
|
|
|
-- Unexport both the srcsha and the dstsha,
|
|
|
|
-- because the wrong content may have
|
|
|
|
-- been renamed to the dstsha due to the
|
|
|
|
-- export conflict.
|
|
|
|
let unexportboth d =
|
|
|
|
[ Git.DiffTree.srcsha d
|
|
|
|
, Git.DiffTree.dstsha d
|
|
|
|
]
|
|
|
|
-- Don't rename to temp, because the
|
2017-09-07 18:32:47 +00:00
|
|
|
-- content is unknown; delete instead.
|
2017-09-06 19:33:40 +00:00
|
|
|
mapdiff
|
2017-09-12 20:59:04 +00:00
|
|
|
(\diff -> startUnexport r ea db (Git.DiffTree.file diff) (unexportboth diff))
|
2017-09-06 19:33:40 +00:00
|
|
|
oldtreesha new
|
2017-09-18 18:24:42 +00:00
|
|
|
updateExportTree db emptyTree new
|
2017-09-18 22:40:16 +00:00
|
|
|
liftIO $ recordExportTreeCurrent db new
|
2017-08-31 19:41:48 +00:00
|
|
|
|
|
|
|
-- Waiting until now to record the export guarantees that,
|
|
|
|
-- if this export is interrupted, there are no files left over
|
|
|
|
-- from a previous export, that are not part of this export.
|
2017-09-12 18:19:26 +00:00
|
|
|
c <- Annex.getState Annex.errcounter
|
2017-09-18 17:57:25 +00:00
|
|
|
when (c == 0) $ do
|
2017-09-12 18:19:26 +00:00
|
|
|
recordExport (uuid r) $ ExportChange
|
|
|
|
{ oldTreeish = map exportedTreeish old
|
|
|
|
, newTreeish = new
|
|
|
|
}
|
2017-09-06 19:33:40 +00:00
|
|
|
where
|
|
|
|
mapdiff a oldtreesha newtreesha = do
|
|
|
|
(diff, cleanup) <- inRepo $
|
|
|
|
Git.DiffTree.diffTreeRecursive oldtreesha newtreesha
|
|
|
|
seekActions $ pure $ map a diff
|
|
|
|
void $ liftIO cleanup
|
2017-09-04 18:00:54 +00:00
|
|
|
|
2017-09-07 18:32:47 +00:00
|
|
|
-- Map of old and new filenames for each changed ExportKey in a diff.
|
|
|
|
type DiffMap = M.Map ExportKey (Maybe TopFilePath, Maybe TopFilePath)
|
|
|
|
|
2017-09-18 18:24:42 +00:00
|
|
|
mkDiffMap :: Git.Ref -> Git.Ref -> ExportHandle -> Annex DiffMap
|
|
|
|
mkDiffMap old new db = do
|
2017-09-07 18:32:47 +00:00
|
|
|
(diff, cleanup) <- inRepo $ Git.DiffTree.diffTreeRecursive old new
|
|
|
|
diffmap <- M.fromListWith combinedm . concat <$> forM diff mkdm
|
|
|
|
void $ liftIO cleanup
|
|
|
|
return diffmap
|
|
|
|
where
|
|
|
|
combinedm (srca, dsta) (srcb, dstb) = (srca <|> srcb, dsta <|> dstb)
|
|
|
|
mkdm i = do
|
2017-09-18 17:57:25 +00:00
|
|
|
srcek <- getek (Git.DiffTree.srcsha i)
|
|
|
|
dstek <- getek (Git.DiffTree.dstsha i)
|
2017-09-18 18:24:42 +00:00
|
|
|
updateExportTree' db srcek dstek i
|
2017-09-07 18:32:47 +00:00
|
|
|
return $ catMaybes
|
|
|
|
[ (, (Just (Git.DiffTree.file i), Nothing)) <$> srcek
|
|
|
|
, (, (Nothing, Just (Git.DiffTree.file i))) <$> dstek
|
|
|
|
]
|
2017-09-18 17:57:25 +00:00
|
|
|
getek sha
|
2017-09-07 18:32:47 +00:00
|
|
|
| sha == nullSha = return Nothing
|
|
|
|
| otherwise = Just <$> exportKey sha
|
|
|
|
|
2017-09-19 18:20:47 +00:00
|
|
|
-- | Upload all exported files that are not yet in the remote,
|
|
|
|
-- Returns True when files were uploaded.
|
|
|
|
fillExport :: Remote -> ExportActions Annex -> ExportHandle -> Git.Ref -> Annex Bool
|
|
|
|
fillExport r ea db new = do
|
|
|
|
(l, cleanup) <- inRepo $ Git.LsTree.lsTree new
|
|
|
|
cvar <- liftIO $ newMVar False
|
|
|
|
seekActions $ pure $ map (startExport r ea db cvar) l
|
|
|
|
void $ liftIO $ cleanup
|
|
|
|
liftIO $ takeMVar cvar
|
|
|
|
|
|
|
|
startExport :: Remote -> ExportActions Annex -> ExportHandle -> MVar Bool -> Git.LsTree.TreeItem -> CommandStart
|
|
|
|
startExport r ea db cvar ti = do
|
2017-08-31 17:29:54 +00:00
|
|
|
ek <- exportKey (Git.LsTree.sha ti)
|
2018-06-14 16:22:12 +00:00
|
|
|
stopUnless (notpresent ek) $ do
|
2017-09-19 18:20:47 +00:00
|
|
|
showStart ("export " ++ name r) f
|
|
|
|
liftIO $ modifyMVar_ cvar (pure . const True)
|
2017-09-20 16:56:17 +00:00
|
|
|
next $ performExport r ea db ek af (Git.LsTree.sha ti) loc
|
2017-08-31 17:29:54 +00:00
|
|
|
where
|
2017-09-18 17:57:25 +00:00
|
|
|
loc = mkExportLocation f
|
2017-09-20 16:56:17 +00:00
|
|
|
f = getTopFilePath (Git.LsTree.file ti)
|
|
|
|
af = AssociatedFile (Just f)
|
2018-06-14 16:22:12 +00:00
|
|
|
notpresent ek = (||)
|
|
|
|
<$> liftIO (notElem loc <$> getExportedLocation db (asKey ek))
|
|
|
|
-- If content was removed from the remote, the export db
|
|
|
|
-- will still list it, so also check location tracking.
|
|
|
|
<*> (notElem (uuid r) <$> loggedLocations (asKey ek))
|
2017-08-31 17:29:54 +00:00
|
|
|
|
2017-09-20 16:56:17 +00:00
|
|
|
performExport :: Remote -> ExportActions Annex -> ExportHandle -> ExportKey -> AssociatedFile -> Sha -> ExportLocation -> CommandPerform
|
|
|
|
performExport r ea db ek af contentsha loc = do
|
2017-09-12 20:59:04 +00:00
|
|
|
let storer = storeExport ea
|
2017-09-01 17:02:07 +00:00
|
|
|
sent <- case ek of
|
|
|
|
AnnexKey k -> ifM (inAnnex k)
|
2017-11-14 20:27:39 +00:00
|
|
|
( notifyTransfer Upload af $
|
2018-03-29 17:04:07 +00:00
|
|
|
-- Using noRetry here because interrupted
|
|
|
|
-- exports cannot be resumed.
|
2017-11-14 20:27:39 +00:00
|
|
|
upload (uuid r) k af noRetry $ \pm -> do
|
|
|
|
let rollback = void $
|
|
|
|
performUnexport r ea db [ek] loc
|
|
|
|
sendAnnex k rollback $ \f ->
|
2018-03-13 01:46:58 +00:00
|
|
|
metered Nothing k (return $ Just f) $ \_ m -> do
|
2017-11-14 20:27:39 +00:00
|
|
|
let m' = combineMeterUpdate pm m
|
|
|
|
storer f k loc m'
|
2017-09-01 17:02:07 +00:00
|
|
|
, do
|
|
|
|
showNote "not available"
|
|
|
|
return False
|
|
|
|
)
|
|
|
|
-- Sending a non-annexed file.
|
2018-03-13 01:46:58 +00:00
|
|
|
GitKey sha1k -> metered Nothing sha1k (return Nothing) $ \_ m ->
|
2017-09-01 17:02:07 +00:00
|
|
|
withTmpFile "export" $ \tmp h -> do
|
|
|
|
b <- catObject contentsha
|
|
|
|
liftIO $ L.hPut h b
|
|
|
|
liftIO $ hClose h
|
|
|
|
storer tmp sha1k loc m
|
|
|
|
if sent
|
2017-09-04 18:33:09 +00:00
|
|
|
then next $ cleanupExport r db ek loc
|
2017-09-01 17:02:07 +00:00
|
|
|
else stop
|
2017-08-31 17:29:54 +00:00
|
|
|
|
2017-09-04 18:33:09 +00:00
|
|
|
cleanupExport :: Remote -> ExportHandle -> ExportKey -> ExportLocation -> CommandCleanup
|
|
|
|
cleanupExport r db ek loc = do
|
2017-09-18 17:57:25 +00:00
|
|
|
liftIO $ addExportedLocation db (asKey ek) loc
|
2017-08-31 17:29:54 +00:00
|
|
|
logChange (asKey ek) (uuid r) InfoPresent
|
|
|
|
return True
|
2017-08-29 18:58:38 +00:00
|
|
|
|
2017-09-12 20:59:04 +00:00
|
|
|
startUnexport :: Remote -> ExportActions Annex -> ExportHandle -> TopFilePath -> [Git.Sha] -> CommandStart
|
|
|
|
startUnexport r ea db f shas = do
|
2017-09-06 19:33:40 +00:00
|
|
|
eks <- forM (filter (/= nullSha) shas) exportKey
|
|
|
|
if null eks
|
|
|
|
then stop
|
|
|
|
else do
|
2017-09-19 18:20:47 +00:00
|
|
|
showStart ("unexport " ++ name r) f'
|
2017-09-12 20:59:04 +00:00
|
|
|
next $ performUnexport r ea db eks loc
|
2017-08-31 19:41:48 +00:00
|
|
|
where
|
2017-09-18 17:57:25 +00:00
|
|
|
loc = mkExportLocation f'
|
2017-09-06 19:33:40 +00:00
|
|
|
f' = getTopFilePath f
|
|
|
|
|
2017-09-12 20:59:04 +00:00
|
|
|
startUnexport' :: Remote -> ExportActions Annex -> ExportHandle -> TopFilePath -> ExportKey -> CommandStart
|
|
|
|
startUnexport' r ea db f ek = do
|
2017-09-19 18:20:47 +00:00
|
|
|
showStart ("unexport " ++ name r) f'
|
2017-09-12 20:59:04 +00:00
|
|
|
next $ performUnexport r ea db [ek] loc
|
2017-09-07 18:32:47 +00:00
|
|
|
where
|
2017-09-18 17:57:25 +00:00
|
|
|
loc = mkExportLocation f'
|
2017-09-07 18:32:47 +00:00
|
|
|
f' = getTopFilePath f
|
|
|
|
|
2017-09-12 20:59:04 +00:00
|
|
|
performUnexport :: Remote -> ExportActions Annex -> ExportHandle -> [ExportKey] -> ExportLocation -> CommandPerform
|
|
|
|
performUnexport r ea db eks loc = do
|
|
|
|
ifM (allM (\ek -> removeExport ea (asKey ek) loc) eks)
|
2017-09-15 19:04:29 +00:00
|
|
|
( next $ cleanupUnexport r ea db eks loc
|
2017-09-06 19:33:40 +00:00
|
|
|
, stop
|
|
|
|
)
|
2017-08-29 18:58:38 +00:00
|
|
|
|
2017-09-15 19:04:29 +00:00
|
|
|
cleanupUnexport :: Remote -> ExportActions Annex -> ExportHandle -> [ExportKey] -> ExportLocation -> CommandCleanup
|
|
|
|
cleanupUnexport r ea db eks loc = do
|
2017-09-04 18:33:09 +00:00
|
|
|
liftIO $ do
|
2017-09-06 19:33:40 +00:00
|
|
|
forM_ eks $ \ek ->
|
2017-09-18 17:57:25 +00:00
|
|
|
removeExportedLocation db (asKey ek) loc
|
2017-09-04 18:33:09 +00:00
|
|
|
flushDbQueue db
|
2017-09-15 19:04:29 +00:00
|
|
|
|
2018-08-30 15:23:57 +00:00
|
|
|
-- An appendonly remote can support removeExportLocation to remove
|
2018-08-30 15:18:20 +00:00
|
|
|
-- the file from the exported tree, but still retain the content
|
|
|
|
-- and allow retrieving it.
|
2018-08-30 15:23:57 +00:00
|
|
|
unless (appendonly r) $ do
|
2018-08-30 15:18:20 +00:00
|
|
|
remaininglocs <- liftIO $
|
|
|
|
concat <$> forM eks (\ek -> getExportedLocation db (asKey ek))
|
|
|
|
when (null remaininglocs) $
|
|
|
|
forM_ eks $ \ek ->
|
|
|
|
logChange (asKey ek) (uuid r) InfoMissing
|
2017-09-15 19:04:29 +00:00
|
|
|
|
|
|
|
removeEmptyDirectories ea db loc (map asKey eks)
|
2017-09-06 19:33:40 +00:00
|
|
|
|
2017-09-12 20:59:04 +00:00
|
|
|
startRecoverIncomplete :: Remote -> ExportActions Annex -> ExportHandle -> Git.Sha -> TopFilePath -> CommandStart
|
|
|
|
startRecoverIncomplete r ea db sha oldf
|
2017-09-06 19:33:40 +00:00
|
|
|
| sha == nullSha = stop
|
|
|
|
| otherwise = do
|
|
|
|
ek <- exportKey sha
|
2017-09-18 17:57:25 +00:00
|
|
|
let loc = exportTempName ek
|
2017-09-19 18:20:47 +00:00
|
|
|
showStart ("unexport " ++ name r) (fromExportLocation loc)
|
2017-09-18 17:57:25 +00:00
|
|
|
liftIO $ removeExportedLocation db (asKey ek) oldloc
|
2017-09-12 20:59:04 +00:00
|
|
|
next $ performUnexport r ea db [ek] loc
|
2017-09-07 19:37:49 +00:00
|
|
|
where
|
2017-09-18 17:57:25 +00:00
|
|
|
oldloc = mkExportLocation oldf'
|
2017-09-07 19:37:49 +00:00
|
|
|
oldf' = getTopFilePath oldf
|
2017-09-06 19:33:40 +00:00
|
|
|
|
2017-09-12 20:59:04 +00:00
|
|
|
startMoveToTempName :: Remote -> ExportActions Annex -> ExportHandle -> TopFilePath -> ExportKey -> CommandStart
|
|
|
|
startMoveToTempName r ea db f ek = do
|
2017-09-19 18:20:47 +00:00
|
|
|
showStart ("rename " ++ name r) (f' ++ " -> " ++ fromExportLocation tmploc)
|
2017-09-12 20:59:04 +00:00
|
|
|
next $ performRename r ea db ek loc tmploc
|
2017-09-06 19:33:40 +00:00
|
|
|
where
|
2017-09-18 17:57:25 +00:00
|
|
|
loc = mkExportLocation f'
|
2017-09-06 19:33:40 +00:00
|
|
|
f' = getTopFilePath f
|
2017-09-18 17:57:25 +00:00
|
|
|
tmploc = exportTempName ek
|
2017-09-06 19:33:40 +00:00
|
|
|
|
2017-09-12 20:59:04 +00:00
|
|
|
startMoveFromTempName :: Remote -> ExportActions Annex -> ExportHandle -> ExportKey -> TopFilePath -> CommandStart
|
|
|
|
startMoveFromTempName r ea db ek f = do
|
2017-09-18 17:57:25 +00:00
|
|
|
let tmploc = exportTempName ek
|
|
|
|
stopUnless (liftIO $ elem tmploc <$> getExportedLocation db (asKey ek)) $ do
|
2017-09-19 18:20:47 +00:00
|
|
|
showStart ("rename " ++ name r) (fromExportLocation tmploc ++ " -> " ++ f')
|
2017-09-12 20:59:04 +00:00
|
|
|
next $ performRename r ea db ek tmploc loc
|
2017-09-06 19:33:40 +00:00
|
|
|
where
|
2017-09-18 17:57:25 +00:00
|
|
|
loc = mkExportLocation f'
|
2017-09-06 19:33:40 +00:00
|
|
|
f' = getTopFilePath f
|
|
|
|
|
2017-09-12 20:59:04 +00:00
|
|
|
performRename :: Remote -> ExportActions Annex -> ExportHandle -> ExportKey -> ExportLocation -> ExportLocation -> CommandPerform
|
|
|
|
performRename r ea db ek src dest = do
|
|
|
|
ifM (renameExport ea (asKey ek) src dest)
|
2017-09-15 19:04:29 +00:00
|
|
|
( next $ cleanupRename ea db ek src dest
|
2017-09-06 19:33:40 +00:00
|
|
|
-- In case the special remote does not support renaming,
|
|
|
|
-- unexport the src instead.
|
2017-09-12 18:08:00 +00:00
|
|
|
, do
|
|
|
|
warning "rename failed; deleting instead"
|
2017-09-12 20:59:04 +00:00
|
|
|
performUnexport r ea db [ek] src
|
2017-09-06 19:33:40 +00:00
|
|
|
)
|
|
|
|
|
2017-09-15 19:04:29 +00:00
|
|
|
cleanupRename :: ExportActions Annex -> ExportHandle -> ExportKey -> ExportLocation -> ExportLocation -> CommandCleanup
|
|
|
|
cleanupRename ea db ek src dest = do
|
2017-09-06 19:33:40 +00:00
|
|
|
liftIO $ do
|
2017-09-18 17:57:25 +00:00
|
|
|
removeExportedLocation db (asKey ek) src
|
|
|
|
addExportedLocation db (asKey ek) dest
|
2017-09-06 19:33:40 +00:00
|
|
|
flushDbQueue db
|
2017-09-15 20:34:45 +00:00
|
|
|
if exportDirectories src /= exportDirectories dest
|
2017-09-15 20:30:49 +00:00
|
|
|
then removeEmptyDirectories ea db src [asKey ek]
|
|
|
|
else return True
|
2017-09-18 22:40:16 +00:00
|
|
|
|
|
|
|
-- | Remove empty directories from the export. Call after removing an
|
|
|
|
-- exported file, and after calling removeExportLocation and flushing the
|
|
|
|
-- database.
|
|
|
|
removeEmptyDirectories :: ExportActions Annex -> ExportHandle -> ExportLocation -> [Key] -> Annex Bool
|
|
|
|
removeEmptyDirectories ea db loc ks
|
|
|
|
| null (exportDirectories loc) = return True
|
|
|
|
| otherwise = case removeExportDirectory ea of
|
|
|
|
Nothing -> return True
|
|
|
|
Just removeexportdirectory -> do
|
|
|
|
ok <- allM (go removeexportdirectory)
|
|
|
|
(reverse (exportDirectories loc))
|
|
|
|
unless ok $ liftIO $ do
|
|
|
|
-- Add location back to export database,
|
|
|
|
-- so this is tried again next time.
|
|
|
|
forM_ ks $ \k ->
|
|
|
|
addExportedLocation db k loc
|
|
|
|
flushDbQueue db
|
|
|
|
return ok
|
|
|
|
where
|
|
|
|
go removeexportdirectory d =
|
|
|
|
ifM (liftIO $ isExportDirectoryEmpty db d)
|
|
|
|
( removeexportdirectory d
|
|
|
|
, return True
|
|
|
|
)
|