summaryrefslogtreecommitdiff
path: root/Git/LsTree.hs
blob: 9129d18fc49a2156d2f8a2765de3f3f5b76a4f63 (plain)
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
{- git ls-tree interface
 -
 - Copyright 2011-2021 Joey Hess <id@joeyh.name>
 -
 - Licensed under the GNU AGPL version 3 or higher.
 -}

module Git.LsTree (
	TreeItem(..),
	LsTreeRecursive(..),
	LsTreeLong(..),
	lsTree,
	lsTree',
	lsTreeStrict,
	lsTreeStrict',
	lsTreeParams,
	lsTreeFiles,
	parseLsTree,
	formatLsTree,
) where

import Common
import Git
import Git.Command
import Git.FilePath
import qualified Git.Quote
import Utility.Attoparsec

import Numeric
import Data.Either
import Data.Char
import System.Posix.Types
import qualified Data.ByteString as S
import qualified Data.ByteString.Lazy as L
import qualified Data.Attoparsec.ByteString as AS
import qualified Data.Attoparsec.ByteString.Lazy as A
import qualified Data.Attoparsec.ByteString.Char8 as A8

data TreeItem = TreeItem
	{ mode :: FileMode
	, typeobj :: S.ByteString
	, sha :: Ref
	, size :: Maybe FileSize
	, file :: TopFilePath
	-- ^ only available when long is used
	} deriving (Show)

data LsTreeRecursive = LsTreeRecursive | LsTreeNonRecursive

{- Enabling --long also gets the size of tree items.
 - This slows down ls-tree some, since it has to look up the size of each
 - blob.
 -}
data LsTreeLong = LsTreeLong Bool

{- Lists the contents of a tree, with lazy output. -}
lsTree :: LsTreeRecursive -> LsTreeLong -> Ref -> Repo -> IO ([TreeItem], IO Bool)
lsTree = lsTree' []

lsTree' :: [CommandParam] -> LsTreeRecursive -> LsTreeLong -> Ref -> Repo -> IO ([TreeItem], IO Bool)
lsTree' ps recursive long t repo = do
	(l, cleanup) <- pipeNullSplit (lsTreeParams recursive long t ps) repo
	return (rights (map (parseLsTree long) l), cleanup)

lsTreeStrict :: LsTreeRecursive -> LsTreeLong -> Ref -> Repo -> IO [TreeItem]
lsTreeStrict = lsTreeStrict' []

lsTreeStrict' :: [CommandParam] -> LsTreeRecursive -> LsTreeLong -> Ref -> Repo -> IO [TreeItem]
lsTreeStrict' ps recursive long t repo = rights . map (parseLsTreeStrict long)
	<$> pipeNullSplitStrict (lsTreeParams recursive long t ps) repo

lsTreeParams :: LsTreeRecursive -> LsTreeLong -> Ref -> [CommandParam] -> [CommandParam]
lsTreeParams recursive long r ps =
	[ Param "ls-tree"
	, Param "--full-tree"
	, Param "-z"
	] ++ recursiveparams ++ longparams ++ ps ++
	[ Param "--"
	, File $ fromRef r
	]
  where
	recursiveparams = case recursive of
		LsTreeRecursive -> [ Param "-r" ]
		LsTreeNonRecursive -> []
	longparams = case long of
		LsTreeLong True -> [ Param "--long" ]
		LsTreeLong False -> []

{- Lists specified files in a tree. -}
lsTreeFiles :: LsTreeLong -> Ref -> [FilePath] -> Repo -> IO [TreeItem]
lsTreeFiles long t fs repo = rights . map (parseLsTree long . L.fromStrict)
	<$> pipeNullSplitStrict ps repo
  where
	ps =
		[ Param "ls-tree"
		, Param "--full-tree"
		, Param "-z"
		, Param "--"
		, File $ fromRef t
		] ++ map File fs

parseLsTree :: LsTreeLong -> L.ByteString -> Either String TreeItem
parseLsTree long b = case A.parse (parserLsTree long) b of
	A.Done _ r  -> Right r
	A.Fail _ _ err -> Left err

parseLsTreeStrict :: LsTreeLong -> S.ByteString -> Either String TreeItem
parseLsTreeStrict long b = go (AS.parse (parserLsTree long) b)
  where
	go (AS.Done _ r) = Right r
	go (AS.Fail _ _ err) = Left err
	go (AS.Partial c) = go (c mempty)

{- Parses a line of ls-tree output, in format:
 -   mode SP type SP sha TAB file
 - Or long format:
 -   mode SP type SP sha SPACES size TAB file
 -
 - The TAB can also be a space. Git does not use that, but an earlier
 - version of formatLsTree did, and this keeps parsing what it output
 - working.
 -}
parserLsTree :: LsTreeLong -> A.Parser TreeItem
parserLsTree long = case long of
	LsTreeLong False -> 
		startparser <*> pure Nothing <* filesep <*> fileparser
	LsTreeLong True ->
		startparser <* sizesep <*> sizeparser <* filesep <*> fileparser
  where
	startparser = TreeItem
		-- mode
		<$> octal
		<* A8.char ' '
		-- type
		<*> A8.takeTill (== ' ')
		<* A8.char ' '
		-- sha
		<*> (Ref <$> A8.takeTill A8.isSpace)

	fileparser = asTopFilePath . Git.Quote.unquote <$> A.takeByteString

	sizeparser = fmap Just A8.decimal

	filesep = A8.space

	sizesep = A.many1 A8.space

{- Inverse of parseLsTree. Note that the long output format is not
 - generated, so any size information is not included. -}
formatLsTree :: TreeItem -> S.ByteString
formatLsTree ti = S.intercalate (S.singleton (fromIntegral (ord ' ')))
	[ encodeBS (showOct (mode ti) "")
	, typeobj ti
	, fromRef' (sha ti)
	] <> (S.cons (fromIntegral (ord '\t')) (getTopFilePath (file ti)))