2023-11-10 16:37:01 +00:00
|
|
|
{- git log
|
|
|
|
-
|
|
|
|
- Copyright 2023 Joey Hess <id@joeyh.name>
|
|
|
|
-
|
|
|
|
- Licensed under the GNU AGPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
|
|
|
module Git.Log where
|
|
|
|
|
|
|
|
import Common
|
|
|
|
import Git
|
|
|
|
import Git.Command
|
2023-12-07 19:50:52 +00:00
|
|
|
import Git.Sha
|
2023-11-10 16:37:01 +00:00
|
|
|
|
|
|
|
import Data.Time
|
|
|
|
import Data.Time.Clock.POSIX
|
|
|
|
|
|
|
|
-- A change made to a file.
|
2023-12-07 21:03:58 +00:00
|
|
|
data LoggedFileChange t = LoggedFileChange
|
2023-11-10 16:37:01 +00:00
|
|
|
{ changetime :: POSIXTime
|
|
|
|
, changed :: t
|
|
|
|
, changedfile :: FilePath
|
|
|
|
, oldref :: Ref
|
|
|
|
, newref :: Ref
|
|
|
|
}
|
|
|
|
deriving (Show)
|
|
|
|
|
2023-12-07 21:03:58 +00:00
|
|
|
-- Get the git log of changes to files.
|
|
|
|
--
|
|
|
|
-- Note that the returned cleanup action should only be
|
2023-11-10 16:37:01 +00:00
|
|
|
-- run after processing the returned list.
|
|
|
|
getGitLog
|
|
|
|
:: Ref
|
2023-12-07 19:50:52 +00:00
|
|
|
-> Maybe Ref
|
2023-11-10 16:37:01 +00:00
|
|
|
-> [FilePath]
|
|
|
|
-> [CommandParam]
|
2023-12-07 19:50:52 +00:00
|
|
|
-> (Sha -> FilePath -> Maybe t)
|
2023-11-10 16:37:01 +00:00
|
|
|
-> Repo
|
2023-12-07 21:03:58 +00:00
|
|
|
-> IO ([LoggedFileChange t], IO Bool)
|
2023-12-07 19:50:52 +00:00
|
|
|
getGitLog ref stopref fs os selector repo = do
|
2023-11-10 16:37:01 +00:00
|
|
|
(ls, cleanup) <- pipeNullSplit ps repo
|
2023-12-07 19:50:52 +00:00
|
|
|
return (parseGitRawLog selector (map decodeBL ls), cleanup)
|
2023-11-10 16:37:01 +00:00
|
|
|
where
|
|
|
|
ps =
|
|
|
|
[ Param "log"
|
|
|
|
, Param "-z"
|
|
|
|
, Param ("--pretty=format:"++commitinfoFormat)
|
|
|
|
, Param "--raw"
|
|
|
|
, Param "--no-abbrev"
|
|
|
|
, Param "--no-renames"
|
|
|
|
] ++ os ++
|
2023-12-07 19:50:52 +00:00
|
|
|
[ case stopref of
|
|
|
|
Just stopref' -> Param $
|
|
|
|
fromRef stopref' <> ".." <> fromRef ref
|
|
|
|
Nothing -> Param (fromRef ref)
|
2023-11-10 16:37:01 +00:00
|
|
|
, Param "--"
|
|
|
|
] ++ map Param fs
|
|
|
|
|
2023-12-07 19:50:52 +00:00
|
|
|
-- The commitinfo is the commit hash followed by its timestamp.
|
2023-11-10 16:37:01 +00:00
|
|
|
commitinfoFormat :: String
|
2023-12-07 19:50:52 +00:00
|
|
|
commitinfoFormat = "%H %ct"
|
2023-11-10 16:37:01 +00:00
|
|
|
|
|
|
|
-- Parses chunked git log --raw output generated by getGitLog,
|
|
|
|
-- which looks something like:
|
|
|
|
--
|
|
|
|
-- [ "commitinfo\n:changeline"
|
|
|
|
-- , "filename"
|
|
|
|
-- , ""
|
|
|
|
-- , "commitinfo\n:changeline"
|
|
|
|
-- , "filename"
|
|
|
|
-- , ":changeline"
|
|
|
|
-- , "filename"
|
|
|
|
-- , ""
|
|
|
|
-- ]
|
|
|
|
--
|
|
|
|
-- The commitinfo is not included before all changelines, so
|
|
|
|
-- keep track of the most recently seen commitinfo.
|
2023-12-07 21:03:58 +00:00
|
|
|
parseGitRawLog :: (Ref -> FilePath -> Maybe t) -> [String] -> [LoggedFileChange t]
|
2023-12-07 19:50:52 +00:00
|
|
|
parseGitRawLog selector = parse (deleteSha, epoch)
|
2023-11-10 16:37:01 +00:00
|
|
|
where
|
|
|
|
epoch = toEnum 0 :: POSIXTime
|
2023-12-07 19:50:52 +00:00
|
|
|
parse old ([]:rest) = parse old rest
|
|
|
|
parse (oldcommitsha, oldts) (c1:c2:rest) = case mrc of
|
|
|
|
Just rc -> rc : parse (commitsha, ts) rest
|
|
|
|
Nothing -> parse (commitsha, ts) (c2:rest)
|
2023-11-10 16:37:01 +00:00
|
|
|
where
|
2023-12-07 19:50:52 +00:00
|
|
|
(commitsha, ts, cl) = case separate (== '\n') c1 of
|
|
|
|
(cl', []) -> (oldcommitsha, oldts, cl')
|
|
|
|
(ci, cl') -> case words ci of
|
|
|
|
(css:tss:[]) -> (Ref (encodeBS css), parseTimeStamp tss, cl')
|
|
|
|
_ -> (oldcommitsha, oldts, cl')
|
2023-11-10 16:37:01 +00:00
|
|
|
mrc = do
|
|
|
|
(old, new) <- parseRawChangeLine cl
|
2023-12-07 19:50:52 +00:00
|
|
|
v <- selector commitsha c2
|
2023-12-07 21:03:58 +00:00
|
|
|
return $ LoggedFileChange
|
2023-11-10 16:37:01 +00:00
|
|
|
{ changetime = ts
|
|
|
|
, changed = v
|
|
|
|
, changedfile = c2
|
|
|
|
, oldref = old
|
|
|
|
, newref = new
|
|
|
|
}
|
|
|
|
parse _ _ = []
|
|
|
|
|
|
|
|
-- Parses something like ":100644 100644 oldsha newsha M"
|
|
|
|
-- extracting the shas.
|
|
|
|
parseRawChangeLine :: String -> Maybe (Git.Ref, Git.Ref)
|
|
|
|
parseRawChangeLine = go . words
|
|
|
|
where
|
|
|
|
go (_:_:oldsha:newsha:_) =
|
|
|
|
Just (Git.Ref (encodeBS oldsha), Git.Ref (encodeBS newsha))
|
|
|
|
go _ = Nothing
|
|
|
|
|
|
|
|
parseTimeStamp :: String -> POSIXTime
|
|
|
|
parseTimeStamp = utcTimeToPOSIXSeconds . fromMaybe (giveup "bad timestamp") .
|
|
|
|
parseTimeM True defaultTimeLocale "%s"
|