Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_git.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 * implied. See the License for the specific language governing
15 * permissions and limitations under the License.
16 *
17 * SPDX-License-Identifier: Apache-2.0
18 */
19
28#ifdef HAVE_LIBGIT2
29
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <unistd.h>
34
35#include "nilorea/n_common.h"
36#include "nilorea/n_log.h"
37#include "nilorea/n_str.h"
38#include "nilorea/n_list.h"
39#include "nilorea/n_git.h"
40
41void usage(void) {
42 fprintf(stderr,
43 " -v version\n"
44 " -V log level: LOG_INFO, LOG_NOTICE, LOG_ERR, LOG_DEBUG\n"
45 " -h help\n");
46}
47
48void process_args(int argc, char** argv) {
49 int getoptret = 0,
51
52 while ((getoptret = getopt(argc, argv, "hvV:")) != EOF) {
53 switch (getoptret) {
54 case 'v':
55 fprintf(stderr, "Date de compilation : %s a %s.\n", __DATE__, __TIME__);
56 exit(1);
57 case 'V':
58 if (!strcmp("LOG_NULL", optarg))
60 else if (!strcmp("LOG_NOTICE", optarg))
62 else if (!strcmp("LOG_INFO", optarg))
64 else if (!strcmp("LOG_ERR", optarg))
66 else if (!strcmp("LOG_DEBUG", optarg))
68 else {
69 fprintf(stderr, "%s is not a valid log level.\n", optarg);
70 exit(-1);
71 }
72 break;
73 default:
74 case '?': {
75 if (optopt == 'V') {
76 fprintf(stderr, "\n Missing log level\n");
77 }
78 usage();
79 exit(1);
80 }
81 case 'h': {
82 usage();
83 exit(1);
84 }
85 }
86 }
88}
89
97static int write_file(const char* dir, const char* filename, const char* content) {
98 char path[1024];
99 snprintf(path, sizeof(path), "%s/%s", dir, filename);
100 FILE* fp = fopen(path, "w");
101 if (!fp) return -1;
102 fprintf(fp, "%s", content);
103 fclose(fp);
104 return 0;
105}
106
111static void cleanup_dir(const char* dir) {
112 char cmd[1088];
113 snprintf(cmd, sizeof(cmd), "rm -rf %s", dir);
114 int ret = system(cmd);
115 (void)ret;
116}
117
118int main(int argc, char** argv) {
119 process_args(argc, argv);
120
121 int errors = 0;
122
123 /* create temp directory */
124 char tmpdir[] = "/tmp/nilorea_git_test_XXXXXX";
125 if (!mkdtemp(tmpdir)) {
126 n_log(LOG_ERR, "mkdtemp failed");
127 return 1;
128 }
129 n_log(LOG_INFO, "Test repo dir: %s", tmpdir);
130
131 /* init repo */
132 N_GIT_REPO* repo = n_git_init(tmpdir);
133 if (!repo) {
134 n_log(LOG_ERR, "FAIL: n_git_init returned NULL");
135 cleanup_dir(tmpdir);
136 return 1;
137 }
138 n_log(LOG_INFO, "PASS: n_git_init");
139
140 /* create a file */
141 if (write_file(tmpdir, "test.txt", "hello") != 0) {
142 n_log(LOG_ERR, "FAIL: could not write test.txt");
143 errors++;
144 goto done;
145 }
146
147 /* stage it */
148 if (n_git_stage(repo, "test.txt") != 0) {
149 n_log(LOG_ERR, "FAIL: n_git_stage");
150 errors++;
151 goto done;
152 }
153 n_log(LOG_INFO, "PASS: n_git_stage");
154
155 /* commit */
156 if (n_git_commit(repo, "initial commit", "Test User", "test@example.org") != 0) {
157 n_log(LOG_ERR, "FAIL: n_git_commit");
158 errors++;
159 goto done;
160 }
161 n_log(LOG_INFO, "PASS: n_git_commit");
162
163 /* verify status is clean */
164 {
165 LIST* st = n_git_status(repo);
166 if (!st) {
167 n_log(LOG_ERR, "FAIL: n_git_status returned NULL");
168 errors++;
169 } else {
170 if (st->nb_items != 0) {
171 n_log(LOG_ERR, "FAIL: status should be clean, got %zu entries", st->nb_items);
172 errors++;
173 } else {
174 n_log(LOG_INFO, "PASS: status is clean after commit");
175 }
176 list_destroy(&st);
177 }
178 }
179
180 /* modify the file */
181 if (write_file(tmpdir, "test.txt", "hello world modified") != 0) {
182 n_log(LOG_ERR, "FAIL: could not modify test.txt");
183 errors++;
184 goto done;
185 }
186
187 /* get diff (should be non-empty) */
188 {
189 N_STR* diff = n_git_diff_workdir(repo);
190 if (!diff || !diff->data || diff->written == 0) {
191 n_log(LOG_ERR, "FAIL: diff should be non-empty after modification");
192 errors++;
193 } else {
194 n_log(LOG_INFO, "PASS: diff is non-empty after modification");
195 n_log(LOG_DEBUG, "Diff output:\n%s", diff->data);
196 }
197 free_nstr(&diff);
198 }
199
200 /* restore the file */
201 if (n_git_checkout_path(repo, "test.txt") != 0) {
202 n_log(LOG_ERR, "FAIL: n_git_checkout_path");
203 errors++;
204 goto done;
205 }
206 n_log(LOG_INFO, "PASS: n_git_checkout_path");
207
208 /* verify diff is now empty */
209 {
210 N_STR* diff = n_git_diff_workdir(repo);
211 if (diff && diff->data && diff->written > 0) {
212 n_log(LOG_ERR, "FAIL: diff should be empty after restore, got: %s", diff->data);
213 errors++;
214 } else {
215 n_log(LOG_INFO, "PASS: diff is empty after restore");
216 }
217 free_nstr(&diff);
218 }
219
220 /* get log (should have 1 entry) */
221 {
222 LIST* log_list = n_git_log(repo, 10);
223 if (!log_list) {
224 n_log(LOG_ERR, "FAIL: n_git_log returned NULL");
225 errors++;
226 } else {
227 if (log_list->nb_items != 1) {
228 n_log(LOG_ERR, "FAIL: log should have 1 entry, got %zu", log_list->nb_items);
229 errors++;
230 } else {
231 n_log(LOG_INFO, "PASS: log has 1 entry");
232 LIST_NODE* node = log_list->start;
233 if (node && node->ptr) {
235 n_log(LOG_INFO, " hash: %s", info->hash);
236 n_log(LOG_INFO, " short: %s", info->short_hash);
237 n_log(LOG_INFO, " msg: %s", info->message);
238 n_log(LOG_INFO, " author: %s", info->author);
239 }
240 }
241 list_destroy(&log_list);
242 }
243 }
244
245 /* test subdirectory staging */
246 {
247 /* create a subdirectory with files */
248 char subdir[1088];
249 snprintf(subdir, sizeof(subdir), "%s/subdir", tmpdir);
250 char mkdircmd[1088 + 16];
251 snprintf(mkdircmd, sizeof(mkdircmd), "mkdir -p %s", subdir);
252 if (system(mkdircmd) != 0) {
253 n_log(LOG_ERR, "FAIL: could not create subdir");
254 errors++;
255 goto done;
256 }
257 if (write_file(tmpdir, "subdir/file_a.txt", "aaa") != 0 ||
258 write_file(tmpdir, "subdir/file_b.txt", "bbb") != 0) {
259 n_log(LOG_ERR, "FAIL: could not write subdir files");
260 errors++;
261 goto done;
262 }
263
264 /* status should report individual files (not collapsed directory) */
265 LIST* st = n_git_status(repo);
266 if (!st) {
267 n_log(LOG_ERR, "FAIL: n_git_status returned NULL for subdir test");
268 errors++;
269 } else {
270 int found_a = 0, found_b = 0;
271 list_foreach(node, st) {
272 N_GIT_STATUS_ENTRY* e = (N_GIT_STATUS_ENTRY*)node->ptr;
273 if (e && strcmp(e->path, "subdir/file_a.txt") == 0) found_a = 1;
274 if (e && strcmp(e->path, "subdir/file_b.txt") == 0) found_b = 1;
275 }
276 if (found_a && found_b) {
277 n_log(LOG_INFO, "PASS: status reports individual subdir files");
278 } else {
279 n_log(LOG_ERR, "FAIL: status did not report individual subdir files (a=%d b=%d)",
280 found_a, found_b);
281 errors++;
282 }
283 list_destroy(&st);
284 }
285
286 /* stage using directory path (trailing slash) */
287 if (n_git_stage(repo, "subdir/") != 0) {
288 n_log(LOG_ERR, "FAIL: n_git_stage with directory path");
289 errors++;
290 } else {
291 n_log(LOG_INFO, "PASS: n_git_stage with directory path");
292 }
293
294 /* commit the subdir files */
295 if (n_git_commit(repo, "add subdir files", "Test User", "test@example.org") != 0) {
296 n_log(LOG_ERR, "FAIL: n_git_commit for subdir");
297 errors++;
298 } else {
299 n_log(LOG_INFO, "PASS: n_git_commit for subdir");
300 }
301
302 /* verify status is clean */
303 st = n_git_status(repo);
304 if (!st) {
305 n_log(LOG_ERR, "FAIL: n_git_status returned NULL after subdir commit");
306 errors++;
307 } else {
308 if (st->nb_items != 0) {
309 n_log(LOG_ERR, "FAIL: status should be clean after subdir commit, got %zu", st->nb_items);
310 errors++;
311 } else {
312 n_log(LOG_INFO, "PASS: status is clean after subdir commit");
313 }
314 list_destroy(&st);
315 }
316 }
317
318 /* test current branch */
319 {
320 char branch[256];
321 if (n_git_current_branch(repo, branch, sizeof(branch)) == 0) {
322 n_log(LOG_INFO, "PASS: current branch = %s", branch);
323 } else {
324 n_log(LOG_ERR, "FAIL: n_git_current_branch");
325 errors++;
326 }
327 }
328
329 /* test branch operations */
330 {
331 if (n_git_create_branch(repo, "test-branch") == 0) {
332 n_log(LOG_INFO, "PASS: n_git_create_branch");
333 } else {
334 n_log(LOG_ERR, "FAIL: n_git_create_branch");
335 errors++;
336 }
337
338 LIST* branches = n_git_list_branches(repo);
339 if (branches) {
340 n_log(LOG_INFO, "PASS: n_git_list_branches (%zu branches)", branches->nb_items);
341 list_destroy(&branches);
342 } else {
343 n_log(LOG_ERR, "FAIL: n_git_list_branches");
344 errors++;
345 }
346
347 if (n_git_delete_branch(repo, "test-branch") == 0) {
348 n_log(LOG_INFO, "PASS: n_git_delete_branch");
349 } else {
350 n_log(LOG_ERR, "FAIL: n_git_delete_branch");
351 errors++;
352 }
353 }
354
355done:
356 n_git_close(&repo);
357 cleanup_dir(tmpdir);
358
359 if (errors > 0) {
360 n_log(LOG_ERR, "RESULT: %d test(s) FAILED", errors);
361 return 1;
362 }
363 n_log(LOG_INFO, "RESULT: all tests PASSED");
364 return 0;
365}
366
367#else /* !HAVE_LIBGIT2 */
368
369#include <stdio.h>
370
371int main(void) {
372 fprintf(stderr, "ex_git: built without HAVE_LIBGIT2, skipping\n");
373 return 0;
374}
375
376#endif /* HAVE_LIBGIT2 */
static void usage(void)
int main(void)
void process_args(int argc, char **argv)
Definition ex_common.c:48
int getoptret
Definition ex_fluid.c:59
int log_level
Definition ex_fluid.c:60
static int write_file(const char *dir, const char *filename, const char *content)
Write a file inside the repo with given content.
Definition ex_git.c:97
static void cleanup_dir(const char *dir)
Remove a directory tree recursively (simple rm -rf via system).
Definition ex_git.c:111
bool done
char short_hash[10]
short (abbreviated) hex hash
Definition n_git.h:105
char author[256]
author name and email
Definition n_git.h:109
char message[512]
first line of commit message
Definition n_git.h:107
char hash[42]
full hex hash
Definition n_git.h:103
char path[512]
relative path of the file
Definition n_git.h:95
void n_git_close(N_GIT_REPO **repo)
Close a Git repository and free resources.
Definition n_git.c:241
int n_git_checkout_path(N_GIT_REPO *repo, const char *filepath)
Restore a single file from HEAD (discard working directory changes).
Definition n_git.c:796
LIST * n_git_list_branches(N_GIT_REPO *repo)
List all local branch names.
Definition n_git.c:868
LIST * n_git_status(N_GIT_REPO *repo)
Get the status of all files in the working directory.
Definition n_git.c:266
LIST * n_git_log(N_GIT_REPO *repo, size_t max_entries)
Retrieve commit log entries starting from HEAD.
Definition n_git.c:596
int n_git_stage(N_GIT_REPO *repo, const char *filepath)
Stage a file or directory by path.
Definition n_git.c:354
int n_git_create_branch(N_GIT_REPO *repo, const char *branch_name)
Create a new branch from HEAD.
Definition n_git.c:911
int n_git_current_branch(N_GIT_REPO *repo, char *out, size_t out_size)
Get the name of the current branch.
Definition n_git.c:1030
int n_git_commit(N_GIT_REPO *repo, const char *message, const char *author_name, const char *author_email)
Create a commit from the current index.
Definition n_git.c:507
N_STR * n_git_diff_workdir(N_GIT_REPO *repo)
Get a diff of the working directory against the index.
Definition n_git.c:684
N_GIT_REPO * n_git_init(const char *path)
Initialize a new Git repository.
Definition n_git.c:195
int n_git_delete_branch(N_GIT_REPO *repo, const char *branch_name)
Delete a local branch.
Definition n_git.c:1079
Commit metadata.
Definition n_git.h:101
Wrapper around a git_repository handle.
Definition n_git.h:81
Single file status entry.
Definition n_git.h:93
void * ptr
void pointer to store
Definition n_list.h:46
LIST_NODE * start
pointer to the start of the list
Definition n_list.h:66
size_t nb_items
number of item currently in the list
Definition n_list.h:61
#define list_foreach(__ITEM_, __LIST_)
ForEach macro helper, safe for node removal during iteration.
Definition n_list.h:89
int list_destroy(LIST **list)
Empty and Free a list container.
Definition n_list.c:548
Structure of a generic LIST container.
Definition n_list.h:59
Structure of a generic list node.
Definition n_list.h:44
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:89
#define LOG_DEBUG
debug-level messages
Definition n_log.h:84
#define LOG_ERR
error conditions
Definition n_log.h:76
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL )
Definition n_log.c:121
#define LOG_NOTICE
normal but significant condition
Definition n_log.h:80
#define LOG_NULL
no log output
Definition n_log.h:46
#define LOG_INFO
informational
Definition n_log.h:82
size_t written
number of meaningful bytes in data, excluding the null terminator; the size including the null termin...
Definition n_str.h:68
char * data
the string
Definition n_str.h:63
#define free_nstr(__ptr)
free a N_STR structure and set the pointer to NULL
Definition n_str.h:203
A box including a string and his lenght.
Definition n_str.h:61
Common headers and low-level functions & define.
libgit2 wrapper for Git repository operations
List structures and definitions.
Generic log system.
N_STR and string function declaration.