1const std = @import("std");
2const testing = std.testing;
3const Allocator = std.mem.Allocator;
4const galloc = @import("galloc.zig");
5
6const git = @cImport({
7 @cInclude("git2.h");
8});
9
10const GitError = error{
11 RepsitoryNotInitialized,
12 RepositoryAlreadyInitialized,
13 Unkown,
14
15 //libgit specific error
16 GitError,
17 GitNotfound,
18 GitExists,
19 GitAmbiguous,
20 GitBufs,
21 GitUser,
22 GitUnbornBranch,
23 GitUnmerged,
24 GitNonFastForward,
25 GitInvalidSpec,
26 GitConflict,
27 GitLocked,
28 GitModified,
29 GitAuth,
30 GitCertificate,
31 GitApplied,
32 GitPeel,
33 GitEof,
34 GitInvalid,
35 GitUncommitted,
36 GitDirectory,
37 GitMergeConflict,
38 GitPassthrough,
39 GitIterOver,
40 GitRetry,
41 GitMismatch,
42 GitIndexDirty,
43 GitApplyFail,
44 GitOwner,
45 GitTimeout,
46 GitUnchanged,
47 GitNotSupported,
48 GitReadonly,
49};
50
51var alloc: galloc.GitAllocator = undefined;
52
53fn malloc(size: usize, _: ?*anyopaque) callconv(.C) ?*anyopaque {
54 return alloc.malloc(size);
55}
56
57fn relloc(ptr: ?*anyopaque, size: usize, _: ?*anyopaque) callconv(.C) ?*anyopaque {
58 const new_ptr = alloc.realloc(ptr, size);
59 return new_ptr;
60}
61
62fn free(ptr: ?*anyopaque) callconv(.C) void {
63 alloc.free(ptr);
64}
65
66fn err(code: c_int) GitError!void {
67 if (code >= 0) return;
68
69 return switch (code) {
70 git.GIT_ERROR => GitError.GitError,
71 git.GIT_ENOTFOUND => GitError.GitNotfound,
72 git.GIT_EEXISTS => GitError.GitExists,
73 git.GIT_EAMBIGUOUS => GitError.GitAmbiguous,
74 git.GIT_EBUFS => GitError.GitBufs,
75 git.GIT_EUSER => GitError.GitUser,
76 git.GIT_EUNBORNBRANCH => GitError.GitUnbornBranch,
77 git.GIT_EUNMERGED => GitError.GitUnmerged,
78 git.GIT_ENONFASTFORWARD => GitError.GitNonFastForward,
79 git.GIT_EINVALIDSPEC => GitError.GitInvalidSpec,
80 git.GIT_ECONFLICT => GitError.GitConflict,
81 git.GIT_ELOCKED => GitError.GitLocked,
82 git.GIT_EMODIFIED => GitError.GitModified,
83 git.GIT_EAUTH => GitError.GitAuth,
84 git.GIT_ECERTIFICATE => GitError.GitCertificate,
85 git.GIT_EAPPLIED => GitError.GitApplied,
86 git.GIT_EPEEL => GitError.GitPeel,
87 git.GIT_EEOF => GitError.GitEof,
88 git.GIT_EINVALID => GitError.GitInvalid,
89 git.GIT_EUNCOMMITTED => GitError.GitUncommitted,
90 git.GIT_EDIRECTORY => GitError.GitDirectory,
91 git.GIT_EMERGECONFLICT => GitError.GitMergeConflict,
92 git.GIT_PASSTHROUGH => GitError.GitPassthrough,
93 git.GIT_ITEROVER => GitError.GitIterOver,
94 git.GIT_RETRY => GitError.GitRetry,
95 git.GIT_EMISMATCH => GitError.GitMismatch,
96 git.GIT_EINDEXDIRTY => GitError.GitIndexDirty,
97 git.GIT_EAPPLYFAIL => GitError.GitApplyFail,
98 git.GIT_EOWNER => GitError.GitOwner,
99 git.GIT_TIMEOUT => GitError.GitTimeout,
100 git.GIT_EUNCHANGED => GitError.GitUnchanged,
101 git.GIT_ENOTSUPPORTED => GitError.GitNotSupported,
102 git.GIT_EREADONLY => GitError.GitReadonly,
103 else => GitError.Unkown,
104 };
105}
106
107const git_allocator = extern struct {
108 gmalloc: ?*const fn (size: usize, payload: ?*anyopaque) callconv(.C) ?*anyopaque,
109 grealloc: ?*const fn (ptr: ?*anyopaque, size: usize, payload: ?*anyopaque) callconv(.C) ?*anyopaque,
110 gfree: ?*const fn (ptr: ?*anyopaque) callconv(.C) void,
111};
112
113pub fn init(a: std.mem.Allocator) GitError!void {
114 alloc = galloc.GitAllocator.init(a);
115
116 const cAlloc = git_allocator{
117 .gmalloc = malloc,
118 .grealloc = relloc,
119 .gfree = free,
120 };
121
122 var code = git.git_libgit2_opts(git.GIT_OPT_SET_ALLOCATOR, &cAlloc);
123 try err(code);
124
125 code = git.git_libgit2_init();
126 try err(code);
127}
128
129pub fn deinit() !void {
130 defer alloc.deinit();
131 try err(git.git_libgit2_shutdown());
132}
133
134pub const Commit = struct {
135 commit: *git.git_commit = undefined,
136
137 pub fn fromGitCommit(c: *git.git_commit) Commit {
138 return Commit{ .commit = c };
139 }
140};
141
142pub const Repository = struct {
143 repository: ?*git.git_repository = null,
144 reference: ?*git.git_reference = null,
145
146 fn validateInit(self: *Repository) GitError!void {
147 if (self.repository != null)
148 return GitError.RepositoryAlreadyInitialized;
149 }
150
151 fn validateNotInit(self: *Repository) GitError!void {
152 if (self.repository == null)
153 return GitError.RepsitoryNotInitialized;
154 }
155
156 fn validateRef(self: *Repository) GitError!void {
157 if (self.ref != null)
158 return GitError.RepositoryAlreadyInitialized;
159 }
160
161 pub fn open(self: *Repository, path: []const u8) GitError!void {
162 try self.validateInit();
163 try err(git.git_repository_open(@ptrCast(&self.repository), path.ptr));
164 }
165
166 pub fn init(self: *Repository, path: []const u8, bare: bool) GitError!void {
167 try self.validateInit();
168 try err(git.git_repository_init(@ptrCast(&self.repository), path.ptr, if (bare) 1 else 0));
169 }
170
171 pub fn setRef(self: *Repository, ref_name: []const u8) GitError!void {
172 try self.validateNotInit();
173
174 if (self.reference) |ref| {
175 git.git_reference_free(ref);
176 self.reference = null;
177 }
178
179 try err(git.git_reference_dwim(@ptrCast(&self.reference), self.repository, ref_name.ptr));
180 }
181
182 pub fn referenceName(self: *Repository) []const u8 {
183 if (self.reference) |ref| {
184 return std.mem.span(git.git_reference_name(ref));
185 }
186
187 return "";
188 }
189
190 pub fn deinit(self: *Repository) void {
191 if (self.reference) |ref| {
192 git.git_reference_free(ref);
193 }
194
195 if (self.repository) |repo| {
196 git.git_repository_free(repo);
197 }
198 }
199};
200
201test "init deinit" {
202 try init(testing.allocator);
203 try deinit();
204}
205
206test "open repository" {
207 try init(testing.allocator);
208 defer deinit() catch {};
209
210 var repository = Repository{};
211 defer repository.deinit();
212
213 try repository.open(".");
214}
215
216test "init repository" {
217 var tmp_dir = testing.tmpDir(.{});
218 defer tmp_dir.cleanup();
219
220 const full_path = try tmp_dir.dir.realpathAlloc(testing.allocator, ".");
221 defer testing.allocator.free(full_path);
222
223 try init(testing.allocator);
224 defer deinit() catch {};
225
226 var repository = Repository{};
227 defer repository.deinit();
228
229 try repository.init(full_path, false);
230}
231
232test "init repository bare" {
233 var tmp_dir = testing.tmpDir(.{});
234 defer tmp_dir.cleanup();
235
236 const full_path = try tmp_dir.dir.realpathAlloc(testing.allocator, ".");
237 defer testing.allocator.free(full_path);
238
239 try init(testing.allocator);
240 defer deinit() catch {};
241
242 var repository = Repository{};
243 defer repository.deinit();
244
245 try repository.init(full_path, false);
246
247 // try opening the repository to test if it is properly created.
248 var tmp = Repository{};
249 defer tmp.deinit();
250
251 try tmp.open(full_path);
252}
253
254test "set reference" {
255 try init(testing.allocator);
256 defer deinit() catch {};
257
258 var repository = Repository{};
259 defer repository.deinit();
260
261 try repository.open(".");
262
263 try repository.setRef("zig");
264 try testing.expect(std.mem.eql(u8, "refs/heads/zig", repository.referenceName()));
265
266 try repository.setRef("master");
267 try testing.expect(std.mem.eql(u8, "refs/heads/master", repository.referenceName()));
268}