refuse to fetch from a remote that has no manifest

Otherwise, it can be confusing to clone from a wrong url, since it fails
to download a manifest and so appears as if the remote exists but is empty.

Sponsored-by: Jack Hill on Patreon
This commit is contained in:
Joey Hess 2024-05-13 09:47:21 -04:00
parent 424afe46d7
commit 3f848564ac
No known key found for this signature in database
GPG key ID: DB12DB0FF05F8F38
2 changed files with 31 additions and 21 deletions

View file

@ -111,7 +111,9 @@ capabilities = do
list :: State -> Remote -> Bool -> Annex State list :: State -> Remote -> Bool -> Annex State
list st rmt forpush = do list st rmt forpush = do
manifest <- downloadManifest rmt manifest <- if forpush
then downloadManifestWhenPresent rmt
else downloadManifestOrFail rmt
l <- forM (inManifest manifest) $ \k -> do l <- forM (inManifest manifest) $ \k -> do
b <- downloadGitBundle rmt k b <- downloadGitBundle rmt k
heads <- inRepo $ Git.Bundle.listHeads b heads <- inRepo $ Git.Bundle.listHeads b
@ -168,7 +170,7 @@ fetch st rmt [] = do
fetch' :: State -> Remote -> Annex () fetch' :: State -> Remote -> Annex ()
fetch' st rmt = do fetch' st rmt = do
manifest <- maybe (downloadManifest rmt) pure (manifestCache st) manifest <- maybe (downloadManifestOrFail rmt) pure (manifestCache st)
forM_ (inManifest manifest) $ \k -> forM_ (inManifest manifest) $ \k ->
downloadGitBundle rmt k >>= inRepo . Git.Bundle.unbundle downloadGitBundle rmt k >>= inRepo . Git.Bundle.unbundle
-- Newline indicates end of fetch. -- Newline indicates end of fetch.
@ -264,7 +266,8 @@ push st rmt ls = do
-- from it. -- from it.
fullPush :: State -> Remote -> [Ref] -> Annex (Bool, State) fullPush :: State -> Remote -> [Ref] -> Annex (Bool, State)
fullPush st rmt refs = guardPush st $ do fullPush st rmt refs = guardPush st $ do
oldmanifest <- maybe (downloadManifest rmt) pure (manifestCache st) oldmanifest <- maybe (downloadManifestWhenPresent rmt) pure
(manifestCache st)
let bs = map Git.Bundle.fullBundleSpec refs let bs = map Git.Bundle.fullBundleSpec refs
bundlekey <- generateAndUploadGitBundle rmt bs oldmanifest bundlekey <- generateAndUploadGitBundle rmt bs oldmanifest
uploadManifest rmt (mkManifest [bundlekey] []) uploadManifest rmt (mkManifest [bundlekey] [])
@ -285,7 +288,7 @@ guardPush st a = catchNonAsync a $ \ex -> do
incrementalPush :: State -> Remote -> M.Map Ref Sha -> M.Map Ref Sha -> Annex (Bool, State) incrementalPush :: State -> Remote -> M.Map Ref Sha -> M.Map Ref Sha -> Annex (Bool, State)
incrementalPush st rmt oldtrackingrefs newtrackingrefs = guardPush st $ do incrementalPush st rmt oldtrackingrefs newtrackingrefs = guardPush st $ do
bs <- calc [] (M.toList newtrackingrefs) bs <- calc [] (M.toList newtrackingrefs)
oldmanifest <- maybe (downloadManifest rmt) pure (manifestCache st) oldmanifest <- maybe (downloadManifestWhenPresent rmt) pure (manifestCache st)
bundlekey <- generateAndUploadGitBundle rmt bs oldmanifest bundlekey <- generateAndUploadGitBundle rmt bs oldmanifest
uploadManifest rmt (oldmanifest <> mkManifest [bundlekey] []) uploadManifest rmt (oldmanifest <> mkManifest [bundlekey] [])
return (True, st { manifestCache = Nothing }) return (True, st { manifestCache = Nothing })
@ -350,13 +353,14 @@ incrementalPush st rmt oldtrackingrefs newtrackingrefs = guardPush st $ do
) )
-- When the push deletes all refs from the remote, upload an empty -- When the push deletes all refs from the remote, upload an empty
-- manifest and then drop all bundles that were listed in it. -- manifest and then drop all bundles that were listed in the manifest.
-- The manifest is emptired first so if this is interrupted, only -- The manifest is emptied first so if this is interrupted, only
-- unused bundles will remain in the remote, rather than leaving the -- unused bundles will remain in the remote, rather than leaving the
-- remote with a manifest that refers to missing bundles. -- remote with a manifest that refers to missing bundles.
pushEmpty :: State -> Remote -> Annex (Bool, State) pushEmpty :: State -> Remote -> Annex (Bool, State)
pushEmpty st rmt = do pushEmpty st rmt = do
manifest <- maybe (downloadManifest rmt) pure (manifestCache st) manifest <- maybe (downloadManifestWhenPresent rmt) pure
(manifestCache st)
uploadManifest rmt mempty uploadManifest rmt mempty
ok <- allM (dropKey rmt) ok <- allM (dropKey rmt)
(genManifestKey (Remote.uuid rmt) : inManifest manifest) (genManifestKey (Remote.uuid rmt) : inManifest manifest)
@ -534,17 +538,27 @@ checkSpecialRemoteProblems rmt
Just "Cannot use this thirdparty-populated special remote as a git remote" Just "Cannot use this thirdparty-populated special remote as a git remote"
| otherwise = Nothing | otherwise = Nothing
-- Downloads the Manifest, or if it does not exist, returns an empty -- Downloads the Manifest when present in the remote. When not present,
-- Manifest. -- returns an empty Manifest.
downloadManifestWhenPresent :: Remote -> Annex Manifest
downloadManifestWhenPresent rmt = fromMaybe mempty <$> downloadManifest rmt
-- Downloads the Manifest, or fails if the remote does not contain it.
downloadManifestOrFail :: Remote -> Annex Manifest
downloadManifestOrFail rmt =
maybe (giveup "No git repository found in this remote.") return
=<< downloadManifest rmt
-- Downloads the Manifest or Nothing if the remote does not contain a
-- manifest.
-- --
-- Throws errors if the remote cannot be accessed or the download fails, -- Throws errors if the remote cannot be accessed or the download fails,
-- or if the manifest file cannot be parsed. -- or if the manifest file cannot be parsed.
-- downloadManifest :: Remote -> Annex (Maybe Manifest)
-- 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.
downloadManifest :: Remote -> Annex Manifest
downloadManifest rmt = ifM (Remote.checkPresent rmt mk) downloadManifest rmt = ifM (Remote.checkPresent rmt mk)
-- Downloads 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.
( withTmpFile "GITMANIFEST" $ \tmp tmph -> do ( withTmpFile "GITMANIFEST" $ \tmp tmph -> do
liftIO $ hClose tmph liftIO $ hClose tmph
_ <- Remote.retrieveKeyFile rmt mk _ <- Remote.retrieveKeyFile rmt mk
@ -552,10 +566,11 @@ downloadManifest rmt = ifM (Remote.checkPresent rmt mk)
nullMeterUpdate Remote.NoVerify nullMeterUpdate Remote.NoVerify
(outks, inks) <- partitionEithers . map parseline . B8.lines (outks, inks) <- partitionEithers . map parseline . B8.lines
<$> liftIO (B.readFile tmp) <$> liftIO (B.readFile tmp)
mkManifest m <- mkManifest
<$> checkvalid [] inks <$> checkvalid [] inks
<*> checkvalid [] outks <*> checkvalid [] outks
, return mempty return (Just m)
, return Nothing
) )
where where
mk = genManifestKey (Remote.uuid rmt) mk = genManifestKey (Remote.uuid rmt)

View file

@ -28,11 +28,6 @@ This is implememented and working. Remaining todo list for it:
stored in the repo. Chicken and egg problem cloning from stored in the repo. Chicken and egg problem cloning from
such a remote. Maybe allow advanced users to force it? such a remote. Maybe allow advanced users to force it?
* When the remote has no manifest, a pull from it should fail,
while a push should succeed. Otherwise, it can be confusing
to clone from a wrong url, since it fails to download
a manifest and so appears as if the remote is empty.
* Improve recovery from interrupted push by using outManifest to clean up * Improve recovery from interrupted push by using outManifest to clean up
after it. (Requires populating outManifest.) after it. (Requires populating outManifest.)