1 | local ffi = require("ffi") |
2 |
|
3 | ffi.include = function(header) |
4 | p = io.popen("echo '#include <"..header..">' | gcc -E -") |
5 | local c = {} |
6 | while true do |
7 | local line = p:read() |
8 | if line then |
9 | if not line:match("^#") then |
10 | table.insert(c, line) |
11 | end |
12 | else |
13 | break |
14 | end |
15 | end |
16 | p:close() |
17 | ffi.cdef(table.concat(c, "\n")) |
18 | end |
19 |
|
20 | ffi.include("git2.h") |
21 | local git2 = ffi.load("git2") |
22 |
|
23 | function git2_error(err, message) |
24 | if err < 0 then |
25 | local git2_error_message = ffi.string(git2.git_error_last().message) |
26 | error(string.format([[libgit2 Error - %s ("%s")]], message, git2_error_message)) |
27 | end |
28 | end |
29 |
|
30 | function exists(repo_dir) |
31 | local err = git2.git_repository_open(nil, repo_dir) |
32 | return err == 0 |
33 | end |
34 |
|
35 | function show(repo_dir, ref_name, file_path) |
36 | local err = 0 |
37 |
|
38 | -- Open git repo |
39 | local repo_obj = ffi.new("git_repository*[1]") |
40 | err = git2.git_repository_open(ffi.cast("git_repository**", repo_obj), repo_dir) |
41 | git2_error(err, "Failed opening git repository") |
42 | repo_obj = repo_obj[0] |
43 |
|
44 | -- Get tree root |
45 | local tree = ffi.new("git_object*[1]") |
46 | err = git2.git_revparse_single(ffi.cast("git_object**", tree), repo_obj, ref_name) |
47 | git2_error(err, "Failed to look up tree from rev name") |
48 | tree = tree[0] |
49 |
|
50 | -- Get tree entry object (blob) |
51 | local blob = ffi.new("git_object*[1]") |
52 | err = git2.git_object_lookup_bypath(ffi.cast("git_object**", blob), tree, file_path, git2.GIT_OBJECT_BLOB) |
53 | git2_error(err, "Failed to look up blob") |
54 | blob = ffi.cast("git_blob*", blob[0]) |
55 |
|
56 | -- Get blob content |
57 | local buf = ffi.new("git_buf") |
58 | err = git2.git_blob_filter(buf, blob, file_path, nil) |
59 | git2_error(err, "Failed to filter blob") |
60 | local raw = ffi.string(buf.ptr) |
61 |
|
62 | -- Free everything |
63 | git2.git_buf_free(buf) |
64 | git2.git_blob_free(blob) |
65 | git2.git_object_free(tree) |
66 | git2.git_repository_free(repo_obj) |
67 |
|
68 | return raw |
69 | end |
70 |
|
71 | function get_ref(repo_dir, ref_name) |
72 | local err = 0 |
73 | ref_name = ref_name or "HEAD" |
74 |
|
75 | -- Open git repo |
76 | local repo_obj = ffi.new("git_repository*[1]") |
77 | err = git2.git_repository_open(ffi.cast("git_repository**", repo_obj), repo_dir) |
78 | git2_error(err, "Failed opening git repository") |
79 | repo_obj = repo_obj[0] |
80 |
|
81 | -- Get object/reference |
82 | local object = ffi.new("git_object*[1]") |
83 | local reference = ffi.new("git_reference*[1]") |
84 | err = git2.git_revparse_ext(ffi.cast("git_object**", object), ffi.cast("git_reference**", reference), repo_obj, ref_name) |
85 | git2_error(err, "Failed to find reference") |
86 | object = object[0] |
87 | reference = reference[0] |
88 |
|
89 | -- Get full name if intermediate reference exists |
90 | local name = ref_name |
91 | if reference ~= nil then |
92 | local ref_type = git2.git_reference_type(reference) |
93 | if ref_type == git2.GIT_REFERENCE_SYMBOLIC then |
94 | name = ffi.string(git2.git_reference_symbolic_target(reference)) |
95 | else |
96 | name = ffi.string(git2.git_reference_name(reference)) |
97 | end |
98 | end |
99 |
|
100 | -- Get OID |
101 | local oid = git2.git_object_id(object) |
102 |
|
103 | -- Format oid as SHA1 hash |
104 | local hash = ffi.new("char[41]") -- SHA1 length (40 chars) + \0 |
105 | err = git2.git_oid_fmt(hash, oid) |
106 | git2_error(err, "Failed formatting OID") |
107 | hash = ffi.string(hash) |
108 |
|
109 | -- Free all |
110 | git2.git_object_free(object) |
111 | if reference ~= nil then |
112 | git2.git_reference_free(reference) |
113 | end |
114 | git2.git_repository_free(repo_obj) |
115 |
|
116 | -- Format |
117 | local ref = {} |
118 | ref.full = name |
119 | if name:match("^refs/heads/") then |
120 | ref.name = string.sub(name, 12, string.len(name)) |
121 | elseif name:match("^refs/tags/") then |
122 | ref.name = string.sub(name, 11, string.len(name)) |
123 | else |
124 | ref.name = ref_name -- just pass input as default output |
125 | end |
126 | ref.hash = hash |
127 | ref.shorthash = string.sub(hash, 1, 7) |
128 |
|
129 | return ref |
130 | end |
131 |
|
132 | function list_refs(repo_dir) |
133 | -- Open git repo |
134 | local repo_obj = ffi.new("git_repository*[1]") |
135 | local err = git2.git_repository_open(ffi.cast("git_repository**", repo_obj), repo_dir) |
136 | repo_obj = repo_obj[0] |
137 |
|
138 | -- List refs |
139 | local refs = ffi.new("git_strarray") |
140 | local err = git2.git_reference_list(refs, repo_obj) |
141 |
|
142 | local ret = {} |
143 | ret.heads = {} |
144 | ret.tags = {} |
145 |
|
146 | for i = 0, tonumber(refs.count)-1 do |
147 |
|
148 | local name = ffi.string(refs.strings[i]) |
149 |
|
150 | local dest |
151 | local prefix_len |
152 | if name:match("^refs/heads/") then |
153 | dest = ret.heads |
154 | prefix_len = 12 |
155 | elseif name:match("^refs/tags/") then |
156 | dest = ret.tags |
157 | prefix_len = 11 |
158 | end |
159 |
|
160 | if dest then |
161 | local oid = ffi.new("git_oid") |
162 | local err = git2.git_reference_name_to_id(oid, repo_obj, refs.strings[i]) |
163 |
|
164 | -- Format oid as SHA1 hash |
165 | local hash = ffi.new("char[41]") -- SHA1 length (40 chars) + \0 |
166 | local err = git2.git_oid_fmt(hash, oid) |
167 | hash = ffi.string(hash) |
168 |
|
169 | local ref = {} |
170 | ref.name = string.sub(name, prefix_len, string.len(name)) |
171 | ref.full = name |
172 | ref.hash = hash |
173 | ref.shorthash = string.sub(hash, 1, 7) |
174 | table.insert(dest, ref) |
175 | end |
176 |
|
177 | end |
178 |
|
179 | if refs ~= nil then |
180 | git2.git_strarray_free(refs) |
181 | end |
182 | if repo_obj ~= nil then |
183 | git2.git_repository_free(repo_obj) |
184 | end |
185 |
|
186 | return ret |
187 | end |
188 |
|
189 | local t = { |
190 | "/home/josh/repos/ncurses-minesweeper", |
191 | } |
192 |
|
193 | git2.git_libgit2_init() |
194 |
|
195 | function serializeTable(val, name, skipnewlines, depth) |
196 | skipnewlines = skipnewlines or false |
197 | depth = depth or 0 |
198 |
|
199 | local tmp = string.rep(" ", depth) |
200 |
|
201 | if name then tmp = tmp .. name .. " = " end |
202 |
|
203 | if type(val) == "table" then |
204 | tmp = tmp .. "{" .. (not skipnewlines and "\n" or "") |
205 |
|
206 | for k, v in pairs(val) do |
207 | tmp = tmp .. serializeTable(v, k, skipnewlines, depth + 1) .. "," .. (not skipnewlines and "\n" or "") |
208 | end |
209 |
|
210 | tmp = tmp .. string.rep(" ", depth) .. "}" |
211 | elseif type(val) == "number" then |
212 | tmp = tmp .. tostring(val) |
213 | elseif type(val) == "string" then |
214 | tmp = tmp .. string.format("%q", val) |
215 | elseif type(val) == "boolean" then |
216 | tmp = tmp .. (val and "true" or "false") |
217 | else |
218 | tmp = tmp .. "\"[inserializeable datatype:" .. type(val) .. "]\"" |
219 | end |
220 |
|
221 | return tmp |
222 | end |
223 |
|
224 |
|
225 | for _, r in pairs(t) do |
226 | if exists(r) then |
227 | print(r) |
228 | local refs = list_refs(r) |
229 | local head = get_ref(r) |
230 | print(serializeTable(head)) |
231 | print() |
232 | end |
233 | end |
234 |
|
235 | git2.git_libgit2_shutdown() |
236 |
|
237 | collectgarbage() |
238 | collectgarbage() |
239 | collectgarbage() |
240 | collectgarbage() |
241 | collectgarbage() |
242 | collectgarbage() |
243 | collectgarbage() |
244 | collectgarbage() |
245 | collectgarbage() |
246 |
|