/
hsgit.hs
220 lines (186 loc) · 7.42 KB
/
hsgit.hs
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
{-# LANGUAGE DeriveDataTypeable #-}
import Control.Exception
import Data.Time
import Data.Time.Clock.POSIX
import Foreign hiding (unsafePerformIO)
import System.IO.Unsafe (unsafePerformIO)
import Foreign.C.String
import Foreign.C.Types
import Bindings.Libgit2
import Data.Typeable
data GitError = GitError String String
deriving (Show,Typeable)
raiseGitError :: String -> CInt -> IO a
raiseGitError msg rc =
c'git_strerror rc >>= peekCString >>= throw . GitError msg
instance Exception GitError
newtype Repo = Repo (Ptr C'git_repository)
newtype Object = Object (Ptr C'git_object)
newtype ObjectType = ObjectType CInt deriving (Eq)
instance Show ObjectType where
show (ObjectType x) = unsafePerformIO $
c'git_object_type2string x >>= peekCString
data Commit = Commit { commitId :: OID
, commitMessage :: String
, commitMessageShort :: String
, commitTime :: UTCTime
, commitCommitter :: Signature
, commitAuthor :: Signature
, commitParents :: [Commit]
, commitTree :: Tree
} deriving Show
data Signature = Signature { signatureName :: String
, signatureEmail :: String
, signatureWhen :: UTCTime
} deriving Show
data Tree = Tree { treeId :: OID
, treeEntries :: [Entry]
} deriving Show
data Entry = Entry { entryId :: OID
, entryAttributes :: CUInt
, entryName :: FilePath
} deriving Show
commitObject, treeObject :: ObjectType
blobObject = ObjectType c'GIT_OBJ_BLOB
commitObject = ObjectType c'GIT_OBJ_COMMIT
treeObject = ObjectType c'GIT_OBJ_TREE
newtype OID = OID (Ptr C'git_oid)
instance Eq OID where
OID x == OID y = unsafePerformIO (c'git_oid_cmp x y) == 0
instance Show OID where
show (OID x) = unsafePerformIO $ do
str <- newCString $ replicate 41 '\0' -- the 41st is the null termination
c'git_oid_fmt str x -- not provided by git_oid_fmt
peekCString str
openRepo :: FilePath -> IO Repo
openRepo fp = alloca $ \ptr -> do
fp' <- newCString fp
rc <- c'git_repository_open ptr fp'
if rc < 0
then raiseGitError ("Cannot open repository " ++ fp) rc
else Repo `fmap` peek ptr
initRepo :: Bool -> FilePath -> IO Repo
initRepo bare fp = alloca $ \ptr -> do
fp' <- newCString fp
rc <- c'git_repository_init ptr fp' (if bare then 0 else 1)
if rc < 0
then raiseGitError ("Cannot initialize repository " ++ fp) rc
else Repo `fmap` peek ptr
freeRepo :: Repo -> IO ()
freeRepo (Repo repo) = c'git_repository_free repo
withRepo :: FilePath -> (Repo -> IO a) -> IO a
withRepo fp action = do
repo <- openRepo fp
val <- action repo
freeRepo repo
return val
mkOID :: String -> OID
mkOID raw = unsafePerformIO $ alloca $ \ptr -> do
idstr <- newCString raw
rc <- c'git_oid_mkstr ptr idstr
if rc < 0
then raiseGitError ("Cannot make OID with " ++ raw) rc
else return $ OID ptr
lookupObject :: Repo -> OID -> ObjectType -> IO Object
lookupObject (Repo repo) (OID oid) (ObjectType typ) = alloca $ \ptr -> do
rc <- c'git_object_lookup ptr repo oid typ
if rc < 0
then raiseGitError ("Cannot find " ++ show (ObjectType typ) ++
" with OID " ++ show (OID oid)) rc
else Object `fmap` peek ptr
lookupCommit :: Repo -> OID -> IO Commit
lookupCommit (Repo repo) (OID oid) = alloca $ \ptr -> do
rc <- c'git_commit_lookup ptr repo oid
if rc < 0
then raiseGitError ("Cannot find commit with OID " ++ show (OID oid)) rc
else peek ptr >>= toCommit
toCommit :: Ptr (C'git_commit) -> IO Commit
toCommit commit = do
id' <- c'git_commit_id commit
time <- (posixSecondsToUTCTime . realToFrac) `fmap`
c'git_commit_time commit
short <- c'git_commit_message_short commit >>= peekCString
msg <- c'git_commit_message commit >>= peekCString
committer <- c'git_commit_committer commit >>= peek >>= toSignature
author <- c'git_commit_author commit >>= peek >>= toSignature
numparents <- c'git_commit_parentcount commit
let getParent n = alloca $ \par -> do
rc <- c'git_commit_parent par commit n
if rc < 0
then raiseGitError ("Cannot get parent " ++ show n ++
" of commit " ++ show (OID id')) rc
else peek par >>= toCommit
parents <- mapM getParent (take (fromIntegral numparents) [0..])
alloca $ \treePtr -> do
rc' <- c'git_commit_tree treePtr commit
tree <- if rc' < 0
then raiseGitError ("Cannot get tree from commit " ++
show (OID id')) rc'
else peek treePtr >>= toTree
return Commit { commitId = OID id'
, commitMessage = msg
, commitMessageShort = short
, commitTime = time
, commitCommitter = committer
, commitAuthor = author
, commitParents = parents
, commitTree = tree
}
closeObject :: Object -> IO ()
closeObject (Object obj) = c'git_object_close obj
withObject :: Repo -> OID -> ObjectType -> (Object -> IO a) -> IO a
withObject repo oid typ f = do
obj <- lookupObject repo oid typ
res <- f obj
closeObject obj
return res
objectType :: Object -> ObjectType
objectType (Object x) = unsafePerformIO $ ObjectType `fmap` c'git_object_type x
toSignature :: C'git_signature -> IO Signature
toSignature (C'git_signature name email (C'git_time time _offset)) = do
name' <- peekCString name
email' <- peekCString email
let time' = posixSecondsToUTCTime $ realToFrac time
return Signature{ signatureName = name'
, signatureEmail = email'
, signatureWhen = time' }
{-
makeTree :: Repo -> IO Tree
makeTree (Repo repo) = alloca $ \ptr -> do
rc <- c'git_tree_new ptr repo
if rc < 0
then raiseGitError "Cannot make tree" rc
else peek ptr >>= toTree
-}
toTree :: Ptr (C'git_tree) -> IO Tree
toTree ptr = do
id' <- c'git_tree_id ptr
entrycount <- fromIntegral `fmap` c'git_tree_entrycount ptr
entries <- mapM (getEntry ptr) (take entrycount [0..])
return Tree { treeId = OID id'
, treeEntries = entries
}
getEntry :: Ptr (C'git_tree) -> CInt -> IO Entry
getEntry ptr idx = do
entry <- c'git_tree_entry_byindex ptr idx
id' <- c'git_tree_entry_id entry
attr <- c'git_tree_entry_attributes entry
name <- c'git_tree_entry_name entry >>= peekCString
return Entry{ entryId = OID id'
, entryAttributes = attr
, entryName = name
}
main = do
withRepo "test.git" $ \repo -> do
let oid1 = mkOID "10754a36c7e1e2b3cdf9d763a9e78ac35bcb56cc"
let oid2 = mkOID "49d40209afd138a37215915e51bed9e8079ba2fc"
let oid3 = mkOID "90754a36c7e1e2b3cdf9d763a9e78ac35bcb56cc"
print (oid1 == oid2)
print (oid2 == oid3)
print oid2
o1 <- lookupObject repo oid2 commitObject
o2 <- lookupCommit repo oid2
print o2
-- withObject repo oid2 commitObject $ \obj -> print (objectType obj)
-- initRepo True "foo.git"
return ()