File: DiffTree.hs

package info (click to toggle)
git-annex 10.20230126-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 69,344 kB
  • sloc: haskell: 74,654; javascript: 9,103; sh: 1,304; makefile: 203; perl: 136; ansic: 44
file content (138 lines) | stat: -rw-r--r-- 4,408 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
{- git diff-tree interface
 -
 - Copyright 2012-2020 Joey Hess <id@joeyh.name>
 -
 - Licensed under the GNU AGPL version 3 or higher.
 -}

module Git.DiffTree (
	DiffTreeItem(..),
	isDiffOf,
	diffTree,
	diffTreeRecursive,
	diffIndex,
	diffWorkTree,
	diffFiles,
	diffLog,
	commitDiff,
	parseDiffRaw,
) where

import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L
import qualified Data.Attoparsec.ByteString.Lazy as A
import qualified Data.Attoparsec.ByteString.Char8 as A8

import Common
import Git
import Git.Sha
import Git.Command
import Git.FilePath
import Git.DiffTreeItem
import qualified Git.Filename
import qualified Git.Ref
import Utility.Attoparsec

{- Checks if the DiffTreeItem modifies a file with a given name
 - or under a directory by that name. -}
isDiffOf :: DiffTreeItem -> TopFilePath -> Bool
isDiffOf diff f = 
	let f' = getTopFilePath f
	in if B.null f'
		then True -- top of repo contains all
		else f' `dirContains` getTopFilePath (file diff)

{- Diffs two tree Refs. -}
diffTree :: Ref -> Ref -> Repo -> IO ([DiffTreeItem], IO Bool)
diffTree src dst = getdiff (Param "diff-tree")
	[Param (fromRef src), Param (fromRef dst), Param "--"]

{- Diffs two tree Refs, recursing into sub-trees -}
diffTreeRecursive :: Ref -> Ref -> Repo -> IO ([DiffTreeItem], IO Bool)
diffTreeRecursive src dst = getdiff (Param "diff-tree")
	[Param "-r", Param (fromRef src), Param (fromRef dst), Param "--"]

{- Diffs between a tree and the index. Does nothing if there is not yet a
 - commit in the repository. -}
diffIndex :: Ref -> Repo -> IO ([DiffTreeItem], IO Bool)
diffIndex ref = diffIndex' ref [Param "--cached"]

{- Diffs between a tree and the working tree. Does nothing if there is not
 - yet a commit in the repository, or if the repository is bare. -}
diffWorkTree :: Ref -> Repo -> IO ([DiffTreeItem], IO Bool)
diffWorkTree ref repo =
	ifM (Git.Ref.headExists repo)
		( diffIndex' ref [] repo
		, return ([], return True)
		)

diffIndex' :: Ref -> [CommandParam] -> Repo -> IO ([DiffTreeItem], IO Bool)
diffIndex' ref params repo =
	ifM (Git.Ref.headExists repo)
		( getdiff (Param "diff-index")
			( params ++ [Param $ fromRef ref] ++ [Param "--"] )
			repo
		, return ([], return True)
		)

{- Diff between the index and work tree. -}
diffFiles :: [CommandParam] -> Repo -> IO ([DiffTreeItem], IO Bool)
diffFiles = getdiff (Param "diff-files")

{- Runs git log in --raw mode to get the changes that were made in
 - a particular commit to particular files. The output format
 - is adjusted to be the same as diff-tree --raw._-}
diffLog :: [CommandParam] -> Repo -> IO ([DiffTreeItem], IO Bool)
diffLog params = getdiff (Param "log")
	(Param "-n1" : Param "--no-abbrev" : Param "--pretty=format:" : params)

{- Uses git show to get the changes made by a commit.
 -
 - Does not support merge commits, and will fail on them. -}
commitDiff :: Sha -> Repo -> IO ([DiffTreeItem], IO Bool)
commitDiff ref = getdiff (Param "show")
	[ Param "--no-abbrev", Param "--pretty=", Param "--raw", Param (fromRef ref) ]

getdiff :: CommandParam -> [CommandParam] -> Repo -> IO ([DiffTreeItem], IO Bool)
getdiff command params repo = do
	(diff, cleanup) <- pipeNullSplit ps repo
	return (parseDiffRaw diff, cleanup)
  where
	ps = 
		command :
		Param "-z" :
		Param "--raw" :
		Param "--no-renames" :
		Param "-l0" :
		params

{- Parses --raw output used by diff-tree and git-log. -}
parseDiffRaw :: [L.ByteString] -> [DiffTreeItem]
parseDiffRaw l = go l
  where
	go [] = []
	go (info:f:rest) = case A.parse (parserDiffRaw (L.toStrict f)) info of
		A.Done _ r -> r : go rest
		A.Fail _ _ err -> error $ "diff-tree parse error: " ++ err
	go (s:[]) = error $ "diff-tree parse error near \"" ++ decodeBL s ++ "\""

-- :<srcmode> SP <dstmode> SP <srcsha> SP <dstsha> SP <status>
--
-- May be prefixed with a newline, which git log --pretty=format
-- adds to the first line of the diff, even with -z.
parserDiffRaw :: RawFilePath -> A.Parser DiffTreeItem
parserDiffRaw f = DiffTreeItem
	<$ A.option '\n' (A8.char '\n')
	<* A8.char ':'
	<*> octal
	<* A8.char ' '
	<*> octal
	<* A8.char ' '
	<*> (maybe (fail "bad srcsha") return . extractSha =<< nextword)
	<* A8.char ' '
	<*> (maybe (fail "bad dstsha") return . extractSha =<< nextword)
	<* A8.char ' '
	<*> A.takeByteString
	<*> pure (asTopFilePath $ fromInternalGitPath $ Git.Filename.decode f)
  where
	nextword = A8.takeTill (== ' ')