b223988e22
--backend is no longer a global option, and is only accepted by commands that actually need it. Three commands that used to support backend but don't any longer are watch, webapp, and assistant. It would be possible to make them support it, but I doubt anyone used the option with these. And in the case of webapp and assistant, the option was handled inconsistently, only taking affect when the command is run with an existing git-annex repo, not when it creates a new one. Also, renamed GlobalOption etc to AnnexOption. Because there are many options of this type that are not actually global (any more) and get added to commands that need them. Sponsored-by: Kevin Mueller on Patreon
163 lines
4.6 KiB
Haskell
163 lines
4.6 KiB
Haskell
{- git-annex command
|
|
-
|
|
- Copyright 2021 Joey Hess <id@joeyh.name>
|
|
-
|
|
- Licensed under the GNU AGPL version 3 or higher.
|
|
-}
|
|
|
|
module Command.WhereUsed where
|
|
|
|
import Command
|
|
import Git
|
|
import Git.Sha
|
|
import Git.FilePath
|
|
import qualified Git.Ref
|
|
import qualified Git.Command
|
|
import qualified Git.DiffTree as DiffTree
|
|
import qualified Annex
|
|
import qualified Annex.Branch
|
|
import Annex.CatFile
|
|
import Database.Keys
|
|
|
|
import Data.Char
|
|
import qualified Data.ByteString as S
|
|
import qualified Data.ByteString.Lazy as L
|
|
|
|
cmd :: Command
|
|
cmd = noCommit $ withAnnexOptions [annexedMatchingOptions] $
|
|
command "whereused" SectionQuery
|
|
"lists repositories that have file content"
|
|
paramNothing (seek <$$> optParser)
|
|
|
|
data WhereUsedOptions = WhereUsedOptions
|
|
{ keyOptions :: KeyOptions
|
|
, historicalOption :: Bool
|
|
}
|
|
|
|
optParser :: CmdParamsDesc -> Parser WhereUsedOptions
|
|
optParser _desc = WhereUsedOptions
|
|
<$> (parseUnusedKeysOption <|> parseSpecificKeyOption)
|
|
<*> switch
|
|
( long "historical"
|
|
<> help "find historical uses"
|
|
)
|
|
|
|
seek :: WhereUsedOptions -> CommandSeek
|
|
seek o = withKeyOptions (Just (keyOptions o)) False dummyfileseeker
|
|
(commandAction . start o) dummyfilecommandseek (WorkTreeItems [])
|
|
where
|
|
dummyfileseeker = AnnexedFileSeeker
|
|
{ startAction = \_ _ _ -> return Nothing
|
|
, checkContentPresent = Nothing
|
|
, usesLocationLog = False
|
|
}
|
|
dummyfilecommandseek = const noop
|
|
|
|
start :: WhereUsedOptions -> (SeekInput, Key, ActionItem) -> CommandStart
|
|
start o (_, key, _) = startingCustomOutput key $ do
|
|
fs <- filterM stillassociated
|
|
=<< mapM (fromRepo . fromTopFilePath)
|
|
=<< getAssociatedFiles key
|
|
liftIO $ forM_ fs $ display key . fromRawFilePath
|
|
|
|
when (historicalOption o && null fs) $
|
|
findHistorical key
|
|
|
|
next $ return True
|
|
where
|
|
-- Some associated files that are in the keys database may no
|
|
-- longer correspond to files in the repository.
|
|
stillassociated f = catKeyFile f >>= \case
|
|
Just k | k == key -> return True
|
|
_ -> return False
|
|
|
|
display :: Key -> String -> IO ()
|
|
display key loc = putStrLn (serializeKey key ++ " " ++ loc)
|
|
|
|
findHistorical :: Key -> Annex ()
|
|
findHistorical key = do
|
|
-- Find most recent change to the key, in all branches and
|
|
-- tags, except the git-annex branch.
|
|
found <- searchLog key
|
|
-- Search all local branches, except git-annex branch.
|
|
[ Param ("--exclude=*/" ++ fromRef (Annex.Branch.name))
|
|
, Param "--glob=*"
|
|
-- Also search remote branches
|
|
, Param ("--exclude=" ++ fromRef (Annex.Branch.name))
|
|
, Param "--remotes=*"
|
|
-- And search tags.
|
|
, Param "--tags=*"
|
|
-- Output the commit hash
|
|
, Param "--pretty=%H"
|
|
] $ \h fs repo -> do
|
|
commitsha <- getSha "log" (pure h)
|
|
commitdesc <- S.takeWhile (/= fromIntegral (ord '\n'))
|
|
<$> Git.Command.pipeReadStrict
|
|
[ Param "describe"
|
|
, Param "--contains"
|
|
, Param "--all"
|
|
, Param (fromRef commitsha)
|
|
] repo
|
|
if S.null commitdesc
|
|
then return False
|
|
else process fs $
|
|
displayreffile (Ref commitdesc)
|
|
|
|
unless found $
|
|
void $ searchLog key
|
|
[ Param "--walk-reflogs"
|
|
-- Output the reflog selector
|
|
, Param "--pretty=%gd"
|
|
] $ \h fs _ -> process fs $
|
|
displayreffile (Ref h)
|
|
where
|
|
process fs a = or <$> forM fs a
|
|
|
|
displayreffile r f = do
|
|
let fref = Git.Ref.branchFileRef r f
|
|
display key (fromRef fref)
|
|
return True
|
|
|
|
searchLog :: Key -> [CommandParam] -> (S.ByteString -> [RawFilePath] -> Repo -> IO Bool) -> Annex Bool
|
|
searchLog key ps a = Annex.inRepo $ \repo -> do
|
|
(output, cleanup) <- Git.Command.pipeNullSplit ps' repo
|
|
found <- case output of
|
|
(h:rest) -> do
|
|
let diff = DiffTree.parseDiffRaw rest
|
|
let fs = map (flip fromTopFilePath repo . DiffTree.file) diff
|
|
rfs <- mapM relPathCwdToFile fs
|
|
a (L.toStrict h) rfs repo
|
|
_ -> return False
|
|
void cleanup
|
|
return found
|
|
where
|
|
ps' =
|
|
[ Param "log"
|
|
, Param "-z"
|
|
-- Don't convert pointer files.
|
|
, Param "--no-textconv"
|
|
-- Don't abbreviate hashes.
|
|
, Param "--no-abbrev"
|
|
-- Only find the most recent commit, for speed.
|
|
, Param "-n1"
|
|
-- Be sure to treat -G as a regexp.
|
|
, Param "--basic-regexp"
|
|
-- Find commits that contain the key. The object has to
|
|
-- end with the key to avoid confusion with longer keys,
|
|
-- so a regexp is used. Since annex pointer files
|
|
-- may contain a newline followed by perhaps something
|
|
-- else, that is also matched.
|
|
, Param ("-G" ++ escapeRegexp (fromRawFilePath (keyFile key)) ++ "($|\n)")
|
|
-- Skip commits where the file was deleted,
|
|
-- only find those where it was added or modified.
|
|
, Param "--diff-filter=ACMRTUX"
|
|
-- Output the raw diff.
|
|
, Param "--raw"
|
|
] ++ ps
|
|
|
|
escapeRegexp :: String -> String
|
|
escapeRegexp = concatMap esc
|
|
where
|
|
esc c
|
|
| isAscii c && isAlphaNum c = [c]
|
|
| otherwise = ['[', c, ']']
|