2024-05-06 16:58:38 +00:00
|
|
|
{- git-remote-annex program
|
|
|
|
-
|
|
|
|
- Copyright 2024 Joey Hess <id@joeyh.name>
|
|
|
|
-
|
|
|
|
- Licensed under the GNU AGPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
2024-05-06 20:25:55 +00:00
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
|
2024-05-06 16:58:38 +00:00
|
|
|
module CmdLine.GitRemoteAnnex where
|
|
|
|
|
2024-05-06 18:07:27 +00:00
|
|
|
import Annex.Common
|
2024-05-06 16:58:38 +00:00
|
|
|
import qualified Annex
|
2024-05-06 20:25:55 +00:00
|
|
|
import qualified Remote
|
2024-05-07 19:13:41 +00:00
|
|
|
import qualified Git.CurrentRepo
|
2024-05-08 20:55:45 +00:00
|
|
|
import qualified Git
|
2024-05-07 19:13:41 +00:00
|
|
|
import qualified Git.Ref
|
|
|
|
import qualified Git.Branch
|
|
|
|
import qualified Git.Bundle
|
2024-05-08 20:55:45 +00:00
|
|
|
import qualified Git.Remote
|
|
|
|
import qualified Git.Remote.Remove
|
|
|
|
import qualified Annex.SpecialRemote as SpecialRemote
|
|
|
|
import qualified Annex.Branch
|
2024-05-08 22:07:26 +00:00
|
|
|
import qualified Types.Remote as Remote
|
2024-05-07 19:13:41 +00:00
|
|
|
import Annex.Transfer
|
2024-05-08 20:55:45 +00:00
|
|
|
import Backend.GitRemoteAnnex
|
|
|
|
import Config
|
|
|
|
import Types.RemoteConfig
|
|
|
|
import Types.ProposedAccepted
|
2024-05-06 20:25:55 +00:00
|
|
|
import Types.Key
|
2024-05-08 20:55:45 +00:00
|
|
|
import Types.GitConfig
|
|
|
|
import Git.Types
|
|
|
|
import Logs.Difference
|
|
|
|
import Annex.Init
|
|
|
|
import Annex.Content
|
|
|
|
import Remote.List
|
|
|
|
import Remote.List.Util
|
2024-05-06 20:25:55 +00:00
|
|
|
import Utility.Tmp
|
2024-05-08 20:55:45 +00:00
|
|
|
import Utility.Env
|
2024-05-06 20:25:55 +00:00
|
|
|
import Utility.Metered
|
2024-05-08 20:55:45 +00:00
|
|
|
|
|
|
|
import Network.URI
|
2024-05-06 20:25:55 +00:00
|
|
|
import qualified Data.ByteString as B
|
|
|
|
import qualified Data.ByteString.Char8 as B8
|
2024-05-07 19:13:41 +00:00
|
|
|
import qualified Data.Map.Strict as M
|
2024-05-08 20:55:45 +00:00
|
|
|
import System.FilePath.ByteString as P
|
2024-05-08 22:07:26 +00:00
|
|
|
import qualified Utility.RawFilePath as R
|
2024-05-06 16:58:38 +00:00
|
|
|
|
|
|
|
run :: [String] -> IO ()
|
2024-05-08 20:55:45 +00:00
|
|
|
run (remotename:url:[]) =
|
2024-05-07 19:13:41 +00:00
|
|
|
-- git strips the "annex::" prefix of the url
|
|
|
|
-- when running this command, so add it back
|
|
|
|
let url' = "annex::" ++ url
|
|
|
|
in case parseSpecialRemoteNameUrl remotename url' of
|
|
|
|
Left e -> giveup e
|
|
|
|
Right src -> do
|
2024-05-08 20:55:45 +00:00
|
|
|
repo <- getRepo
|
|
|
|
state <- Annex.new repo
|
2024-05-07 19:13:41 +00:00
|
|
|
Annex.eval state (run' src)
|
2024-05-06 18:07:27 +00:00
|
|
|
run (_remotename:[]) = giveup "remote url not configured"
|
|
|
|
run _ = giveup "expected remote name and url parameters"
|
|
|
|
|
2024-05-06 20:25:55 +00:00
|
|
|
run' :: SpecialRemoteConfig -> Annex ()
|
2024-05-08 22:07:26 +00:00
|
|
|
run' src = do
|
|
|
|
sab <- startAnnexBranch
|
2024-05-06 20:25:55 +00:00
|
|
|
-- Prevent any usual git-annex output to stdout, because
|
|
|
|
-- the output of this command is being parsed by git.
|
2024-05-08 20:55:45 +00:00
|
|
|
doQuietAction $
|
2024-05-08 22:07:26 +00:00
|
|
|
withSpecialRemote src sab $ \rmt -> do
|
2024-05-08 20:55:45 +00:00
|
|
|
ls <- lines <$> liftIO getContents
|
|
|
|
go rmt ls emptyState
|
2024-05-06 18:07:27 +00:00
|
|
|
where
|
2024-05-07 19:13:41 +00:00
|
|
|
go rmt (l:ls) st =
|
2024-05-06 18:07:27 +00:00
|
|
|
let (c, v) = splitLine l
|
|
|
|
in case c of
|
2024-05-07 19:13:41 +00:00
|
|
|
"capabilities" -> capabilities >> go rmt ls st
|
2024-05-06 18:07:27 +00:00
|
|
|
"list" -> case v of
|
2024-05-07 19:13:41 +00:00
|
|
|
"" -> list st rmt False >>= go rmt ls
|
|
|
|
"for-push" -> list st rmt True >>= go rmt ls
|
2024-05-06 18:07:27 +00:00
|
|
|
_ -> protocolError l
|
2024-05-07 19:13:41 +00:00
|
|
|
"fetch" -> fetch st rmt (l:ls) >>= \ls' -> go rmt ls' st
|
|
|
|
"push" -> push st rmt (l:ls) >>= \ls' -> go rmt ls' st
|
|
|
|
"" -> return ()
|
2024-05-06 18:07:27 +00:00
|
|
|
_ -> protocolError l
|
2024-05-07 19:13:41 +00:00
|
|
|
go _ [] _ = return ()
|
|
|
|
|
|
|
|
data State = State
|
|
|
|
{ manifestCache :: Maybe Manifest
|
|
|
|
, trackingRefs :: M.Map Ref Sha
|
|
|
|
}
|
|
|
|
|
|
|
|
emptyState :: State
|
|
|
|
emptyState = State
|
|
|
|
{ manifestCache = Nothing
|
|
|
|
, trackingRefs = mempty
|
|
|
|
}
|
2024-05-06 18:07:27 +00:00
|
|
|
|
|
|
|
protocolError :: String -> a
|
|
|
|
protocolError l = giveup $ "gitremote-helpers protocol error at " ++ show l
|
|
|
|
|
|
|
|
capabilities :: Annex ()
|
|
|
|
capabilities = do
|
|
|
|
liftIO $ putStrLn "fetch"
|
|
|
|
liftIO $ putStrLn "push"
|
|
|
|
liftIO $ putStrLn ""
|
|
|
|
liftIO $ hFlush stdout
|
|
|
|
|
2024-05-07 19:13:41 +00:00
|
|
|
list :: State -> Remote -> Bool -> Annex State
|
|
|
|
list st rmt forpush = do
|
|
|
|
manifest <- downloadManifest rmt
|
|
|
|
l <- forM (inManifest manifest) $ \k -> do
|
|
|
|
b <- downloadGitBundle rmt k
|
|
|
|
heads <- inRepo $ Git.Bundle.listHeads b
|
|
|
|
-- Get all the objects from the bundle. This is done here
|
|
|
|
-- so that the tracking refs can be updated with what is
|
|
|
|
-- listed, and so what when a full repush is done, all
|
|
|
|
-- objects are available to be pushed.
|
|
|
|
when forpush $
|
|
|
|
inRepo $ Git.Bundle.unbundle b
|
|
|
|
-- The bundle may contain tracking refs, or regular refs,
|
|
|
|
-- make sure we're operating on regular refs.
|
|
|
|
return $ map (\(s, r) -> (fromTrackingRef rmt r, s)) heads
|
|
|
|
|
|
|
|
-- Later refs replace earlier refs with the same name.
|
|
|
|
let refmap = M.fromList $ concat l
|
|
|
|
let reflist = M.toList refmap
|
|
|
|
let trackingrefmap = M.mapKeys (toTrackingRef rmt) refmap
|
|
|
|
|
|
|
|
-- When listing for a push, update the tracking refs to match what
|
|
|
|
-- was listed. This is necessary in order for a full repush to know
|
|
|
|
-- what to push.
|
|
|
|
when forpush $
|
|
|
|
updateTrackingRefs rmt trackingrefmap
|
|
|
|
|
|
|
|
-- Respond to git with a list of refs.
|
|
|
|
liftIO $ do
|
|
|
|
forM_ reflist $ \(ref, sha) ->
|
|
|
|
B8.putStrLn $ fromRef' sha <> " " <> fromRef' ref
|
|
|
|
-- Newline terminates list of refs.
|
|
|
|
putStrLn ""
|
|
|
|
hFlush stdout
|
|
|
|
|
|
|
|
-- Remember the tracking refs.
|
|
|
|
return $ st
|
|
|
|
{ manifestCache = Just manifest
|
|
|
|
, trackingRefs = trackingrefmap
|
|
|
|
}
|
2024-05-06 18:07:27 +00:00
|
|
|
|
|
|
|
-- Any number of fetch commands can be sent by git, asking for specific
|
|
|
|
-- things. We fetch everything new at once, so find the end of the fetch
|
|
|
|
-- commands (which is supposed to be a blank line) before fetching.
|
2024-05-07 19:13:41 +00:00
|
|
|
fetch :: State -> Remote -> [String] -> Annex [String]
|
|
|
|
fetch st rmt (l:ls) = case splitLine l of
|
|
|
|
("fetch", _) -> fetch st rmt ls
|
2024-05-06 18:07:27 +00:00
|
|
|
("", _) -> do
|
2024-05-07 19:13:41 +00:00
|
|
|
fetch' st rmt
|
2024-05-06 18:07:27 +00:00
|
|
|
return ls
|
|
|
|
_ -> do
|
2024-05-07 19:13:41 +00:00
|
|
|
fetch' st rmt
|
2024-05-06 18:07:27 +00:00
|
|
|
return (l:ls)
|
2024-05-07 19:13:41 +00:00
|
|
|
fetch st rmt [] = do
|
|
|
|
fetch' st rmt
|
2024-05-06 18:07:27 +00:00
|
|
|
return []
|
|
|
|
|
2024-05-07 19:13:41 +00:00
|
|
|
fetch' :: State -> Remote -> Annex ()
|
|
|
|
fetch' st rmt = do
|
|
|
|
manifest <- maybe (downloadManifest rmt) pure (manifestCache st)
|
|
|
|
forM_ (inManifest manifest) $ \k ->
|
|
|
|
downloadGitBundle rmt k >>= inRepo . Git.Bundle.unbundle
|
2024-05-07 19:34:55 +00:00
|
|
|
-- Newline indicates end of fetch.
|
|
|
|
liftIO $ do
|
|
|
|
putStrLn ""
|
|
|
|
hFlush stdout
|
2024-05-06 18:07:27 +00:00
|
|
|
|
2024-05-07 19:13:41 +00:00
|
|
|
push :: State -> Remote -> [String] -> Annex [String]
|
|
|
|
push st rmt ls = do
|
2024-05-06 18:07:27 +00:00
|
|
|
let (refspecs, ls') = collectRefSpecs ls
|
|
|
|
error "TODO push refspecs"
|
|
|
|
return ls'
|
|
|
|
|
|
|
|
data RefSpec = RefSpec
|
|
|
|
{ forcedPush :: Bool
|
|
|
|
, srcRef :: Maybe String -- empty when deleting a ref
|
|
|
|
, dstRef :: String
|
|
|
|
}
|
|
|
|
deriving (Show)
|
|
|
|
|
|
|
|
-- Any number of push commands can be sent by git, specifying the refspecs
|
|
|
|
-- to push. They should be followed by a blank line.
|
|
|
|
collectRefSpecs :: [String] -> ([RefSpec], [String])
|
|
|
|
collectRefSpecs = go []
|
|
|
|
where
|
|
|
|
go c (l:ls) = case splitLine l of
|
|
|
|
("push", refspec) -> go (parseRefSpec refspec:c) ls
|
|
|
|
("", _) -> (c, ls)
|
|
|
|
_ -> (c, (l:ls))
|
|
|
|
go c [] = (c, [])
|
|
|
|
|
|
|
|
parseRefSpec :: String -> RefSpec
|
|
|
|
parseRefSpec ('+':s) = (parseRefSpec s) { forcedPush = True }
|
|
|
|
parseRefSpec s =
|
|
|
|
let (src, cdst) = break (== ':') s
|
|
|
|
dst = if null cdst then cdst else drop 1 cdst
|
|
|
|
in RefSpec
|
|
|
|
{ forcedPush = False
|
|
|
|
, srcRef = if null src then Nothing else Just src
|
|
|
|
, dstRef = dst
|
|
|
|
}
|
|
|
|
|
|
|
|
-- "foo bar" to ("foo", "bar")
|
|
|
|
-- "foo" to ("foo", "")
|
|
|
|
splitLine :: String -> (String, String)
|
|
|
|
splitLine l =
|
|
|
|
let (c, sv) = break (== ' ') l
|
|
|
|
v = if null sv then sv else drop 1 sv
|
|
|
|
in (c, v)
|
2024-05-06 18:50:41 +00:00
|
|
|
|
2024-05-07 19:13:41 +00:00
|
|
|
data SpecialRemoteConfig
|
|
|
|
= SpecialRemoteConfig
|
|
|
|
{ specialRemoteUUID :: UUID
|
2024-05-08 20:55:45 +00:00
|
|
|
, specialRemoteConfig :: RemoteConfig
|
|
|
|
, specialRemoteName :: Maybe RemoteName
|
|
|
|
, specialRemoteUrl :: String
|
2024-05-07 19:13:41 +00:00
|
|
|
}
|
|
|
|
| ExistingSpecialRemote RemoteName
|
2024-05-06 18:50:41 +00:00
|
|
|
deriving (Show)
|
|
|
|
|
|
|
|
-- The url for a special remote looks like
|
2024-05-07 19:13:41 +00:00
|
|
|
-- "annex::uuid?param=value¶m=value..."
|
|
|
|
--
|
|
|
|
-- Also accept an url of "annex::", when a remote name is provided,
|
|
|
|
-- to use an already enabled special remote.
|
|
|
|
parseSpecialRemoteNameUrl :: String -> String -> Either String SpecialRemoteConfig
|
|
|
|
parseSpecialRemoteNameUrl remotename url
|
|
|
|
| url == "annex::" && remotename /= url = Right $
|
|
|
|
ExistingSpecialRemote remotename
|
2024-05-08 20:55:45 +00:00
|
|
|
| "annex::" `isPrefixOf` remotename = parseSpecialRemoteUrl url Nothing
|
|
|
|
| otherwise = parseSpecialRemoteUrl url (Just remotename)
|
2024-05-07 19:13:41 +00:00
|
|
|
|
2024-05-08 20:55:45 +00:00
|
|
|
parseSpecialRemoteUrl :: String -> Maybe RemoteName -> Either String SpecialRemoteConfig
|
|
|
|
parseSpecialRemoteUrl url remotename = case parseURI url of
|
2024-05-06 18:50:41 +00:00
|
|
|
Nothing -> Left "URL parse failed"
|
|
|
|
Just u -> case uriScheme u of
|
|
|
|
"annex:" -> case uriPath u of
|
|
|
|
"" -> Left "annex: URL did not include a UUID"
|
2024-05-07 18:37:29 +00:00
|
|
|
(':':p) -> Right $ SpecialRemoteConfig
|
2024-05-06 18:50:41 +00:00
|
|
|
{ specialRemoteUUID = toUUID p
|
2024-05-08 20:55:45 +00:00
|
|
|
, specialRemoteConfig = parsequery u
|
|
|
|
, specialRemoteName = remotename
|
|
|
|
, specialRemoteUrl = url
|
2024-05-06 18:50:41 +00:00
|
|
|
}
|
2024-05-07 18:37:29 +00:00
|
|
|
_ -> Left "annex: URL malformed"
|
2024-05-06 18:50:41 +00:00
|
|
|
_ -> Left "Not an annex: URL"
|
|
|
|
where
|
2024-05-08 20:55:45 +00:00
|
|
|
parsequery u = M.fromList $
|
|
|
|
map parsekv $ splitc '&' (drop 1 (uriQuery u))
|
2024-05-06 20:25:55 +00:00
|
|
|
parsekv kv =
|
|
|
|
let (k, sv) = break (== '=') kv
|
2024-05-06 18:50:41 +00:00
|
|
|
v = if null sv then sv else drop 1 sv
|
2024-05-08 20:55:45 +00:00
|
|
|
in (Proposed (unEscapeString k), Proposed (unEscapeString v))
|
|
|
|
|
|
|
|
-- Runs an action with a Remote as specified by the SpecialRemoteConfig.
|
2024-05-08 22:07:26 +00:00
|
|
|
withSpecialRemote :: SpecialRemoteConfig -> StartAnnexBranch -> (Remote -> Annex a) -> Annex a
|
|
|
|
withSpecialRemote (ExistingSpecialRemote remotename) _ a =
|
2024-05-08 20:55:45 +00:00
|
|
|
getEnabledSpecialRemoteByName remotename >>=
|
|
|
|
maybe (giveup $ "There is no special remote named " ++ remotename)
|
|
|
|
a
|
2024-05-08 22:07:26 +00:00
|
|
|
withSpecialRemote cfg@(SpecialRemoteConfig {}) sab a = case specialRemoteName cfg of
|
2024-05-08 20:55:45 +00:00
|
|
|
-- The name could be the name of an existing special remote,
|
|
|
|
-- if so use it as long as its UUID matches the UUID from the url.
|
|
|
|
Just remotename -> getEnabledSpecialRemoteByName remotename >>= \case
|
|
|
|
Just rmt
|
2024-05-08 22:07:26 +00:00
|
|
|
| Remote.uuid rmt == specialRemoteUUID cfg -> a rmt
|
2024-05-08 20:55:45 +00:00
|
|
|
| otherwise -> giveup $ "The uuid in the annex:: url does not match the uuid of the remote named " ++ remotename
|
|
|
|
-- When cloning from an annex:: url,
|
|
|
|
-- this is used to set up the origin remote.
|
|
|
|
Nothing -> (initremote remotename >>= a)
|
2024-05-08 22:07:26 +00:00
|
|
|
`finally` cleanupInitialization sab
|
2024-05-08 20:55:45 +00:00
|
|
|
Nothing -> inittempremote
|
2024-05-08 22:07:26 +00:00
|
|
|
`finally` cleanupInitialization sab
|
2024-05-08 20:55:45 +00:00
|
|
|
where
|
|
|
|
-- Initialize a new special remote with the provided configuration
|
|
|
|
-- and name.
|
|
|
|
--
|
|
|
|
-- The configuration is not stored in the git-annex branch, because
|
|
|
|
-- it's expected that the git repository stored on the special
|
|
|
|
-- remote includes its configuration, perhaps under a different
|
|
|
|
-- name, and perhaps slightly different (when the annex:: url
|
|
|
|
-- omitted some unimportant part of the configuration).
|
|
|
|
initremote remotename = do
|
|
|
|
let c = M.insert SpecialRemote.nameField (Proposed remotename)
|
|
|
|
(specialRemoteConfig cfg)
|
|
|
|
t <- either giveup return (SpecialRemote.findType c)
|
|
|
|
dummycfg <- liftIO dummyRemoteGitConfig
|
2024-05-08 22:07:26 +00:00
|
|
|
(c', _u) <- Remote.setup t Remote.Init (Just (specialRemoteUUID cfg))
|
2024-05-08 20:55:45 +00:00
|
|
|
Nothing c dummycfg
|
|
|
|
`onException` cleanupremote remotename
|
|
|
|
setConfig (remoteConfig c' "url") (specialRemoteUrl cfg)
|
|
|
|
remotesChanged
|
|
|
|
getEnabledSpecialRemoteByName remotename >>= \case
|
|
|
|
Just rmt -> case checkSpecialRemoteProblems rmt of
|
|
|
|
Nothing -> return rmt
|
|
|
|
Just problem -> do
|
|
|
|
cleanupremote remotename
|
|
|
|
giveup problem
|
|
|
|
Nothing -> do
|
|
|
|
cleanupremote remotename
|
|
|
|
giveup "Unable to find special remote after setup."
|
|
|
|
|
|
|
|
-- Temporarily initialize a special remote, and remove it after
|
|
|
|
-- the action is run.
|
|
|
|
inittempremote =
|
|
|
|
let remotename = Git.Remote.makeLegalName $
|
|
|
|
"annex-temp-" ++ fromUUID (specialRemoteUUID cfg)
|
|
|
|
in bracket
|
|
|
|
(initremote remotename)
|
|
|
|
(const $ cleanupremote remotename)
|
|
|
|
a
|
|
|
|
|
|
|
|
cleanupremote remotename = do
|
|
|
|
l <- inRepo Git.Remote.listRemotes
|
|
|
|
when (remotename `elem` l) $
|
|
|
|
inRepo $ Git.Remote.Remove.remove remotename
|
|
|
|
|
|
|
|
-- When a special remote has already been enabled, just use it.
|
|
|
|
getEnabledSpecialRemoteByName :: RemoteName -> Annex (Maybe Remote)
|
|
|
|
getEnabledSpecialRemoteByName remotename =
|
2024-05-07 19:13:41 +00:00
|
|
|
Remote.byNameOnly remotename >>= \case
|
2024-05-08 20:55:45 +00:00
|
|
|
Nothing -> return Nothing
|
|
|
|
Just rmt ->
|
|
|
|
maybe (return (Just rmt)) giveup
|
|
|
|
(checkSpecialRemoteProblems rmt)
|
|
|
|
|
|
|
|
-- Avoid using special remotes that are thirdparty populated, because
|
|
|
|
-- there is no way to push the git repository keys into one.
|
|
|
|
--
|
|
|
|
-- XXX Avoid using special remotes that are encrypted by key
|
|
|
|
-- material stored in the git repository, since that would present a
|
|
|
|
-- chicken and egg problem when cloning.
|
|
|
|
checkSpecialRemoteProblems :: Remote -> Maybe String
|
|
|
|
checkSpecialRemoteProblems rmt
|
2024-05-08 22:07:26 +00:00
|
|
|
| Remote.thirdPartyPopulated (Remote.remotetype rmt) =
|
2024-05-08 20:55:45 +00:00
|
|
|
Just "Cannot use this thirdparty-populated special remote as a git remote"
|
|
|
|
| otherwise = Nothing
|
2024-05-06 20:25:55 +00:00
|
|
|
|
|
|
|
-- The manifest contains an ordered list of git bundle keys.
|
2024-05-07 19:13:41 +00:00
|
|
|
newtype Manifest = Manifest { inManifest :: [Key] }
|
2024-05-06 20:25:55 +00:00
|
|
|
|
|
|
|
-- Downloads the Manifest, or if it does not exist, returns an empty
|
|
|
|
-- Manifest.
|
|
|
|
--
|
|
|
|
-- Throws errors if the remote cannot be accessed or the download fails,
|
|
|
|
-- or if the manifest file cannot be parsed.
|
2024-05-07 19:13:41 +00:00
|
|
|
--
|
|
|
|
-- This downloads the manifest to a temporary file, rather than using
|
|
|
|
-- the usual Annex.Transfer.download. The content of manifests is not
|
|
|
|
-- stable, and so it needs to re-download it fresh every time.
|
2024-05-06 20:25:55 +00:00
|
|
|
downloadManifest :: Remote -> Annex Manifest
|
2024-05-08 22:07:26 +00:00
|
|
|
downloadManifest rmt = ifM (Remote.checkPresent rmt mk)
|
2024-05-06 20:25:55 +00:00
|
|
|
( withTmpFile "GITMANIFEST" $ \tmp tmph -> do
|
|
|
|
liftIO $ hClose tmph
|
2024-05-08 22:07:26 +00:00
|
|
|
_ <- Remote.retrieveKeyFile rmt mk
|
2024-05-06 20:25:55 +00:00
|
|
|
(AssociatedFile Nothing) tmp
|
2024-05-08 22:07:26 +00:00
|
|
|
nullMeterUpdate Remote.NoVerify
|
2024-05-06 20:25:55 +00:00
|
|
|
ks <- map deserializeKey' . B8.lines <$> liftIO (B.readFile tmp)
|
|
|
|
Manifest <$> checkvalid [] ks
|
|
|
|
, return (Manifest [])
|
|
|
|
)
|
|
|
|
where
|
2024-05-07 19:13:41 +00:00
|
|
|
mk = genManifestKey (Remote.uuid rmt)
|
2024-05-06 20:25:55 +00:00
|
|
|
|
|
|
|
checkvalid c [] = return (reverse c)
|
|
|
|
checkvalid c (Just k:ks) = case fromKey keyVariety k of
|
2024-05-07 19:13:41 +00:00
|
|
|
GitBundleKey -> checkvalid (k:c) ks
|
2024-05-06 20:25:55 +00:00
|
|
|
_ -> giveup $ "Wrong type of key in manifest " ++ serializeKey k
|
|
|
|
checkvalid _ (Nothing:_) =
|
|
|
|
giveup $ "Error parsing manifest " ++ serializeKey mk
|
2024-05-07 19:13:41 +00:00
|
|
|
|
|
|
|
-- Downloads a git bundle to the annex objects directory, unless
|
|
|
|
-- the object file is already present. Returns the filename of the object
|
|
|
|
-- file.
|
|
|
|
--
|
|
|
|
-- Throws errors if the download fails, or the checksum does not verify.
|
|
|
|
--
|
|
|
|
-- This does not update the location log to indicate that the local
|
|
|
|
-- repository contains the git bundle object. Reasons not to include:
|
|
|
|
-- 1. When this is being used in a git clone, the repository will not have
|
|
|
|
-- a UUID yet.
|
|
|
|
-- 2. It would unncessarily bloat the git-annex branch, which would then
|
|
|
|
-- lead to more things needing to be pushed to the special remote,
|
|
|
|
-- and so more things pulled from it, etc.
|
|
|
|
-- 3. Git bundle objects are not usually transferred between repositories
|
|
|
|
-- except special remotes (although the user can if they want to).
|
|
|
|
downloadGitBundle :: Remote -> Key -> Annex FilePath
|
|
|
|
downloadGitBundle rmt k =
|
|
|
|
ifM (download rmt k (AssociatedFile Nothing) stdRetry noNotification)
|
|
|
|
( decodeBS <$> calcRepo (gitAnnexLocation k)
|
|
|
|
, giveup $ "Failed to download " ++ serializeKey k
|
|
|
|
)
|
|
|
|
|
|
|
|
-- Tracking refs are used to remember the refs that are currently on the
|
|
|
|
-- remote. This is different from git's remote tracking branches, since it
|
|
|
|
-- needs to track all refs on the remote, not only the refs that the user
|
|
|
|
-- chooses to fetch.
|
|
|
|
--
|
|
|
|
-- For refs/heads/master, the tracking ref is
|
|
|
|
-- refs/namespaces/git-remote-annex/uuid/refs/heads/master,
|
|
|
|
-- using the uuid of the remote. See gitnamespaces(7).
|
|
|
|
trackingRefPrefix :: Remote -> B.ByteString
|
|
|
|
trackingRefPrefix rmt = "refs/namespaces/git-remote-annex/"
|
|
|
|
<> fromUUID (Remote.uuid rmt) <> "/"
|
|
|
|
|
|
|
|
toTrackingRef :: Remote -> Ref -> Ref
|
|
|
|
toTrackingRef rmt (Ref r) = Ref $ trackingRefPrefix rmt <> r
|
|
|
|
|
|
|
|
-- If the ref is not a tracking ref, it is returned as-is.
|
|
|
|
fromTrackingRef :: Remote -> Ref -> Ref
|
|
|
|
fromTrackingRef rmt = Git.Ref.removeBase (decodeBS (trackingRefPrefix rmt))
|
|
|
|
|
|
|
|
-- Update the tracking refs to be those in the map, and no others.
|
|
|
|
updateTrackingRefs :: Remote -> M.Map Ref Sha -> Annex ()
|
|
|
|
updateTrackingRefs rmt new = do
|
|
|
|
old <- inRepo $ Git.Ref.forEachRef
|
|
|
|
[Param (decodeBS (trackingRefPrefix rmt))]
|
|
|
|
|
|
|
|
-- Delete all tracking refs that are not in the map.
|
|
|
|
forM_ (filter (\p -> M.notMember (fst p) new) old) $ \(s, r) ->
|
|
|
|
inRepo $ Git.Ref.delete s r
|
|
|
|
|
|
|
|
-- Update all changed tracking refs.
|
|
|
|
let oldmap = M.fromList (map (\(s, r) -> (r, s)) old)
|
|
|
|
forM_ (M.toList new) $ \(r, s) ->
|
|
|
|
case M.lookup r oldmap of
|
|
|
|
Just s' | s' == s -> noop
|
|
|
|
_ -> inRepo $ Git.Branch.update' r s
|
2024-05-08 20:55:45 +00:00
|
|
|
|
|
|
|
-- git clone does not bother to set GIT_WORK_TREE when running this
|
|
|
|
-- program, and it does not run it inside the new git repo either.
|
|
|
|
-- GIT_DIR is set to the new git directory. So, have to override
|
|
|
|
-- the worktree to be the parent of the gitdir.
|
|
|
|
getRepo :: IO Repo
|
|
|
|
getRepo = getEnv "GIT_WORK_TREE" >>= \case
|
|
|
|
Just _ -> Git.CurrentRepo.get
|
|
|
|
Nothing -> fixup <$> Git.CurrentRepo.get
|
|
|
|
where
|
|
|
|
fixup r@(Repo { location = loc@(Local { worktree = Just _ }) }) =
|
|
|
|
r { location = loc { worktree = Just (P.takeDirectory (gitdir loc)) } }
|
|
|
|
fixup r = r
|
|
|
|
|
2024-05-08 22:07:26 +00:00
|
|
|
-- Records what the git-annex branch was at the beginning of this command.
|
|
|
|
data StartAnnexBranch
|
|
|
|
= AnnexBranchExistedAlready Ref
|
|
|
|
| AnnexBranchCreatedEmpty Ref
|
|
|
|
|
|
|
|
startAnnexBranch :: Annex StartAnnexBranch
|
|
|
|
startAnnexBranch = ifM (null <$> Annex.Branch.siblingBranches)
|
|
|
|
( AnnexBranchCreatedEmpty <$> Annex.Branch.getBranch
|
|
|
|
, AnnexBranchExistedAlready <$> Annex.Branch.getBranch
|
|
|
|
)
|
|
|
|
|
2024-05-08 20:55:45 +00:00
|
|
|
-- This is run after git has used this process to fetch or push from a
|
|
|
|
-- special remote that was specified using a git-annex url. If the git
|
|
|
|
-- repository was not initialized for use by git-annex already, it is still
|
|
|
|
-- not initialized at this point.
|
|
|
|
--
|
2024-05-08 22:07:26 +00:00
|
|
|
-- If the git-annex branch did not exist when this command started,
|
|
|
|
-- the current contents of it were created in passing by this command,
|
|
|
|
-- which is hard to avoid. But if a git-annex branch is fetched from the
|
|
|
|
-- special remote and contains Differences, it would not be possible to
|
|
|
|
-- merge it into the git-annex branch that was created while running this
|
|
|
|
-- command. To avoid that problem, when the git-annex branch was created
|
|
|
|
-- at the start of this command, it's deleted.
|
2024-05-08 20:55:45 +00:00
|
|
|
--
|
|
|
|
-- If there is still not a sibling git-annex branch, this deletes all annex
|
|
|
|
-- objects for git bundles from the annex objects directory, and deletes
|
|
|
|
-- the annex objects directory. That is necessary to avoid the
|
|
|
|
-- Annex.Init.objectDirNotPresent check preventing a later initialization.
|
|
|
|
-- And if the later initialization includes Differences, the git bundle
|
|
|
|
-- objects downloaded by this process would be in the wrong locations.
|
|
|
|
--
|
|
|
|
-- When there is now a sibling git-annex branch, this handles
|
|
|
|
-- initialization. When the initialized git-annex branch has Differences,
|
|
|
|
-- the git bundle objects are in the wrong place, so have to be deleted.
|
2024-05-08 22:07:26 +00:00
|
|
|
cleanupInitialization :: StartAnnexBranch -> Annex ()
|
|
|
|
cleanupInitialization sab = do
|
|
|
|
case sab of
|
|
|
|
AnnexBranchExistedAlready _ -> noop
|
|
|
|
AnnexBranchCreatedEmpty _ -> do
|
|
|
|
inRepo $ Git.Branch.delete Annex.Branch.fullname
|
|
|
|
indexfile <- fromRepo gitAnnexIndex
|
|
|
|
liftIO $ removeWhenExistsWith R.removeLink indexfile
|
|
|
|
ifM Annex.Branch.hasSibling
|
|
|
|
( do
|
|
|
|
autoInitialize' (pure True) remoteList
|
|
|
|
differences <- allDifferences <$> recordedDifferences
|
|
|
|
when (differences /= mempty) $
|
|
|
|
deletebundleobjects
|
|
|
|
, deletebundleobjects
|
|
|
|
)
|
2024-05-08 20:55:45 +00:00
|
|
|
where
|
|
|
|
deletebundleobjects = do
|
|
|
|
annexobjectdir <- fromRepo gitAnnexObjectDir
|
|
|
|
ks <- listKeys InAnnex
|
|
|
|
forM_ ks $ \k -> case fromKey keyVariety k of
|
|
|
|
GitBundleKey -> lockContentForRemoval k noop removeAnnex
|
|
|
|
_ -> noop
|
|
|
|
void $ liftIO $ tryIO $ removeDirectory (decodeBS annexobjectdir)
|