172 lines
5.4 KiB
Haskell
172 lines
5.4 KiB
Haskell
{- git-annex command
|
|
-
|
|
- Copyright 2012 Joey Hess <joey@kitenet.net>
|
|
-
|
|
- Licensed under the GNU GPL version 3 or higher.
|
|
-}
|
|
|
|
module Command.Log where
|
|
|
|
import qualified Data.Set as S
|
|
import qualified Data.Map as M
|
|
import qualified Data.ByteString.Lazy.Char8 as L
|
|
import Data.Time.Clock.POSIX
|
|
import Data.Time
|
|
import System.Locale
|
|
import Data.Char
|
|
|
|
import Common.Annex
|
|
import Command
|
|
import qualified Logs.Location
|
|
import qualified Logs.Presence
|
|
import Annex.CatFile
|
|
import qualified Annex.Branch
|
|
import qualified Git
|
|
import Git.Command
|
|
import qualified Remote
|
|
import qualified Option
|
|
import qualified Annex
|
|
|
|
data RefChange = RefChange
|
|
{ changetime :: POSIXTime
|
|
, oldref :: Git.Ref
|
|
, newref :: Git.Ref
|
|
}
|
|
|
|
type Outputter = Bool -> POSIXTime -> [UUID] -> Annex ()
|
|
|
|
def :: [Command]
|
|
def = [withOptions options $
|
|
command "log" paramPaths seek "shows location log"]
|
|
|
|
options :: [Option]
|
|
options = passthruOptions ++ [gourceOption]
|
|
|
|
passthruOptions :: [Option]
|
|
passthruOptions = map odate ["since", "after", "until", "before"] ++
|
|
[ Option.field ['n'] "max-count" paramNumber
|
|
"limit number of logs displayed"
|
|
]
|
|
where
|
|
odate n = Option.field [] n paramDate $
|
|
"show log " ++ n ++ " date"
|
|
|
|
gourceOption :: Option
|
|
gourceOption = Option.flag [] "gource" "format output for gource"
|
|
|
|
seek :: [CommandSeek]
|
|
seek = [withValue Remote.uuidDescriptions $ \m ->
|
|
withValue (liftIO getCurrentTimeZone) $ \zone ->
|
|
withValue (concat <$> mapM getoption passthruOptions) $ \os ->
|
|
withFlag gourceOption $ \gource ->
|
|
withFilesInGit $ whenAnnexed $ start m zone os gource]
|
|
where
|
|
getoption o = maybe [] (use o) <$>
|
|
Annex.getField (Option.name o)
|
|
use o v = [Param ("--" ++ Option.name o), Param v]
|
|
|
|
start :: M.Map UUID String -> TimeZone -> [CommandParam] -> Bool ->
|
|
FilePath -> (Key, Backend) -> CommandStart
|
|
start m zone os gource file (key, _) = do
|
|
showLog output =<< readLog <$> getLog key os
|
|
-- getLog produces a zombie; reap it
|
|
liftIO reapZombies
|
|
stop
|
|
where
|
|
output
|
|
| gource = gourceOutput lookupdescription file
|
|
| otherwise = normalOutput lookupdescription file zone
|
|
lookupdescription u = fromMaybe (fromUUID u) $ M.lookup u m
|
|
|
|
showLog :: Outputter -> [RefChange] -> Annex ()
|
|
showLog outputter ps = do
|
|
sets <- mapM (getset newref) ps
|
|
previous <- maybe (return genesis) (getset oldref) (lastMaybe ps)
|
|
sequence_ $ compareChanges outputter $ sets ++ [previous]
|
|
where
|
|
genesis = (0, S.empty)
|
|
getset select change = do
|
|
s <- S.fromList <$> get (select change)
|
|
return (changetime change, s)
|
|
get ref = map toUUID . Logs.Presence.getLog . L.unpack <$>
|
|
catObject ref
|
|
|
|
normalOutput :: (UUID -> String) -> FilePath -> TimeZone -> Outputter
|
|
normalOutput lookupdescription file zone present ts us =
|
|
liftIO $ mapM_ (putStrLn . format) us
|
|
where
|
|
time = showTimeStamp zone ts
|
|
addel = if present then "+" else "-"
|
|
format u = unwords [ addel, time, file, "|",
|
|
fromUUID u ++ " -- " ++ lookupdescription u ]
|
|
|
|
gourceOutput :: (UUID -> String) -> FilePath -> Outputter
|
|
gourceOutput lookupdescription file present ts us =
|
|
liftIO $ mapM_ (putStrLn . intercalate "|" . format) us
|
|
where
|
|
time = takeWhile isDigit $ show ts
|
|
addel = if present then "A" else "M"
|
|
format u = [ time, lookupdescription u, addel, file ]
|
|
|
|
{- Generates a display of the changes (which are ordered with newest first),
|
|
- by comparing each change with the previous change.
|
|
- Uses a formatter to generate a display of items that are added and
|
|
- removed. -}
|
|
compareChanges :: Ord a => (Bool -> POSIXTime -> [a] -> b) -> [(POSIXTime, S.Set a)] -> [b]
|
|
compareChanges format changes = concatMap diff $ zip changes (drop 1 changes)
|
|
where
|
|
diff ((ts, new), (_, old)) =
|
|
[format True ts added, format False ts removed]
|
|
where
|
|
added = S.toList $ S.difference new old
|
|
removed = S.toList $ S.difference old new
|
|
|
|
{- Gets the git log for a given location log file.
|
|
-
|
|
- This is complicated by git log using paths relative to the current
|
|
- directory, even when looking at files in a different branch. A wacky
|
|
- relative path to the log file has to be used.
|
|
-
|
|
- The --remove-empty is a significant optimisation. It relies on location
|
|
- log files never being deleted in normal operation. Letting git stop
|
|
- once the location log file is gone avoids it checking all the way back
|
|
- to commit 0 to see if it used to exist, so generally speeds things up a
|
|
- *lot* for newish files. -}
|
|
getLog :: Key -> [CommandParam] -> Annex [String]
|
|
getLog key os = do
|
|
top <- fromRepo Git.repoPath
|
|
p <- liftIO $ relPathCwdToFile top
|
|
let logfile = p </> Logs.Location.logFile key
|
|
inRepo $ pipeNullSplitZombie $
|
|
[ Params "log -z --pretty=format:%ct --raw --abbrev=40"
|
|
, Param "--remove-empty"
|
|
] ++ os ++
|
|
[ Param $ show Annex.Branch.fullname
|
|
, Param "--"
|
|
, Param logfile
|
|
]
|
|
|
|
readLog :: [String] -> [RefChange]
|
|
readLog = mapMaybe (parse . lines)
|
|
where
|
|
parse (ts:raw:[]) = let (old, new) = parseRaw raw in
|
|
Just RefChange
|
|
{ changetime = parseTimeStamp ts
|
|
, oldref = old
|
|
, newref = new
|
|
}
|
|
parse _ = Nothing
|
|
|
|
-- Parses something like ":100644 100644 oldsha newsha M"
|
|
parseRaw :: String -> (Git.Ref, Git.Ref)
|
|
parseRaw l = go $ words l
|
|
where
|
|
go (_:_:oldsha:newsha:_) = (Git.Ref oldsha, Git.Ref newsha)
|
|
go _ = error $ "unable to parse git log output: " ++ l
|
|
|
|
parseTimeStamp :: String -> POSIXTime
|
|
parseTimeStamp = utcTimeToPOSIXSeconds . fromMaybe (error "bad timestamp") .
|
|
parseTime defaultTimeLocale "%s"
|
|
|
|
showTimeStamp :: TimeZone -> POSIXTime -> String
|
|
showTimeStamp zone = show . utcToLocalTime zone . posixSecondsToUTCTime
|