Merge branch 'directguard'
This commit is contained in:
commit
d99bdbbb84
34 changed files with 963 additions and 531 deletions
25
Annex.hs
25
Annex.hs
|
@ -12,7 +12,6 @@ module Annex (
|
||||||
AnnexState(..),
|
AnnexState(..),
|
||||||
PreferredContentMap,
|
PreferredContentMap,
|
||||||
new,
|
new,
|
||||||
newState,
|
|
||||||
run,
|
run,
|
||||||
eval,
|
eval,
|
||||||
getState,
|
getState,
|
||||||
|
@ -41,10 +40,12 @@ import Control.Concurrent
|
||||||
import Common
|
import Common
|
||||||
import qualified Git
|
import qualified Git
|
||||||
import qualified Git.Config
|
import qualified Git.Config
|
||||||
|
import Git.Types hiding (remotes)
|
||||||
import Git.CatFile
|
import Git.CatFile
|
||||||
import Git.CheckAttr
|
import Git.CheckAttr
|
||||||
import Git.CheckIgnore
|
import Git.CheckIgnore
|
||||||
import Git.SharedRepository
|
import Git.SharedRepository
|
||||||
|
import Git.Config
|
||||||
import qualified Git.Queue
|
import qualified Git.Queue
|
||||||
import Types.Backend
|
import Types.Backend
|
||||||
import Types.GitConfig
|
import Types.GitConfig
|
||||||
|
@ -112,9 +113,9 @@ data AnnexState = AnnexState
|
||||||
}
|
}
|
||||||
|
|
||||||
newState :: Git.Repo -> AnnexState
|
newState :: Git.Repo -> AnnexState
|
||||||
newState gitrepo = AnnexState
|
newState r = AnnexState
|
||||||
{ repo = gitrepo
|
{ repo = if annexDirect c then fixupDirect r else r
|
||||||
, gitconfig = extractGitConfig gitrepo
|
, gitconfig = c
|
||||||
, backends = []
|
, backends = []
|
||||||
, remotes = []
|
, remotes = []
|
||||||
, output = defaultMessageState
|
, output = defaultMessageState
|
||||||
|
@ -144,6 +145,8 @@ newState gitrepo = AnnexState
|
||||||
, inodeschanged = Nothing
|
, inodeschanged = Nothing
|
||||||
, useragent = Nothing
|
, useragent = Nothing
|
||||||
}
|
}
|
||||||
|
where
|
||||||
|
c = extractGitConfig r
|
||||||
|
|
||||||
{- Makes an Annex state object for the specified git repo.
|
{- Makes an Annex state object for the specified git repo.
|
||||||
- Ensures the config is read, if it was not already. -}
|
- Ensures the config is read, if it was not already. -}
|
||||||
|
@ -247,3 +250,17 @@ withCurrentState :: Annex a -> Annex (IO a)
|
||||||
withCurrentState a = do
|
withCurrentState a = do
|
||||||
s <- getState id
|
s <- getState id
|
||||||
return $ eval s a
|
return $ eval s a
|
||||||
|
|
||||||
|
{- Direct mode repos have core.bare=true, but are not really bare.
|
||||||
|
- Fix up the Repo to be a non-bare repo, and arrange for git commands
|
||||||
|
- run by git-annex to be passed parameters that override this setting. -}
|
||||||
|
fixupDirect :: Git.Repo -> Git.Repo
|
||||||
|
fixupDirect r@(Repo { location = Local { gitdir = d, worktree = Nothing } }) =
|
||||||
|
r
|
||||||
|
{ location = Local { gitdir = d </> ".git", worktree = Just d }
|
||||||
|
, gitGlobalOpts = gitGlobalOpts r ++
|
||||||
|
[ Param "-c"
|
||||||
|
, Param $ coreBare ++ "=" ++ boolConfig False
|
||||||
|
]
|
||||||
|
}
|
||||||
|
fixupDirect r = r
|
||||||
|
|
|
@ -27,6 +27,7 @@ import qualified Annex
|
||||||
import Git.Types
|
import Git.Types
|
||||||
import Git.FilePath
|
import Git.FilePath
|
||||||
import Git.FileMode
|
import Git.FileMode
|
||||||
|
import qualified Git.Ref
|
||||||
|
|
||||||
catFile :: Git.Branch -> FilePath -> Annex L.ByteString
|
catFile :: Git.Branch -> FilePath -> Annex L.ByteString
|
||||||
catFile branch file = do
|
catFile branch file = do
|
||||||
|
@ -108,9 +109,6 @@ catKeyChecked needhead ref@(Ref r) =
|
||||||
map snd . filter (\p -> fst p == file)
|
map snd . filter (\p -> fst p == file)
|
||||||
|
|
||||||
{- From a file in the repository back to the key.
|
{- From a file in the repository back to the key.
|
||||||
-
|
|
||||||
- Prefixing the file with ./ makes this work even if in a subdirectory
|
|
||||||
- of a repo.
|
|
||||||
-
|
-
|
||||||
- Ideally, this should reflect the key that's staged in the index,
|
- Ideally, this should reflect the key that's staged in the index,
|
||||||
- not the key that's committed to HEAD. Unfortunately, git cat-file
|
- not the key that's committed to HEAD. Unfortunately, git cat-file
|
||||||
|
@ -134,8 +132,8 @@ catKeyChecked needhead ref@(Ref r) =
|
||||||
catKeyFile :: FilePath -> Annex (Maybe Key)
|
catKeyFile :: FilePath -> Annex (Maybe Key)
|
||||||
catKeyFile f = ifM (Annex.getState Annex.daemon)
|
catKeyFile f = ifM (Annex.getState Annex.daemon)
|
||||||
( catKeyFileHEAD f
|
( catKeyFileHEAD f
|
||||||
, catKeyChecked True (Ref $ ":./" ++ f)
|
, catKeyChecked True $ Git.Ref.fileRef f
|
||||||
)
|
)
|
||||||
|
|
||||||
catKeyFileHEAD :: FilePath -> Annex (Maybe Key)
|
catKeyFileHEAD :: FilePath -> Annex (Maybe Key)
|
||||||
catKeyFileHEAD f = catKeyChecked False (Ref $ "HEAD:./" ++ f)
|
catKeyFileHEAD f = catKeyChecked False $ Git.Ref.fileFromRef Git.Ref.headRef f
|
||||||
|
|
|
@ -8,13 +8,18 @@
|
||||||
module Annex.Direct where
|
module Annex.Direct where
|
||||||
|
|
||||||
import Common.Annex
|
import Common.Annex
|
||||||
|
import qualified Annex
|
||||||
import qualified Git
|
import qualified Git
|
||||||
import qualified Git.LsFiles
|
import qualified Git.LsFiles
|
||||||
import qualified Git.Merge
|
import qualified Git.Merge
|
||||||
import qualified Git.DiffTree as DiffTree
|
import qualified Git.DiffTree as DiffTree
|
||||||
|
import qualified Git.Config
|
||||||
|
import qualified Git.Ref
|
||||||
|
import qualified Git.Branch
|
||||||
import Git.Sha
|
import Git.Sha
|
||||||
import Git.FilePath
|
import Git.FilePath
|
||||||
import Git.Types
|
import Git.Types
|
||||||
|
import Config
|
||||||
import Annex.CatFile
|
import Annex.CatFile
|
||||||
import qualified Annex.Queue
|
import qualified Annex.Queue
|
||||||
import Logs.Location
|
import Logs.Location
|
||||||
|
@ -231,3 +236,66 @@ changedDirect oldk f = do
|
||||||
locs <- removeAssociatedFile oldk f
|
locs <- removeAssociatedFile oldk f
|
||||||
whenM (pure (null locs) <&&> not <$> inAnnex oldk) $
|
whenM (pure (null locs) <&&> not <$> inAnnex oldk) $
|
||||||
logStatus oldk InfoMissing
|
logStatus oldk InfoMissing
|
||||||
|
|
||||||
|
{- Enable/disable direct mode. -}
|
||||||
|
setDirect :: Bool -> Annex ()
|
||||||
|
setDirect wantdirect = do
|
||||||
|
if wantdirect
|
||||||
|
then do
|
||||||
|
switchHEAD
|
||||||
|
setbare
|
||||||
|
else do
|
||||||
|
setbare
|
||||||
|
switchHEADBack
|
||||||
|
setConfig (annexConfig "direct") val
|
||||||
|
Annex.changeGitConfig $ \c -> c { annexDirect = wantdirect }
|
||||||
|
where
|
||||||
|
val = Git.Config.boolConfig wantdirect
|
||||||
|
setbare = setConfig (ConfigKey Git.Config.coreBare) val
|
||||||
|
|
||||||
|
{- Since direct mode sets core.bare=true, incoming pushes could change
|
||||||
|
- the currently checked out branch. To avoid this problem, HEAD
|
||||||
|
- is changed to a internal ref that nothing is going to push to.
|
||||||
|
-
|
||||||
|
- For refs/heads/master, use refs/heads/annex/direct/master;
|
||||||
|
- this way things that show HEAD (eg shell prompts) will
|
||||||
|
- hopefully show just "master". -}
|
||||||
|
directBranch :: Ref -> Ref
|
||||||
|
directBranch orighead = case split "/" $ show orighead of
|
||||||
|
("refs":"heads":"annex":"direct":_) -> orighead
|
||||||
|
("refs":"heads":rest) ->
|
||||||
|
Ref $ "refs/heads/annex/direct/" ++ intercalate "/" rest
|
||||||
|
_ -> Ref $ "refs/heads/" ++ show (Git.Ref.base orighead)
|
||||||
|
|
||||||
|
{- Converts a directBranch back to the original branch.
|
||||||
|
-
|
||||||
|
- Any other ref is left unchanged.
|
||||||
|
-}
|
||||||
|
fromDirectBranch :: Ref -> Ref
|
||||||
|
fromDirectBranch directhead = case split "/" $ show directhead of
|
||||||
|
("refs":"heads":"annex":"direct":rest) ->
|
||||||
|
Ref $ "refs/heads/" ++ intercalate "/" rest
|
||||||
|
_ -> directhead
|
||||||
|
|
||||||
|
switchHEAD :: Annex ()
|
||||||
|
switchHEAD = maybe noop switch =<< inRepo Git.Branch.currentUnsafe
|
||||||
|
where
|
||||||
|
switch orighead = do
|
||||||
|
let newhead = directBranch orighead
|
||||||
|
maybe noop (inRepo . Git.Branch.update newhead)
|
||||||
|
=<< inRepo (Git.Ref.sha orighead)
|
||||||
|
inRepo $ Git.Branch.checkout newhead
|
||||||
|
|
||||||
|
switchHEADBack :: Annex ()
|
||||||
|
switchHEADBack = maybe noop switch =<< inRepo Git.Branch.currentUnsafe
|
||||||
|
where
|
||||||
|
switch currhead = do
|
||||||
|
let orighead = fromDirectBranch currhead
|
||||||
|
v <- inRepo $ Git.Ref.sha currhead
|
||||||
|
case v of
|
||||||
|
Just headsha
|
||||||
|
| orighead /= currhead -> do
|
||||||
|
inRepo $ Git.Branch.update orighead headsha
|
||||||
|
inRepo $ Git.Branch.checkout orighead
|
||||||
|
inRepo $ Git.Branch.delete currhead
|
||||||
|
_ -> inRepo $ Git.Branch.checkout orighead
|
||||||
|
|
42
Annex/Hook.hs
Normal file
42
Annex/Hook.hs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
{- git-annex git hooks
|
||||||
|
-
|
||||||
|
- Note that it's important that the scripts not change, otherwise
|
||||||
|
- removing old hooks using an old version of the script would fail.
|
||||||
|
-
|
||||||
|
- Copyright 2013 Joey Hess <joey@kitenet.net>
|
||||||
|
-
|
||||||
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Annex.Hook where
|
||||||
|
|
||||||
|
import Common.Annex
|
||||||
|
import qualified Git.Hook as Git
|
||||||
|
import Utility.Shell
|
||||||
|
import Config
|
||||||
|
|
||||||
|
preCommitHook :: Git.Hook
|
||||||
|
preCommitHook = Git.Hook "pre-commit" (mkHookScript "git annex pre-commit .")
|
||||||
|
|
||||||
|
mkHookScript :: String -> String
|
||||||
|
mkHookScript s = unlines
|
||||||
|
[ shebang_local
|
||||||
|
, "# automatically configured by git-annex"
|
||||||
|
, s
|
||||||
|
]
|
||||||
|
|
||||||
|
hookWrite :: Git.Hook -> Annex ()
|
||||||
|
hookWrite h =
|
||||||
|
-- cannot have git hooks in a crippled filesystem (no execute bit)
|
||||||
|
unlessM crippledFileSystem $
|
||||||
|
unlessM (inRepo $ Git.hookWrite h) $
|
||||||
|
hookWarning h "already exists, not configuring"
|
||||||
|
|
||||||
|
hookUnWrite :: Git.Hook -> Annex ()
|
||||||
|
hookUnWrite h = unlessM (inRepo $ Git.hookUnWrite h) $
|
||||||
|
hookWarning h "contents modified; not deleting. Edit it to remove call to git annex."
|
||||||
|
|
||||||
|
hookWarning :: Git.Hook -> String -> Annex ()
|
||||||
|
hookWarning h msg = do
|
||||||
|
r <- gitRepo
|
||||||
|
warning $ Git.hookName h ++ " hook (" ++ Git.hookFile h r ++ ") " ++ msg
|
|
@ -19,18 +19,21 @@ defaultVersion :: Version
|
||||||
defaultVersion = "3"
|
defaultVersion = "3"
|
||||||
|
|
||||||
directModeVersion :: Version
|
directModeVersion :: Version
|
||||||
directModeVersion = "4"
|
directModeVersion = "5"
|
||||||
|
|
||||||
supportedVersions :: [Version]
|
supportedVersions :: [Version]
|
||||||
supportedVersions = [defaultVersion, directModeVersion]
|
supportedVersions = [defaultVersion, directModeVersion]
|
||||||
|
|
||||||
upgradableVersions :: [Version]
|
upgradableVersions :: [Version]
|
||||||
#ifndef mingw32_HOST_OS
|
#ifndef mingw32_HOST_OS
|
||||||
upgradableVersions = ["0", "1", "2"]
|
upgradableVersions = ["0", "1", "2", "4"]
|
||||||
#else
|
#else
|
||||||
upgradableVersions = ["2"]
|
upgradableVersions = ["2", "4"]
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
autoUpgradeableVersions :: [Version]
|
||||||
|
autoUpgradeableVersions = ["4"]
|
||||||
|
|
||||||
versionField :: ConfigKey
|
versionField :: ConfigKey
|
||||||
versionField = annexConfig "version"
|
versionField = annexConfig "version"
|
||||||
|
|
||||||
|
@ -42,12 +45,3 @@ setVersion = setConfig versionField
|
||||||
|
|
||||||
removeVersion :: Annex ()
|
removeVersion :: Annex ()
|
||||||
removeVersion = unsetConfig versionField
|
removeVersion = unsetConfig versionField
|
||||||
|
|
||||||
checkVersion :: Version -> Annex ()
|
|
||||||
checkVersion v
|
|
||||||
| v `elem` supportedVersions = noop
|
|
||||||
| v `elem` upgradableVersions = err "Upgrade this repository: git-annex upgrade"
|
|
||||||
| otherwise = err "Upgrade git-annex."
|
|
||||||
where
|
|
||||||
err msg = error $ "Repository version " ++ v ++
|
|
||||||
" is not supported. " ++ msg
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ import Utility.DataUnits
|
||||||
import Utility.Network
|
import Utility.Network
|
||||||
import Remote (prettyUUID)
|
import Remote (prettyUUID)
|
||||||
import Annex.UUID
|
import Annex.UUID
|
||||||
|
import Annex.Direct
|
||||||
import Types.StandardGroups
|
import Types.StandardGroups
|
||||||
import Logs.PreferredContent
|
import Logs.PreferredContent
|
||||||
import Logs.UUID
|
import Logs.UUID
|
||||||
|
|
384
Command/Info.hs
Normal file
384
Command/Info.hs
Normal file
|
@ -0,0 +1,384 @@
|
||||||
|
{- git-annex command
|
||||||
|
-
|
||||||
|
- Copyright 2011 Joey Hess <joey@kitenet.net>
|
||||||
|
-
|
||||||
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
|
-}
|
||||||
|
|
||||||
|
{-# LANGUAGE BangPatterns #-}
|
||||||
|
|
||||||
|
module Command.Info where
|
||||||
|
|
||||||
|
import "mtl" Control.Monad.State.Strict
|
||||||
|
import qualified Data.Map as M
|
||||||
|
import Text.JSON
|
||||||
|
import Data.Tuple
|
||||||
|
import Data.Ord
|
||||||
|
import System.PosixCompat.Files
|
||||||
|
|
||||||
|
import Common.Annex
|
||||||
|
import qualified Remote
|
||||||
|
import qualified Command.Unused
|
||||||
|
import qualified Git
|
||||||
|
import qualified Annex
|
||||||
|
import Command
|
||||||
|
import Utility.DataUnits
|
||||||
|
import Utility.DiskFree
|
||||||
|
import Annex.Content
|
||||||
|
import Types.Key
|
||||||
|
import Logs.UUID
|
||||||
|
import Logs.Trust
|
||||||
|
import Remote
|
||||||
|
import Config
|
||||||
|
import Utility.Percentage
|
||||||
|
import Logs.Transfer
|
||||||
|
import Types.TrustLevel
|
||||||
|
import Types.FileMatcher
|
||||||
|
import qualified Limit
|
||||||
|
|
||||||
|
-- a named computation that produces a statistic
|
||||||
|
type Stat = StatState (Maybe (String, StatState String))
|
||||||
|
|
||||||
|
-- data about a set of keys
|
||||||
|
data KeyData = KeyData
|
||||||
|
{ countKeys :: Integer
|
||||||
|
, sizeKeys :: Integer
|
||||||
|
, unknownSizeKeys :: Integer
|
||||||
|
, backendsKeys :: M.Map String Integer
|
||||||
|
}
|
||||||
|
|
||||||
|
data NumCopiesStats = NumCopiesStats
|
||||||
|
{ numCopiesVarianceMap :: M.Map Variance Integer
|
||||||
|
}
|
||||||
|
|
||||||
|
newtype Variance = Variance Int
|
||||||
|
deriving (Eq, Ord)
|
||||||
|
|
||||||
|
instance Show Variance where
|
||||||
|
show (Variance n)
|
||||||
|
| n >= 0 = "numcopies +" ++ show n
|
||||||
|
| otherwise = "numcopies " ++ show n
|
||||||
|
|
||||||
|
-- cached info that multiple Stats use
|
||||||
|
data StatInfo = StatInfo
|
||||||
|
{ presentData :: Maybe KeyData
|
||||||
|
, referencedData :: Maybe KeyData
|
||||||
|
, numCopiesStats :: Maybe NumCopiesStats
|
||||||
|
}
|
||||||
|
|
||||||
|
-- a state monad for running Stats in
|
||||||
|
type StatState = StateT StatInfo Annex
|
||||||
|
|
||||||
|
def :: [Command]
|
||||||
|
def = [noCommit $ command "info" paramPaths seek
|
||||||
|
SectionQuery "shows general information about the annex"]
|
||||||
|
|
||||||
|
seek :: [CommandSeek]
|
||||||
|
seek = [withWords start]
|
||||||
|
|
||||||
|
start :: [FilePath] -> CommandStart
|
||||||
|
start [] = do
|
||||||
|
globalInfo
|
||||||
|
stop
|
||||||
|
start ps = do
|
||||||
|
mapM_ localInfo =<< filterM isdir ps
|
||||||
|
stop
|
||||||
|
where
|
||||||
|
isdir = liftIO . catchBoolIO . (isDirectory <$$> getFileStatus)
|
||||||
|
|
||||||
|
globalInfo :: Annex ()
|
||||||
|
globalInfo = do
|
||||||
|
stats <- selStats global_fast_stats global_slow_stats
|
||||||
|
showCustom "info" $ do
|
||||||
|
evalStateT (mapM_ showStat stats) (StatInfo Nothing Nothing Nothing)
|
||||||
|
return True
|
||||||
|
|
||||||
|
localInfo :: FilePath -> Annex ()
|
||||||
|
localInfo dir = showCustom (unwords ["info", dir]) $ do
|
||||||
|
stats <- selStats (tostats local_fast_stats) (tostats local_slow_stats)
|
||||||
|
evalStateT (mapM_ showStat stats) =<< getLocalStatInfo dir
|
||||||
|
return True
|
||||||
|
where
|
||||||
|
tostats = map (\s -> s dir)
|
||||||
|
|
||||||
|
selStats :: [Stat] -> [Stat] -> Annex [Stat]
|
||||||
|
selStats fast_stats slow_stats = do
|
||||||
|
fast <- Annex.getState Annex.fast
|
||||||
|
return $ if fast
|
||||||
|
then fast_stats
|
||||||
|
else fast_stats ++ slow_stats
|
||||||
|
|
||||||
|
{- Order is significant. Less expensive operations, and operations
|
||||||
|
- that share data go together.
|
||||||
|
-}
|
||||||
|
global_fast_stats :: [Stat]
|
||||||
|
global_fast_stats =
|
||||||
|
[ repository_mode
|
||||||
|
, remote_list Trusted
|
||||||
|
, remote_list SemiTrusted
|
||||||
|
, remote_list UnTrusted
|
||||||
|
, transfer_list
|
||||||
|
, disk_size
|
||||||
|
]
|
||||||
|
global_slow_stats :: [Stat]
|
||||||
|
global_slow_stats =
|
||||||
|
[ tmp_size
|
||||||
|
, bad_data_size
|
||||||
|
, local_annex_keys
|
||||||
|
, local_annex_size
|
||||||
|
, known_annex_files
|
||||||
|
, known_annex_size
|
||||||
|
, bloom_info
|
||||||
|
, backend_usage
|
||||||
|
]
|
||||||
|
local_fast_stats :: [FilePath -> Stat]
|
||||||
|
local_fast_stats =
|
||||||
|
[ local_dir
|
||||||
|
, const local_annex_keys
|
||||||
|
, const local_annex_size
|
||||||
|
, const known_annex_files
|
||||||
|
, const known_annex_size
|
||||||
|
]
|
||||||
|
local_slow_stats :: [FilePath -> Stat]
|
||||||
|
local_slow_stats =
|
||||||
|
[ const numcopies_stats
|
||||||
|
]
|
||||||
|
|
||||||
|
stat :: String -> (String -> StatState String) -> Stat
|
||||||
|
stat desc a = return $ Just (desc, a desc)
|
||||||
|
|
||||||
|
nostat :: Stat
|
||||||
|
nostat = return Nothing
|
||||||
|
|
||||||
|
json :: JSON j => (j -> String) -> StatState j -> String -> StatState String
|
||||||
|
json serialize a desc = do
|
||||||
|
j <- a
|
||||||
|
lift $ maybeShowJSON [(desc, j)]
|
||||||
|
return $ serialize j
|
||||||
|
|
||||||
|
nojson :: StatState String -> String -> StatState String
|
||||||
|
nojson a _ = a
|
||||||
|
|
||||||
|
showStat :: Stat -> StatState ()
|
||||||
|
showStat s = maybe noop calc =<< s
|
||||||
|
where
|
||||||
|
calc (desc, a) = do
|
||||||
|
(lift . showHeader) desc
|
||||||
|
lift . showRaw =<< a
|
||||||
|
|
||||||
|
repository_mode :: Stat
|
||||||
|
repository_mode = stat "repository mode" $ json id $ lift $
|
||||||
|
ifM isDirect
|
||||||
|
( return "direct", return "indirect" )
|
||||||
|
|
||||||
|
remote_list :: TrustLevel -> Stat
|
||||||
|
remote_list level = stat n $ nojson $ lift $ do
|
||||||
|
us <- M.keys <$> (M.union <$> uuidMap <*> remoteMap Remote.name)
|
||||||
|
rs <- fst <$> trustPartition level us
|
||||||
|
s <- prettyPrintUUIDs n rs
|
||||||
|
return $ if null s then "0" else show (length rs) ++ "\n" ++ beginning s
|
||||||
|
where
|
||||||
|
n = showTrustLevel level ++ " repositories"
|
||||||
|
|
||||||
|
local_dir :: FilePath -> Stat
|
||||||
|
local_dir dir = stat "directory" $ json id $ return dir
|
||||||
|
|
||||||
|
local_annex_keys :: Stat
|
||||||
|
local_annex_keys = stat "local annex keys" $ json show $
|
||||||
|
countKeys <$> cachedPresentData
|
||||||
|
|
||||||
|
local_annex_size :: Stat
|
||||||
|
local_annex_size = stat "local annex size" $ json id $
|
||||||
|
showSizeKeys <$> cachedPresentData
|
||||||
|
|
||||||
|
known_annex_files :: Stat
|
||||||
|
known_annex_files = stat "annexed files in working tree" $ json show $
|
||||||
|
countKeys <$> cachedReferencedData
|
||||||
|
|
||||||
|
known_annex_size :: Stat
|
||||||
|
known_annex_size = stat "size of annexed files in working tree" $ json id $
|
||||||
|
showSizeKeys <$> cachedReferencedData
|
||||||
|
|
||||||
|
tmp_size :: Stat
|
||||||
|
tmp_size = staleSize "temporary directory size" gitAnnexTmpDir
|
||||||
|
|
||||||
|
bad_data_size :: Stat
|
||||||
|
bad_data_size = staleSize "bad keys size" gitAnnexBadDir
|
||||||
|
|
||||||
|
bloom_info :: Stat
|
||||||
|
bloom_info = stat "bloom filter size" $ json id $ do
|
||||||
|
localkeys <- countKeys <$> cachedPresentData
|
||||||
|
capacity <- fromIntegral <$> lift Command.Unused.bloomCapacity
|
||||||
|
let note = aside $
|
||||||
|
if localkeys >= capacity
|
||||||
|
then "appears too small for this repository; adjust annex.bloomcapacity"
|
||||||
|
else showPercentage 1 (percentage capacity localkeys) ++ " full"
|
||||||
|
|
||||||
|
-- Two bloom filters are used at the same time, so double the size
|
||||||
|
-- of one.
|
||||||
|
size <- roughSize memoryUnits False . (* 2) . fromIntegral . fst <$>
|
||||||
|
lift Command.Unused.bloomBitsHashes
|
||||||
|
|
||||||
|
return $ size ++ note
|
||||||
|
|
||||||
|
transfer_list :: Stat
|
||||||
|
transfer_list = stat "transfers in progress" $ nojson $ lift $ do
|
||||||
|
uuidmap <- Remote.remoteMap id
|
||||||
|
ts <- getTransfers
|
||||||
|
return $ if null ts
|
||||||
|
then "none"
|
||||||
|
else multiLine $
|
||||||
|
map (uncurry $ line uuidmap) $ sort ts
|
||||||
|
where
|
||||||
|
line uuidmap t i = unwords
|
||||||
|
[ showLcDirection (transferDirection t) ++ "ing"
|
||||||
|
, fromMaybe (key2file $ transferKey t) (associatedFile i)
|
||||||
|
, if transferDirection t == Upload then "to" else "from"
|
||||||
|
, maybe (fromUUID $ transferUUID t) Remote.name $
|
||||||
|
M.lookup (transferUUID t) uuidmap
|
||||||
|
]
|
||||||
|
|
||||||
|
disk_size :: Stat
|
||||||
|
disk_size = stat "available local disk space" $ json id $ lift $
|
||||||
|
calcfree
|
||||||
|
<$> (annexDiskReserve <$> Annex.getGitConfig)
|
||||||
|
<*> inRepo (getDiskFree . gitAnnexDir)
|
||||||
|
where
|
||||||
|
calcfree reserve (Just have) = unwords
|
||||||
|
[ roughSize storageUnits False $ nonneg $ have - reserve
|
||||||
|
, "(+" ++ roughSize storageUnits False reserve
|
||||||
|
, "reserved)"
|
||||||
|
]
|
||||||
|
calcfree _ _ = "unknown"
|
||||||
|
|
||||||
|
nonneg x
|
||||||
|
| x >= 0 = x
|
||||||
|
| otherwise = 0
|
||||||
|
|
||||||
|
backend_usage :: Stat
|
||||||
|
backend_usage = stat "backend usage" $ nojson $
|
||||||
|
calc
|
||||||
|
<$> (backendsKeys <$> cachedReferencedData)
|
||||||
|
<*> (backendsKeys <$> cachedPresentData)
|
||||||
|
where
|
||||||
|
calc x y = multiLine $
|
||||||
|
map (\(n, b) -> b ++ ": " ++ show n) $
|
||||||
|
reverse $ sort $ map swap $ M.toList $
|
||||||
|
M.unionWith (+) x y
|
||||||
|
|
||||||
|
numcopies_stats :: Stat
|
||||||
|
numcopies_stats = stat "numcopies stats" $ nojson $
|
||||||
|
calc <$> (maybe M.empty numCopiesVarianceMap <$> cachedNumCopiesStats)
|
||||||
|
where
|
||||||
|
calc = multiLine
|
||||||
|
. map (\(variance, count) -> show variance ++ ": " ++ show count)
|
||||||
|
. reverse . sortBy (comparing snd) . M.toList
|
||||||
|
|
||||||
|
cachedPresentData :: StatState KeyData
|
||||||
|
cachedPresentData = do
|
||||||
|
s <- get
|
||||||
|
case presentData s of
|
||||||
|
Just v -> return v
|
||||||
|
Nothing -> do
|
||||||
|
v <- foldKeys <$> lift getKeysPresent
|
||||||
|
put s { presentData = Just v }
|
||||||
|
return v
|
||||||
|
|
||||||
|
cachedReferencedData :: StatState KeyData
|
||||||
|
cachedReferencedData = do
|
||||||
|
s <- get
|
||||||
|
case referencedData s of
|
||||||
|
Just v -> return v
|
||||||
|
Nothing -> do
|
||||||
|
!v <- lift $ Command.Unused.withKeysReferenced
|
||||||
|
emptyKeyData addKey
|
||||||
|
put s { referencedData = Just v }
|
||||||
|
return v
|
||||||
|
|
||||||
|
-- currently only available for local info
|
||||||
|
cachedNumCopiesStats :: StatState (Maybe NumCopiesStats)
|
||||||
|
cachedNumCopiesStats = numCopiesStats <$> get
|
||||||
|
|
||||||
|
getLocalStatInfo :: FilePath -> Annex StatInfo
|
||||||
|
getLocalStatInfo dir = do
|
||||||
|
fast <- Annex.getState Annex.fast
|
||||||
|
matcher <- Limit.getMatcher
|
||||||
|
(presentdata, referenceddata, numcopiesstats) <-
|
||||||
|
Command.Unused.withKeysFilesReferencedIn dir initial
|
||||||
|
(update matcher fast)
|
||||||
|
return $ StatInfo (Just presentdata) (Just referenceddata) (Just numcopiesstats)
|
||||||
|
where
|
||||||
|
initial = (emptyKeyData, emptyKeyData, emptyNumCopiesStats)
|
||||||
|
update matcher fast key file vs@(presentdata, referenceddata, numcopiesstats) =
|
||||||
|
ifM (matcher $ FileInfo file file)
|
||||||
|
( do
|
||||||
|
!presentdata' <- ifM (inAnnex key)
|
||||||
|
( return $ addKey key presentdata
|
||||||
|
, return presentdata
|
||||||
|
)
|
||||||
|
let !referenceddata' = addKey key referenceddata
|
||||||
|
!numcopiesstats' <- if fast
|
||||||
|
then return numcopiesstats
|
||||||
|
else updateNumCopiesStats key file numcopiesstats
|
||||||
|
return $! (presentdata', referenceddata', numcopiesstats')
|
||||||
|
, return vs
|
||||||
|
)
|
||||||
|
|
||||||
|
emptyKeyData :: KeyData
|
||||||
|
emptyKeyData = KeyData 0 0 0 M.empty
|
||||||
|
|
||||||
|
emptyNumCopiesStats :: NumCopiesStats
|
||||||
|
emptyNumCopiesStats = NumCopiesStats M.empty
|
||||||
|
|
||||||
|
foldKeys :: [Key] -> KeyData
|
||||||
|
foldKeys = foldl' (flip addKey) emptyKeyData
|
||||||
|
|
||||||
|
addKey :: Key -> KeyData -> KeyData
|
||||||
|
addKey key (KeyData count size unknownsize backends) =
|
||||||
|
KeyData count' size' unknownsize' backends'
|
||||||
|
where
|
||||||
|
{- All calculations strict to avoid thunks when repeatedly
|
||||||
|
- applied to many keys. -}
|
||||||
|
!count' = count + 1
|
||||||
|
!backends' = M.insertWith' (+) (keyBackendName key) 1 backends
|
||||||
|
!size' = maybe size (+ size) ks
|
||||||
|
!unknownsize' = maybe (unknownsize + 1) (const unknownsize) ks
|
||||||
|
ks = keySize key
|
||||||
|
|
||||||
|
updateNumCopiesStats :: Key -> FilePath -> NumCopiesStats -> Annex NumCopiesStats
|
||||||
|
updateNumCopiesStats key file (NumCopiesStats m) = do
|
||||||
|
!variance <- Variance <$> numCopiesCheck file key (-)
|
||||||
|
let !m' = M.insertWith' (+) variance 1 m
|
||||||
|
let !ret = NumCopiesStats m'
|
||||||
|
return ret
|
||||||
|
|
||||||
|
showSizeKeys :: KeyData -> String
|
||||||
|
showSizeKeys d = total ++ missingnote
|
||||||
|
where
|
||||||
|
total = roughSize storageUnits False $ sizeKeys d
|
||||||
|
missingnote
|
||||||
|
| unknownSizeKeys d == 0 = ""
|
||||||
|
| otherwise = aside $
|
||||||
|
"+ " ++ show (unknownSizeKeys d) ++
|
||||||
|
" unknown size"
|
||||||
|
|
||||||
|
staleSize :: String -> (Git.Repo -> FilePath) -> Stat
|
||||||
|
staleSize label dirspec = go =<< lift (dirKeys dirspec)
|
||||||
|
where
|
||||||
|
go [] = nostat
|
||||||
|
go keys = onsize =<< sum <$> keysizes keys
|
||||||
|
onsize 0 = nostat
|
||||||
|
onsize size = stat label $
|
||||||
|
json (++ aside "clean up with git-annex unused") $
|
||||||
|
return $ roughSize storageUnits False size
|
||||||
|
keysizes keys = do
|
||||||
|
dir <- lift $ fromRepo dirspec
|
||||||
|
liftIO $ forM keys $ \k -> catchDefaultIO 0 $
|
||||||
|
fromIntegral . fileSize
|
||||||
|
<$> getFileStatus (dir </> keyFile k)
|
||||||
|
|
||||||
|
aside :: String -> String
|
||||||
|
aside s = " (" ++ s ++ ")"
|
||||||
|
|
||||||
|
multiLine :: [String] -> String
|
||||||
|
multiLine = concatMap (\l -> "\n\t" ++ l)
|
|
@ -1,384 +1,89 @@
|
||||||
{- git-annex command
|
{- git-annex command
|
||||||
-
|
-
|
||||||
- Copyright 2011 Joey Hess <joey@kitenet.net>
|
- Copyright 2013 Joey Hess <joey@kitenet.net>
|
||||||
-
|
-
|
||||||
- Licensed under the GNU GPL version 3 or higher.
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
{-# LANGUAGE BangPatterns #-}
|
|
||||||
|
|
||||||
module Command.Status where
|
module Command.Status where
|
||||||
|
|
||||||
import "mtl" Control.Monad.State.Strict
|
|
||||||
import qualified Data.Map as M
|
|
||||||
import Text.JSON
|
|
||||||
import Data.Tuple
|
|
||||||
import Data.Ord
|
|
||||||
import System.PosixCompat.Files
|
|
||||||
|
|
||||||
import Common.Annex
|
import Common.Annex
|
||||||
import qualified Remote
|
|
||||||
import qualified Command.Unused
|
|
||||||
import qualified Git
|
|
||||||
import qualified Annex
|
|
||||||
import Command
|
import Command
|
||||||
import Utility.DataUnits
|
import Annex.CatFile
|
||||||
import Utility.DiskFree
|
import Annex.Content.Direct
|
||||||
import Annex.Content
|
|
||||||
import Types.Key
|
|
||||||
import Logs.UUID
|
|
||||||
import Logs.Trust
|
|
||||||
import Remote
|
|
||||||
import Config
|
import Config
|
||||||
import Utility.Percentage
|
import qualified Git.LsFiles as LsFiles
|
||||||
import Logs.Transfer
|
import qualified Git.Ref
|
||||||
import Types.TrustLevel
|
import qualified Git
|
||||||
import Types.FileMatcher
|
|
||||||
import qualified Limit
|
|
||||||
|
|
||||||
-- a named computation that produces a statistic
|
|
||||||
type Stat = StatState (Maybe (String, StatState String))
|
|
||||||
|
|
||||||
-- data about a set of keys
|
|
||||||
data KeyData = KeyData
|
|
||||||
{ countKeys :: Integer
|
|
||||||
, sizeKeys :: Integer
|
|
||||||
, unknownSizeKeys :: Integer
|
|
||||||
, backendsKeys :: M.Map String Integer
|
|
||||||
}
|
|
||||||
|
|
||||||
data NumCopiesStats = NumCopiesStats
|
|
||||||
{ numCopiesVarianceMap :: M.Map Variance Integer
|
|
||||||
}
|
|
||||||
|
|
||||||
newtype Variance = Variance Int
|
|
||||||
deriving (Eq, Ord)
|
|
||||||
|
|
||||||
instance Show Variance where
|
|
||||||
show (Variance n)
|
|
||||||
| n >= 0 = "numcopies +" ++ show n
|
|
||||||
| otherwise = "numcopies " ++ show n
|
|
||||||
|
|
||||||
-- cached info that multiple Stats use
|
|
||||||
data StatInfo = StatInfo
|
|
||||||
{ presentData :: Maybe KeyData
|
|
||||||
, referencedData :: Maybe KeyData
|
|
||||||
, numCopiesStats :: Maybe NumCopiesStats
|
|
||||||
}
|
|
||||||
|
|
||||||
-- a state monad for running Stats in
|
|
||||||
type StatState = StateT StatInfo Annex
|
|
||||||
|
|
||||||
def :: [Command]
|
def :: [Command]
|
||||||
def = [noCommit $ command "status" paramPaths seek
|
def = [noCommit $ noMessages $
|
||||||
SectionQuery "shows status information about the annex"]
|
command "status" paramPaths seek SectionCommon
|
||||||
|
"show the working tree status"]
|
||||||
|
|
||||||
seek :: [CommandSeek]
|
seek :: [CommandSeek]
|
||||||
seek = [withWords start]
|
seek =
|
||||||
|
[ withWords start
|
||||||
|
]
|
||||||
|
|
||||||
start :: [FilePath] -> CommandStart
|
start :: [FilePath] -> CommandStart
|
||||||
start [] = do
|
start [] = do
|
||||||
globalStatus
|
-- Like git status, when run without a directory, behave as if
|
||||||
stop
|
-- given the path to the top of the repository.
|
||||||
start ps = do
|
cwd <- liftIO getCurrentDirectory
|
||||||
mapM_ localStatus =<< filterM isdir ps
|
top <- fromRepo Git.repoPath
|
||||||
stop
|
next $ perform [relPathDirToFile cwd top]
|
||||||
where
|
start locs = next $ perform locs
|
||||||
isdir = liftIO . catchBoolIO . (isDirectory <$$> getFileStatus)
|
|
||||||
|
|
||||||
globalStatus :: Annex ()
|
|
||||||
globalStatus = do
|
|
||||||
stats <- selStats global_fast_stats global_slow_stats
|
|
||||||
showCustom "status" $ do
|
|
||||||
evalStateT (mapM_ showStat stats) (StatInfo Nothing Nothing Nothing)
|
|
||||||
return True
|
|
||||||
|
|
||||||
localStatus :: FilePath -> Annex ()
|
|
||||||
localStatus dir = showCustom (unwords ["status", dir]) $ do
|
|
||||||
stats <- selStats (tostats local_fast_stats) (tostats local_slow_stats)
|
|
||||||
evalStateT (mapM_ showStat stats) =<< getLocalStatInfo dir
|
|
||||||
return True
|
|
||||||
where
|
|
||||||
tostats = map (\s -> s dir)
|
|
||||||
|
|
||||||
selStats :: [Stat] -> [Stat] -> Annex [Stat]
|
|
||||||
selStats fast_stats slow_stats = do
|
|
||||||
fast <- Annex.getState Annex.fast
|
|
||||||
return $ if fast
|
|
||||||
then fast_stats
|
|
||||||
else fast_stats ++ slow_stats
|
|
||||||
|
|
||||||
{- Order is significant. Less expensive operations, and operations
|
|
||||||
- that share data go together.
|
|
||||||
-}
|
|
||||||
global_fast_stats :: [Stat]
|
|
||||||
global_fast_stats =
|
|
||||||
[ repository_mode
|
|
||||||
, remote_list Trusted
|
|
||||||
, remote_list SemiTrusted
|
|
||||||
, remote_list UnTrusted
|
|
||||||
, transfer_list
|
|
||||||
, disk_size
|
|
||||||
]
|
|
||||||
global_slow_stats :: [Stat]
|
|
||||||
global_slow_stats =
|
|
||||||
[ tmp_size
|
|
||||||
, bad_data_size
|
|
||||||
, local_annex_keys
|
|
||||||
, local_annex_size
|
|
||||||
, known_annex_files
|
|
||||||
, known_annex_size
|
|
||||||
, bloom_info
|
|
||||||
, backend_usage
|
|
||||||
]
|
|
||||||
local_fast_stats :: [FilePath -> Stat]
|
|
||||||
local_fast_stats =
|
|
||||||
[ local_dir
|
|
||||||
, const local_annex_keys
|
|
||||||
, const local_annex_size
|
|
||||||
, const known_annex_files
|
|
||||||
, const known_annex_size
|
|
||||||
]
|
|
||||||
local_slow_stats :: [FilePath -> Stat]
|
|
||||||
local_slow_stats =
|
|
||||||
[ const numcopies_stats
|
|
||||||
]
|
|
||||||
|
|
||||||
stat :: String -> (String -> StatState String) -> Stat
|
|
||||||
stat desc a = return $ Just (desc, a desc)
|
|
||||||
|
|
||||||
nostat :: Stat
|
|
||||||
nostat = return Nothing
|
|
||||||
|
|
||||||
json :: JSON j => (j -> String) -> StatState j -> String -> StatState String
|
|
||||||
json serialize a desc = do
|
|
||||||
j <- a
|
|
||||||
lift $ maybeShowJSON [(desc, j)]
|
|
||||||
return $ serialize j
|
|
||||||
|
|
||||||
nojson :: StatState String -> String -> StatState String
|
|
||||||
nojson a _ = a
|
|
||||||
|
|
||||||
showStat :: Stat -> StatState ()
|
|
||||||
showStat s = maybe noop calc =<< s
|
|
||||||
where
|
|
||||||
calc (desc, a) = do
|
|
||||||
(lift . showHeader) desc
|
|
||||||
lift . showRaw =<< a
|
|
||||||
|
|
||||||
repository_mode :: Stat
|
|
||||||
repository_mode = stat "repository mode" $ json id $ lift $
|
|
||||||
ifM isDirect
|
|
||||||
( return "direct", return "indirect" )
|
|
||||||
|
|
||||||
remote_list :: TrustLevel -> Stat
|
|
||||||
remote_list level = stat n $ nojson $ lift $ do
|
|
||||||
us <- M.keys <$> (M.union <$> uuidMap <*> remoteMap Remote.name)
|
|
||||||
rs <- fst <$> trustPartition level us
|
|
||||||
s <- prettyPrintUUIDs n rs
|
|
||||||
return $ if null s then "0" else show (length rs) ++ "\n" ++ beginning s
|
|
||||||
where
|
|
||||||
n = showTrustLevel level ++ " repositories"
|
|
||||||
|
|
||||||
local_dir :: FilePath -> Stat
|
perform :: [FilePath] -> CommandPerform
|
||||||
local_dir dir = stat "directory" $ json id $ return dir
|
perform locs = do
|
||||||
|
(l, cleanup) <- inRepo $ LsFiles.modifiedOthers locs
|
||||||
|
getstatus <- ifM isDirect
|
||||||
|
( return statusDirect
|
||||||
|
, return $ Just <$$> statusIndirect
|
||||||
|
)
|
||||||
|
forM_ l $ \f -> maybe noop (showFileStatus f) =<< getstatus f
|
||||||
|
void $ liftIO cleanup
|
||||||
|
next $ return True
|
||||||
|
|
||||||
local_annex_keys :: Stat
|
data Status
|
||||||
local_annex_keys = stat "local annex keys" $ json show $
|
= NewFile
|
||||||
countKeys <$> cachedPresentData
|
| DeletedFile
|
||||||
|
| ModifiedFile
|
||||||
|
|
||||||
local_annex_size :: Stat
|
showStatus :: Status -> String
|
||||||
local_annex_size = stat "local annex size" $ json id $
|
showStatus NewFile = "?"
|
||||||
showSizeKeys <$> cachedPresentData
|
showStatus DeletedFile = "D"
|
||||||
|
showStatus ModifiedFile = "M"
|
||||||
|
|
||||||
known_annex_files :: Stat
|
showFileStatus :: FilePath -> Status -> Annex ()
|
||||||
known_annex_files = stat "annexed files in working tree" $ json show $
|
showFileStatus f s = liftIO $ putStrLn $ showStatus s ++ " " ++ f
|
||||||
countKeys <$> cachedReferencedData
|
|
||||||
|
|
||||||
known_annex_size :: Stat
|
statusDirect :: FilePath -> Annex (Maybe Status)
|
||||||
known_annex_size = stat "size of annexed files in working tree" $ json id $
|
statusDirect f = checkstatus =<< liftIO (catchMaybeIO $ getFileStatus f)
|
||||||
showSizeKeys <$> cachedReferencedData
|
|
||||||
|
|
||||||
tmp_size :: Stat
|
|
||||||
tmp_size = staleSize "temporary directory size" gitAnnexTmpDir
|
|
||||||
|
|
||||||
bad_data_size :: Stat
|
|
||||||
bad_data_size = staleSize "bad keys size" gitAnnexBadDir
|
|
||||||
|
|
||||||
bloom_info :: Stat
|
|
||||||
bloom_info = stat "bloom filter size" $ json id $ do
|
|
||||||
localkeys <- countKeys <$> cachedPresentData
|
|
||||||
capacity <- fromIntegral <$> lift Command.Unused.bloomCapacity
|
|
||||||
let note = aside $
|
|
||||||
if localkeys >= capacity
|
|
||||||
then "appears too small for this repository; adjust annex.bloomcapacity"
|
|
||||||
else showPercentage 1 (percentage capacity localkeys) ++ " full"
|
|
||||||
|
|
||||||
-- Two bloom filters are used at the same time, so double the size
|
|
||||||
-- of one.
|
|
||||||
size <- roughSize memoryUnits False . (* 2) . fromIntegral . fst <$>
|
|
||||||
lift Command.Unused.bloomBitsHashes
|
|
||||||
|
|
||||||
return $ size ++ note
|
|
||||||
|
|
||||||
transfer_list :: Stat
|
|
||||||
transfer_list = stat "transfers in progress" $ nojson $ lift $ do
|
|
||||||
uuidmap <- Remote.remoteMap id
|
|
||||||
ts <- getTransfers
|
|
||||||
return $ if null ts
|
|
||||||
then "none"
|
|
||||||
else multiLine $
|
|
||||||
map (uncurry $ line uuidmap) $ sort ts
|
|
||||||
where
|
where
|
||||||
line uuidmap t i = unwords
|
checkstatus Nothing = return $ Just DeletedFile
|
||||||
[ showLcDirection (transferDirection t) ++ "ing"
|
checkstatus (Just s)
|
||||||
, fromMaybe (key2file $ transferKey t) (associatedFile i)
|
-- Git thinks that present direct mode files modifed,
|
||||||
, if transferDirection t == Upload then "to" else "from"
|
-- so have to check.
|
||||||
, maybe (fromUUID $ transferUUID t) Remote.name $
|
| not (isSymbolicLink s) = checkkey s =<< catKeyFile f
|
||||||
M.lookup (transferUUID t) uuidmap
|
| otherwise = Just <$> checkNew f
|
||||||
]
|
|
||||||
|
checkkey s (Just k) = ifM (sameFileStatus k s)
|
||||||
|
( return Nothing
|
||||||
|
, return $ Just ModifiedFile
|
||||||
|
)
|
||||||
|
checkkey _ Nothing = Just <$> checkNew f
|
||||||
|
|
||||||
disk_size :: Stat
|
statusIndirect :: FilePath -> Annex Status
|
||||||
disk_size = stat "available local disk space" $ json id $ lift $
|
statusIndirect f = ifM (liftIO $ isJust <$> catchMaybeIO (getFileStatus f))
|
||||||
calcfree
|
( checkNew f
|
||||||
<$> (annexDiskReserve <$> Annex.getGitConfig)
|
, return DeletedFile
|
||||||
<*> inRepo (getDiskFree . gitAnnexDir)
|
)
|
||||||
where
|
where
|
||||||
calcfree reserve (Just have) = unwords
|
|
||||||
[ roughSize storageUnits False $ nonneg $ have - reserve
|
|
||||||
, "(+" ++ roughSize storageUnits False reserve
|
|
||||||
, "reserved)"
|
|
||||||
]
|
|
||||||
calcfree _ _ = "unknown"
|
|
||||||
|
|
||||||
nonneg x
|
checkNew :: FilePath -> Annex Status
|
||||||
| x >= 0 = x
|
checkNew f = ifM (isJust <$> catObjectDetails (Git.Ref.fileRef f))
|
||||||
| otherwise = 0
|
( return ModifiedFile
|
||||||
|
, return NewFile
|
||||||
backend_usage :: Stat
|
)
|
||||||
backend_usage = stat "backend usage" $ nojson $
|
|
||||||
calc
|
|
||||||
<$> (backendsKeys <$> cachedReferencedData)
|
|
||||||
<*> (backendsKeys <$> cachedPresentData)
|
|
||||||
where
|
|
||||||
calc x y = multiLine $
|
|
||||||
map (\(n, b) -> b ++ ": " ++ show n) $
|
|
||||||
reverse $ sort $ map swap $ M.toList $
|
|
||||||
M.unionWith (+) x y
|
|
||||||
|
|
||||||
numcopies_stats :: Stat
|
|
||||||
numcopies_stats = stat "numcopies stats" $ nojson $
|
|
||||||
calc <$> (maybe M.empty numCopiesVarianceMap <$> cachedNumCopiesStats)
|
|
||||||
where
|
|
||||||
calc = multiLine
|
|
||||||
. map (\(variance, count) -> show variance ++ ": " ++ show count)
|
|
||||||
. reverse . sortBy (comparing snd) . M.toList
|
|
||||||
|
|
||||||
cachedPresentData :: StatState KeyData
|
|
||||||
cachedPresentData = do
|
|
||||||
s <- get
|
|
||||||
case presentData s of
|
|
||||||
Just v -> return v
|
|
||||||
Nothing -> do
|
|
||||||
v <- foldKeys <$> lift getKeysPresent
|
|
||||||
put s { presentData = Just v }
|
|
||||||
return v
|
|
||||||
|
|
||||||
cachedReferencedData :: StatState KeyData
|
|
||||||
cachedReferencedData = do
|
|
||||||
s <- get
|
|
||||||
case referencedData s of
|
|
||||||
Just v -> return v
|
|
||||||
Nothing -> do
|
|
||||||
!v <- lift $ Command.Unused.withKeysReferenced
|
|
||||||
emptyKeyData addKey
|
|
||||||
put s { referencedData = Just v }
|
|
||||||
return v
|
|
||||||
|
|
||||||
-- currently only available for local status
|
|
||||||
cachedNumCopiesStats :: StatState (Maybe NumCopiesStats)
|
|
||||||
cachedNumCopiesStats = numCopiesStats <$> get
|
|
||||||
|
|
||||||
getLocalStatInfo :: FilePath -> Annex StatInfo
|
|
||||||
getLocalStatInfo dir = do
|
|
||||||
fast <- Annex.getState Annex.fast
|
|
||||||
matcher <- Limit.getMatcher
|
|
||||||
(presentdata, referenceddata, numcopiesstats) <-
|
|
||||||
Command.Unused.withKeysFilesReferencedIn dir initial
|
|
||||||
(update matcher fast)
|
|
||||||
return $ StatInfo (Just presentdata) (Just referenceddata) (Just numcopiesstats)
|
|
||||||
where
|
|
||||||
initial = (emptyKeyData, emptyKeyData, emptyNumCopiesStats)
|
|
||||||
update matcher fast key file vs@(presentdata, referenceddata, numcopiesstats) =
|
|
||||||
ifM (matcher $ FileInfo file file)
|
|
||||||
( do
|
|
||||||
!presentdata' <- ifM (inAnnex key)
|
|
||||||
( return $ addKey key presentdata
|
|
||||||
, return presentdata
|
|
||||||
)
|
|
||||||
let !referenceddata' = addKey key referenceddata
|
|
||||||
!numcopiesstats' <- if fast
|
|
||||||
then return numcopiesstats
|
|
||||||
else updateNumCopiesStats key file numcopiesstats
|
|
||||||
return $! (presentdata', referenceddata', numcopiesstats')
|
|
||||||
, return vs
|
|
||||||
)
|
|
||||||
|
|
||||||
emptyKeyData :: KeyData
|
|
||||||
emptyKeyData = KeyData 0 0 0 M.empty
|
|
||||||
|
|
||||||
emptyNumCopiesStats :: NumCopiesStats
|
|
||||||
emptyNumCopiesStats = NumCopiesStats M.empty
|
|
||||||
|
|
||||||
foldKeys :: [Key] -> KeyData
|
|
||||||
foldKeys = foldl' (flip addKey) emptyKeyData
|
|
||||||
|
|
||||||
addKey :: Key -> KeyData -> KeyData
|
|
||||||
addKey key (KeyData count size unknownsize backends) =
|
|
||||||
KeyData count' size' unknownsize' backends'
|
|
||||||
where
|
|
||||||
{- All calculations strict to avoid thunks when repeatedly
|
|
||||||
- applied to many keys. -}
|
|
||||||
!count' = count + 1
|
|
||||||
!backends' = M.insertWith' (+) (keyBackendName key) 1 backends
|
|
||||||
!size' = maybe size (+ size) ks
|
|
||||||
!unknownsize' = maybe (unknownsize + 1) (const unknownsize) ks
|
|
||||||
ks = keySize key
|
|
||||||
|
|
||||||
updateNumCopiesStats :: Key -> FilePath -> NumCopiesStats -> Annex NumCopiesStats
|
|
||||||
updateNumCopiesStats key file (NumCopiesStats m) = do
|
|
||||||
!variance <- Variance <$> numCopiesCheck file key (-)
|
|
||||||
let !m' = M.insertWith' (+) variance 1 m
|
|
||||||
let !ret = NumCopiesStats m'
|
|
||||||
return ret
|
|
||||||
|
|
||||||
showSizeKeys :: KeyData -> String
|
|
||||||
showSizeKeys d = total ++ missingnote
|
|
||||||
where
|
|
||||||
total = roughSize storageUnits False $ sizeKeys d
|
|
||||||
missingnote
|
|
||||||
| unknownSizeKeys d == 0 = ""
|
|
||||||
| otherwise = aside $
|
|
||||||
"+ " ++ show (unknownSizeKeys d) ++
|
|
||||||
" unknown size"
|
|
||||||
|
|
||||||
staleSize :: String -> (Git.Repo -> FilePath) -> Stat
|
|
||||||
staleSize label dirspec = go =<< lift (dirKeys dirspec)
|
|
||||||
where
|
|
||||||
go [] = nostat
|
|
||||||
go keys = onsize =<< sum <$> keysizes keys
|
|
||||||
onsize 0 = nostat
|
|
||||||
onsize size = stat label $
|
|
||||||
json (++ aside "clean up with git-annex unused") $
|
|
||||||
return $ roughSize storageUnits False size
|
|
||||||
keysizes keys = do
|
|
||||||
dir <- lift $ fromRepo dirspec
|
|
||||||
liftIO $ forM keys $ \k -> catchDefaultIO 0 $
|
|
||||||
fromIntegral . fileSize
|
|
||||||
<$> getFileStatus (dir </> keyFile k)
|
|
||||||
|
|
||||||
aside :: String -> String
|
|
||||||
aside s = " (" ++ s ++ ")"
|
|
||||||
|
|
||||||
multiLine :: [String] -> String
|
|
||||||
multiLine = concatMap (\l -> "\n\t" ++ l)
|
|
||||||
|
|
|
@ -75,10 +75,10 @@ prepMerge :: Annex ()
|
||||||
prepMerge = liftIO . setCurrentDirectory =<< fromRepo Git.repoPath
|
prepMerge = liftIO . setCurrentDirectory =<< fromRepo Git.repoPath
|
||||||
|
|
||||||
syncBranch :: Git.Ref -> Git.Ref
|
syncBranch :: Git.Ref -> Git.Ref
|
||||||
syncBranch = Git.Ref.under "refs/heads/synced/"
|
syncBranch = Git.Ref.under "refs/heads/synced" . fromDirectBranch
|
||||||
|
|
||||||
remoteBranch :: Remote -> Git.Ref -> Git.Ref
|
remoteBranch :: Remote -> Git.Ref -> Git.Ref
|
||||||
remoteBranch remote = Git.Ref.under $ "refs/remotes/" ++ Remote.name remote
|
remoteBranch remote = Git.Ref.underBase $ "refs/remotes/" ++ Remote.name remote
|
||||||
|
|
||||||
syncRemotes :: [String] -> Annex [Remote]
|
syncRemotes :: [String] -> Annex [Remote]
|
||||||
syncRemotes rs = ifM (Annex.getState Annex.fast) ( nub <$> pickfast , wanted )
|
syncRemotes rs = ifM (Annex.getState Annex.fast) ( nub <$> pickfast , wanted )
|
||||||
|
@ -138,7 +138,13 @@ mergeLocal (Just branch) = go =<< needmerge
|
||||||
pushLocal :: Maybe Git.Ref -> CommandStart
|
pushLocal :: Maybe Git.Ref -> CommandStart
|
||||||
pushLocal Nothing = stop
|
pushLocal Nothing = stop
|
||||||
pushLocal (Just branch) = do
|
pushLocal (Just branch) = do
|
||||||
|
-- Update the sync branch to match the new state of the branch
|
||||||
inRepo $ updateBranch $ syncBranch branch
|
inRepo $ updateBranch $ syncBranch branch
|
||||||
|
-- In direct mode, we're operating on some special direct mode
|
||||||
|
-- branch, rather than the intended branch, so update the indended
|
||||||
|
-- branch.
|
||||||
|
whenM isDirect $
|
||||||
|
inRepo $ updateBranch $ fromDirectBranch branch
|
||||||
stop
|
stop
|
||||||
|
|
||||||
updateBranch :: Git.Ref -> Git.Repo -> IO ()
|
updateBranch :: Git.Ref -> Git.Repo -> IO ()
|
||||||
|
@ -232,7 +238,7 @@ pushBranch remote branch g = tryIO (directpush g) `after` syncpush g
|
||||||
, refspec branch
|
, refspec branch
|
||||||
]
|
]
|
||||||
directpush = Git.Command.runQuiet $ pushparams
|
directpush = Git.Command.runQuiet $ pushparams
|
||||||
[show $ Git.Ref.base branch]
|
[show $ Git.Ref.base $ fromDirectBranch branch]
|
||||||
pushparams branches =
|
pushparams branches =
|
||||||
[ Param "push"
|
[ Param "push"
|
||||||
, Param $ Remote.name remote
|
, Param $ Remote.name remote
|
||||||
|
|
|
@ -11,6 +11,7 @@ import Common.Annex
|
||||||
import Command
|
import Command
|
||||||
import Upgrade
|
import Upgrade
|
||||||
import Annex.Version
|
import Annex.Version
|
||||||
|
import Config
|
||||||
|
|
||||||
def :: [Command]
|
def :: [Command]
|
||||||
def = [dontCheck repoExists $ -- because an old version may not seem to exist
|
def = [dontCheck repoExists $ -- because an old version may not seem to exist
|
||||||
|
@ -23,6 +24,9 @@ seek = [withNothing start]
|
||||||
start :: CommandStart
|
start :: CommandStart
|
||||||
start = do
|
start = do
|
||||||
showStart "upgrade" "."
|
showStart "upgrade" "."
|
||||||
r <- upgrade
|
r <- upgrade False
|
||||||
setVersion defaultVersion
|
ifM isDirect
|
||||||
|
( setVersion directModeVersion
|
||||||
|
, setVersion defaultVersion
|
||||||
|
)
|
||||||
next $ next $ return r
|
next $ next $ return r
|
||||||
|
|
|
@ -71,11 +71,6 @@ getNumCopies Nothing = annexNumCopies <$> Annex.getGitConfig
|
||||||
isDirect :: Annex Bool
|
isDirect :: Annex Bool
|
||||||
isDirect = annexDirect <$> Annex.getGitConfig
|
isDirect = annexDirect <$> Annex.getGitConfig
|
||||||
|
|
||||||
setDirect :: Bool -> Annex ()
|
|
||||||
setDirect b = do
|
|
||||||
setConfig (annexConfig "direct") (Git.Config.boolConfig b)
|
|
||||||
Annex.changeGitConfig $ \c -> c { annexDirect = b }
|
|
||||||
|
|
||||||
crippledFileSystem :: Annex Bool
|
crippledFileSystem :: Annex Bool
|
||||||
crippledFileSystem = annexCrippledFileSystem <$> Annex.getGitConfig
|
crippledFileSystem = annexCrippledFileSystem <$> Annex.getGitConfig
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ import Common
|
||||||
import Git
|
import Git
|
||||||
import Git.Sha
|
import Git.Sha
|
||||||
import Git.Command
|
import Git.Command
|
||||||
import Git.Ref (headRef)
|
import qualified Git.Ref
|
||||||
|
|
||||||
{- The currently checked out branch.
|
{- The currently checked out branch.
|
||||||
-
|
-
|
||||||
|
@ -36,7 +36,7 @@ current r = do
|
||||||
{- The current branch, which may not really exist yet. -}
|
{- The current branch, which may not really exist yet. -}
|
||||||
currentUnsafe :: Repo -> IO (Maybe Git.Ref)
|
currentUnsafe :: Repo -> IO (Maybe Git.Ref)
|
||||||
currentUnsafe r = parse . firstLine
|
currentUnsafe r = parse . firstLine
|
||||||
<$> pipeReadStrict [Param "symbolic-ref", Param $ show headRef] r
|
<$> pipeReadStrict [Param "symbolic-ref", Param $ show Git.Ref.headRef] r
|
||||||
where
|
where
|
||||||
parse l
|
parse l
|
||||||
| null l = Nothing
|
| null l = Nothing
|
||||||
|
@ -97,7 +97,7 @@ commit message branch parentrefs repo = do
|
||||||
sha <- getSha "commit-tree" $ pipeWriteRead
|
sha <- getSha "commit-tree" $ pipeWriteRead
|
||||||
(map Param $ ["commit-tree", show tree] ++ ps)
|
(map Param $ ["commit-tree", show tree] ++ ps)
|
||||||
(Just $ flip hPutStr message) repo
|
(Just $ flip hPutStr message) repo
|
||||||
run [Param "update-ref", Param $ show branch, Param $ show sha] repo
|
update branch sha repo
|
||||||
return sha
|
return sha
|
||||||
where
|
where
|
||||||
ps = concatMap (\r -> ["-p", show r]) parentrefs
|
ps = concatMap (\r -> ["-p", show r]) parentrefs
|
||||||
|
@ -105,3 +105,29 @@ commit message branch parentrefs repo = do
|
||||||
{- A leading + makes git-push force pushing a branch. -}
|
{- A leading + makes git-push force pushing a branch. -}
|
||||||
forcePush :: String -> String
|
forcePush :: String -> String
|
||||||
forcePush b = "+" ++ b
|
forcePush b = "+" ++ b
|
||||||
|
|
||||||
|
{- Updates a branch (or other ref) to a new Sha. -}
|
||||||
|
update :: Branch -> Sha -> Repo -> IO ()
|
||||||
|
update branch sha = run
|
||||||
|
[ Param "update-ref"
|
||||||
|
, Param $ show branch
|
||||||
|
, Param $ show sha
|
||||||
|
]
|
||||||
|
|
||||||
|
{- Checks out a branch, creating it if necessary. -}
|
||||||
|
checkout :: Branch -> Repo -> IO ()
|
||||||
|
checkout branch = run
|
||||||
|
[ Param "checkout"
|
||||||
|
, Param "-q"
|
||||||
|
, Param "-B"
|
||||||
|
, Param $ show $ Git.Ref.base branch
|
||||||
|
]
|
||||||
|
|
||||||
|
{- Removes a branch. -}
|
||||||
|
delete :: Branch -> Repo -> IO ()
|
||||||
|
delete branch = run
|
||||||
|
[ Param "branch"
|
||||||
|
, Param "-q"
|
||||||
|
, Param "-D"
|
||||||
|
, Param $ show $ Git.Ref.base branch
|
||||||
|
]
|
||||||
|
|
|
@ -21,7 +21,8 @@ import Git.FilePath
|
||||||
|
|
||||||
{- Constructs a git command line operating on the specified repo. -}
|
{- Constructs a git command line operating on the specified repo. -}
|
||||||
gitCommandLine :: [CommandParam] -> Repo -> [CommandParam]
|
gitCommandLine :: [CommandParam] -> Repo -> [CommandParam]
|
||||||
gitCommandLine params Repo { location = l@(Local _ _ ) } = setdir : settree ++ params
|
gitCommandLine params r@(Repo { location = l@(Local _ _ ) }) =
|
||||||
|
setdir : settree ++ gitGlobalOpts r ++ params
|
||||||
where
|
where
|
||||||
setdir = Param $ "--git-dir=" ++ gitpath (gitdir l)
|
setdir = Param $ "--git-dir=" ++ gitpath (gitdir l)
|
||||||
settree = case worktree l of
|
settree = case worktree l of
|
||||||
|
|
|
@ -153,7 +153,10 @@ boolConfig True = "true"
|
||||||
boolConfig False = "false"
|
boolConfig False = "false"
|
||||||
|
|
||||||
isBare :: Repo -> Bool
|
isBare :: Repo -> Bool
|
||||||
isBare r = fromMaybe False $ isTrue =<< getMaybe "core.bare" r
|
isBare r = fromMaybe False $ isTrue =<< getMaybe coreBare r
|
||||||
|
|
||||||
|
coreBare :: String
|
||||||
|
coreBare = "core.bare"
|
||||||
|
|
||||||
{- Runs a command to get the configuration of a repo,
|
{- Runs a command to get the configuration of a repo,
|
||||||
- and returns a repo populated with the configuration, as well as the raw
|
- and returns a repo populated with the configuration, as well as the raw
|
||||||
|
|
|
@ -230,6 +230,7 @@ newFrom l = return Repo
|
||||||
, remotes = []
|
, remotes = []
|
||||||
, remoteName = Nothing
|
, remoteName = Nothing
|
||||||
, gitEnv = Nothing
|
, gitEnv = Nothing
|
||||||
|
, gitGlobalOpts = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
54
Git/Hook.hs
Normal file
54
Git/Hook.hs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
{- git hooks
|
||||||
|
-
|
||||||
|
- Copyright 2013 Joey Hess <joey@kitenet.net>
|
||||||
|
-
|
||||||
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Git.Hook where
|
||||||
|
|
||||||
|
import Common
|
||||||
|
import Git
|
||||||
|
import Utility.Tmp
|
||||||
|
|
||||||
|
data Hook = Hook
|
||||||
|
{ hookName :: FilePath
|
||||||
|
, hookScript :: String
|
||||||
|
}
|
||||||
|
|
||||||
|
hookFile :: Hook -> Repo -> FilePath
|
||||||
|
hookFile h r = localGitDir r </> "hooks" </> hookName h
|
||||||
|
|
||||||
|
{- Writes a hook. Returns False if the hook already exists with a different
|
||||||
|
- content. -}
|
||||||
|
hookWrite :: Hook -> Repo -> IO Bool
|
||||||
|
hookWrite h r = do
|
||||||
|
let f = hookFile h r
|
||||||
|
ifM (doesFileExist f)
|
||||||
|
( expectedContent h r
|
||||||
|
, do
|
||||||
|
viaTmp writeFile f (hookScript h)
|
||||||
|
p <- getPermissions f
|
||||||
|
setPermissions f $ p {executable = True}
|
||||||
|
return True
|
||||||
|
)
|
||||||
|
|
||||||
|
{- Removes a hook. Returns False if the hook contained something else, and
|
||||||
|
- could not be removed. -}
|
||||||
|
hookUnWrite :: Hook -> Repo -> IO Bool
|
||||||
|
hookUnWrite h r = do
|
||||||
|
let f = hookFile h r
|
||||||
|
ifM (doesFileExist f)
|
||||||
|
( ifM (expectedContent h r)
|
||||||
|
( do
|
||||||
|
removeFile f
|
||||||
|
return True
|
||||||
|
, return False
|
||||||
|
)
|
||||||
|
, return True
|
||||||
|
)
|
||||||
|
|
||||||
|
expectedContent :: Hook -> Repo -> IO Bool
|
||||||
|
expectedContent h r = do
|
||||||
|
content <- readFile $ hookFile h r
|
||||||
|
return $ content == hookScript h
|
|
@ -11,6 +11,7 @@ module Git.LsFiles (
|
||||||
allFiles,
|
allFiles,
|
||||||
deleted,
|
deleted,
|
||||||
modified,
|
modified,
|
||||||
|
modifiedOthers,
|
||||||
staged,
|
staged,
|
||||||
stagedNotDeleted,
|
stagedNotDeleted,
|
||||||
stagedOthersDetails,
|
stagedOthersDetails,
|
||||||
|
@ -65,6 +66,12 @@ modified l repo = pipeNullSplit params repo
|
||||||
where
|
where
|
||||||
params = [Params "ls-files --modified -z --"] ++ map File l
|
params = [Params "ls-files --modified -z --"] ++ map File l
|
||||||
|
|
||||||
|
{- Files that have been modified or are not checked into git. -}
|
||||||
|
modifiedOthers :: [FilePath] -> Repo -> IO ([FilePath], IO Bool)
|
||||||
|
modifiedOthers l repo = pipeNullSplit params repo
|
||||||
|
where
|
||||||
|
params = [Params "ls-files --modified --others -z --"] ++ map File l
|
||||||
|
|
||||||
{- Returns a list of all files that are staged for commit. -}
|
{- Returns a list of all files that are staged for commit. -}
|
||||||
staged :: [FilePath] -> Repo -> IO ([FilePath], IO Bool)
|
staged :: [FilePath] -> Repo -> IO ([FilePath], IO Bool)
|
||||||
staged = staged' []
|
staged = staged' []
|
||||||
|
|
29
Git/Ref.hs
29
Git/Ref.hs
|
@ -29,17 +29,42 @@ base = Ref . remove "refs/heads/" . remove "refs/remotes/" . show
|
||||||
| prefix `isPrefixOf` s = drop (length prefix) s
|
| prefix `isPrefixOf` s = drop (length prefix) s
|
||||||
| otherwise = s
|
| otherwise = s
|
||||||
|
|
||||||
|
{- Given a directory and any ref, takes the basename of the ref and puts
|
||||||
|
- it under the directory. -}
|
||||||
|
under :: String -> Ref -> Ref
|
||||||
|
under dir r = Ref $ dir ++ "/" ++
|
||||||
|
(reverse $ takeWhile (/= '/') $ reverse $ show r)
|
||||||
|
|
||||||
{- Given a directory such as "refs/remotes/origin", and a ref such as
|
{- Given a directory such as "refs/remotes/origin", and a ref such as
|
||||||
- refs/heads/master, yields a version of that ref under the directory,
|
- refs/heads/master, yields a version of that ref under the directory,
|
||||||
- such as refs/remotes/origin/master. -}
|
- such as refs/remotes/origin/master. -}
|
||||||
under :: String -> Ref -> Ref
|
underBase :: String -> Ref -> Ref
|
||||||
under dir r = Ref $ dir </> show (base r)
|
underBase dir r = Ref $ dir ++ "/" ++ show (base r)
|
||||||
|
|
||||||
|
{- A Ref that can be used to refer to a file in the repository, as staged
|
||||||
|
- in the index.
|
||||||
|
-
|
||||||
|
- Prefixing the file with ./ makes this work even if in a subdirectory
|
||||||
|
- of a repo.
|
||||||
|
-}
|
||||||
|
fileRef :: FilePath -> Ref
|
||||||
|
fileRef f = Ref $ ":./" ++ f
|
||||||
|
|
||||||
|
{- A Ref that can be used to refer to a file in the repository as it
|
||||||
|
- appears in a given Ref. -}
|
||||||
|
fileFromRef :: Ref -> FilePath -> Ref
|
||||||
|
fileFromRef (Ref r) f = let (Ref fr) = fileRef f in Ref (r ++ fr)
|
||||||
|
|
||||||
{- Checks if a ref exists. -}
|
{- Checks if a ref exists. -}
|
||||||
exists :: Ref -> Repo -> IO Bool
|
exists :: Ref -> Repo -> IO Bool
|
||||||
exists ref = runBool
|
exists ref = runBool
|
||||||
[Param "show-ref", Param "--verify", Param "-q", Param $ show ref]
|
[Param "show-ref", Param "--verify", Param "-q", Param $ show ref]
|
||||||
|
|
||||||
|
{- The file used to record a ref. (Git also stores some refs in a
|
||||||
|
- packed-refs file.) -}
|
||||||
|
file :: Ref -> Repo -> FilePath
|
||||||
|
file ref repo = localGitDir repo </> show ref
|
||||||
|
|
||||||
{- Checks if HEAD exists. It generally will, except for in a repository
|
{- Checks if HEAD exists. It generally will, except for in a repository
|
||||||
- that was just created. -}
|
- that was just created. -}
|
||||||
headExists :: Repo -> IO Bool
|
headExists :: Repo -> IO Bool
|
||||||
|
|
|
@ -10,6 +10,7 @@ module Git.Types where
|
||||||
import Network.URI
|
import Network.URI
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
import System.Posix.Types
|
import System.Posix.Types
|
||||||
|
import Utility.SafeCommand
|
||||||
|
|
||||||
{- Support repositories on local disk, and repositories accessed via an URL.
|
{- Support repositories on local disk, and repositories accessed via an URL.
|
||||||
-
|
-
|
||||||
|
@ -38,6 +39,8 @@ data Repo = Repo
|
||||||
, remoteName :: Maybe String
|
, remoteName :: Maybe String
|
||||||
-- alternate environment to use when running git commands
|
-- alternate environment to use when running git commands
|
||||||
, gitEnv :: Maybe [(String, String)]
|
, gitEnv :: Maybe [(String, String)]
|
||||||
|
-- global options to pass to git when running git commands
|
||||||
|
, gitGlobalOpts :: [CommandParam]
|
||||||
} deriving (Show, Eq)
|
} deriving (Show, Eq)
|
||||||
|
|
||||||
{- A git ref. Can be a sha1, or a branch or tag name. -}
|
{- A git ref. Can be a sha1, or a branch or tag name. -}
|
||||||
|
|
|
@ -46,6 +46,7 @@ import qualified Command.Whereis
|
||||||
import qualified Command.List
|
import qualified Command.List
|
||||||
import qualified Command.Log
|
import qualified Command.Log
|
||||||
import qualified Command.Merge
|
import qualified Command.Merge
|
||||||
|
import qualified Command.Info
|
||||||
import qualified Command.Status
|
import qualified Command.Status
|
||||||
import qualified Command.Migrate
|
import qualified Command.Migrate
|
||||||
import qualified Command.Uninit
|
import qualified Command.Uninit
|
||||||
|
@ -143,6 +144,7 @@ cmds = concat
|
||||||
, Command.List.def
|
, Command.List.def
|
||||||
, Command.Log.def
|
, Command.Log.def
|
||||||
, Command.Merge.def
|
, Command.Merge.def
|
||||||
|
, Command.Info.def
|
||||||
, Command.Status.def
|
, Command.Status.def
|
||||||
, Command.Migrate.def
|
, Command.Migrate.def
|
||||||
, Command.Map.def
|
, Command.Map.def
|
||||||
|
|
|
@ -11,6 +11,7 @@ import System.Console.GetOpt
|
||||||
|
|
||||||
import Common.Annex
|
import Common.Annex
|
||||||
import qualified Git.Config
|
import qualified Git.Config
|
||||||
|
import Git.Types
|
||||||
import Command
|
import Command
|
||||||
import Types.TrustLevel
|
import Types.TrustLevel
|
||||||
import qualified Annex
|
import qualified Annex
|
||||||
|
@ -59,12 +60,14 @@ options = Option.common ++
|
||||||
"Trust Amazon Glacier inventory"
|
"Trust Amazon Glacier inventory"
|
||||||
] ++ Option.matcher
|
] ++ Option.matcher
|
||||||
where
|
where
|
||||||
|
trustArg t = ReqArg (Remote.forceTrust t) paramRemote
|
||||||
setnumcopies v = maybe noop
|
setnumcopies v = maybe noop
|
||||||
(\n -> Annex.changeState $ \s -> s { Annex.forcenumcopies = Just n })
|
(\n -> Annex.changeState $ \s -> s { Annex.forcenumcopies = Just n })
|
||||||
(readish v)
|
(readish v)
|
||||||
setuseragent v = Annex.changeState $ \s -> s { Annex.useragent = Just v }
|
setuseragent v = Annex.changeState $ \s -> s { Annex.useragent = Just v }
|
||||||
setgitconfig v = Annex.changeGitRepo =<< inRepo (Git.Config.store v)
|
setgitconfig v = inRepo (Git.Config.store v)
|
||||||
trustArg t = ReqArg (Remote.forceTrust t) paramRemote
|
>>= pure . (\r -> r { gitGlobalOpts = gitGlobalOpts r ++ [Param "-c", Param v] })
|
||||||
|
>>= Annex.changeGitRepo
|
||||||
|
|
||||||
keyOptions :: [Option]
|
keyOptions :: [Option]
|
||||||
keyOptions =
|
keyOptions =
|
||||||
|
|
98
Init.hs
98
Init.hs
|
@ -12,11 +12,10 @@ module Init (
|
||||||
isInitialized,
|
isInitialized,
|
||||||
initialize,
|
initialize,
|
||||||
uninitialize,
|
uninitialize,
|
||||||
probeCrippledFileSystem
|
probeCrippledFileSystem,
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Common.Annex
|
import Common.Annex
|
||||||
import Utility.Tmp
|
|
||||||
import Utility.Network
|
import Utility.Network
|
||||||
import qualified Annex
|
import qualified Annex
|
||||||
import qualified Git
|
import qualified Git
|
||||||
|
@ -26,7 +25,6 @@ import qualified Annex.Branch
|
||||||
import Logs.UUID
|
import Logs.UUID
|
||||||
import Annex.Version
|
import Annex.Version
|
||||||
import Annex.UUID
|
import Annex.UUID
|
||||||
import Utility.Shell
|
|
||||||
import Config
|
import Config
|
||||||
import Annex.Direct
|
import Annex.Direct
|
||||||
import Annex.Content.Direct
|
import Annex.Content.Direct
|
||||||
|
@ -36,6 +34,8 @@ import Backend
|
||||||
import Utility.UserInfo
|
import Utility.UserInfo
|
||||||
import Utility.FileMode
|
import Utility.FileMode
|
||||||
#endif
|
#endif
|
||||||
|
import Annex.Hook
|
||||||
|
import Upgrade
|
||||||
|
|
||||||
genDescription :: Maybe String -> Annex String
|
genDescription :: Maybe String -> Annex String
|
||||||
genDescription (Just d) = return d
|
genDescription (Just d) = return d
|
||||||
|
@ -53,10 +53,19 @@ genDescription Nothing = do
|
||||||
initialize :: Maybe String -> Annex ()
|
initialize :: Maybe String -> Annex ()
|
||||||
initialize mdescription = do
|
initialize mdescription = do
|
||||||
prepUUID
|
prepUUID
|
||||||
setVersion defaultVersion
|
|
||||||
checkCrippledFileSystem
|
|
||||||
checkFifoSupport
|
checkFifoSupport
|
||||||
gitPreCommitHookWrite
|
checkCrippledFileSystem
|
||||||
|
unlessM isBare $
|
||||||
|
hookWrite preCommitHook
|
||||||
|
ifM (crippledFileSystem <&&> not <$> isBare)
|
||||||
|
( do
|
||||||
|
enableDirectMode
|
||||||
|
setDirect True
|
||||||
|
setVersion directModeVersion
|
||||||
|
, do
|
||||||
|
setVersion defaultVersion
|
||||||
|
setDirect False
|
||||||
|
)
|
||||||
createInodeSentinalFile
|
createInodeSentinalFile
|
||||||
u <- getUUID
|
u <- getUUID
|
||||||
{- This will make the first commit to git, so ensure git is set up
|
{- This will make the first commit to git, so ensure git is set up
|
||||||
|
@ -67,16 +76,19 @@ initialize mdescription = do
|
||||||
|
|
||||||
uninitialize :: Annex ()
|
uninitialize :: Annex ()
|
||||||
uninitialize = do
|
uninitialize = do
|
||||||
gitPreCommitHookUnWrite
|
hookUnWrite preCommitHook
|
||||||
removeRepoUUID
|
removeRepoUUID
|
||||||
removeVersion
|
removeVersion
|
||||||
|
|
||||||
{- Will automatically initialize if there is already a git-annex
|
{- Will automatically initialize if there is already a git-annex
|
||||||
- branch from somewhere. Otherwise, require a manual init
|
- branch from somewhere. Otherwise, require a manual init
|
||||||
- to avoid git-annex accidentially being run in git
|
- to avoid git-annex accidentially being run in git
|
||||||
- repos that did not intend to use it. -}
|
- repos that did not intend to use it.
|
||||||
|
-
|
||||||
|
- Checks repository version and handles upgrades too.
|
||||||
|
-}
|
||||||
ensureInitialized :: Annex ()
|
ensureInitialized :: Annex ()
|
||||||
ensureInitialized = getVersion >>= maybe needsinit checkVersion
|
ensureInitialized = getVersion >>= maybe needsinit checkUpgrade
|
||||||
where
|
where
|
||||||
needsinit = ifM Annex.Branch.hasSibling
|
needsinit = ifM Annex.Branch.hasSibling
|
||||||
( initialize Nothing
|
( initialize Nothing
|
||||||
|
@ -87,45 +99,8 @@ ensureInitialized = getVersion >>= maybe needsinit checkVersion
|
||||||
isInitialized :: Annex Bool
|
isInitialized :: Annex Bool
|
||||||
isInitialized = maybe Annex.Branch.hasSibling (const $ return True) =<< getVersion
|
isInitialized = maybe Annex.Branch.hasSibling (const $ return True) =<< getVersion
|
||||||
|
|
||||||
{- set up a git pre-commit hook, if one is not already present -}
|
isBare :: Annex Bool
|
||||||
gitPreCommitHookWrite :: Annex ()
|
isBare = fromRepo Git.repoIsLocalBare
|
||||||
gitPreCommitHookWrite = unlessBare $ do
|
|
||||||
hook <- preCommitHook
|
|
||||||
ifM (liftIO $ doesFileExist hook)
|
|
||||||
( do
|
|
||||||
content <- liftIO $ readFile hook
|
|
||||||
when (content /= preCommitScript) $
|
|
||||||
warning $ "pre-commit hook (" ++ hook ++ ") already exists, not configuring"
|
|
||||||
, unlessM crippledFileSystem $
|
|
||||||
liftIO $ do
|
|
||||||
viaTmp writeFile hook preCommitScript
|
|
||||||
p <- getPermissions hook
|
|
||||||
setPermissions hook $ p {executable = True}
|
|
||||||
)
|
|
||||||
|
|
||||||
gitPreCommitHookUnWrite :: Annex ()
|
|
||||||
gitPreCommitHookUnWrite = unlessBare $ do
|
|
||||||
hook <- preCommitHook
|
|
||||||
whenM (liftIO $ doesFileExist hook) $
|
|
||||||
ifM (liftIO $ (==) preCommitScript <$> readFile hook)
|
|
||||||
( liftIO $ removeFile hook
|
|
||||||
, warning $ "pre-commit hook (" ++ hook ++
|
|
||||||
") contents modified; not deleting." ++
|
|
||||||
" Edit it to remove call to git annex."
|
|
||||||
)
|
|
||||||
|
|
||||||
unlessBare :: Annex () -> Annex ()
|
|
||||||
unlessBare = unlessM $ fromRepo Git.repoIsLocalBare
|
|
||||||
|
|
||||||
preCommitHook :: Annex FilePath
|
|
||||||
preCommitHook = (</>) <$> fromRepo Git.localGitDir <*> pure "hooks/pre-commit"
|
|
||||||
|
|
||||||
preCommitScript :: String
|
|
||||||
preCommitScript = unlines
|
|
||||||
[ shebang_local
|
|
||||||
, "# automatically configured by git-annex"
|
|
||||||
, "git annex pre-commit ."
|
|
||||||
]
|
|
||||||
|
|
||||||
{- A crippled filesystem is one that does not allow making symlinks,
|
{- A crippled filesystem is one that does not allow making symlinks,
|
||||||
- or removing write access from files. -}
|
- or removing write access from files. -}
|
||||||
|
@ -158,25 +133,15 @@ checkCrippledFileSystem = whenM probeCrippledFileSystem $ do
|
||||||
warning "Detected a crippled filesystem."
|
warning "Detected a crippled filesystem."
|
||||||
setCrippledFileSystem True
|
setCrippledFileSystem True
|
||||||
|
|
||||||
{- Normally git disables core.symlinks itself when the filesystem does
|
{- Normally git disables core.symlinks itself when the
|
||||||
- not support them, but in Cygwin, git does support symlinks, while
|
- filesystem does not support them, but in Cygwin, git
|
||||||
- git-annex, not linking with Cygwin, does not. -}
|
- does support symlinks, while git-annex, not linking
|
||||||
|
- with Cygwin, does not. -}
|
||||||
whenM (coreSymlinks <$> Annex.getGitConfig) $ do
|
whenM (coreSymlinks <$> Annex.getGitConfig) $ do
|
||||||
warning "Disabling core.symlinks."
|
warning "Disabling core.symlinks."
|
||||||
setConfig (ConfigKey "core.symlinks")
|
setConfig (ConfigKey "core.symlinks")
|
||||||
(Git.Config.boolConfig False)
|
(Git.Config.boolConfig False)
|
||||||
|
|
||||||
unlessBare $ do
|
|
||||||
unlessM isDirect $ do
|
|
||||||
warning "Enabling direct mode."
|
|
||||||
top <- fromRepo Git.repoPath
|
|
||||||
(l, clean) <- inRepo $ Git.LsFiles.inRepo [top]
|
|
||||||
forM_ l $ \f ->
|
|
||||||
maybe noop (`toDirect` f) =<< isAnnexLink f
|
|
||||||
void $ liftIO clean
|
|
||||||
setDirect True
|
|
||||||
setVersion directModeVersion
|
|
||||||
|
|
||||||
probeFifoSupport :: Annex Bool
|
probeFifoSupport :: Annex Bool
|
||||||
probeFifoSupport = do
|
probeFifoSupport = do
|
||||||
#ifdef mingw32_HOST_OS
|
#ifdef mingw32_HOST_OS
|
||||||
|
@ -199,3 +164,12 @@ checkFifoSupport = unlessM probeFifoSupport $ do
|
||||||
warning "Detected a filesystem without fifo support."
|
warning "Detected a filesystem without fifo support."
|
||||||
warning "Disabling ssh connection caching."
|
warning "Disabling ssh connection caching."
|
||||||
setConfig (annexConfig "sshcaching") (Git.Config.boolConfig False)
|
setConfig (annexConfig "sshcaching") (Git.Config.boolConfig False)
|
||||||
|
|
||||||
|
enableDirectMode :: Annex ()
|
||||||
|
enableDirectMode = unlessM isDirect $ do
|
||||||
|
warning "Enabling direct mode."
|
||||||
|
top <- fromRepo Git.repoPath
|
||||||
|
(l, clean) <- inRepo $ Git.LsFiles.inRepo [top]
|
||||||
|
forM_ l $ \f ->
|
||||||
|
maybe noop (`toDirect` f) =<< isAnnexLink f
|
||||||
|
void $ liftIO clean
|
||||||
|
|
76
Test.hs
76
Test.hs
|
@ -215,12 +215,21 @@ test_add env = "git-annex add" ~: TestList [basic, sha1dup, subdirs]
|
||||||
git_annex env "add" [wormannexedfile, "--backend=WORM"] @? "add with WORM failed"
|
git_annex env "add" [wormannexedfile, "--backend=WORM"] @? "add with WORM failed"
|
||||||
annexed_present wormannexedfile
|
annexed_present wormannexedfile
|
||||||
checkbackend wormannexedfile backendWORM
|
checkbackend wormannexedfile backendWORM
|
||||||
boolSystem "git" [Params "rm --force -q", File wormannexedfile] @? "git rm failed"
|
ifM (annexeval Config.isDirect)
|
||||||
writeFile ingitfile $ content ingitfile
|
( do
|
||||||
boolSystem "git" [Param "add", File ingitfile] @? "git add failed"
|
boolSystem "rm" [Params "-f", File wormannexedfile] @? "rm failed"
|
||||||
boolSystem "git" [Params "commit -q -m commit"] @? "git commit failed"
|
writeFile ingitfile $ content ingitfile
|
||||||
git_annex env "add" [ingitfile] @? "add ingitfile should be no-op"
|
not <$> boolSystem "git" [Param "add", File ingitfile] @? "git add failed to fail in direct mode"
|
||||||
unannexed ingitfile
|
boolSystem "rm" [Params "-f", File ingitfile] @? "rm failed"
|
||||||
|
git_annex env "sync" [] @? "sync failed"
|
||||||
|
, do
|
||||||
|
boolSystem "git" [Params "rm --force -q", File wormannexedfile] @? "git rm failed"
|
||||||
|
writeFile ingitfile $ content ingitfile
|
||||||
|
boolSystem "git" [Param "add", File ingitfile] @? "git add failed"
|
||||||
|
boolSystem "git" [Params "commit -q -m commit"] @? "git commit failed"
|
||||||
|
git_annex env "add" [ingitfile] @? "add ingitfile should be no-op"
|
||||||
|
unannexed ingitfile
|
||||||
|
)
|
||||||
sha1dup = TestCase $ intmpclonerepo env $ do
|
sha1dup = TestCase $ intmpclonerepo env $ do
|
||||||
writeFile sha1annexedfiledup $ content sha1annexedfiledup
|
writeFile sha1annexedfiledup $ content sha1annexedfiledup
|
||||||
git_annex env "add" [sha1annexedfiledup, "--backend=SHA1"] @? "add of second file with same SHA1 failed"
|
git_annex env "add" [sha1annexedfiledup, "--backend=SHA1"] @? "add of second file with same SHA1 failed"
|
||||||
|
@ -265,8 +274,9 @@ test_unannex env = "git-annex unannex" ~: TestList [nocopy, withcopy]
|
||||||
unannexed annexedfile
|
unannexed annexedfile
|
||||||
git_annex env "unannex" [annexedfile] @? "unannex failed on non-annexed file"
|
git_annex env "unannex" [annexedfile] @? "unannex failed on non-annexed file"
|
||||||
unannexed annexedfile
|
unannexed annexedfile
|
||||||
git_annex env "unannex" [ingitfile] @? "unannex ingitfile should be no-op"
|
unlessM (annexeval Config.isDirect) $ do
|
||||||
unannexed ingitfile
|
git_annex env "unannex" [ingitfile] @? "unannex ingitfile should be no-op"
|
||||||
|
unannexed ingitfile
|
||||||
|
|
||||||
test_drop :: TestEnv -> Test
|
test_drop :: TestEnv -> Test
|
||||||
test_drop env = "git-annex drop" ~: TestList [noremote, withremote, untrustedremote]
|
test_drop env = "git-annex drop" ~: TestList [noremote, withremote, untrustedremote]
|
||||||
|
@ -280,8 +290,9 @@ test_drop env = "git-annex drop" ~: TestList [noremote, withremote, untrustedrem
|
||||||
git_annex env "drop" ["--force", annexedfile] @? "drop --force failed"
|
git_annex env "drop" ["--force", annexedfile] @? "drop --force failed"
|
||||||
annexed_notpresent annexedfile
|
annexed_notpresent annexedfile
|
||||||
git_annex env "drop" [annexedfile] @? "drop of dropped file failed"
|
git_annex env "drop" [annexedfile] @? "drop of dropped file failed"
|
||||||
git_annex env "drop" [ingitfile] @? "drop ingitfile should be no-op"
|
unlessM (annexeval Config.isDirect) $ do
|
||||||
unannexed ingitfile
|
git_annex env "drop" [ingitfile] @? "drop ingitfile should be no-op"
|
||||||
|
unannexed ingitfile
|
||||||
withremote = "with remote" ~: TestCase $ intmpclonerepo env $ do
|
withremote = "with remote" ~: TestCase $ intmpclonerepo env $ do
|
||||||
git_annex env "get" [annexedfile] @? "get failed"
|
git_annex env "get" [annexedfile] @? "get failed"
|
||||||
annexed_present annexedfile
|
annexed_present annexedfile
|
||||||
|
@ -306,11 +317,12 @@ test_get env = "git-annex get" ~: TestCase $ intmpclonerepo env $ do
|
||||||
git_annex env "get" [annexedfile] @? "get of file already here failed"
|
git_annex env "get" [annexedfile] @? "get of file already here failed"
|
||||||
inmainrepo env $ annexed_present annexedfile
|
inmainrepo env $ annexed_present annexedfile
|
||||||
annexed_present annexedfile
|
annexed_present annexedfile
|
||||||
inmainrepo env $ unannexed ingitfile
|
unlessM (annexeval Config.isDirect) $ do
|
||||||
unannexed ingitfile
|
inmainrepo env $ unannexed ingitfile
|
||||||
git_annex env "get" [ingitfile] @? "get ingitfile should be no-op"
|
unannexed ingitfile
|
||||||
inmainrepo env $ unannexed ingitfile
|
git_annex env "get" [ingitfile] @? "get ingitfile should be no-op"
|
||||||
unannexed ingitfile
|
inmainrepo env $ unannexed ingitfile
|
||||||
|
unannexed ingitfile
|
||||||
|
|
||||||
test_move :: TestEnv -> Test
|
test_move :: TestEnv -> Test
|
||||||
test_move env = "git-annex move" ~: TestCase $ intmpclonerepo env $ do
|
test_move env = "git-annex move" ~: TestCase $ intmpclonerepo env $ do
|
||||||
|
@ -328,14 +340,15 @@ test_move env = "git-annex move" ~: TestCase $ intmpclonerepo env $ do
|
||||||
git_annex env "move" ["--to", "origin", annexedfile] @? "move --to of file already there failed"
|
git_annex env "move" ["--to", "origin", annexedfile] @? "move --to of file already there failed"
|
||||||
inmainrepo env $ annexed_present annexedfile
|
inmainrepo env $ annexed_present annexedfile
|
||||||
annexed_notpresent annexedfile
|
annexed_notpresent annexedfile
|
||||||
unannexed ingitfile
|
unlessM (annexeval Config.isDirect) $ do
|
||||||
inmainrepo env $ unannexed ingitfile
|
unannexed ingitfile
|
||||||
git_annex env "move" ["--to", "origin", ingitfile] @? "move of ingitfile should be no-op"
|
inmainrepo env $ unannexed ingitfile
|
||||||
unannexed ingitfile
|
git_annex env "move" ["--to", "origin", ingitfile] @? "move of ingitfile should be no-op"
|
||||||
inmainrepo env $ unannexed ingitfile
|
unannexed ingitfile
|
||||||
git_annex env "move" ["--from", "origin", ingitfile] @? "move of ingitfile should be no-op"
|
inmainrepo env $ unannexed ingitfile
|
||||||
unannexed ingitfile
|
git_annex env "move" ["--from", "origin", ingitfile] @? "move of ingitfile should be no-op"
|
||||||
inmainrepo env $ unannexed ingitfile
|
unannexed ingitfile
|
||||||
|
inmainrepo env $ unannexed ingitfile
|
||||||
|
|
||||||
test_copy :: TestEnv -> Test
|
test_copy :: TestEnv -> Test
|
||||||
test_copy env = "git-annex copy" ~: TestCase $ intmpclonerepo env $ do
|
test_copy env = "git-annex copy" ~: TestCase $ intmpclonerepo env $ do
|
||||||
|
@ -353,14 +366,15 @@ test_copy env = "git-annex copy" ~: TestCase $ intmpclonerepo env $ do
|
||||||
git_annex env "move" ["--to", "origin", annexedfile] @? "move --to of file already there failed"
|
git_annex env "move" ["--to", "origin", annexedfile] @? "move --to of file already there failed"
|
||||||
annexed_notpresent annexedfile
|
annexed_notpresent annexedfile
|
||||||
inmainrepo env $ annexed_present annexedfile
|
inmainrepo env $ annexed_present annexedfile
|
||||||
unannexed ingitfile
|
unlessM (annexeval Config.isDirect) $ do
|
||||||
inmainrepo env $ unannexed ingitfile
|
unannexed ingitfile
|
||||||
git_annex env "copy" ["--to", "origin", ingitfile] @? "copy of ingitfile should be no-op"
|
inmainrepo env $ unannexed ingitfile
|
||||||
unannexed ingitfile
|
git_annex env "copy" ["--to", "origin", ingitfile] @? "copy of ingitfile should be no-op"
|
||||||
inmainrepo env $ unannexed ingitfile
|
unannexed ingitfile
|
||||||
git_annex env "copy" ["--from", "origin", ingitfile] @? "copy of ingitfile should be no-op"
|
inmainrepo env $ unannexed ingitfile
|
||||||
checkregularfile ingitfile
|
git_annex env "copy" ["--from", "origin", ingitfile] @? "copy of ingitfile should be no-op"
|
||||||
checkcontent ingitfile
|
checkregularfile ingitfile
|
||||||
|
checkcontent ingitfile
|
||||||
|
|
||||||
test_preferred_content :: TestEnv -> Test
|
test_preferred_content :: TestEnv -> Test
|
||||||
test_preferred_content env = "git-annex preferred-content" ~: TestCase $ intmpclonerepo env $ do
|
test_preferred_content env = "git-annex preferred-content" ~: TestCase $ intmpclonerepo env $ do
|
||||||
|
|
17
Upgrade.hs
17
Upgrade.hs
|
@ -16,9 +16,21 @@ import qualified Upgrade.V0
|
||||||
import qualified Upgrade.V1
|
import qualified Upgrade.V1
|
||||||
#endif
|
#endif
|
||||||
import qualified Upgrade.V2
|
import qualified Upgrade.V2
|
||||||
|
import qualified Upgrade.V4
|
||||||
|
|
||||||
upgrade :: Annex Bool
|
checkUpgrade :: Version -> Annex ()
|
||||||
upgrade = go =<< getVersion
|
checkUpgrade v
|
||||||
|
| v `elem` supportedVersions = noop
|
||||||
|
| v `elem` autoUpgradeableVersions = unlessM (upgrade True) $
|
||||||
|
err "Automatic upgrade failed!"
|
||||||
|
| v `elem` upgradableVersions = err "Upgrade this repository: git-annex upgrade"
|
||||||
|
| otherwise = err "Upgrade git-annex."
|
||||||
|
where
|
||||||
|
err msg = error $ "Repository version " ++ v ++
|
||||||
|
" is not supported. " ++ msg
|
||||||
|
|
||||||
|
upgrade :: Bool -> Annex Bool
|
||||||
|
upgrade automatic = go =<< getVersion
|
||||||
where
|
where
|
||||||
#ifndef mingw32_HOST_OS
|
#ifndef mingw32_HOST_OS
|
||||||
go (Just "0") = Upgrade.V0.upgrade
|
go (Just "0") = Upgrade.V0.upgrade
|
||||||
|
@ -28,4 +40,5 @@ upgrade = go =<< getVersion
|
||||||
go (Just "1") = error "upgrade from v1 on Windows not supported"
|
go (Just "1") = error "upgrade from v1 on Windows not supported"
|
||||||
#endif
|
#endif
|
||||||
go (Just "2") = Upgrade.V2.upgrade
|
go (Just "2") = Upgrade.V2.upgrade
|
||||||
|
go (Just "4") = Upgrade.V4.upgrade automatic
|
||||||
go _ = return True
|
go _ = return True
|
||||||
|
|
23
Upgrade/V4.hs
Normal file
23
Upgrade/V4.hs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{- git-annex v4 -> v5 uppgrade support
|
||||||
|
-
|
||||||
|
- Copyright 2013 Joey Hess <joey@kitenet.net>
|
||||||
|
-
|
||||||
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Upgrade.V4 where
|
||||||
|
|
||||||
|
import Common.Annex
|
||||||
|
import Config
|
||||||
|
import Annex.Direct
|
||||||
|
|
||||||
|
{- Direct mode only upgrade. -}
|
||||||
|
upgrade :: Bool -> Annex Bool
|
||||||
|
upgrade automatic = ifM isDirect
|
||||||
|
( do
|
||||||
|
unless automatic $
|
||||||
|
showAction "v4 to v5"
|
||||||
|
setDirect True
|
||||||
|
return True
|
||||||
|
, return False
|
||||||
|
)
|
16
debian/changelog
vendored
16
debian/changelog
vendored
|
@ -1,3 +1,19 @@
|
||||||
|
git-annex (5.20131102) UNRELEASED; urgency=low
|
||||||
|
|
||||||
|
* Direct mode repositories now have core.bare=true set, to prevent
|
||||||
|
accidentally running git commands that try to operate on the work tree,
|
||||||
|
and so do the wrong thing in direct mode.
|
||||||
|
* annex.version is now set to 5 for direct mode repositories.
|
||||||
|
This upgrade is handled fully automatically, no need to run
|
||||||
|
git annex upgrade
|
||||||
|
* The "status" command has been renamed to "info", to allow
|
||||||
|
"git annex status" to be used in direct mode repositories, now that
|
||||||
|
"git status" won't work in them.
|
||||||
|
* The -c option now not only modifies the git configuration seen by
|
||||||
|
git-annex, but it is passed along to every git command git-annex runs.
|
||||||
|
|
||||||
|
-- Joey Hess <joeyh@debian.org> Sat, 02 Nov 2013 14:54:36 -0400
|
||||||
|
|
||||||
git-annex (4.20131107) UNRELEASED; urgency=low
|
git-annex (4.20131107) UNRELEASED; urgency=low
|
||||||
|
|
||||||
* Allow optionally configuring git-annex with -fEKG to enable awesome
|
* Allow optionally configuring git-annex with -fEKG to enable awesome
|
||||||
|
|
|
@ -39,7 +39,7 @@ Now configure the remote and do the initial push:
|
||||||
git remote add origin example.com:bare-annex.git
|
git remote add origin example.com:bare-annex.git
|
||||||
git push origin master git-annex
|
git push origin master git-annex
|
||||||
|
|
||||||
Now `git annex status` should show the configured bare remote. If it does not, you may have to pull from the remote first (older versions of `git-annex`)
|
Now `git annex info` should show the configured bare remote. If it does not, you may have to pull from the remote first (older versions of `git-annex`)
|
||||||
|
|
||||||
If you wish to configure git such that you can push/pull without arguments, set the upstream branch:
|
If you wish to configure git such that you can push/pull without arguments, set the upstream branch:
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,7 @@ git, and in turn point at the content of large files that is stored in
|
||||||
|
|
||||||
The advantage of direct mode is that you can access files directly,
|
The advantage of direct mode is that you can access files directly,
|
||||||
including modifying them. The disadvantage is that most regular git
|
including modifying them. The disadvantage is that most regular git
|
||||||
commands cannot safely be used, and only a subset of git-annex commands
|
commands cannot be used in a direct mode repository.
|
||||||
can be used.
|
|
||||||
|
|
||||||
Normally, git-annex repositories start off in indirect mode. With some
|
Normally, git-annex repositories start off in indirect mode. With some
|
||||||
exceptions:
|
exceptions:
|
||||||
|
@ -21,7 +20,7 @@ exceptions:
|
||||||
Any repository can be converted to use direct mode at any time, and if you
|
Any repository can be converted to use direct mode at any time, and if you
|
||||||
decide not to use it, you can convert back to indirect mode just as easily.
|
decide not to use it, you can convert back to indirect mode just as easily.
|
||||||
Also, you can have one clone of a repository using direct mode, and another
|
Also, you can have one clone of a repository using direct mode, and another
|
||||||
using indirect mode; direct mode interoperates.
|
using indirect mode.
|
||||||
|
|
||||||
To start using direct mode:
|
To start using direct mode:
|
||||||
|
|
||||||
|
@ -52,7 +51,6 @@ computers, and manage your files, this should not be a concern for you.
|
||||||
## use a direct mode repository
|
## use a direct mode repository
|
||||||
|
|
||||||
You can use most git-annex commands as usual in a direct mode repository.
|
You can use most git-annex commands as usual in a direct mode repository.
|
||||||
A very few commands don't work in direct mode, and will refuse to do anything.
|
|
||||||
|
|
||||||
Direct mode also works well with the git-annex assistant.
|
Direct mode also works well with the git-annex assistant.
|
||||||
|
|
||||||
|
@ -63,23 +61,32 @@ the changes to other repositories for `git annex sync` there to pick up,
|
||||||
and will pull and merge any changes made on other repositories into the
|
and will pull and merge any changes made on other repositories into the
|
||||||
local repository.
|
local repository.
|
||||||
|
|
||||||
While you generally will just use `git annex sync`, if you want to,
|
|
||||||
you can use `git commit --staged`, or plain `git commit`.
|
|
||||||
But not `git commit -a`, or `git commit <file>` ..
|
|
||||||
that'd commit whole large files into git!
|
|
||||||
|
|
||||||
## what doesn't work in direct mode
|
## what doesn't work in direct mode
|
||||||
|
|
||||||
`git annex status` shows incomplete information. A few other commands,
|
A very few git-annex commands don't work in direct mode, and will refuse
|
||||||
like `git annex unlock` don't make sense in direct mode and will refuse to
|
to do anything. For example, `git annex unlock` doesn't make sense in
|
||||||
run.
|
direct mode.
|
||||||
|
|
||||||
As for git commands, you can probably use some git working tree
|
As for git commands, direct mode prevents using any git command that would
|
||||||
manipulation commands, like `git checkout` and `git revert` in useful
|
modify or access the work tree. So you cannot `git commit` or `git pull`
|
||||||
ways... But beware, these commands can replace files that are present in
|
(use `git annex sync` for both instead), or run `git status`.
|
||||||
your repository with broken symlinks. If that file was the only copy you
|
These git commands will complain "fatal: This operation must be run in a work tree".
|
||||||
had of something, it'll be lost.
|
|
||||||
|
|
||||||
This is one more reason it's wise to make git-annex untrust your direct mode
|
The reason for this is that git doesn't understand how git-annex uses the
|
||||||
repositories. Still, you can lose data using these sort of git commands, so
|
work tree in direct mode. Where git expects the symlinks that get checked
|
||||||
use extreme caution.
|
into git to be checked out in the work tree, direct mode instead replaces
|
||||||
|
them with the actual content of files, as managed by git-annex.
|
||||||
|
|
||||||
|
There are still lots of git commands you can use in direct mode. For
|
||||||
|
example, you can run `git log` on files, run `git push`, `git config`,
|
||||||
|
`git remote add` etc.
|
||||||
|
|
||||||
|
## forcing git to use the work tree in direct mode
|
||||||
|
|
||||||
|
This is for experts only. You can lose data doing this, or check enormous
|
||||||
|
files directly into your git repository, and it's your fault if you do!
|
||||||
|
Also, there should be no good reason to need to do this, ever.
|
||||||
|
|
||||||
|
Ok, with the warnings out of the way, all you need to do to make any
|
||||||
|
git command access the work tree in direct mode is pass it
|
||||||
|
`-c core.bare=false`
|
||||||
|
|
|
@ -103,6 +103,13 @@ subdirectories).
|
||||||
|
|
||||||
To avoid contacting the remote to check if it has every file, specify `--fast`
|
To avoid contacting the remote to check if it has every file, specify `--fast`
|
||||||
|
|
||||||
|
* `status` [path ...]`
|
||||||
|
|
||||||
|
Similar to `git status --short`, displays the status of the files in the
|
||||||
|
working tree. Shows files that are not checked into git, files that
|
||||||
|
have been deleted, and files that have been modified.
|
||||||
|
Particulary useful in direct mode.
|
||||||
|
|
||||||
* `unlock [path ...]`
|
* `unlock [path ...]`
|
||||||
|
|
||||||
Normally, the content of annexed files is protected from being changed.
|
Normally, the content of annexed files is protected from being changed.
|
||||||
|
@ -563,10 +570,6 @@ subdirectories).
|
||||||
|
|
||||||
# QUERY COMMANDS
|
# QUERY COMMANDS
|
||||||
|
|
||||||
* `version`
|
|
||||||
|
|
||||||
Shows the version of git-annex, as well as repository version information.
|
|
||||||
|
|
||||||
* `find [path ...]`
|
* `find [path ...]`
|
||||||
|
|
||||||
Outputs a list of annexed files in the specified path. With no path,
|
Outputs a list of annexed files in the specified path. With no path,
|
||||||
|
@ -607,23 +610,26 @@ subdirectories).
|
||||||
To generate output suitable for the gource visualisation program,
|
To generate output suitable for the gource visualisation program,
|
||||||
specify `--gource`.
|
specify `--gource`.
|
||||||
|
|
||||||
* `status [directory ...]`
|
* `info [directory ...]`
|
||||||
|
|
||||||
Displays some statistics and other information, including how much data
|
Displays some statistics and other information, including how much data
|
||||||
is in the annex and a list of all known repositories.
|
is in the annex and a list of all known repositories.
|
||||||
|
|
||||||
To only show the data that can be gathered quickly, use `--fast`.
|
To only show the data that can be gathered quickly, use `--fast`.
|
||||||
|
|
||||||
When a directory is specified, shows a differently formatted status
|
When a directory is specified, shows a differently formatted info
|
||||||
display for that directory. In this mode, all of the file matching
|
display for that directory. In this mode, all of the file matching
|
||||||
options can be used to filter the files that will be included in
|
options can be used to filter the files that will be included in
|
||||||
the status.
|
the information.
|
||||||
|
|
||||||
For example, suppose you want to run "git annex get .", but
|
For example, suppose you want to run "git annex get .", but
|
||||||
would first like to see how much disk space that will use.
|
would first like to see how much disk space that will use.
|
||||||
Then run:
|
Then run:
|
||||||
|
|
||||||
git annex status --fast . --not --in here
|
git annex info --fast . --not --in here
|
||||||
|
* `version`
|
||||||
|
|
||||||
|
Shows the version of git-annex, as well as repository version information.
|
||||||
|
|
||||||
* `map`
|
* `map`
|
||||||
|
|
||||||
|
@ -698,12 +704,21 @@ subdirectories).
|
||||||
|
|
||||||
* `pre-commit [path ...]`
|
* `pre-commit [path ...]`
|
||||||
|
|
||||||
|
This is meant to be called from git's pre-commit hook. `git annex init`
|
||||||
|
automatically creates a pre-commit hook using this.
|
||||||
|
|
||||||
Fixes up symlinks that are staged as part of a commit, to ensure they
|
Fixes up symlinks that are staged as part of a commit, to ensure they
|
||||||
point to annexed content. Also handles injecting changes to unlocked
|
point to annexed content. Also handles injecting changes to unlocked
|
||||||
files into the annex.
|
files into the annex.
|
||||||
|
|
||||||
This is meant to be called from git's pre-commit hook. `git annex init`
|
* `update-hook refname olvrev newrev`
|
||||||
automatically creates a pre-commit hook using this.
|
|
||||||
|
This is meant to be called from git's update hook. `git annex init`
|
||||||
|
automatically creates an update hook using this.
|
||||||
|
|
||||||
|
This denies updates being pushed for the currently checked out branch.
|
||||||
|
While receive.denyCurrentBranch normally prevents that, it does
|
||||||
|
not for fake bare repositories, as used by direct mode.
|
||||||
|
|
||||||
* `fromkey key file`
|
* `fromkey key file`
|
||||||
|
|
||||||
|
@ -788,7 +803,7 @@ subdirectories).
|
||||||
Rather than the normal output, generate JSON. This is intended to be
|
Rather than the normal output, generate JSON. This is intended to be
|
||||||
parsed by programs that use git-annex. Each line of output is a JSON
|
parsed by programs that use git-annex. Each line of output is a JSON
|
||||||
object. Note that json output is only usable with some git-annex commands,
|
object. Note that json output is only usable with some git-annex commands,
|
||||||
like status and find.
|
like info and find.
|
||||||
|
|
||||||
* `--debug`
|
* `--debug`
|
||||||
|
|
||||||
|
@ -1088,7 +1103,7 @@ Here are all the supported configuration settings.
|
||||||
up to 500000 keys. If your repository is larger than that,
|
up to 500000 keys. If your repository is larger than that,
|
||||||
you can adjust this to avoid `git annex unused` not noticing some unused
|
you can adjust this to avoid `git annex unused` not noticing some unused
|
||||||
data files. Increasing this will make `git-annex unused` consume more memory;
|
data files. Increasing this will make `git-annex unused` consume more memory;
|
||||||
run `git annex status` for memory usage numbers.
|
run `git annex info` for memory usage numbers.
|
||||||
|
|
||||||
* `annex.bloomaccuracy`
|
* `annex.bloomaccuracy`
|
||||||
|
|
||||||
|
@ -1096,7 +1111,7 @@ Here are all the supported configuration settings.
|
||||||
`git annex unused`. The default accuracy is 1000 --
|
`git annex unused`. The default accuracy is 1000 --
|
||||||
1 unused file out of 1000 will be missed by `git annex unused`. Increasing
|
1 unused file out of 1000 will be missed by `git annex unused`. Increasing
|
||||||
the accuracy will make `git annex unused` consume more memory;
|
the accuracy will make `git annex unused` consume more memory;
|
||||||
run `git annex status` for memory usage numbers.
|
run `git annex info` for memory usage numbers.
|
||||||
|
|
||||||
* `annex.sshcaching`
|
* `annex.sshcaching`
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ Now you can run normal annex operations, as long as the port forwarding shell is
|
||||||
|
|
||||||
git annex sync
|
git annex sync
|
||||||
git annex get on-the-go some/big/file
|
git annex get on-the-go some/big/file
|
||||||
git annex status
|
git annex info
|
||||||
|
|
||||||
You can add more computers by repeating with a different port, e.g. 2202 or 2203 (or any other).
|
You can add more computers by repeating with a different port, e.g. 2202 or 2203 (or any other).
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ On `angela`, we want to synchronise the git annex metadata with `marcos`. We nee
|
||||||
git init
|
git init
|
||||||
git remote add marcos marcos.example.com:/srv/mp3
|
git remote add marcos marcos.example.com:/srv/mp3
|
||||||
git fetch marcos
|
git fetch marcos
|
||||||
git annex status # this should display the two repos
|
git annex info # this should display the two repos
|
||||||
git annex add .
|
git annex add .
|
||||||
|
|
||||||
This will, again, checksum all files and add them to git annex. Once that is done, you can verify that the files are really the same as marcos with `whereis`:
|
This will, again, checksum all files and add them to git annex. Once that is done, you can verify that the files are really the same as marcos with `whereis`:
|
||||||
|
|
|
@ -4,6 +4,6 @@
|
||||||
subject="comment 1"
|
subject="comment 1"
|
||||||
date="2013-07-12T19:36:28Z"
|
date="2013-07-12T19:36:28Z"
|
||||||
content="""
|
content="""
|
||||||
Ah, I just found that git annex status can do the same :)
|
Ah, I just found that git annex info can do the same :)
|
||||||
Disregard this.
|
Disregard this.
|
||||||
"""]]
|
"""]]
|
||||||
|
|
|
@ -77,6 +77,29 @@ This seems really promising. But of course, git-annex has its own set of
|
||||||
behaviors in a bare repo, so will need to recognise that this repo is not
|
behaviors in a bare repo, so will need to recognise that this repo is not
|
||||||
really bare, and avoid them.
|
really bare, and avoid them.
|
||||||
|
|
||||||
|
> [[done]]!! --[[Joey]]
|
||||||
|
|
||||||
(Git may also have some bare repo behaviors that are unwanted. One example
|
(Git may also have some bare repo behaviors that are unwanted. One example
|
||||||
is that git allows pushes to the current branch in a bare repo,
|
is that git allows pushes to the current branch in a bare repo,
|
||||||
even when `receive.denyCurrentBranch` is set.)
|
even when `receive.denyCurrentBranch` is set.)
|
||||||
|
|
||||||
|
> This is indeed a problem. Indeed, `git annex sync` successfully
|
||||||
|
> pushes changes to the master branch of a fake bare direct mode repo.
|
||||||
|
>
|
||||||
|
> And then, syncing in the repo that was pushed to causes the changes
|
||||||
|
> that were pushed to the master branch to get reverted! This happens
|
||||||
|
> because sync commits; commit sees that files are staged in index
|
||||||
|
> differing from the (pushed) master, and commits the "changes"
|
||||||
|
> which revert it.
|
||||||
|
>
|
||||||
|
> Could fix this using an update hook, to reject the updated of the master
|
||||||
|
> branch. However, won't work on crippled filesystems! (No +x bit)
|
||||||
|
>
|
||||||
|
> Could make git annex sync detect this. It could reset the master
|
||||||
|
> branch to the last one committed, before committing. Seems very racy
|
||||||
|
> and hard to get right!
|
||||||
|
>
|
||||||
|
> Could make direct mode operate on a different branch, like
|
||||||
|
> `annex/direct/master` rather than `master`. Avoid pushing to that
|
||||||
|
> branch (`git annex sync` can map back from it to `master` and push there
|
||||||
|
> instead). A bit clumsy, but works.
|
||||||
|
|
|
@ -18,10 +18,18 @@ conflicts first before upgrading git-annex.
|
||||||
|
|
||||||
## Upgrade events, so far
|
## Upgrade events, so far
|
||||||
|
|
||||||
|
### v4 -> v5 (git-annex version 5.x)
|
||||||
|
|
||||||
|
v5 is only used for [[direct_mode]]. The upgrade from v4 to v5 is handled
|
||||||
|
automatically.
|
||||||
|
|
||||||
|
This upgrade involves changing direct mode repositories to operate with
|
||||||
|
core.bare=true.
|
||||||
|
|
||||||
### v3 -> v4 (git-annex version 4.x)
|
### v3 -> v4 (git-annex version 4.x)
|
||||||
|
|
||||||
v4 is only used for [[direct_mode]], and no upgrade needs to be done from
|
v4 was only used for [[direct_mode]], to ensure that a version of git-annex
|
||||||
existing v3 repositories, they will continue to work.
|
that understands direct mode was used with a direct mode repository.
|
||||||
|
|
||||||
### v2 -> v3 (git-annex version 3.x)
|
### v2 -> v3 (git-annex version 3.x)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue