1 | -- resty-gitweb@git/git_commands.lua |
2 | -- git commands and parser functions |
3 |
|
4 | -- Copyright (c) 2020 Joshua 'joshuas3' Stockin |
5 | -- <https://git.joshstock.in/resty-gitweb> |
6 | -- This software is licensed under the MIT License. |
7 |
|
8 | local ffi = require("ffi") |
9 | local utils = require("utils/utils") |
10 | local git2_error = require("git/git2_error") |
11 |
|
12 | local _M = {} |
13 |
|
14 | local git = function(repo_dir, command) |
15 | local formatted_command = string.format( |
16 | "git --git-dir=%s %s", |
17 | repo_dir, command |
18 | ) |
19 | return utils.process(formatted_command) |
20 | end |
21 |
|
22 | _M.get_head = function(repo_dir, ref_name) |
23 | local err = 0 |
24 | ref_name = ref_name or "HEAD" |
25 |
|
26 | -- Open git repo |
27 | local repo_obj = ffi.new("git_repository*[1]") |
28 | err = git2.git_repository_open(ffi.cast("git_repository**", repo_obj), repo_dir) |
29 | git2_error(err, "Failed opening git repository") |
30 | repo_obj = repo_obj[0] |
31 |
|
32 | -- Get object/reference |
33 | local object = ffi.new("git_object*[1]") |
34 | local reference = ffi.new("git_reference*[1]") |
35 | err = git2.git_revparse_ext(ffi.cast("git_object**", object), ffi.cast("git_reference**", reference), repo_obj, ref_name) |
36 | git2_error(err, "Failed to find reference") |
37 | object = object[0] |
38 | reference = reference[0] |
39 |
|
40 | -- Get full name if intermediate reference exists |
41 | local name = ref_name |
42 | if reference ~= nil then |
43 | local ref_type = git2.git_reference_type(reference) |
44 | if ref_type == git2.GIT_REFERENCE_SYMBOLIC then |
45 | name = ffi.string(git2.git_reference_symbolic_target(reference)) |
46 | else |
47 | name = ffi.string(git2.git_reference_name(reference)) |
48 | end |
49 | end |
50 |
|
51 | -- Get OID |
52 | local oid = git2.git_object_id(object) |
53 |
|
54 | -- Format oid as SHA1 hash |
55 | local hash = ffi.new("char[41]") -- SHA1 length (40 chars) + \0 |
56 | err = git2.git_oid_fmt(hash, oid) |
57 | git2_error(err, "Failed formatting OID") |
58 | hash = ffi.string(hash) |
59 |
|
60 | -- Free all |
61 | git2.git_object_free(object) |
62 | if reference ~= nil then |
63 | git2.git_reference_free(reference) |
64 | end |
65 | git2.git_repository_free(repo_obj) |
66 |
|
67 | -- Format |
68 | local ref = {} |
69 | ref.full = name |
70 | if name:match("^refs/heads/") then |
71 | ref.name = string.sub(name, 12, string.len(name)) |
72 | elseif name:match("^refs/tags/") then |
73 | ref.name = string.sub(name, 11, string.len(name)) |
74 | else |
75 | ref.name = ref_name -- just pass input as default output |
76 | end |
77 | ref.hash = hash |
78 | ref.shorthash = string.sub(hash, 1, 7) |
79 |
|
80 | return ref |
81 | end |
82 |
|
83 | _M.count = function(repo_dir, hash) |
84 | hash = hash or "@" |
85 | local output = git(repo_dir, "rev-list --count "..hash.." --") |
86 | return tonumber(string.trim(output)) |
87 | end |
88 |
|
89 | _M.log = function(repo_dir, hash, file, number, skip, gpg) |
90 | hash = hash or "@" |
91 | file = file or "" |
92 | number = tostring(number or 25) |
93 | skip = tostring(skip or 0) |
94 | gpg = gpg or false |
95 | local output |
96 | if gpg then |
97 | output = git(repo_dir, "log --pretty=tformat:'%x00%x01%H%x00%cI%x00%cn%x00%ce%x00%s%x00%b%x00%G?%x00%GK%x00%GG%x00' --numstat -n "..number.." --skip "..skip.." "..hash.." -- "..file) |
98 | else |
99 | output = git(repo_dir, "log --pretty=tformat:'%x00%x01%H%x00%cI%x00%cn%x00%ce%x00%s%x00%b%x00' --numstat -n "..number.." --skip "..skip.." "..hash.." -- "..file) |
100 | end |
101 | local commits = {} |
102 | local a = string.split(output,"\0\1") |
103 | local f = false |
104 | for i,v in pairs(a) do |
105 | if f == true then |
106 | local commit = {} |
107 | local c = string.split(v, "\0") |
108 | commit.hash = c[1] |
109 | commit.shorthash = string.sub(c[1], 1,7) |
110 | commit.timestamp = c[2] |
111 | commit.author = c[3] |
112 | commit.email = c[4] |
113 | commit.subject = c[5] |
114 | commit.body = string.trim(c[6]) |
115 | local diffs |
116 | if gpg then |
117 | commit.gpggood = c[7] |
118 | commit.gpgkey = c[8] |
119 | commit.gpgfull = string.trim(c[9]) |
120 | diffs = string.trim(c[10]) |
121 | else |
122 | diffs = string.trim(c[7]) |
123 | end |
124 | commit.diff = {} |
125 | local b = string.split(diffs, "\n") |
126 | commit.diff.plus = 0 |
127 | commit.diff.minus = 0 |
128 | commit.diff.num = 0 |
129 | commit.diff.files = {} |
130 | for i,v in pairs(b) do |
131 | local d = string.split(v,"\t") |
132 | local x = {} |
133 | x.plus = tonumber(d[1]) or 0 |
134 | commit.diff.plus = commit.diff.plus + x.plus |
135 | x.minus = tonumber(d[2]) or 0 |
136 | commit.diff.minus = commit.diff.minus + x.minus |
137 | commit.diff.files[d[3]] = x |
138 | commit.diff.num = commit.diff.num + 1 |
139 | end |
140 | table.insert(commits, commit) |
141 | else |
142 | f = true |
143 | end |
144 | end |
145 | return commits |
146 | end |
147 |
|
148 | _M.commit = function(repo_dir, hash) |
149 | local commit = _M.log(repo_dir, hash, "", 1, 0, true)[1] |
150 | commit.count = _M.count(repo_dir, hash) |
151 | return commit |
152 | end |
153 |
|
154 | _M.list_refs = function(repo_dir) |
155 | -- Open git repo |
156 | local repo_obj = ffi.new("git_repository*[1]") |
157 | local err = git2.git_repository_open(ffi.cast("git_repository**", repo_obj), repo_dir) |
158 | repo_obj = repo_obj[0] |
159 |
|
160 | -- List refs |
161 | local refs = ffi.new("git_strarray") |
162 | local err = git2.git_reference_list(ffi.cast("git_strarray*", refs), repo_obj) |
163 |
|
164 | local ret = {} |
165 | ret.heads = {} |
166 | ret.tags = {} |
167 |
|
168 | for i = 0, tonumber(refs.count)-1 do |
169 |
|
170 | local name = ffi.string(refs.strings[i]) |
171 |
|
172 | local dest |
173 | local prefix_len |
174 | if name:match("^refs/heads/") then |
175 | dest = ret.heads |
176 | prefix_len = 12 |
177 | elseif name:match("^refs/tags/") then |
178 | dest = ret.tags |
179 | prefix_len = 11 |
180 | end |
181 |
|
182 | if dest then |
183 | local oid = ffi.new("git_oid") |
184 | local err = git2.git_reference_name_to_id(ffi.cast("git_oid*", oid), repo_obj, refs.strings[i]) |
185 |
|
186 | -- Format oid as SHA1 hash |
187 | local hash = ffi.new("char[41]") -- SHA1 length (40 chars) + \0 |
188 | local err = git2.git_oid_fmt(hash, ffi.cast("git_oid*", oid)) |
189 | hash = ffi.string(hash) |
190 |
|
191 | local ref = {} |
192 | ref.name = string.sub(name, prefix_len, string.len(name)) |
193 | ref.full = name |
194 | ref.hash = hash |
195 | ref.shorthash = string.sub(hash, 1, 7) |
196 | table.insert(dest, ref) |
197 | end |
198 |
|
199 | end |
200 |
|
201 | if refs then |
202 | git2.git_strarray_free(ffi.cast("git_strarray*", refs)) |
203 | end |
204 | if repo_obj then |
205 | git2.git_repository_free(ffi.cast("git_repository*", repo_obj)) |
206 | end |
207 |
|
208 | return ret |
209 | end |
210 |
|
211 | local list_dirs = function(repo_dir, hash, path) |
212 | hash = hash or "@" |
213 | path = path or "" |
214 | local output = git(repo_dir, "ls-tree -d --name-only "..hash.." -- "..path) |
215 | local dirs = string.split(output, "\n") |
216 | table.remove(dirs, #dirs) -- remove trailing \n |
217 | return dirs |
218 | end |
219 |
|
220 | local list_all = function(repo_dir, hash, path) |
221 | hash = hash or "@" |
222 | path = path or "" |
223 | local output = git(repo_dir, "ls-tree --name-only "..hash.." -- "..path) |
224 | local all = string.split(output, "\n") |
225 | table.remove(all, #all) -- remove trailing \n |
226 | return all |
227 | end |
228 |
|
229 | _M.list_tree = function(repo_dir, hash, path) |
230 | hash = hash or "@" |
231 | path = path or "" |
232 | local files = list_all(repo_dir, hash, path) |
233 | local dirs = list_dirs(repo_dir, hash, path) |
234 | local ret = {} |
235 | ret.dirs = {} |
236 | ret.files = {} |
237 | for i,v in pairs(files) do -- iterate over all objects, separate directories from files |
238 | local not_dir = true |
239 | for _,d in pairs(dirs) do -- check if object is directory |
240 | if v == d then |
241 | not_dir = false |
242 | break |
243 | end |
244 | end |
245 | if not_dir then |
246 | table.insert(ret.files, v) |
247 | else |
248 | table.insert(ret.dirs, v) |
249 | end |
250 | end |
251 | return ret |
252 | end |
253 |
|
254 | _M.repo = require("git/repo") |
255 | _M.read_blob = require("git/read_blob") |
256 | return _M |
257 |
|