git-annex 7.20190912 → 7.20191009
raw patch · 33 files changed
+557/−167 lines, 33 filesdep +unliftio-core
Dependencies added: unliftio-core
Files
- Annex/AdjustedBranch/Name.hs +1/−1
- Annex/Content/PointerFile.hs +15/−2
- Annex/FileMatcher.hs +8/−6
- Annex/Hook.hs +14/−4
- Annex/Init.hs +2/−2
- Annex/Magic.hs +5/−4
- Assistant/Threads/Cronner.hs +1/−1
- CHANGELOG +32/−0
- COPYRIGHT +4/−0
- CmdLine/GitAnnex/Options.hs +28/−4
- Command/Adjust.hs +4/−0
- Command/Assistant.hs +1/−1
- Command/RemoteDaemon.hs +2/−2
- Command/Watch.hs +1/−1
- Config/Smudge.hs +21/−6
- Database/Fsck.hs +1/−1
- Database/Handle.hs +49/−2
- Database/Keys/SQL.hs +1/−1
- Git/AutoCorrect.hs +14/−14
- Git/CatFile.hs +4/−0
- Git/Credential.hs +64/−0
- Limit.hs +56/−12
- Remote/GitLFS.hs +72/−27
- Test/Framework.hs +13/−4
- Types/AdjustedBranch.hs +2/−1
- Utility/GitLFS.hs +62/−44
- Utility/Su.hs +3/−1
- doc/git-annex-adjust.mdwn +16/−2
- doc/git-annex-copy.mdwn +1/−1
- doc/git-annex-matching-options.mdwn +37/−0
- doc/git-annex-remotedaemon.mdwn +4/−9
- doc/git-annex.mdwn +16/−13
- git-annex.cabal +3/−1
Annex/AdjustedBranch/Name.hs view
@@ -49,7 +49,7 @@ serializeAdjustment FixAdjustment = "fixed" serializeAdjustment UnFixAdjustment = "unfixed" deserializeAdjustment "unlocked" = Just UnlockAdjustment- deserializeAdjustment "locked" = Just UnlockAdjustment+ deserializeAdjustment "locked" = Just LockAdjustment deserializeAdjustment "fixed" = Just FixAdjustment deserializeAdjustment "unfixed" = Just UnFixAdjustment deserializeAdjustment _ = Nothing
Annex/Content/PointerFile.hs view
@@ -9,15 +9,20 @@ module Annex.Content.PointerFile where +#if ! defined(mingw32_HOST_OS)+import System.Posix.Files+#else import System.PosixCompat.Files+#endif import Annex.Common import Annex.Perms import Annex.Link import Annex.ReplaceFile import Annex.InodeSentinal-import Utility.InodeCache import Annex.Content.LowLevel+import Utility.InodeCache+import Utility.Touch {- Populates a pointer file with the content of a key. -@@ -48,10 +53,18 @@ - Does not check if the pointer file is modified. -} depopulatePointerFile :: Key -> FilePath -> Annex () depopulatePointerFile key file = do- mode <- liftIO $ catchMaybeIO $ fileMode <$> getFileStatus file+ st <- liftIO $ catchMaybeIO $ getFileStatus file+ let mode = fmap fileMode st secureErase file liftIO $ nukeFile file ic <- replaceFile file $ \tmp -> do liftIO $ writePointerFile tmp key mode+#if ! defined(mingw32_HOST_OS)+ -- Don't advance mtime; this avoids unncessary re-smudging+ -- by git in some cases.+ liftIO $ maybe noop+ (\t -> touch tmp t False)+ (fmap modificationTimeHiRes st)+#endif withTSDelta (liftIO . genInodeCache tmp) maybe noop (restagePointerFile (Restage True) file) ic
Annex/FileMatcher.hs view
@@ -113,14 +113,14 @@ where splitparens = segmentDelim (`elem` "()") -commonKeylessTokens :: [ParseToken (MatchFiles Annex)]-commonKeylessTokens =+commonKeylessTokens :: LimitBy -> [ParseToken (MatchFiles Annex)]+commonKeylessTokens lb = [ SimpleToken "anything" (simply limitAnything) , SimpleToken "nothing" (simply limitNothing) , ValueToken "include" (usev limitInclude) , ValueToken "exclude" (usev limitExclude)- , ValueToken "largerthan" (usev $ limitSize (>))- , ValueToken "smallerthan" (usev $ limitSize (<))+ , ValueToken "largerthan" (usev $ limitSize lb (>))+ , ValueToken "smallerthan" (usev $ limitSize lb (<)) ] commonKeyedTokens :: [ParseToken (MatchFiles Annex)]@@ -147,7 +147,7 @@ [ SimpleToken "standard" (call $ matchStandard pcd) , SimpleToken "groupwanted" (call $ matchGroupWanted pcd) , SimpleToken "inpreferreddir" (simply $ limitInDir preferreddir)- ] ++ commonKeylessTokens+ ] ++ commonKeylessTokens LimitAnnexFiles where preferreddir = fromMaybe "public" $ M.lookup "preferreddir" =<< (`M.lookup` configMap pcd) =<< repoUUID pcd@@ -182,7 +182,9 @@ let mimer n = ValueToken n $ const $ Left $ "\""++n++"\" not supported; not built with MagicMime support" #endif- let parse = parseToken $ commonKeyedTokens ++ commonKeylessTokens +++ let parse = parseToken $+ commonKeyedTokens +++ commonKeylessTokens LimitDiskFiles ++ #ifdef WITH_MAGICMIME [ mimer "mimetype" $ matchMagic "mimetype" getMagicMimeType providedMimeType
Annex/Hook.hs view
@@ -1,10 +1,10 @@ {- git-annex git hooks -- - Note that it's important that the scripts installed by git-annex- - not change, otherwise removing old hooks using an old version of- - the script would fail.+ - Note that it's important that the content of scripts installed by+ - git-annex not change, otherwise removing old hooks using an old+ - version of the script would fail. -- - Copyright 2013-2018 Joey Hess <id@joeyh.name>+ - Copyright 2013-2019 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -}@@ -17,6 +17,16 @@ import Utility.Shell import qualified Data.Map as M++-- Remove all hooks.+unHook :: Annex ()+unHook = do+ hookUnWrite preCommitHook+ hookUnWrite postReceiveHook+ hookUnWrite postCheckoutHook+ hookUnWrite postMergeHook+ hookUnWrite preCommitAnnexHook+ hookUnWrite postUpdateAnnexHook preCommitHook :: Git.Hook preCommitHook = Git.Hook "pre-commit" (mkHookScript "git annex pre-commit .") []
Annex/Init.hs view
@@ -128,8 +128,8 @@ uninitialize :: Annex () uninitialize = do- hookUnWrite preCommitHook- hookUnWrite postReceiveHook+ unHook+ deconfigureSmudgeFilter removeRepoUUID removeVersion
Annex/Magic.hs view
@@ -17,6 +17,7 @@ ) where import Types.Mime+import Control.Monad.IO.Class #ifdef WITH_MAGICMIME import Magic import Utility.Env@@ -52,8 +53,8 @@ getMagicMime _ _ = return Nothing #endif -getMagicMimeType :: Magic -> FilePath -> IO (Maybe MimeType)-getMagicMimeType m f = fmap fst <$> getMagicMime m f+getMagicMimeType :: MonadIO m => Magic -> FilePath -> m (Maybe MimeType)+getMagicMimeType m f = liftIO $ fmap fst <$> getMagicMime m f -getMagicMimeEncoding :: Magic -> FilePath -> IO (Maybe MimeEncoding)-getMagicMimeEncoding m f = fmap snd <$> getMagicMime m f+getMagicMimeEncoding :: MonadIO m => Magic -> FilePath -> m(Maybe MimeEncoding)+getMagicMimeEncoding m f = liftIO $ fmap snd <$> getMagicMime m f
Assistant/Threads/Cronner.hs view
@@ -132,7 +132,7 @@ tolate nowt tz = case mmaxt of Just maxt -> nowt > maxt -- allow the job to start 10 minutes late- Nothing ->diffUTCTime + Nothing -> diffUTCTime (localTimeToUTC tz nowt) (localTimeToUTC tz t) > 600 run nowt = do
CHANGELOG view
@@ -1,3 +1,35 @@+git-annex (7.20191009) upstream; urgency=medium++ * Fix bug in handling of annex.largefiles that use largerthan/smallerthan.+ When adding a modified file, it incorrectly used the file size of the+ old version of the file, not the current size.+ * Added --mimetype and --mimeencoding file matching options.+ * Added --unlocked and --locked file matching options.+ * Added adjust --lock, to enter an adjusted branch where files are locked.+ * git-lfs: Added support for http basic auth.+ * git-lfs: Only do endpoint discovery once when concurrency is enabled.+ * fsck --incremental/--more: Fix bug that prevented the incremental fsck+ information from being updated every 5 minutes as it was supposed to be;+ it was only updated after 1000 files were checked, which may be more+ files that are possible to fsck in a given fsck time window.+ Thanks to Peter Simons for help with analysis of this bug.+ * Test: Use more robust directory removal when built with directory-1.2.7.+ * Close sqlite databases more robustly.+ * remotedaemon: Don't list --stop in help since it's not supported.+ * enable-tor: Run kdesu with -c option.+ * enable-tor: Use pkexec to run command as root when gksu and kdesu are not+ available.+ * When dropping an unlocked file, preserve its mtime, which avoids+ git status unncessarily running the clean filter on the file.+ * uninit: Remove several git hooks that git-annex init sets up.+ * uninit: Remove the smudge and clean filters that git-annex init sets up.+ * Work around git cat-file --batch's odd stripping of carriage return+ from the end of the line (some windows infection), avoiding crashing+ when the repo contains a filename ending in a carriage return.+ * git-annex-standalone.rpm: Fix the git-annex-shell symlink.++ -- Joey Hess <id@joeyh.name> Wed, 09 Oct 2019 12:31:31 -0400+ git-annex (7.20190912) upstream; urgency=medium * Default to v7 for new repositories.
COPYRIGHT view
@@ -10,6 +10,10 @@ © 2014 Sören Brunk License: AGPL-3+ +Files: doc/special_remotes/external/*+Copyright: © 2013 Joey Hess <id@joeyh.name>+License: GPL-3++ Files: Remote/Ddar.hs Copyright: © 2011 Joey Hess <id@joeyh.name> © 2014 Robie Basak <robie@justgohome.co.uk>
CmdLine/GitAnnex/Options.hs view
@@ -282,6 +282,26 @@ <> help "match files accessed within a time interval" <> hidden )+ , globalSetter Limit.addMimeType $ strOption+ ( long "mimetype" <> metavar paramGlob+ <> help "match files by mime type"+ <> hidden+ )+ , globalSetter Limit.addMimeEncoding $ strOption+ ( long "mimeencoding" <> metavar paramGlob+ <> help "match files by mime encoding"+ <> hidden+ )+ , globalFlag Limit.addUnlocked+ ( long "unlocked"+ <> help "match files that are unlocked"+ <> hidden+ )+ , globalFlag Limit.addLocked+ ( long "locked"+ <> help "match files that are locked"+ <> hidden+ ) ] -- Options to match files which may not yet be annexed.@@ -387,16 +407,20 @@ , stopDaemonOption :: Bool } -parseDaemonOptions :: Parser DaemonOptions-parseDaemonOptions = DaemonOptions- <$> switch+parseDaemonOptions :: Bool -> Parser DaemonOptions+parseDaemonOptions canstop+ | canstop = DaemonOptions <$> foreground <*> stop+ | otherwise = DaemonOptions <$> foreground <*> pure False+ where+ foreground = switch ( long "foreground" <> help "do not daemonize" )- <*> switch+ stop = switch ( long "stop" <> help "stop daemon" )+ completeRemotes :: HasCompleter f => Mod f a completeRemotes = completer $ mkCompleter $ \input -> do r <- maybe (pure Nothing) (Just <$$> Git.Config.read)
Command/Adjust.hs view
@@ -26,6 +26,10 @@ ( long "unlock" <> help "unlock annexed files" )+ <|> flag' LockAdjustment+ ( long "lock"+ <> help "lock annexed files"+ ) <|> flag' FixAdjustment ( long "fix" <> help "fix symlinks to annnexed files"
Command/Assistant.hs view
@@ -34,7 +34,7 @@ optParser :: CmdParamsDesc -> Parser AssistantOptions optParser _ = AssistantOptions- <$> parseDaemonOptions+ <$> parseDaemonOptions True <*> switch ( long "autostart" <> help "start in known repositories"
Command/RemoteDaemon.hs view
@@ -15,9 +15,9 @@ cmd :: Command cmd = noCommit $- command "remotedaemon" SectionMaintenance+ command "remotedaemon" SectionCommon "persistent communication with remotes"- paramNothing (run <$$> const parseDaemonOptions)+ paramNothing (run <$$> const (parseDaemonOptions False)) run :: DaemonOptions -> CommandSeek run o
Command/Watch.hs view
@@ -15,7 +15,7 @@ cmd = notBareRepo $ command "watch" SectionCommon "watch for changes and autocommit"- paramNothing (seek <$$> const parseDaemonOptions)+ paramNothing (seek <$$> const (parseDaemonOptions True)) seek :: DaemonOptions -> CommandSeek seek o = commandAction $ start False o Nothing
Config/Smudge.hs view
@@ -1,6 +1,6 @@ {- Git smudge filter configuration -- - Copyright 2011-2018 Joey Hess <id@joeyh.name>+ - Copyright 2011-2019 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -}@@ -32,10 +32,25 @@ gfs <- readattr gf liftIO $ unless ("filter=annex" `isInfixOf` (lfs ++ gfs)) $ do createDirectoryIfMissing True (takeDirectory lf)- writeFile lf (lfs ++ "\n" ++ stdattr)+ writeFile lf (lfs ++ "\n" ++ unlines stdattr) where readattr = liftIO . catchDefaultIO "" . readFileStrict- stdattr = unlines- [ "* filter=annex"- , ".* !filter"- ]++stdattr :: [String]+stdattr =+ [ "* filter=annex"+ , ".* !filter"+ ]++-- Note that this removes the local git attributes for filtering, +-- which is what git-annex installed, but it does not change anything+-- that may have been committed to a .gitattributes in the repository.+-- git-annex does not commit that.+deconfigureSmudgeFilter :: Annex ()+deconfigureSmudgeFilter = do+ lf <- Annex.fromRepo Git.attributesLocal+ ls <- liftIO $ catchDefaultIO [] $ lines <$> readFileStrict lf+ liftIO $ writeFile lf $ unlines $+ filter (\l -> l `notElem` stdattr && not (null l)) ls+ unsetConfig (ConfigKey "filter.annex.smudge")+ unsetConfig (ConfigKey "filter.annex.clean")
Database/Fsck.hs view
@@ -83,7 +83,7 @@ | sz > 1000 = return True | otherwise = do now <- getCurrentTime- return $ diffUTCTime lastcommittime now > 300+ return $ diffUTCTime now lastcommittime > 300 {- Doesn't know about keys that were just added with addDb. -} inDb :: FsckHandle -> Key -> IO Bool
Database/Handle.hs view
@@ -1,10 +1,12 @@ {- Persistent sqlite database handles. -- - Copyright 2015-2018 Joey Hess <id@joeyh.name>+ - Copyright 2015-2019 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -} +{-# LANGUAGE TypeFamilies, FlexibleContexts #-}+ module Database.Handle ( DbHandle, DbConcurrency(..),@@ -23,6 +25,8 @@ import qualified Database.Sqlite as Sqlite import Control.Monad import Control.Monad.IO.Class (liftIO)+import Control.Monad.IO.Unlift (MonadUnliftIO, withRunInIO)+import Control.Monad.Logger (MonadLogger) import Control.Concurrent import Control.Concurrent.Async import Control.Exception (throwIO, BlockedIndefinitelyOnMVar(..))@@ -193,7 +197,7 @@ go conn retries = do r <- try $ runResourceT $ runNoLoggingT $- withSqlConn (wrapConnection conn) $+ withSqlConnRobustly (wrapConnection conn) $ runSqlConn a case r of Right v -> return v@@ -237,3 +241,46 @@ nullselect = T.pack $ "SELECT null from " ++ tablename ++ " limit 1" briefdelay = threadDelay 1000 -- 1/1000th second++-- Like withSqlConn, but more robust.+withSqlConnRobustly+ :: (MonadUnliftIO m, MonadLogger m, IsPersistBackend backend, BaseBackend backend ~ SqlBackend)+ => (LogFunc -> IO backend)+ -> (backend -> m a)+ -> m a+withSqlConnRobustly open f = do+ logFunc <- askLogFunc+ withRunInIO $ \run -> bracket+ (open logFunc)+ closeRobustly+ (run . f)++-- Sqlite can throw ErrorBusy while closing a database; this catches+-- the exception and retries.+closeRobustly+ :: (IsPersistBackend backend, BaseBackend backend ~ SqlBackend)+ => backend+ -> IO ()+closeRobustly conn = go maxretries briefdelay+ where+ briefdelay = 1000 -- 1/1000th second++ -- Try up to 14 times; with the delay doubling each time,+ -- the maximum delay before giving up is 16 seconds.+ maxretries = 14 :: Int++ go retries delay = do+ r <- try $ close' conn+ case r of+ Right () -> return ()+ Left ex@(Sqlite.SqliteException { Sqlite.seError = e })+ | e == Sqlite.ErrorBusy -> do+ threadDelay delay+ let delay' = delay * 2+ let retries' = retries - 1+ if retries' < 1+ then rethrow "while closing database connection" ex+ else go retries' delay'+ | otherwise -> rethrow "while closing database connection" ex+ + rethrow msg e = throwIO $ userError $ show e ++ "(" ++ msg ++ ")"
Database/Keys/SQL.hs view
@@ -57,7 +57,7 @@ | sz > 1000 = return True | otherwise = do now <- getCurrentTime- return $ diffUTCTime lastcommittime now > 300+ return $ diffUTCTime now lastcommittime > 300 addAssociatedFile :: IKey -> TopFilePath -> WriteHandle -> IO () addAssociatedFile ik f = queueDb $ do
Git/AutoCorrect.hs view
@@ -40,13 +40,14 @@ similarEnough (_, cst) = cst < similarityFloor {- Takes action based on git's autocorrect configuration, in preparation for- - an autocorrected command being run. -}+ - an autocorrected command being run.+ -} prepare :: String -> (c -> String) -> [c] -> Maybe Repo -> IO () prepare input showmatch matches r = case readish . Git.Config.get "help.autocorrect" "0" =<< r of Just n | n == 0 -> list- | n < 0 -> warn+ | n < 0 -> warn Nothing | otherwise -> sleep n Nothing -> list where@@ -55,17 +56,16 @@ , "" , "Did you mean one of these?" ] ++ map (\m -> "\t" ++ showmatch m) matches- warn = - hPutStr stderr $ unlines- [ "WARNING: You called a command named '" ++- input ++ "', which does not exist."- , "Continuing under the assumption that you meant '" ++- showmatch (Prelude.head matches) ++ "'"- ]+ warn :: Maybe Float -> IO ()+ warn mdelaysec = hPutStr stderr $ unlines+ [ "WARNING: You called a git-annex command named '" +++ input ++ "', which does not exist."+ , case mdelaysec of+ Nothing -> "Continuing under the assumption that you meant " ++ match+ Just sec -> "Continuing in " ++ show sec ++ " seconds, assuming that you meant " ++ match+ ]+ where+ match = "'" ++ showmatch (Prelude.head matches) ++ "'." sleep n = do- warn- hPutStrLn stderr $ unwords- [ "in"- , show (fromIntegral n / 10 :: Float)- , "seconds automatically..."]+ warn (Just (fromIntegral n / 10 :: Float)) threadDelay (n * 100000) -- deciseconds to microseconds
Git/CatFile.hs view
@@ -132,6 +132,10 @@ -- filename itself contains a newline, have to fall back to another -- method of getting the information. | '\n' `elem` s = newlinefallback+ -- git strips carriage return from the end of a line, out of some+ -- misplaced desire to support windows, so also use the newline+ -- fallback for those.+ | "\r" `isSuffixOf` s = newlinefallback | otherwise = CoProcess.query hdl send receive where send to = hPutStrLn to s
+ Git/Credential.hs view
@@ -0,0 +1,64 @@+{- git credential interface+ -+ - Copyright 2019 Joey Hess <id@joeyh.name>+ -+ - Licensed under the GNU AGPL version 3 or higher.+ -}++module Git.Credential where++import Common+import Git+import Git.Command+import Utility.Url++import qualified Data.Map as M++data Credential = Credential { fromCredential :: M.Map String String }++credentialUsername :: Credential -> Maybe String+credentialUsername = M.lookup "username" . fromCredential++credentialPassword :: Credential -> Maybe String+credentialPassword = M.lookup "password" . fromCredential++-- | This may prompt the user for login information, or get cached login+-- information.+getUrlCredential :: URLString -> Repo -> IO Credential+getUrlCredential = runCredential "fill" . urlCredential++-- | Call if the credential the user entered works, and can be cached for+-- later use if git is configured to do so.+approveUrlCredential :: Credential -> Repo -> IO ()+approveUrlCredential c = void . runCredential "approve" c++-- | Call if the credential the user entered does not work.+rejectUrlCredential :: Credential -> Repo -> IO ()+rejectUrlCredential c = void . runCredential "reject" c++urlCredential :: URLString -> Credential+urlCredential = Credential . M.singleton "url"++runCredential :: String -> Credential -> Repo -> IO Credential+runCredential action input r =+ parseCredential <$> pipeWriteRead + [ Param "credential"+ , Param action+ ]+ (Just (flip hPutStr formatinput))+ r+ where+ formatinput = concat+ [ formatCredential input+ , "\n" -- blank line signifies end of input+ ]++formatCredential :: Credential -> String+formatCredential = unlines . map (\(k, v) -> k ++"=" ++ v) . M.toList . fromCredential++parseCredential :: String -> Credential+parseCredential = Credential . M.fromList . map go . lines+ where+ go l = case break (== '=') l of+ (k, _:v) -> (k, v)+ (k, []) -> (k, "")
Limit.hs view
@@ -1,6 +1,6 @@ {- user-specified limits on files to act on -- - Copyright 2011-2017 Joey Hess <id@joeyh.name>+ - Copyright 2011-2019 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -}@@ -16,6 +16,7 @@ import Annex.Action import Annex.UUID import Annex.Magic+import Annex.Link import Logs.Trust import Annex.NumCopies import Types.Key@@ -37,6 +38,10 @@ import qualified Data.Set as S import qualified Data.Map as M +{- Some limits can look at the current status of files on+ - disk, or in the annex. This allows controlling which happens. -}+data LimitBy = LimitDiskFiles | LimitAnnexFiles+ {- Checks if there are user-specified limits. -} limited :: Annex Bool limited = (not . Utility.Matcher.isEmpty) <$> getMatcher'@@ -94,12 +99,33 @@ go (MatchingKey _ (AssociatedFile Nothing)) = pure False go (MatchingKey _ (AssociatedFile (Just af))) = pure $ matchGlob cglob af -matchMagic :: String -> (Magic -> FilePath -> IO (Maybe String)) -> (ProvidedInfo -> OptInfo String) -> Maybe Magic -> MkLimit Annex+addMimeType :: String -> Annex ()+addMimeType = addMagicLimit "mimetype" getMagicMimeType providedMimeType++addMimeEncoding :: String -> Annex ()+addMimeEncoding = addMagicLimit "mimeencoding" getMagicMimeEncoding providedMimeEncoding++addMagicLimit :: String -> (Magic -> FilePath -> Annex (Maybe String)) -> (ProvidedInfo -> OptInfo String) -> String -> Annex ()+addMagicLimit limitname querymagic selectprovidedinfo glob = do+ magic <- liftIO initMagicMime+ addLimit $ matchMagic limitname querymagic' selectprovidedinfo magic glob+ where+ querymagic' magic f = liftIO (isPointerFile f) >>= \case+ -- Avoid getting magic of a pointer file, which would+ -- wrongly be detected as text.+ Just _ -> return Nothing+ -- When the file is an annex symlink, get magic of the+ -- object file.+ Nothing -> isAnnexLink f >>= \case+ Just k -> withObjectLoc k $ querymagic magic+ Nothing -> querymagic magic f++matchMagic :: String -> (Magic -> FilePath -> Annex (Maybe String)) -> (ProvidedInfo -> OptInfo String) -> Maybe Magic -> MkLimit Annex matchMagic _limitname querymagic selectprovidedinfo (Just magic) glob = Right $ const go where cglob = compileGlob glob CaseSensative -- memoized go (MatchingKey _ _) = pure False- go (MatchingFile fi) = liftIO $ catchBoolIO $+ go (MatchingFile fi) = catchBoolIO $ maybe False (matchGlob cglob) <$> querymagic magic (currFile fi) go (MatchingInfo p) =@@ -107,6 +133,22 @@ matchMagic limitname _ _ Nothing _ = Left $ "unable to load magic database; \""++limitname++"\" cannot be used" +addUnlocked :: Annex ()+addUnlocked = addLimit $ Right $ const $ matchLockStatus False++addLocked :: Annex ()+addLocked = addLimit $ Right $ const $ matchLockStatus True++matchLockStatus :: Bool -> MatchInfo -> Annex Bool+matchLockStatus _ (MatchingKey _ _) = pure False+matchLockStatus _ (MatchingInfo _) = pure False+matchLockStatus wantlocked (MatchingFile fi) = liftIO $ do+ islocked <- isPointerFile (currFile fi) >>= \case+ Just _key -> return False+ Nothing -> isSymbolicLink+ <$> getSymbolicLinkStatus (currFile fi)+ return (islocked == wantlocked)+ {- Adds a limit to skip files not believed to be present - in a specfied repository. Optionally on a prior date. -} addIn :: String -> Annex ()@@ -264,26 +306,28 @@ {- Adds a limit to skip files that are too large or too small -} addLargerThan :: String -> Annex ()-addLargerThan = addLimit . limitSize (>)+addLargerThan = addLimit . limitSize LimitAnnexFiles (>) addSmallerThan :: String -> Annex ()-addSmallerThan = addLimit . limitSize (<)+addSmallerThan = addLimit . limitSize LimitAnnexFiles (<) -limitSize :: (Maybe Integer -> Maybe Integer -> Bool) -> MkLimit Annex-limitSize vs s = case readSize dataUnits s of+limitSize :: LimitBy -> (Maybe Integer -> Maybe Integer -> Bool) -> MkLimit Annex+limitSize lb vs s = case readSize dataUnits s of Nothing -> Left "bad size" Just sz -> Right $ go sz where- go sz _ (MatchingFile fi) = lookupFileKey fi >>= check fi sz+ go sz _ (MatchingFile fi) = case lb of+ LimitAnnexFiles -> lookupFileKey fi >>= \case+ Just key -> checkkey sz key+ Nothing -> return False+ LimitDiskFiles -> do+ filesize <- liftIO $ catchMaybeIO $ getFileSize (currFile fi)+ return $ filesize `vs` Just sz go sz _ (MatchingKey key _) = checkkey sz key go sz _ (MatchingInfo p) = getInfo (providedFileSize p) >>= \sz' -> return (Just sz' `vs` Just sz) checkkey sz key = return $ keySize key `vs` Just sz- check _ sz (Just key) = checkkey sz key- check fi sz Nothing = do- filesize <- liftIO $ catchMaybeIO $ getFileSize (currFile fi)- return $ filesize `vs` Just sz addMetaData :: String -> Annex () addMetaData = addLimit . limitMetaData
Remote/GitLFS.hs view
@@ -17,6 +17,7 @@ import qualified Git.Types as Git import qualified Git.Url import qualified Git.GCrypt+import qualified Git.Credential as Git import Config import Config.Cost import Remote.Helper.Special@@ -42,6 +43,7 @@ import qualified Data.ByteString.Lazy as L import qualified Data.Text as T import qualified Data.Text.Encoding as E+import qualified Control.Concurrent.MSemN as MSemN remote :: RemoteType remote = RemoteType@@ -64,7 +66,8 @@ g <- Annex.gitRepo liftIO $ Git.GCrypt.encryptedRemote g r else pure r- h <- liftIO $ newTVarIO $ LFSHandle Nothing Nothing r' gc+ sem <- liftIO $ MSemN.new 1+ h <- liftIO $ newTVarIO $ LFSHandle Nothing Nothing sem r' gc cst <- remoteCost gc expensiveRemoteCost return $ Just $ specialRemote' specialcfg c (simplyPrepare $ store u h)@@ -159,31 +162,34 @@ data LFSHandle = LFSHandle { downloadEndpoint :: Maybe LFS.Endpoint , uploadEndpoint :: Maybe LFS.Endpoint+ , getEndPointLock :: MSemN.MSemN Int , remoteRepo :: Git.Repo , remoteGitConfig :: RemoteGitConfig } +-- Only let one thread at a time do endpoint discovery.+withEndPointLock :: LFSHandle -> Annex a -> Annex a+withEndPointLock h = bracket_+ (liftIO $ MSemN.wait l 1)+ (liftIO $ MSemN.signal l 1)+ where+ l = getEndPointLock h+ discoverLFSEndpoint :: LFS.TransferRequestOperation -> LFSHandle -> Annex (Maybe LFS.Endpoint) discoverLFSEndpoint tro h | Git.repoIsSsh r = gossh | Git.repoIsHttp r = gohttp- | otherwise = do- warning "git-lfs endpoint has unsupported URI scheme"- return Nothing+ | otherwise = unsupportedurischeme where r = remoteRepo h lfsrepouri = case Git.location r of Git.Url u -> u _ -> giveup $ "unsupported git-lfs remote location " ++ Git.repoLocation r- gohttp = case tro of- LFS.RequestDownload -> return $ LFS.guessEndpoint lfsrepouri- LFS.RequestUpload -> do- -- git-lfs does support storing over http,- -- but it would need prompting for http basic- -- authentication each time git-annex discovered- -- the endpoint.- warning "Storing content in git-lfs currently needs a ssh repository url, not http."- return Nothing+ + unsupportedurischeme = do+ warning "git-lfs endpoint has unsupported URI scheme"+ return Nothing+ gossh = case mkSshHost <$> Git.Url.hostuser r of Nothing -> do warning "Unable to parse ssh url for git-lfs remote."@@ -216,14 +222,56 @@ warning $ "unexpected response from git-lfs remote when doing ssh endpoint discovery" return Nothing Just endpoint -> return (Just endpoint)+ + -- The endpoint may or may not need http basic authentication,+ -- which involves using git-credential to prompt for the password.+ --+ -- To determine if it does, make a download or upload request to+ -- it, not including any objects in the request, and see if+ -- the server requests authentication.+ gohttp = case LFS.guessEndpoint lfsrepouri of+ Nothing -> unsupportedurischeme+ Just endpoint -> do+ let testreq = LFS.startTransferRequest endpoint transfernothing+ flip catchNonAsync (const (returnendpoint endpoint)) $ do+ resp <- makeSmallAPIRequest testreq+ if needauth (responseStatus resp)+ then do+ cred <- prompt $ inRepo $ Git.getUrlCredential (show lfsrepouri)+ let endpoint' = addbasicauth cred endpoint+ let testreq' = LFS.startTransferRequest endpoint' transfernothing+ flip catchNonAsync (const (returnendpoint endpoint')) $ do+ resp' <- makeSmallAPIRequest testreq'+ inRepo $ if needauth (responseStatus resp')+ then Git.rejectUrlCredential cred+ else Git.approveUrlCredential cred+ returnendpoint endpoint'+ else returnendpoint endpoint+ where+ transfernothing = LFS.TransferRequest+ { LFS.req_operation = tro+ , LFS.req_transfers = [LFS.Basic]+ , LFS.req_ref = Nothing+ , LFS.req_objects = []+ }+ returnendpoint = return . Just + needauth status = status == unauthorized401++ addbasicauth cred endpoint =+ case (Git.credentialUsername cred, Git.credentialPassword cred) of+ (Just u, Just p) ->+ LFS.modifyEndpointRequest endpoint $+ applyBasicAuth (encodeBS u) (encodeBS p)+ _ -> endpoint+ -- The endpoint is cached for later use. getLFSEndpoint :: LFS.TransferRequestOperation -> TVar LFSHandle -> Annex (Maybe LFS.Endpoint) getLFSEndpoint tro hv = do h <- liftIO $ atomically $ readTVar hv case f h of Just endpoint -> return (Just endpoint)- Nothing -> discoverLFSEndpoint tro h >>= \case+ Nothing -> withEndPointLock h $ discoverLFSEndpoint tro h >>= \case Just endpoint -> do liftIO $ atomically $ writeTVar hv $ case tro of@@ -256,16 +304,14 @@ => LFS.TransferRequest -> LFS.Endpoint -> Annex (Either String (LFS.TransferResponse op))-sendTransferRequest req endpoint = - case LFS.startTransferRequest endpoint req of- Just httpreq -> do- httpresp <- makeSmallAPIRequest $ setRequestCheckStatus httpreq- return $ case LFS.parseTransferResponse (responseBody httpresp) of- LFS.ParsedTransferResponse resp -> Right resp- LFS.ParsedTransferResponseError tro -> Left $- T.unpack $ LFS.resperr_message tro- LFS.ParseFailed err -> Left err- Nothing -> return $ Left "unable to parse git-lfs endpoint url"+sendTransferRequest req endpoint = do+ let httpreq = LFS.startTransferRequest endpoint req+ httpresp <- makeSmallAPIRequest $ setRequestCheckStatus httpreq+ return $ case LFS.parseTransferResponse (responseBody httpresp) of+ LFS.ParsedTransferResponse resp -> Right resp+ LFS.ParsedTransferResponseError tro -> Left $+ T.unpack $ LFS.resperr_message tro+ LFS.ParseFailed err -> Left err extractKeySha256 :: Key -> Maybe LFS.SHA256 extractKeySha256 k = case keyVariety k of@@ -409,9 +455,8 @@ -- Unable to find enough information to request the key -- from git-lfs, so it's not present there. Nothing -> return False- Just (req, sha256, size) -> case LFS.startTransferRequest endpoint req of- Nothing -> giveup "unable to parse git-lfs endpoint url"- Just httpreq -> go sha256 size =<< makeSmallAPIRequest httpreq+ Just (req, sha256, size) -> go sha256 size + =<< makeSmallAPIRequest (LFS.startTransferRequest endpoint req) where go sha256 size httpresp | responseStatus httpresp == status200 = go' sha256 size $
Test/Framework.hs view
@@ -5,6 +5,8 @@ - Licensed under the GNU AGPL version 3 or higher. -} +{-# LANGUAGE CPP #-}+ module Test.Framework where import Test.Tasty@@ -223,26 +225,33 @@ Utility.Env.Set.setEnv "GIT_CONFIG_NOSYSTEM" "1" True a +removeDirectoryForCleanup :: FilePath -> IO ()+#if MIN_VERSION_directory(1,2,7)+removeDirectoryForCleanup = removePathForcibly+#else+removeDirectoryForCleanup = removeDirectoryRecursive+#endif+ cleanup :: FilePath -> IO () cleanup dir = whenM (doesDirectoryExist dir) $ do Command.Uninit.prepareRemoveAnnexDir' dir -- This can fail if files in the directory are still open by a -- subprocess.- void $ tryIO $ removeDirectoryRecursive dir+ void $ tryIO $ removeDirectoryForCleanup dir finalCleanup :: IO () finalCleanup = whenM (doesDirectoryExist tmpdir) $ do Annex.Action.reapZombies Command.Uninit.prepareRemoveAnnexDir' tmpdir- catchIO (removeDirectoryRecursive tmpdir) $ \e -> do+ catchIO (removeDirectoryForCleanup tmpdir) $ \e -> do print e putStrLn "sleeping 10 seconds and will retry directory cleanup" Utility.ThreadScheduler.threadDelaySeconds $ Utility.ThreadScheduler.Seconds 10 whenM (doesDirectoryExist tmpdir) $ do Annex.Action.reapZombies- removeDirectoryRecursive tmpdir- + removeDirectoryForCleanup tmpdir+ checklink :: FilePath -> Assertion checklink f = ifM (annexeval Config.crippledFileSystem) ( (isJust <$> annexeval (Annex.Link.getAnnexLinkTarget f))
Types/AdjustedBranch.hs view
@@ -39,7 +39,8 @@ instance ReversableAdjustment LinkAdjustment where reverseAdjustment UnlockAdjustment = LockAdjustment- reverseAdjustment LockAdjustment = UnlockAdjustment+ -- Keep the file locked intentionally when reversing LockAdjustment.+ reverseAdjustment LockAdjustment = LockAdjustment reverseAdjustment FixAdjustment = UnFixAdjustment reverseAdjustment UnFixAdjustment = FixAdjustment
Utility/GitLFS.hs view
@@ -7,18 +7,29 @@ - Licensed under the GNU AGPL version 3 or higher. -} +-- | This implementation of the git-lfs API uses http Request and Response,+-- but leaves actually connecting up the http client to the user.+--+-- You'll want to use a Manager that supports https, since the protocol+-- uses http basic auth.+--+-- Some LFS servers, notably Github's, may require a User-Agent header+-- in some of the requests, in order to allow eg, uploads. No such header+-- is added by default, so be sure to add your own.+ {-# LANGUAGE DeriveGeneric, FlexibleInstances, FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE LambdaCase #-} module Utility.GitLFS (- -- * transfer requests+ -- * Transfer requests TransferRequest(..), TransferRequestOperation(..), TransferAdapter(..), TransferRequestObject(..), startTransferRequest,- -- * responses to transfer requests++ -- * Responses to transfer requests TransferResponse(..), TransferResponseOperation(..), IsTransferResponseOperation,@@ -27,19 +38,23 @@ OperationParams(..), ParsedTransferResponse(..), parseTransferResponse,- -- * making transfers++ -- * Making transfers downloadOperationRequest, uploadOperationRequests,- -- * endpoint discovery++ -- * Endpoint discovery Endpoint, guessEndpoint,- HostUser,+ modifyEndpointRequest, sshDiscoverEndpointCommand, parseSshDiscoverEndpointResponse,- -- * errors++ -- * Errors TransferResponseError(..), TransferResponseObjectError(..),- -- * additional data types++ -- * Additional data types Url, SHA256, GitRef(..),@@ -48,16 +63,6 @@ HTTPHeaderValue, ) where --- | This implementation of the git-lfs API uses http Request and Response,--- but leaves actually connecting up the http client to the user.------ You'll want to use a Manager that supports https, since the protocol--- uses http basic auth.------ Some LFS servers, notably Github's, may require a User-Agent header--- in some of the requests, in order to allow eg, uploads. No such header--- is added by dedault, so be sure to add your own.- import Data.Aeson import Data.Aeson.Types import GHC.Generics@@ -278,9 +283,7 @@ type SHA256 = T.Text -- | The endpoint of a git-lfs server.-data Endpoint- = EndpointURI URI.URI- | EndpointDiscovered SshDiscoveryResponse+data Endpoint = Endpoint Request deriving (Show) -- | Command to run via ssh with to discover an endpoint. The FilePath is@@ -298,20 +301,41 @@ RequestUpload -> "upload" ] +-- Internal smart constructor for an Endpoint.+-- +-- Since this uses the LFS batch API, it adds /objects/batch+-- to the endpoint url. It also adds the necessary headers to use JSON.+mkEndpoint :: URI.URI -> Maybe Endpoint+mkEndpoint uri = do+ r <- requestFromURI uri+ let r' = addLfsJsonHeaders $ r { path = path r <> "/objects/batch" }+ return (Endpoint r')+ -- | Parse the json output when doing ssh endpoint discovery. parseSshDiscoverEndpointResponse :: L.ByteString -> Maybe Endpoint-parseSshDiscoverEndpointResponse resp = EndpointDiscovered <$> decode resp+parseSshDiscoverEndpointResponse resp = do+ sr <- decode resp+ uri <- URI.parseURI (T.unpack (endpoint_href sr))+ endpoint <- mkEndpoint uri+ return $ modifyEndpointRequest endpoint $ case endpoint_header sr of+ Nothing -> id+ Just headers ->+ let headers' = map convheader (M.toList headers)+ in \req -> req+ { requestHeaders = requestHeaders req ++ headers' }+ where+ convheader (k, v) = (CI.mk (E.encodeUtf8 k), E.encodeUtf8 v) -- | Guesses the LFS endpoint from the http url of a git remote. -- -- https://github.com/git-lfs/git-lfs/blob/master/docs/api/server-discovery.md guessEndpoint :: URI.URI -> Maybe Endpoint guessEndpoint uri = case URI.uriScheme uri of- "https:" -> Just endpoint- "http:" -> Just endpoint+ "https:" -> endpoint+ "http:" -> endpoint _ -> Nothing where- endpoint = EndpointURI $ uri+ endpoint = mkEndpoint $ uri -- force https because the git-lfs protocol uses http -- basic auth tokens, which should not be exposed { URI.uriScheme = "https:"@@ -327,28 +351,22 @@ droptrailing c = reverse . dropWhile (== c) . reverse +-- | When an Endpoint is used to generate a Request, this allows adjusting+-- that Request.+--+-- This can be used to add http basic authentication to an Endpoint:+--+-- > modifyEndpointRequest (guessEndpoint u) (applyBasicAuth "user" "pass")+modifyEndpointRequest :: Endpoint -> (Request -> Request) -> Endpoint+modifyEndpointRequest (Endpoint r) f = Endpoint (f r)+ -- | Makes a Request that will start the process of making a transfer to or -- from the LFS endpoint.-startTransferRequest :: Endpoint -> TransferRequest -> Maybe Request-startTransferRequest (EndpointURI uri) tr = do- r <- requestFromURI uri- return $ addLfsJsonHeaders $ r- -- Since this uses the LFS batch API, it adds /objects/batch- -- to the endpoint url.- { path = path r <> "/objects/batch"- , method = "POST"- , requestBody = RequestBodyLBS (encode tr)- }-startTransferRequest (EndpointDiscovered sr) tr = do- uri <- URI.parseURI (T.unpack (endpoint_href sr))- req <- startTransferRequest (EndpointURI uri) tr- let headers = map convheader $ maybe [] M.toList $ endpoint_header sr- return $ req { requestHeaders = requestHeaders req ++ headers }- where- convheader (k, v) = (CI.mk (E.encodeUtf8 k), E.encodeUtf8 v)---- | "user@host" or just the hostname.-type HostUser = String+startTransferRequest :: Endpoint -> TransferRequest -> Request+startTransferRequest (Endpoint r) tr = r+ { method = "POST"+ , requestBody = RequestBodyLBS (encode tr)+ } addLfsJsonHeaders :: Request -> Request addLfsJsonHeaders r = r
Utility/Su.hs view
@@ -76,7 +76,9 @@ [ SuCommand (MayPromptPassword SomePassword) "gksu" [Param shellcmd] , SuCommand (MayPromptPassword SomePassword) "kdesu"- [Param shellcmd]+ [Param "-c", Param shellcmd]+ , SuCommand (MayPromptPassword SomePassword) "pkexec"+ ([Param cmd] ++ ps) -- Available in Debian's menu package; knows about lots of -- ways to gain root. , SuCommand (MayPromptPassword SomePassword) "su-to-root"
doc/git-annex-adjust.mdwn view
@@ -4,7 +4,7 @@ # SYNOPSIS -git annex adjust `--unlock|--fix|--hide-missing [--unlock|--fix]`+git annex adjust `--unlock|--lock|--fix|--hide-missing [--unlock|--lock|--fix]` # DESCRIPTION @@ -49,6 +49,20 @@ disk space, any modification made to a file will cause the old version of the file to be lost from the local repository. So, enable annex.thin with care. + When in an adjusted unlocked branch, `git annex add` will add files+ unlocked instead of the default behavior of adding them locked.++* `--lock`++ Lock all annexed file in the adjusted branch. This may be preferred+ by those who like seeing broken symlinks when the content of an+ annexed file is not present.++ When in an adjusted locked branch, `git annex add` will add files locked,+ as usual. However, `git add` (and `git commit -a` etc) still add files+ unlocked. This is because it's not possible for those git commands to+ add files locked.+ * `--fix` Fix the symlinks to annexed files to point to the local git annex@@ -74,7 +88,7 @@ still operate on them, and can be used to download missing files from remotes. - This option can be combined with --unlock or --fix.+ This option can be combined with --unlock, --lock, or --fix. # SEE ALSO
doc/git-annex-copy.mdwn view
@@ -69,7 +69,7 @@ * `--key=keyname` - Use this option to move a specified key.+ Use this option to copy a specified key. * file matching options
doc/git-annex-matching-options.mdwn view
@@ -159,6 +159,43 @@ If the OS or filesystem does not support access times, this will not match any files. +* `--unlocked`++ Matches annexed files that are unlocked.++* `--locked`++ Matches annexed files that are locked.++* `--mimetype=glob`++ Looks up the MIME type of a file, and checks if the glob matches it.++ For example, `--mimetype="text/*"` will match many varieties of text files,+ including "text/plain", but also "text/x-shellscript", "text/x-makefile",+ etc.++ The MIME types are the same that are displayed by running `file --mime-type`++ If the file's annexed content is not present, the file will not match.++ This is only available to use when git-annex was built with the+ MagicMime build flag.++* `--mimeencoding=glob`++ Looks up the MIME encoding of a file, and checks if the glob matches it.++ For example, `--mimeencoding=binary` will match many kinds of binary+ files.++ The MIME encodings are the same that are displayed by running `file --mime-encoding`++ If the file's annexed content is not present, the file will not match.++ This is only available to use when git-annex was built with the+ MagicMime build flag.+ * `--not` Inverts the next matching option. For example, to only act on
doc/git-annex-remotedaemon.mdwn view
@@ -9,20 +9,15 @@ # DESCRIPTION The remotedaemon provides persistent communication with remotes.-It detects when git branches on remotes have changes, and fetches-the changes from them. -The assistant runs the remotedaemon and communicates with it on-stdio using a simple textual protocol.- Several types of remotes are supported: For ssh remotes, the remotedaemon tries to maintain a connection to the remote git repository, and uses git-annex-shell notifychanges to detect-when the remote git repository has changed. For this to work, the git-remote must have [[git-annex-shell]](1) installed, with notifychanges-support. The first version of git-annex-shell that supports it is-5.20140405.+when the remote git repository has changed, and fetches changes from it.+For this to work, the git remote must have [[git-annex-shell]](1)+installed, with notifychanges support. The first version of git-annex-shell+that supports it is 5.20140405. For tor-annex remotes, the remotedaemon runs a tor hidden service, accepting connections from other nodes and serving up the contents of the
doc/git-annex.mdwn view
@@ -197,6 +197,12 @@ See [[git-annex-webapp]](1) for details. +* `remotedaemon`++ Persistant communication with remotes.++ See [[git-annex-remotedaemon]](1) for details.+ # REPOSITORY SETUP COMMANDS * `init [description]`@@ -687,12 +693,6 @@ See [[git-annex-smudge]](1) for details. -* `remotedaemon`-- Detects when network remotes have received git pushes and fetches from them.-- See [[git-annex-remotedaemon]](1) for details.- * `findref [ref]` Lists files in a git ref. (deprecated)@@ -868,7 +868,7 @@ the repository that use insecure hashes. To configure the behavior in new clones of the repository,- this can be set in [[git-annex-config]].+ this can be set using [[git-annex-config]]. * `annex.maxextensionlength` @@ -906,8 +906,11 @@ * `annex.addunlocked` Set to true to make commands like `git-annex add` that add files to the- repository add them in unlocked form. The default is to add files in- locked form.+ repository add them in unlocked form. The default is for these commands+ to add files in locked form. + + (Using `git add` always adds files in unlocked form and it is not+ affected by this setting.) When a repository has core.symlinks set to false, it implicitly sets annex.addunlocked to true.@@ -941,7 +944,7 @@ This controls which refs `git-annex unused` considers to be used. See REFSPEC FORMAT in [[git-annex-unused]](1) for details. -* `annex.jobs+* `annex.jobs` Configure the number of concurrent jobs to run. Default is 1. @@ -1051,14 +1054,14 @@ and the git-annex post-receive hook. To configure the behavior in all clones of the repository,- this can be set in [[git-annex-config]].+ this can be set in [[git-annex-config]](1). * `annex.synccontent` Set to true to make git-annex sync default to syncing content. To configure the behavior in all clones of the repository,- this can be set in [[git-annex-config]].+ this can be set in [[git-annex-config]](1). * `annex.debug` @@ -1630,7 +1633,7 @@ from automatically committing changes to files in the repository. To configure the behavior in all clones of the repository,- this can be set in [[git-annex-config]].+ this can be set in [[git-annex-config]](1). * `annex.startupscan`
git-annex.cabal view
@@ -1,5 +1,5 @@ Name: git-annex-Version: 7.20190912+Version: 7.20191009 Cabal-Version: >= 1.8 License: AGPL-3 Maintainer: Joey Hess <id@joeyh.name>@@ -344,6 +344,7 @@ persistent-sqlite (>= 2.8.1), persistent (>= 2.8.1), persistent-template,+ unliftio-core, microlens, aeson, vector,@@ -833,6 +834,7 @@ Git.Config Git.ConfigTypes Git.Construct+ Git.Credential Git.CurrentRepo Git.DiffTree Git.DiffTreeItem