summaryrefslogtreecommitdiff
path: root/lib/gall/ll/git2.c
blob: ce7224405517b95cc4f745d5321021ebdcd32ab9 (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
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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
/* git2.c
 *
 * Simple binding to the parts of libgit2 in use by Gall.
 *
 * Copyright 2014 Daniel Silverstone <dsilvers@digital-scurf.org>
 */

/**
 * Low-Level C binding for Gall's use of libgit2.
 *
 * @module gall.ll.git2
 */

#include <lua.h>
#include <lauxlib.h>
#include "git2.h"

typedef struct {
	git_repository *repo;
	git_odb *odb;
} repo_et_al_t;

static inline git_repository *to_repo(lua_State *L, int idx)
{
	repo_et_al_t *real = lua_touserdata(L, idx);
	if (real == NULL)
		return NULL;
	return real->repo;
}

static inline git_odb *to_odb(lua_State *L, int idx)
{
	repo_et_al_t *real = lua_touserdata(L, idx);
	if (real == NULL)
		return NULL;
	return real->odb;
}

/* Push the most recent error from libgit2 */
static int push_git2_error(lua_State *L, int retcode)
{
	const git_error *err = giterr_last();
	lua_pushnil(L);
	if (err == NULL) {
		lua_pushfstring(L, "Unknown error: %d", retcode);
	} else {
		lua_pushstring(L, err->message);
	}
	return 2;
}



static int L_gc_repo(lua_State *L)
{
	repo_et_al_t *real = lua_touserdata(L, 1);
	if (real->odb != NULL)
		git_odb_free(real->odb);
	if (real->repo != NULL)
		git_repository_free(real->repo);
	real->repo = NULL;
	real->odb = NULL;
	return 0;
}

/**
 * Open a repository from disk.
 *
 * This function opens a repository on disk using libgit2.  This low level
 * repository type can be used in routines such as @{lookup_symbolic_ref} or
 * @{lookup_sha_from_ref}.
 *
 * @function open_repo
 * @tparam string repopath The path to the repository
 * @treturn llrepo The repository (or nil on error)
 * @treturn string An error message (if repo is nil)
 */

/* Trivial Repository binding, GC will free the repo */
static int L_open_repo(lua_State *L)
{
	const char *repopath = luaL_checkstring(L, 1);
	int ret;
	repo_et_al_t *real = lua_newuserdata(L, sizeof(*real));
	real->repo = NULL; real->odb = NULL;
	
	ret = git_repository_open(&real->repo, repopath);
	if (ret != 0) {
		return push_git2_error(L, ret);
	}
	
	ret = git_repository_odb(&real->odb, real->repo);
	if (ret != 0) {
		push_git2_error(L, ret);
		git_repository_free(real->repo);
		return 2;
	}
	
	lua_pushvalue(L, lua_upvalueindex(1));
	lua_setmetatable(L, -2);
	return 1;
}

/**
 * Dereference a symbolic ref.
 *
 * @function lookup_symbolic_ref
 * @tparam llrepo repository The repository to look up the ref in.
 * @tparam string ref The symbolic ref name to dereference.
 * @treturn string The symbolic referent (or nil on error)
 * @treturn string The error message if the referent is nil
 */

static int L_lookup_symbolic_ref(lua_State *L)
{
	git_repository *repo = to_repo(L, 1);
	const char *refname = luaL_checkstring(L, 2);
	git_reference *ref = NULL;
	int ret;
	if ((ret = git_reference_lookup(&ref, repo, refname)) != 0) {
		return push_git2_error(L, ret);
	}
	if (git_reference_type(ref) != GIT_REF_SYMBOLIC) {
		git_reference_free(ref);
		lua_pushnil(L);
		lua_pushfstring(L, "%s is not symbolic", refname);
		return 2;
	}
	lua_pushstring(L, git_reference_symbolic_target(ref));
	git_reference_free(ref);
	return 1;
}

static int format_oid(lua_State *L, const git_oid *oid)
{
	char oidstr[40];
	git_oid_fmt(oidstr, oid);
	lua_pushlstring(L, oidstr, 40);
	return 1;
}

/**
 * Lookup the SHA1 pointed at by a reference.
 *
 * @function lookup_sha_from_ref
 * @tparam llrepo repository The repository to look up the reference in.
 * @tparam string ref The reference to look up.
 * @treturn string The OID (SHA1) of the referent (or nil on error).
 * @treturn string The error message if the OID is nil.
 */

static int L_lookup_sha_from_ref(lua_State *L)
{
	git_repository *repo = to_repo(L, 1);
	const char *refname = luaL_checkstring(L, 2);
	git_oid ref;
	int ret;
	if ((ret = git_reference_name_to_id(&ref, repo, refname)) != 0) {
		return push_git2_error(L, ret);
	}
	return format_oid(L, &ref);
}

static int parse_oid(lua_State *L, int spos, git_oid *oid)
{
	const char *oid_s = luaL_checkstring(L, spos);
	int ret = git_oid_fromstr(oid, oid_s);
	if (ret != 0)
		return push_git2_error(L, ret);
	return 0;
}

/**
 * Calculate the merge-base of a pair of OIDs.
 *
 * @function merge_base
 * @tparam llrepo repository The repository to calculate the merge-base within.
 * @tparam string left The left-side of the merge as an OID.
 * @tparam string right The right-side of the merge as an OID.
 * @treturn string The OID of the merge-base if available, or nil on error.
 * @treturn string The error message if the merge-base is nil.
 */

static int L_merge_base(lua_State *L)
{
	git_repository *repo = to_repo(L, 1);
	git_oid left, right, out;
	int ret;	
	if (parse_oid(L, 2, &left) != 0)
		return 2;
	if (parse_oid(L, 3, &right) != 0)
		return 2;
	ret = git_merge_base(&out, repo, &left, &right);
	if (ret == 0)
		return format_oid(L, &out);
	if (ret == GIT_ENOTFOUND) {
		lua_pushnil(L);
		lua_pushliteral(L, "ENOTFOUND");
		return 2;
	}
	return push_git2_error(L, ret);
}

/**
 * Set a symbolic reference's referent.
 *
 * @function set_symbolic_ref
 * @tparam llrepo repository The repository to set the reference within.
 * @tparam string reference The reference to set.
 * @tparam string referent The reference name to act as referent for reference.
 * @treturn boolean On success, true, otherwise nil.
 * @treturn string On failure, the error message.
 */

static int L_set_symbolic_ref(lua_State *L)
{
	git_repository *repo = to_repo(L, 1);
	git_reference *ref;
	int ret;
	if ((ret = git_reference_symbolic_create(&ref, repo,
						 luaL_checkstring(L, 2),
						 luaL_checkstring(L, 3),
						 1, NULL
#if LIBGIT2_VER_MINOR < 23
						 , NULL
#endif
		     )) != 0) {
		return push_git2_error(L, ret);
	}
	git_reference_free(ref);
	lua_pushboolean(L, 1);
	return 1;
}

static int L_gc_odb_object(lua_State *L)
{
	git_odb_object **obj = lua_touserdata(L, 1);
	if (*obj != NULL)
		git_odb_object_free(*obj);
	*obj = NULL;
	return 0;
}

/**
 * Retrieve a generic object from git.
 *
 * @function get_object
 * @tparam llrepo repository The repository to retrieve the object from.
 * @tparam string oid The object to return (as a SHA1)
 * @treturn gitobject The git object whose OID was provided (or nil)
 * @treturn string The error message if the returned git object was nil
 */

static int L_get_object(lua_State *L)
{
	git_odb *odb = to_odb(L, 1);
	git_odb_object **obj = lua_newuserdata(L, sizeof(*obj));
	git_oid oid;
	int ret;
	if (parse_oid(L, 2, &oid) != 0)
		return 2;
	if ((ret = git_odb_read(obj, odb, &oid)) != 0)
		return push_git2_error(L, ret);
	lua_pushvalue(L, lua_upvalueindex(1));
	lua_setmetatable(L, -2);
	return 1;
}

/**
 * Retrieve the size of a git object.
 *
 * @function get_object_size
 * @tparam gitobject obj The object whose size you wish to query
 * @treturn number The size of the object in bytes
 */

static int L_get_object_size(lua_State *L)
{
	git_odb_object **obj = lua_touserdata(L, 1);
	lua_pushnumber(L, git_odb_object_size(*obj));
	return 1;
}

static void push_object_type_str(lua_State *L, git_otype ty)
{
	switch(ty) {
	case GIT_OBJ_ANY:
		lua_pushliteral(L, "any");
		break;
	case GIT_OBJ_BAD:
		lua_pushliteral(L, "bad");
		break;
	case GIT_OBJ__EXT1:
		lua_pushliteral(L, "reserved");
		break;
	case GIT_OBJ_COMMIT:
		lua_pushliteral(L, "commit");
		break;
	case GIT_OBJ_TREE:
		lua_pushliteral(L, "tree");
		break;
	case GIT_OBJ_BLOB:
		lua_pushliteral(L, "blob");
		break;
	case GIT_OBJ_TAG:
		lua_pushliteral(L, "tag");
		break;
	case GIT_OBJ__EXT2:
		lua_pushliteral(L, "reserved");
		break;
	case GIT_OBJ_OFS_DELTA:
		lua_pushliteral(L, "delta");
		break;
	case GIT_OBJ_REF_DELTA:
		lua_pushliteral(L, "refdelta");
		break;
	default:
		lua_pushliteral(L, "unknown");
		break;
	}
}


/**
 * Retrieve a git object's type
 *
 * @function get_object_type
 * @tparam gitobject obj The object whose type you wish to retrieve.
 * @treturn string The type of the object provided.
 */
static int L_get_object_type(lua_State *L)
{
	git_odb_object **obj = lua_touserdata(L, 1);
	push_object_type_str(L, git_odb_object_type(*obj));
	return 1;
}

/**
 * Retrieve the raw content of an object
 *
 * @function get_object_raw
 * @tparam gitobject obj The object whose content you wish to retrieve.
 * @treturn string The raw content of the object as a string.
 */

static int L_get_object_raw(lua_State *L)
{
	git_odb_object **obj = lua_touserdata(L, 1);
	lua_pushlstring(L, git_odb_object_data(*obj),
			git_odb_object_size(*obj));
	return 1;
}

/**
 * Retrieve a tree's content as a table
 *
 * The returned table is a numerically indexed table of entries.  Each entry
 * is a table with `name` (string) `sha` (string) and `perms` (number) entries.
 *
 * @function get_tree_table
 * @tparam llrepo repository The repository to query for the tree's content.
 * @tparam string tree The OID of the tree object to retrieve
 * @treturn table The tree as a table, or nil on error
 * @treturn string The error message if the returned tree table is nil
 */

static int L_get_tree_table(lua_State *L)
{
	git_repository *repo = to_repo(L, 1);
	git_oid oid;
	git_tree *tree;
	size_t ent;
	int ret;
	if (parse_oid(L, 2, &oid) != 0)
		return 2;
	if ((ret = git_tree_lookup(&tree, repo, &oid)) != 0)
		return push_git2_error(L, ret);
	lua_newtable(L);
	for (ent = 0; ent < git_tree_entrycount(tree); ++ent) {
		const git_tree_entry *tree_ent =
			git_tree_entry_byindex(tree, ent);
		lua_pushnumber(L, ent+1);
		lua_newtable(L);
		lua_pushliteral(L, "name");
		lua_pushstring(L, git_tree_entry_name(tree_ent));
		lua_settable(L, -3);
		lua_pushliteral(L, "sha");
		format_oid(L, git_tree_entry_id(tree_ent));
		lua_settable(L, -3);
		lua_pushliteral(L, "perms");
		lua_pushnumber(L, git_tree_entry_filemode_raw(tree_ent));
		lua_settable(L, -3);
		lua_settable(L, -3);
	}
	
	git_tree_free(tree);
	return 1;
}

/**
 * The version of libgit2 which this instance of gall was built against.
 *
 * When Gall is compiled, the version of libgit2 is baked into the C binding
 * to be checked against when run.
 *
 * @field LIBGIT2_VERSION
 */

int luaopen_gall_ll_git2(lua_State *L)
{
	lua_newtable(L);
	lua_pushliteral(L, "LIBGIT2_VERSION");
	lua_pushliteral(L, LIBGIT2_VERSION);
	lua_settable(L, -3);
	
	lua_pushliteral(L, "open_repo");
	lua_newtable(L);
	lua_pushliteral(L, "__gc");
	lua_pushcclosure(L, L_gc_repo, 0);
	lua_settable(L, -3);
	lua_pushcclosure(L, L_open_repo, 1);
	lua_settable(L, -3);

	lua_pushliteral(L, "get_object");
	lua_newtable(L);
	lua_pushliteral(L, "__gc");
	lua_pushcclosure(L, L_gc_odb_object, 0);
	lua_settable(L, -3);
	lua_pushcclosure(L, L_get_object, 1);
	lua_settable(L, -3);

#define BASIC_FUNC(FN)				\
	do {					\
		lua_pushliteral(L, #FN);	\
		lua_pushcclosure(L, L_##FN, 0);	\
		lua_settable(L, -3);		\
	} while (0)
	BASIC_FUNC(lookup_symbolic_ref);
	BASIC_FUNC(lookup_sha_from_ref);
	BASIC_FUNC(set_symbolic_ref);
	BASIC_FUNC(merge_base);
	BASIC_FUNC(get_object_size);
	BASIC_FUNC(get_object_type);
	BASIC_FUNC(get_object_raw);
	BASIC_FUNC(get_tree_table);

#if LIBGIT2_SOVERSION > 22
	git_libgit2_init();
#endif
	return 1;
}