1{- git diff-tree interface 2 - 3 - Copyright 2012-2020 Joey Hess <id@joeyh.name> 4 - 5 - Licensed under the GNU AGPL version 3 or higher. 6 -} 7 8module Git.DiffTree ( 9 DiffTreeItem(..), 10 isDiffOf, 11 diffTree, 12 diffTreeRecursive, 13 diffIndex, 14 diffWorkTree, 15 diffFiles, 16 diffLog, 17 commitDiff, 18 parseDiffRaw, 19) where 20 21import qualified Data.ByteString as B 22import qualified Data.ByteString.Lazy as L 23import qualified Data.Attoparsec.ByteString.Lazy as A 24import qualified Data.Attoparsec.ByteString.Char8 as A8 25 26import Common 27import Git 28import Git.Sha 29import Git.Command 30import Git.FilePath 31import Git.DiffTreeItem 32import qualified Git.Filename 33import qualified Git.Ref 34import Utility.Attoparsec 35 36{- Checks if the DiffTreeItem modifies a file with a given name 37 - or under a directory by that name. -} 38isDiffOf :: DiffTreeItem -> TopFilePath -> Bool 39isDiffOf diff f = 40 let f' = getTopFilePath f 41 in if B.null f' 42 then True -- top of repo contains all 43 else f' `dirContains` getTopFilePath (file diff) 44 45{- Diffs two tree Refs. -} 46diffTree :: Ref -> Ref -> Repo -> IO ([DiffTreeItem], IO Bool) 47diffTree src dst = getdiff (Param "diff-tree") 48 [Param (fromRef src), Param (fromRef dst), Param "--"] 49 50{- Diffs two tree Refs, recursing into sub-trees -} 51diffTreeRecursive :: Ref -> Ref -> Repo -> IO ([DiffTreeItem], IO Bool) 52diffTreeRecursive src dst = getdiff (Param "diff-tree") 53 [Param "-r", Param (fromRef src), Param (fromRef dst), Param "--"] 54 55{- Diffs between a tree and the index. Does nothing if there is not yet a 56 - commit in the repository. -} 57diffIndex :: Ref -> Repo -> IO ([DiffTreeItem], IO Bool) 58diffIndex ref = diffIndex' ref [Param "--cached"] 59 60{- Diffs between a tree and the working tree. Does nothing if there is not 61 - yet a commit in the repository, or if the repository is bare. -} 62diffWorkTree :: Ref -> Repo -> IO ([DiffTreeItem], IO Bool) 63diffWorkTree ref repo = 64 ifM (Git.Ref.headExists repo) 65 ( diffIndex' ref [] repo 66 , return ([], return True) 67 ) 68 69diffIndex' :: Ref -> [CommandParam] -> Repo -> IO ([DiffTreeItem], IO Bool) 70diffIndex' ref params repo = 71 ifM (Git.Ref.headExists repo) 72 ( getdiff (Param "diff-index") 73 ( params ++ [Param $ fromRef ref] ++ [Param "--"] ) 74 repo 75 , return ([], return True) 76 ) 77 78{- Diff between the index and work tree. -} 79diffFiles :: [CommandParam] -> Repo -> IO ([DiffTreeItem], IO Bool) 80diffFiles = getdiff (Param "diff-files") 81 82{- Runs git log in --raw mode to get the changes that were made in 83 - a particular commit to particular files. The output format 84 - is adjusted to be the same as diff-tree --raw._-} 85diffLog :: [CommandParam] -> Repo -> IO ([DiffTreeItem], IO Bool) 86diffLog params = getdiff (Param "log") 87 (Param "-n1" : Param "--no-abbrev" : Param "--pretty=format:" : params) 88 89{- Uses git show to get the changes made by a commit. 90 - 91 - Does not support merge commits, and will fail on them. -} 92commitDiff :: Sha -> Repo -> IO ([DiffTreeItem], IO Bool) 93commitDiff ref = getdiff (Param "show") 94 [ Param "--no-abbrev", Param "--pretty=", Param "--raw", Param (fromRef ref) ] 95 96getdiff :: CommandParam -> [CommandParam] -> Repo -> IO ([DiffTreeItem], IO Bool) 97getdiff command params repo = do 98 (diff, cleanup) <- pipeNullSplit ps repo 99 return (parseDiffRaw diff, cleanup) 100 where 101 ps = 102 command : 103 Param "-z" : 104 Param "--raw" : 105 Param "--no-renames" : 106 Param "-l0" : 107 params 108 109{- Parses --raw output used by diff-tree and git-log. -} 110parseDiffRaw :: [L.ByteString] -> [DiffTreeItem] 111parseDiffRaw l = go l 112 where 113 go [] = [] 114 go (info:f:rest) = case A.parse (parserDiffRaw (L.toStrict f)) info of 115 A.Done _ r -> r : go rest 116 A.Fail _ _ err -> error $ "diff-tree parse error: " ++ err 117 go (s:[]) = error $ "diff-tree parse error near \"" ++ decodeBL s ++ "\"" 118 119-- :<srcmode> SP <dstmode> SP <srcsha> SP <dstsha> SP <status> 120-- 121-- May be prefixed with a newline, which git log --pretty=format 122-- adds to the first line of the diff, even with -z. 123parserDiffRaw :: RawFilePath -> A.Parser DiffTreeItem 124parserDiffRaw f = DiffTreeItem 125 <$ A.option '\n' (A8.char '\n') 126 <* A8.char ':' 127 <*> octal 128 <* A8.char ' ' 129 <*> octal 130 <* A8.char ' ' 131 <*> (maybe (fail "bad srcsha") return . extractSha =<< nextword) 132 <* A8.char ' ' 133 <*> (maybe (fail "bad dstsha") return . extractSha =<< nextword) 134 <* A8.char ' ' 135 <*> A.takeByteString 136 <*> pure (asTopFilePath $ fromInternalGitPath $ Git.Filename.decode f) 137 where 138 nextword = A8.takeTill (== ' ') 139