Merge branch 'directguard'

This commit is contained in:
Joey Hess 2013-11-07 14:12:13 -04:00
commit d99bdbbb84
34 changed files with 963 additions and 531 deletions

View file

@ -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

View file

@ -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

View file

@ -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
View 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

View file

@ -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

View file

@ -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
View 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)

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
]

View file

@ -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

View file

@ -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

View file

@ -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
View 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

View file

@ -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' []

View file

@ -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

View file

@ -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. -}

View file

@ -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

View file

@ -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
View file

@ -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
View file

@ -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

View file

@ -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
View 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
View file

@ -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

View file

@ -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:

View file

@ -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`

View file

@ -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`

View file

@ -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).

View file

@ -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`:

View file

@ -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.
"""]] """]]

View file

@ -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.

View file

@ -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)