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 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
27#ifdef HAVE_LIBGIT2
28
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <unistd.h>
33
34#include "nilorea/n_common.h"
35#include "nilorea/n_log.h"
36#include "nilorea/n_str.h"
37#include "nilorea/n_list.h"
38#include "nilorea/n_git.h"
39
40void usage(void) {
41 fprintf(stderr,
42 " -v version\n"
43 " -V log level: LOG_INFO, LOG_NOTICE, LOG_ERR, LOG_DEBUG\n"
44 " -h help\n");
45}
46
47void process_args(int argc, char** argv) {
48 int getoptret = 0,
50
51 while ((getoptret = getopt(argc, argv, "hvV:")) != EOF) {
52 switch (getoptret) {
53 case 'v':
54 fprintf(stderr, "Date de compilation : %s a %s.\n", __DATE__, __TIME__);
55 exit(1);
56 case 'V':
57 if (!strcmp("LOG_NULL", optarg))
59 else if (!strcmp("LOG_NOTICE", optarg))
61 else if (!strcmp("LOG_INFO", optarg))
63 else if (!strcmp("LOG_ERR", optarg))
65 else if (!strcmp("LOG_DEBUG", optarg))
67 else {
68 fprintf(stderr, "%s is not a valid log level.\n", optarg);
69 exit(-1);
70 }
71 break;
72 default:
73 case '?': {
74 if (optopt == 'V') {
75 fprintf(stderr, "\n Missing log level\n");
76 }
77 usage();
78 exit(1);
79 }
80 case 'h': {
81 usage();
82 exit(1);
83 }
84 }
85 }
87}
88
96static int write_file(const char* dir, const char* filename, const char* content) {
97 char path[1024];
98 snprintf(path, sizeof(path), "%s/%s", dir, filename);
99 FILE* fp = fopen(path, "w");
100 if (!fp) return -1;
101 fprintf(fp, "%s", content);
102 fclose(fp);
103 return 0;
104}
105
110static void cleanup_dir(const char* dir) {
111 char cmd[1088];
112 snprintf(cmd, sizeof(cmd), "rm -rf %s", dir);
113 int ret = system(cmd);
114 (void)ret;
115}
116
117int main(int argc, char** argv) {
118 process_args(argc, argv);
119
120 int errors = 0;
121
122 /* create temp directory */
123 char tmpdir[] = "/tmp/nilorea_git_test_XXXXXX";
124 if (!mkdtemp(tmpdir)) {
125 n_log(LOG_ERR, "mkdtemp failed");
126 return 1;
127 }
128 n_log(LOG_INFO, "Test repo dir: %s", tmpdir);
129
130 /* init repo */
131 N_GIT_REPO* repo = n_git_init(tmpdir);
132 if (!repo) {
133 n_log(LOG_ERR, "FAIL: n_git_init returned NULL");
134 cleanup_dir(tmpdir);
135 return 1;
136 }
137 n_log(LOG_INFO, "PASS: n_git_init");
138
139 /* create a file */
140 if (write_file(tmpdir, "test.txt", "hello") != 0) {
141 n_log(LOG_ERR, "FAIL: could not write test.txt");
142 errors++;
143 goto done;
144 }
145
146 /* stage it */
147 if (n_git_stage(repo, "test.txt") != 0) {
148 n_log(LOG_ERR, "FAIL: n_git_stage");
149 errors++;
150 goto done;
151 }
152 n_log(LOG_INFO, "PASS: n_git_stage");
153
154 /* commit */
155 if (n_git_commit(repo, "initial commit", "Test User", "test@example.org") != 0) {
156 n_log(LOG_ERR, "FAIL: n_git_commit");
157 errors++;
158 goto done;
159 }
160 n_log(LOG_INFO, "PASS: n_git_commit");
161
162 /* verify status is clean */
163 {
164 LIST* st = n_git_status(repo);
165 if (!st) {
166 n_log(LOG_ERR, "FAIL: n_git_status returned NULL");
167 errors++;
168 } else {
169 if (st->nb_items != 0) {
170 n_log(LOG_ERR, "FAIL: status should be clean, got %zu entries", st->nb_items);
171 errors++;
172 } else {
173 n_log(LOG_INFO, "PASS: status is clean after commit");
174 }
175 list_destroy(&st);
176 }
177 }
178
179 /* modify the file */
180 if (write_file(tmpdir, "test.txt", "hello world modified") != 0) {
181 n_log(LOG_ERR, "FAIL: could not modify test.txt");
182 errors++;
183 goto done;
184 }
185
186 /* get diff (should be non-empty) */
187 {
188 N_STR* diff = n_git_diff_workdir(repo);
189 if (!diff || !diff->data || diff->written == 0) {
190 n_log(LOG_ERR, "FAIL: diff should be non-empty after modification");
191 errors++;
192 } else {
193 n_log(LOG_INFO, "PASS: diff is non-empty after modification");
194 n_log(LOG_DEBUG, "Diff output:\n%s", diff->data);
195 }
196 free_nstr(&diff);
197 }
198
199 /* restore the file */
200 if (n_git_checkout_path(repo, "test.txt") != 0) {
201 n_log(LOG_ERR, "FAIL: n_git_checkout_path");
202 errors++;
203 goto done;
204 }
205 n_log(LOG_INFO, "PASS: n_git_checkout_path");
206
207 /* verify diff is now empty */
208 {
209 N_STR* diff = n_git_diff_workdir(repo);
210 if (diff && diff->data && diff->written > 0) {
211 n_log(LOG_ERR, "FAIL: diff should be empty after restore, got: %s", diff->data);
212 errors++;
213 } else {
214 n_log(LOG_INFO, "PASS: diff is empty after restore");
215 }
216 free_nstr(&diff);
217 }
218
219 /* get log (should have 1 entry) */
220 {
221 LIST* log_list = n_git_log(repo, 10);
222 if (!log_list) {
223 n_log(LOG_ERR, "FAIL: n_git_log returned NULL");
224 errors++;
225 } else {
226 if (log_list->nb_items != 1) {
227 n_log(LOG_ERR, "FAIL: log should have 1 entry, got %zu", log_list->nb_items);
228 errors++;
229 } else {
230 n_log(LOG_INFO, "PASS: log has 1 entry");
231 LIST_NODE* node = log_list->start;
232 if (node && node->ptr) {
234 n_log(LOG_INFO, " hash: %s", info->hash);
235 n_log(LOG_INFO, " short: %s", info->short_hash);
236 n_log(LOG_INFO, " msg: %s", info->message);
237 n_log(LOG_INFO, " author: %s", info->author);
238 }
239 }
240 list_destroy(&log_list);
241 }
242 }
243
244 /* test current branch */
245 {
246 char branch[256];
247 if (n_git_current_branch(repo, branch, sizeof(branch)) == 0) {
248 n_log(LOG_INFO, "PASS: current branch = %s", branch);
249 } else {
250 n_log(LOG_ERR, "FAIL: n_git_current_branch");
251 errors++;
252 }
253 }
254
255 /* test branch operations */
256 {
257 if (n_git_create_branch(repo, "test-branch") == 0) {
258 n_log(LOG_INFO, "PASS: n_git_create_branch");
259 } else {
260 n_log(LOG_ERR, "FAIL: n_git_create_branch");
261 errors++;
262 }
263
264 LIST* branches = n_git_list_branches(repo);
265 if (branches) {
266 n_log(LOG_INFO, "PASS: n_git_list_branches (%zu branches)", branches->nb_items);
267 list_destroy(&branches);
268 } else {
269 n_log(LOG_ERR, "FAIL: n_git_list_branches");
270 errors++;
271 }
272
273 if (n_git_delete_branch(repo, "test-branch") == 0) {
274 n_log(LOG_INFO, "PASS: n_git_delete_branch");
275 } else {
276 n_log(LOG_ERR, "FAIL: n_git_delete_branch");
277 errors++;
278 }
279 }
280
281done:
282 n_git_close(&repo);
283 cleanup_dir(tmpdir);
284
285 if (errors > 0) {
286 n_log(LOG_ERR, "RESULT: %d test(s) FAILED", errors);
287 return 1;
288 }
289 n_log(LOG_INFO, "RESULT: all tests PASSED");
290 return 0;
291}
292
293#else /* !HAVE_LIBGIT2 */
294
295#include <stdio.h>
296
297int main(void) {
298 fprintf(stderr, "ex_git: built without HAVE_LIBGIT2, skipping\n");
299 return 0;
300}
301
302#endif /* HAVE_LIBGIT2 */
static void usage(void)
void process_args(int argc, char **argv)
Definition ex_common.c:47
int main(void)
int getoptret
Definition ex_fluid.c:60
int log_level
Definition ex_fluid.c:61
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:96
static void cleanup_dir(const char *dir)
Remove a directory tree recursively (simple rm -rf via system).
Definition ex_git.c:110
bool done
char short_hash[10]
short (abbreviated) hex hash
Definition n_git.h:102
char author[256]
author name and email
Definition n_git.h:106
char message[512]
first line of commit message
Definition n_git.h:104
char hash[42]
full hex hash
Definition n_git.h:100
void n_git_close(N_GIT_REPO **repo)
Close a Git repository and free resources.
Definition n_git.c:185
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:679
LIST * n_git_list_branches(N_GIT_REPO *repo)
List all local branch names.
Definition n_git.c:749
LIST * n_git_status(N_GIT_REPO *repo)
Get the status of all files in the working directory.
Definition n_git.c:210
LIST * n_git_log(N_GIT_REPO *repo, size_t max_entries)
Retrieve commit log entries starting from HEAD.
Definition n_git.c:491
int n_git_stage(N_GIT_REPO *repo, const char *filepath)
Stage a single file by path.
Definition n_git.c:290
int n_git_create_branch(N_GIT_REPO *repo, const char *branch_name)
Create a new branch from HEAD.
Definition n_git.c:790
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:879
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:412
N_STR * n_git_diff_workdir(N_GIT_REPO *repo)
Get a diff of the working directory against the index.
Definition n_git.c:567
N_GIT_REPO * n_git_init(const char *path)
Initialize a new Git repository.
Definition n_git.c:155
int n_git_delete_branch(N_GIT_REPO *repo, const char *branch_name)
Delete a local branch.
Definition n_git.c:909
Commit metadata.
Definition n_git.h:98
Wrapper around a git_repository handle.
Definition n_git.h:80
void * ptr
void pointer to store
Definition n_list.h:45
LIST_NODE * start
pointer to the start of the list
Definition n_list.h:65
size_t nb_items
number of item currently in the list
Definition n_list.h:60
int list_destroy(LIST **list)
Empty and Free a list container.
Definition n_list.c:547
Structure of a generic LIST container.
Definition n_list.h:58
Structure of a generic list node.
Definition n_list.h:43
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:88
#define LOG_DEBUG
debug-level messages
Definition n_log.h:83
#define LOG_ERR
error conditions
Definition n_log.h:75
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL )
Definition n_log.c:120
#define LOG_NOTICE
normal but significant condition
Definition n_log.h:79
#define LOG_NULL
no log output
Definition n_log.h:45
#define LOG_INFO
informational
Definition n_log.h:81
size_t written
size of the written data inside the string
Definition n_str.h:66
char * data
the string
Definition n_str.h:62
#define free_nstr(__ptr)
free a N_STR structure and set the pointer to NULL
Definition n_str.h:201
A box including a string and his lenght.
Definition n_str.h:60
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.