Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_git.c

Nilorea Library n_git module test.

Nilorea Library n_git module test

Author
Castagnier Mickael
Version
1.0
Date
26/03/2026
/*
* Nilorea Library
* Copyright (C) 2005-2026 Castagnier Mickael
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifdef HAVE_LIBGIT2
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "nilorea/n_log.h"
#include "nilorea/n_str.h"
#include "nilorea/n_list.h"
#include "nilorea/n_git.h"
void usage(void) {
fprintf(stderr,
" -v version\n"
" -V log level: LOG_INFO, LOG_NOTICE, LOG_ERR, LOG_DEBUG\n"
" -h help\n");
}
void process_args(int argc, char** argv) {
int getoptret = 0,
while ((getoptret = getopt(argc, argv, "hvV:")) != EOF) {
switch (getoptret) {
case 'v':
fprintf(stderr, "Date de compilation : %s a %s.\n", __DATE__, __TIME__);
exit(1);
case 'V':
if (!strcmp("LOG_NULL", optarg))
else if (!strcmp("LOG_NOTICE", optarg))
else if (!strcmp("LOG_INFO", optarg))
else if (!strcmp("LOG_ERR", optarg))
else if (!strcmp("LOG_DEBUG", optarg))
else {
fprintf(stderr, "%s is not a valid log level.\n", optarg);
exit(-1);
}
break;
default:
case '?': {
if (optopt == 'V') {
fprintf(stderr, "\n Missing log level\n");
}
usage();
exit(1);
}
case 'h': {
usage();
exit(1);
}
}
}
}
static int write_file(const char* dir, const char* filename, const char* content) {
char path[1024];
snprintf(path, sizeof(path), "%s/%s", dir, filename);
FILE* fp = fopen(path, "w");
if (!fp) return -1;
fprintf(fp, "%s", content);
fclose(fp);
return 0;
}
static void cleanup_dir(const char* dir) {
char cmd[1088];
snprintf(cmd, sizeof(cmd), "rm -rf %s", dir);
int ret = system(cmd);
(void)ret;
}
int main(int argc, char** argv) {
process_args(argc, argv);
int errors = 0;
/* create temp directory */
char tmpdir[] = "/tmp/nilorea_git_test_XXXXXX";
if (!mkdtemp(tmpdir)) {
n_log(LOG_ERR, "mkdtemp failed");
return 1;
}
n_log(LOG_INFO, "Test repo dir: %s", tmpdir);
/* init repo */
N_GIT_REPO* repo = n_git_init(tmpdir);
if (!repo) {
n_log(LOG_ERR, "FAIL: n_git_init returned NULL");
cleanup_dir(tmpdir);
return 1;
}
n_log(LOG_INFO, "PASS: n_git_init");
/* create a file */
if (write_file(tmpdir, "test.txt", "hello") != 0) {
n_log(LOG_ERR, "FAIL: could not write test.txt");
errors++;
goto done;
}
/* stage it */
if (n_git_stage(repo, "test.txt") != 0) {
n_log(LOG_ERR, "FAIL: n_git_stage");
errors++;
goto done;
}
n_log(LOG_INFO, "PASS: n_git_stage");
/* commit */
if (n_git_commit(repo, "initial commit", "Test User", "test@example.org") != 0) {
n_log(LOG_ERR, "FAIL: n_git_commit");
errors++;
goto done;
}
n_log(LOG_INFO, "PASS: n_git_commit");
/* verify status is clean */
{
LIST* st = n_git_status(repo);
if (!st) {
n_log(LOG_ERR, "FAIL: n_git_status returned NULL");
errors++;
} else {
if (st->nb_items != 0) {
n_log(LOG_ERR, "FAIL: status should be clean, got %zu entries", st->nb_items);
errors++;
} else {
n_log(LOG_INFO, "PASS: status is clean after commit");
}
}
}
/* modify the file */
if (write_file(tmpdir, "test.txt", "hello world modified") != 0) {
n_log(LOG_ERR, "FAIL: could not modify test.txt");
errors++;
goto done;
}
/* get diff (should be non-empty) */
{
N_STR* diff = n_git_diff_workdir(repo);
if (!diff || !diff->data || diff->written == 0) {
n_log(LOG_ERR, "FAIL: diff should be non-empty after modification");
errors++;
} else {
n_log(LOG_INFO, "PASS: diff is non-empty after modification");
n_log(LOG_DEBUG, "Diff output:\n%s", diff->data);
}
free_nstr(&diff);
}
/* restore the file */
if (n_git_checkout_path(repo, "test.txt") != 0) {
n_log(LOG_ERR, "FAIL: n_git_checkout_path");
errors++;
goto done;
}
n_log(LOG_INFO, "PASS: n_git_checkout_path");
/* verify diff is now empty */
{
N_STR* diff = n_git_diff_workdir(repo);
if (diff && diff->data && diff->written > 0) {
n_log(LOG_ERR, "FAIL: diff should be empty after restore, got: %s", diff->data);
errors++;
} else {
n_log(LOG_INFO, "PASS: diff is empty after restore");
}
free_nstr(&diff);
}
/* get log (should have 1 entry) */
{
LIST* log_list = n_git_log(repo, 10);
if (!log_list) {
n_log(LOG_ERR, "FAIL: n_git_log returned NULL");
errors++;
} else {
if (log_list->nb_items != 1) {
n_log(LOG_ERR, "FAIL: log should have 1 entry, got %zu", log_list->nb_items);
errors++;
} else {
n_log(LOG_INFO, "PASS: log has 1 entry");
LIST_NODE* node = log_list->start;
if (node && node->ptr) {
n_log(LOG_INFO, " hash: %s", info->hash);
n_log(LOG_INFO, " short: %s", info->short_hash);
n_log(LOG_INFO, " msg: %s", info->message);
n_log(LOG_INFO, " author: %s", info->author);
}
}
list_destroy(&log_list);
}
}
/* test subdirectory staging */
{
/* create a subdirectory with files */
char subdir[1088];
snprintf(subdir, sizeof(subdir), "%s/subdir", tmpdir);
char mkdircmd[1088 + 16];
snprintf(mkdircmd, sizeof(mkdircmd), "mkdir -p %s", subdir);
if (system(mkdircmd) != 0) {
n_log(LOG_ERR, "FAIL: could not create subdir");
errors++;
goto done;
}
if (write_file(tmpdir, "subdir/file_a.txt", "aaa") != 0 ||
write_file(tmpdir, "subdir/file_b.txt", "bbb") != 0) {
n_log(LOG_ERR, "FAIL: could not write subdir files");
errors++;
goto done;
}
/* status should report individual files (not collapsed directory) */
LIST* st = n_git_status(repo);
if (!st) {
n_log(LOG_ERR, "FAIL: n_git_status returned NULL for subdir test");
errors++;
} else {
int found_a = 0, found_b = 0;
list_foreach(node, st) {
if (e && strcmp(e->path, "subdir/file_a.txt") == 0) found_a = 1;
if (e && strcmp(e->path, "subdir/file_b.txt") == 0) found_b = 1;
}
if (found_a && found_b) {
n_log(LOG_INFO, "PASS: status reports individual subdir files");
} else {
n_log(LOG_ERR, "FAIL: status did not report individual subdir files (a=%d b=%d)",
found_a, found_b);
errors++;
}
}
/* stage using directory path (trailing slash) */
if (n_git_stage(repo, "subdir/") != 0) {
n_log(LOG_ERR, "FAIL: n_git_stage with directory path");
errors++;
} else {
n_log(LOG_INFO, "PASS: n_git_stage with directory path");
}
/* commit the subdir files */
if (n_git_commit(repo, "add subdir files", "Test User", "test@example.org") != 0) {
n_log(LOG_ERR, "FAIL: n_git_commit for subdir");
errors++;
} else {
n_log(LOG_INFO, "PASS: n_git_commit for subdir");
}
/* verify status is clean */
st = n_git_status(repo);
if (!st) {
n_log(LOG_ERR, "FAIL: n_git_status returned NULL after subdir commit");
errors++;
} else {
if (st->nb_items != 0) {
n_log(LOG_ERR, "FAIL: status should be clean after subdir commit, got %zu", st->nb_items);
errors++;
} else {
n_log(LOG_INFO, "PASS: status is clean after subdir commit");
}
}
}
/* test current branch */
{
char branch[256];
if (n_git_current_branch(repo, branch, sizeof(branch)) == 0) {
n_log(LOG_INFO, "PASS: current branch = %s", branch);
} else {
n_log(LOG_ERR, "FAIL: n_git_current_branch");
errors++;
}
}
/* test branch operations */
{
if (n_git_create_branch(repo, "test-branch") == 0) {
n_log(LOG_INFO, "PASS: n_git_create_branch");
} else {
n_log(LOG_ERR, "FAIL: n_git_create_branch");
errors++;
}
LIST* branches = n_git_list_branches(repo);
if (branches) {
n_log(LOG_INFO, "PASS: n_git_list_branches (%zu branches)", branches->nb_items);
list_destroy(&branches);
} else {
n_log(LOG_ERR, "FAIL: n_git_list_branches");
errors++;
}
if (n_git_delete_branch(repo, "test-branch") == 0) {
n_log(LOG_INFO, "PASS: n_git_delete_branch");
} else {
n_log(LOG_ERR, "FAIL: n_git_delete_branch");
errors++;
}
}
n_git_close(&repo);
cleanup_dir(tmpdir);
if (errors > 0) {
n_log(LOG_ERR, "RESULT: %d test(s) FAILED", errors);
return 1;
}
n_log(LOG_INFO, "RESULT: all tests PASSED");
return 0;
}
#else /* !HAVE_LIBGIT2 */
#include <stdio.h>
int main(void) {
fprintf(stderr, "ex_git: built without HAVE_LIBGIT2, skipping\n");
return 0;
}
#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
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.