From 2e03edac2b9434c289e53426ff4998c5827ca36a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Rivera?= Date: Fri, 14 Aug 2026 15:14:53 +0200 Subject: [PATCH 1/6] flake: add libgit2 dependency --- flake.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flake.nix b/flake.nix index a45087b..cc67480 100644 --- a/flake.nix +++ b/flake.nix @@ -30,6 +30,7 @@ ripgrep fzf gnused + libgit2 ]; buildPhase = '' @@ -51,6 +52,7 @@ valgrind clang-tools gnumake + libgit2 ]; }; From 82d016fdd7a38202f302a600c849aa563a610dbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Rivera?= Date: Fri, 14 Aug 2026 17:28:15 +0200 Subject: [PATCH 2/6] feat: git version control for backup --- makefile | 4 +- src/main.c | 77 +++++++++++++++++--- src/utils.c | 198 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/utils.h | 10 +++ 4 files changed, 275 insertions(+), 14 deletions(-) diff --git a/makefile b/makefile index 6c91b0a..189d565 100644 --- a/makefile +++ b/makefile @@ -4,9 +4,9 @@ VERSION := $(shell git describe --tags --always --dirty) CFLAGS := -Wall -Wextra -Werror -O2 \ -DVERSION=\"$(VERSION)\" \ - $(shell pkg-config --cflags libcjson ncurses) + $(shell pkg-config --cflags libcjson ncurses libgit2) -LDFLAGS := $(shell pkg-config --libs libcjson ncurses) +LDFLAGS := $(shell pkg-config --libs libcjson ncurses libgit2) SRC := src/main.c src/ui.c src/utils.c src/notes.c TARGET := notewrapper diff --git a/src/main.c b/src/main.c index 1916e4a..31dc783 100644 --- a/src/main.c +++ b/src/main.c @@ -96,9 +96,21 @@ int main(int argc, char *argv[]) { debug("Parsing the JSON config"); cJSON *json = cJSON_Parse(data); - if (!json) { - free(data); - } + + if (!json) { + const char *error_ptr = cJSON_GetErrorPtr(); + + if (error_ptr) { + error( + 1, + "program", + "JSON parse error near: %.30s", + error_ptr + ); + } + + free(data); + } error(!json, "program", "JSON parse error"); // Parse all of the directories which will( or do) contain the vaults @@ -210,6 +222,9 @@ int main(int argc, char *argv[]) { char **backupDirectoriesArray = malloc(numDirectories * sizeof(char *)); char **rsyncArgs = NULL; int rsyncArgsNumber = 0; + int gitEnabled = 0; + char *gitSignatureName = NULL; + char *gitSignatureEmail = NULL; cJSON *backupJSON = cJSON_GetObjectItem(json, "backup"); if (backupJSON && cJSON_IsObject(backupJSON)) { cJSON *doesBackupJSON = cJSON_GetObjectItem(backupJSON, "enable"); @@ -285,11 +300,36 @@ int main(int argc, char *argv[]) { "value is from an unexpected type", configPath); } + + // Configure Git backup + cJSON *gitJSON = cJSON_GetObjectItem(backupJSON, "git"); + + if (gitJSON && cJSON_IsObject(gitJSON)) { + cJSON *gitEnableJSON = cJSON_GetObjectItem(gitJSON, "enable"); + cJSON *gitNameJSON = cJSON_GetObjectItem(gitJSON, "name"); + cJSON *gitEmailJSON = cJSON_GetObjectItem(gitJSON, "email"); + + error(!gitEnableJSON || !cJSON_IsBool(gitEnableJSON), "user", "Entry \"git.enable\" in %s must be a bool.", configPath); + error(!gitNameJSON || !cJSON_IsString(gitNameJSON), "user", "Entry \"git.name\" in %s must be a string.", configPath); + error(!gitEmailJSON || !cJSON_IsString(gitEmailJSON), "user", "Entry \"git.email\" in %s must be a string.", configPath); + + gitEnabled = cJSON_IsTrue(gitEnableJSON) ? 1 : 0; + + if (gitEnabled) { + gitSignatureName = strdup(cJSON_GetStringValue(gitNameJSON)); + gitSignatureEmail = strdup(cJSON_GetStringValue(gitEmailJSON)); + + error(gitSignatureName == NULL || gitSignatureEmail == NULL, "program", "malloc failed"); + + debug("git in %s is enabled with name \"%s\" and email \"%s\"", configPath, gitSignatureName, gitSignatureEmail); + } else { + debug("git in %s is disabled", configPath); + } + } else { + error(1, "user", "Entry \"git\" in %s must be an object containing \"enable\", \"name\", and \"email\".", configPath); + } } else { - error(1, "user", - "%s did not contained a enable value inside the backup section or the value is " - "from an unexpected type", - configPath); + error(1, "user", "%s did not contained a enable value inside the backup section or the value is from an unexpected type", configPath); } // handle rsyncs array of arguments. cJSON *rsyncArgsJSON = cJSON_GetObjectItem(backupJSON, "rsyncArgs"); @@ -314,9 +354,7 @@ int main(int argc, char *argv[]) { configPath); } } else { - debug("In %s, \"backup\" wasn't set or we encountered a abnormal type. Defaulting to " - "{\"enable\": false}.", - configPath); + debug("In %s, \"backup\" wasn't set or we encountered a abnormal type. Defaulting to {\"enable\": false}.", configPath); } backup_config_end: @@ -541,6 +579,12 @@ int main(int argc, char *argv[]) { initscr(); // initialize ncurses + // initialized libgit2 + if (gitEnabled) { + int libgit2InitializationTimes = git_libgit2_init(); + error(libgit2InitializationTimes != 1, "program", "libgit2 has been initialized %d times (instead of one)", libgit2InitializationTimes); + } + int shouldExit = 0; while (!shouldExit) { // this loop is the vault selector @@ -620,6 +664,11 @@ int main(int argc, char *argv[]) { int shouldChangeVault = 0; // we must find the directory from which the vault comes again. char *notesDirectoryString = getDirectoryFromVault(vaultSelected, vaultsArray, vaultsCount, vaultsCountForEachDirectory, directoriesArray, numDirectories, shouldDebug); + + if (gitEnabled) { // we check if we need to git init the vault each time we enter it + ensureGitDirectory(notesDirectoryString, vaultSelected, gitSignatureName, gitSignatureEmail, shouldDebug); + } + while (!shouldExit && !shouldChangeVault) { // this loop is the note selector int filesCount = 0; @@ -712,6 +761,14 @@ int main(int argc, char *argv[]) { appendToFile(fullPath, "\n", shouldDebug); } openEditor(fullPath, editorToOpen, shouldRender, shouldJumpToEnd, shouldDebug); + if (gitEnabled) { // After closing a file, we need to update the git repo (if git is enable + char *date = getDateAndTime(); + char *commitMsg = malloc(29); + snprintf(commitMsg, 29, "Update - %s", date); + gitBackupUpdate(notesDirectoryString, vaultSelected, gitSignatureName, gitSignatureEmail, commitMsg, shouldDebug); + free(date); + free(commitMsg); + } free(fullPath); } else if (strcmp(noteSelected, "Create new note") == 0) { note_creation: diff --git a/src/utils.c b/src/utils.c index 0f49b6e..d8f6bba 100644 --- a/src/utils.c +++ b/src/utils.c @@ -113,6 +113,185 @@ static void ensureDir(const char *path, const int shouldDebug) { } } +void ensureGitDirectory(const char *path, const char *vault, const char *signatureName, const char *signatureEmail, const int shouldDebug) { + git_repository *repo = NULL; + char *fullPath = malloc(PATH_MAX); + snprintf(fullPath, PATH_MAX, "%s/%s", path, vault); + + int git_repo_error_code = git_repository_open(&repo, fullPath); + if (git_repo_error_code) { // error (most probably that the git repo wasn't initialized) + int git_init_error_code = git_repository_init(&repo, fullPath, 0); // the 0 indicates that the directory isn't bare. See https://libgit2.org/docs/reference/main/repository/git_repository_init.html + error(git_init_error_code, "program", "git initialization didn't worked. Error code : %d\nSee https://libgit2.org/docs/reference/main/repository/git_repository_init.html for documentation\n\n%s", git_init_error_code, git_error_last()->message); + debug("git repo %s was initialized.", fullPath); + + // we need to setup the .gitignore + char *gitignorePath = malloc(PATH_MAX); + snprintf(gitignorePath, PATH_MAX, "%s/.gitignore", fullPath); + FILE *gitignore = fopen(gitignorePath, "a"); + error(gitignore == NULL, "program", "failed to open/create %s\n%s", gitignorePath, git_error_last()->message); + fputs("*\n", gitignore); + fputs("!*/\n", gitignore); + fputs("!*.md\n", gitignore); + fclose(gitignore); + debug("Succesfully wrote .gitignore to %s", gitignorePath); + git_repository_free(repo); // we free to be able to open it in gitBackupUpdate() + char *date = getDateAndTime(); + char *commitMsg = malloc(37); + snprintf(commitMsg, 37, "Initial commit - %s", date); + gitBackupUpdate(path, vault, signatureName, signatureEmail, commitMsg, shouldDebug); + free(date); + free(commitMsg); + } else { // the git repo already exists + debug("%s is already a git repo. Skipping initialization...", fullPath); + git_repository_free(repo); + } + + free(fullPath); +} + +void gitBackupUpdate(const char *path, const char *vault, const char *signatureName, const char *signatureEmail, const char *commitMsg, const int shouldDebug) { + git_repository *repo = NULL; + git_index *index = NULL; + git_tree *tree = NULL; + git_tree *head_tree = NULL; + git_commit *head_commit = NULL; + git_signature *signature = NULL; + git_diff *diff = NULL; + + git_oid tree_id; + git_oid commit_id; + + char *fullPath = malloc(PATH_MAX); + + error(fullPath == NULL, "program", "Failed to allocate path"); + + snprintf(fullPath, PATH_MAX, "%s/%s", path, vault); + + /* Open repository */ + int return_code = git_repository_open(&repo, fullPath); + + error(return_code, "program", "Failed to open Git repository\n%s", git_error_last()->message); + + /* Get the index */ + return_code = git_repository_index(&index, repo); + error(return_code, "program", "Failed to get Git index\n%s", git_error_last()->message); + + /* git add . */ + return_code = git_index_add_all(index, NULL, 0, NULL, NULL); + error(return_code, "program", "Failed to add files\n%s", git_error_last()->message); + + return_code = git_index_write(index); + error(return_code, "program", "Failed to write index\n%s", git_error_last()->message); + + /* + * Check whether there are changes to commit. + * + * For a new repository there is no HEAD yet, so the index + * itself tells us whether there is anything to commit. + */ + int has_changes = 0; + + if (git_repository_head_unborn(repo)) { + has_changes = git_index_entrycount(index) > 0; + } else { + git_oid head_oid; + + return_code = git_reference_name_to_id( + &head_oid, + repo, + "HEAD" + ); + error(return_code, "program", "Failed to get HEAD\n%s", git_error_last()->message); + + return_code = git_commit_lookup( + &head_commit, + repo, + &head_oid + ); + error(return_code, "program", "Failed to lookup HEAD commit\n%s", git_error_last()->message); + + return_code = git_commit_tree( + &head_tree, + head_commit + ); + error(return_code, "program", "Failed to get HEAD tree\n%s", git_error_last()->message); + + return_code = git_diff_tree_to_index( + &diff, + repo, + head_tree, + index, + NULL + ); + + error(return_code, "program", "Failed to create diff\n%s", git_error_last()->message); + + has_changes = git_diff_num_deltas(diff) > 0; + } + + /* Nothing changed, so don't create a commit */ + if (!has_changes) { + debug("No changes to commit in %s", fullPath); + goto cleanup; + } + + /* index → tree */ + return_code = git_index_write_tree(&tree_id, index); + error(return_code, "program", "Failed to write tree\n%s", git_error_last()->message); + + return_code = git_tree_lookup(&tree, repo, &tree_id); + error(return_code, "program", "Failed to lookup tree\n%s", git_error_last()->message); + + /* Author/committer */ + return_code = git_signature_now( + &signature, + signatureName, + signatureEmail + ); + error(return_code, "program", "Failed to create signature\n%s", git_error_last()->message); + + /* Create commit */ + if (git_repository_head_unborn(repo)) { + return_code = git_commit_create_v( + &commit_id, + repo, + "HEAD", + signature, + signature, + NULL, + commitMsg, + tree, + 0 + ); + } else { + return_code = git_commit_create_v( + &commit_id, + repo, + "HEAD", + signature, + signature, + NULL, + commitMsg, + tree, + 1, + head_commit + ); + } + error(return_code, "program", "Failed to create commit\n%s", git_error_last()->message); + + debug("Created commit %s in %s", commitMsg, fullPath); + +cleanup: + git_diff_free(diff); + git_signature_free(signature); + git_tree_free(tree); + git_tree_free(head_tree); + git_commit_free(head_commit); + git_index_free(index); + git_repository_free(repo); + free(fullPath); +} + void initAppFilesAndDirs(const char *home, const int shouldDebug) { // sometimes .cache and .config doesn't exist. Such as with github actions machines char config_dir[PATH_MAX]; @@ -157,15 +336,30 @@ void initAppFilesAndDirs(const char *home, const int shouldDebug) { " \"enable\": false,\n" " \"directory\": {\n" " \"~/Documents/\": \"path/to/backup/\"\n" - " },\n" + " },\n" " \"interval\": \"weekly\",\n" - " \"rsyncArgs\": [\"-Lqah\", \"--update\"]\n" + " \"rsyncArgs\": [\"-Lqah\", \"--update\"],\n" + " \"git\": {\n" + " \"enable\": false,\n" + " \"email\": \"mail@example.com\",\n" + " \"name\": \"Example Example\"\n" + " }\n" " }\n" "}\n"); fclose(w); } +char *getDateAndTime() { + char *return_value = malloc(20); + time_t now = time(NULL); + struct tm tm_now; + localtime_r(&now, &tm_now); + + strftime(return_value, sizeof(return_value), "%Y-%m-%%d %H:%M:%%S", &tm_now); + return return_value; +} + void handleBackups(char **sourceDirectoryArray, const int sourceNumber, char **destinationDirectoryArray, const char *homeDir, const int interval, const char **rsyncArguments, const int rsyncArgumentsNumber, const int forceBackup, int *backupMessage, const int shouldDebug) { int shouldBackup = 0; diff --git a/src/utils.h b/src/utils.h index be1d260..207aee9 100644 --- a/src/utils.h +++ b/src/utils.h @@ -17,6 +17,8 @@ #include #include #include +#include + #ifndef VERSION #define VERSION "dev" #endif @@ -85,4 +87,12 @@ will be added in the function. backupMessage will be set to 1 if a backup was launched.*/ void handleBackups(char **sourceDirectoryArray, const int sourceDirectoryNumber, char **destinationDirectoryArray, const char *homeDir, const int interval, const char **rsyncArgs, const int rsyncArgsNumber, const int forceBackup, int *backupMessage, const int shouldDebug); +/*Equivalent of git init. +If the directory wasn't git initialized. We do it.*/ +void ensureGitDirectory(const char *path, const char *vault, const char *signatureName, const char *signatureEmail, const int shouldDebug); +/*Configures git, stages everything, commits everything*/ +void gitBackupUpdate(const char *path, const char *vault, const char *signatureName, const char *signatureEmail, const char *commitMsg, const int shouldDebug); +// yyyy-mm-dd hh:mm:ss +// Don't forget to free the pointer. +char *getDateAndTime(); #endif From eb0d6b6e5de5fbd4fb0acf10321a2e7f1653e87b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Rivera?= Date: Fri, 14 Aug 2026 17:33:20 +0200 Subject: [PATCH 3/6] docs: document git backup --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5b74354..d031bba 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ Before building NoteWrapper, you must install the following dependencies: * `sed` * `ripgrep` * `fzf` +* `libgit2` You must also have a [supported editor (and their associated plugin if needed)](#editor-support) installed: @@ -200,7 +201,12 @@ Edit `~/.config/notewrapper/config.json`. If it does not exist, it will be creat "/other/paths/": "path/to/backup2" }, "interval": "weekly", - "rsyncArgs": ["-Lqah", "--update"] + "rsyncArgs": ["-Lqah", "--update"], + "git": { + "enable": false, + "name": "Example Example", + "email": "mail@example.com" + } } } ``` @@ -218,6 +224,8 @@ Edit `~/.config/notewrapper/config.json`. If it does not exist, it will be creat * `backup.directory`: backup's destination for each directory * `backup.interval`: backup frequency (`daily`, `weekly`, `monthly`, or integer) * `backup.rsyncArgs`: arguments passed to `rsync` +* `git.enable`: enable git version control for backups +* `git.name` and `git.email` are for the commits signature Note: * Directories must end with `/`. @@ -228,6 +236,8 @@ Note: * `journalRegex` must match if the file name ends with `.md` and if it doesn't. * It is recommended to keep `-q` or `--quiet` flag in `rsyncArgs` to avoid interference with `ncurses`. * If `rsync` fails, you will see it inside `ncurses`. +* If you want the git backup but without rsync, you can leave `backup.directory` empty or map each directory to itself. When enabled, git will apply to all your vaults. +* Each vault gets its own git repository that will be created the first time you select the vault with the option enabled. --- From a83da3e1aa2b4f7b324b9520abc42d04afc04e9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Rivera?= Date: Fri, 14 Aug 2026 17:36:48 +0200 Subject: [PATCH 4/6] docs: clarify editor specific features Added better explaination and footnote to avoid people oppening issues/disscussion threads in Vivify instead of NoteWrapper. See https://github.com/jannis-baum/Vivify/discussions/299 --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d031bba..11eb366 100644 --- a/README.md +++ b/README.md @@ -158,11 +158,13 @@ NoteWrapper relies on certain editor features, so not all functionality is suppo ### Features requiring editor support: -* **Bufferless rendering**: updates the rendered view while typing (without saving) -* **Cursor following**: rendered view follows the cursor position -* **Jump to end on open**: automatically moves the cursor to the end of the file +* **Bufferless rendering**: updates the rendered view while typing (without saving). You won't need to save the file to see it updated in your browser. +* **Cursor following**: rendered view follows the cursor position. When you scroll in your editor, it will automatically scroll your browser view. +* **Jump to end on open**: automatically moves the cursor to the end of the file when oppening. -The first two features depend on [Vivify's editor integration](https://github.com/jannis-baum/Vivify?tab=readme-ov-file#existing-integration) and are mainly useful if you want external Markdown rendering in your browser. +The first two features depend on [Vivify's editor integration](https://github.com/jannis-baum/Vivify?tab=readme-ov-file#existing-integration)[^1] and are mainly useful if you want external Markdown rendering in your browser. + +[^1]: Eventhough the features are Vivify-related, please report bugs and ask questions in this repo. If your editor does not support these features, you can implement a plugin using [Vivify's API](https://github.com/jannis-baum/Vivify?tab=readme-ov-file#editor-support). From df0301fdcd943d0d377f83e07ecea67be733b7a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Rivera?= Date: Fri, 14 Aug 2026 17:44:59 +0200 Subject: [PATCH 5/6] fix: clang format --- src/main.c | 53 ++++++++++----------- src/utils.c | 135 ++++++++++++++++++---------------------------------- src/utils.h | 2 +- 3 files changed, 72 insertions(+), 118 deletions(-) diff --git a/src/main.c b/src/main.c index b9d53c5..e9a106b 100644 --- a/src/main.c +++ b/src/main.c @@ -97,20 +97,15 @@ int main(int argc, char *argv[]) { cJSON *json = cJSON_Parse(data); - if (!json) { - const char *error_ptr = cJSON_GetErrorPtr(); - - if (error_ptr) { - error( - 1, - "program", - "JSON parse error near: %.30s", - error_ptr - ); - } - - free(data); - } + if (!json) { + const char *error_ptr = cJSON_GetErrorPtr(); + + if (error_ptr) { + error(1, "program", "JSON parse error near: %.30s", error_ptr); + } + + free(data); + } error(!json, "program", "JSON parse error"); // Parse all of the directories which will( or do) contain the vaults @@ -303,24 +298,24 @@ int main(int argc, char *argv[]) { // Configure Git backup cJSON *gitJSON = cJSON_GetObjectItem(backupJSON, "git"); - + if (gitJSON && cJSON_IsObject(gitJSON)) { cJSON *gitEnableJSON = cJSON_GetObjectItem(gitJSON, "enable"); cJSON *gitNameJSON = cJSON_GetObjectItem(gitJSON, "name"); cJSON *gitEmailJSON = cJSON_GetObjectItem(gitJSON, "email"); - + error(!gitEnableJSON || !cJSON_IsBool(gitEnableJSON), "user", "Entry \"git.enable\" in %s must be a bool.", configPath); error(!gitNameJSON || !cJSON_IsString(gitNameJSON), "user", "Entry \"git.name\" in %s must be a string.", configPath); error(!gitEmailJSON || !cJSON_IsString(gitEmailJSON), "user", "Entry \"git.email\" in %s must be a string.", configPath); - + gitEnabled = cJSON_IsTrue(gitEnableJSON) ? 1 : 0; - + if (gitEnabled) { gitSignatureName = strdup(cJSON_GetStringValue(gitNameJSON)); gitSignatureEmail = strdup(cJSON_GetStringValue(gitEmailJSON)); - + error(gitSignatureName == NULL || gitSignatureEmail == NULL, "program", "malloc failed"); - + debug("git in %s is enabled with name \"%s\" and email \"%s\"", configPath, gitSignatureName, gitSignatureEmail); } else { debug("git in %s is disabled", configPath); @@ -581,8 +576,8 @@ int main(int argc, char *argv[]) { // initialized libgit2 if (gitEnabled) { - int libgit2InitializationTimes = git_libgit2_init(); - error(libgit2InitializationTimes != 1, "program", "libgit2 has been initialized %d times (instead of one)", libgit2InitializationTimes); + int libgit2InitializationTimes = git_libgit2_init(); + error(libgit2InitializationTimes != 1, "program", "libgit2 has been initialized %d times (instead of one)", libgit2InitializationTimes); } int shouldExit = 0; @@ -667,7 +662,7 @@ int main(int argc, char *argv[]) { char *notesDirectoryString = getDirectoryFromVault(vaultSelected, vaultsArray, vaultsCount, vaultsCountForEachDirectory, directoriesArray, numDirectories, shouldDebug); if (gitEnabled) { // we check if we need to git init the vault each time we enter it - ensureGitDirectory(notesDirectoryString, vaultSelected, gitSignatureName, gitSignatureEmail, shouldDebug); + ensureGitDirectory(notesDirectoryString, vaultSelected, gitSignatureName, gitSignatureEmail, shouldDebug); } while (!shouldExit && !shouldChangeVault) { @@ -773,12 +768,12 @@ int main(int argc, char *argv[]) { } openEditor(fullPath, editorToOpen, shouldRender, shouldJumpToEnd, shouldDebug); if (gitEnabled) { // After closing a file, we need to update the git repo (if git is enable - char *date = getDateAndTime(); - char *commitMsg = malloc(29); - snprintf(commitMsg, 29, "Update - %s", date); - gitBackupUpdate(notesDirectoryString, vaultSelected, gitSignatureName, gitSignatureEmail, commitMsg, shouldDebug); - free(date); - free(commitMsg); + char *date = getDateAndTime(); + char *commitMsg = malloc(29); + snprintf(commitMsg, 29, "Update - %s", date); + gitBackupUpdate(notesDirectoryString, vaultSelected, gitSignatureName, gitSignatureEmail, commitMsg, shouldDebug); + free(date); + free(commitMsg); } free(fullPath); } else if (strcmp(noteSelected, "Create new note") == 0) { diff --git a/src/utils.c b/src/utils.c index d8f6bba..3e7bcc0 100644 --- a/src/utils.c +++ b/src/utils.c @@ -114,39 +114,40 @@ static void ensureDir(const char *path, const int shouldDebug) { } void ensureGitDirectory(const char *path, const char *vault, const char *signatureName, const char *signatureEmail, const int shouldDebug) { - git_repository *repo = NULL; - char *fullPath = malloc(PATH_MAX); - snprintf(fullPath, PATH_MAX, "%s/%s", path, vault); - - int git_repo_error_code = git_repository_open(&repo, fullPath); - if (git_repo_error_code) { // error (most probably that the git repo wasn't initialized) - int git_init_error_code = git_repository_init(&repo, fullPath, 0); // the 0 indicates that the directory isn't bare. See https://libgit2.org/docs/reference/main/repository/git_repository_init.html - error(git_init_error_code, "program", "git initialization didn't worked. Error code : %d\nSee https://libgit2.org/docs/reference/main/repository/git_repository_init.html for documentation\n\n%s", git_init_error_code, git_error_last()->message); - debug("git repo %s was initialized.", fullPath); - - // we need to setup the .gitignore - char *gitignorePath = malloc(PATH_MAX); - snprintf(gitignorePath, PATH_MAX, "%s/.gitignore", fullPath); - FILE *gitignore = fopen(gitignorePath, "a"); - error(gitignore == NULL, "program", "failed to open/create %s\n%s", gitignorePath, git_error_last()->message); - fputs("*\n", gitignore); - fputs("!*/\n", gitignore); - fputs("!*.md\n", gitignore); - fclose(gitignore); - debug("Succesfully wrote .gitignore to %s", gitignorePath); - git_repository_free(repo); // we free to be able to open it in gitBackupUpdate() - char *date = getDateAndTime(); - char *commitMsg = malloc(37); - snprintf(commitMsg, 37, "Initial commit - %s", date); - gitBackupUpdate(path, vault, signatureName, signatureEmail, commitMsg, shouldDebug); - free(date); - free(commitMsg); - } else { // the git repo already exists - debug("%s is already a git repo. Skipping initialization...", fullPath); - git_repository_free(repo); - } + git_repository *repo = NULL; + char *fullPath = malloc(PATH_MAX); + snprintf(fullPath, PATH_MAX, "%s/%s", path, vault); - free(fullPath); + int git_repo_error_code = git_repository_open(&repo, fullPath); + if (git_repo_error_code) { // error (most probably that the git repo wasn't initialized) + int git_init_error_code = git_repository_init(&repo, fullPath, 0); // the 0 indicates that the directory isn't bare. See https://libgit2.org/docs/reference/main/repository/git_repository_init.html + error(git_init_error_code, "program", "git initialization didn't worked. Error code : %d\nSee https://libgit2.org/docs/reference/main/repository/git_repository_init.html for documentation\n\n%s", + git_init_error_code, git_error_last()->message); + debug("git repo %s was initialized.", fullPath); + + // we need to setup the .gitignore + char *gitignorePath = malloc(PATH_MAX); + snprintf(gitignorePath, PATH_MAX, "%s/.gitignore", fullPath); + FILE *gitignore = fopen(gitignorePath, "a"); + error(gitignore == NULL, "program", "failed to open/create %s\n%s", gitignorePath, git_error_last()->message); + fputs("*\n", gitignore); + fputs("!*/\n", gitignore); + fputs("!*.md\n", gitignore); + fclose(gitignore); + debug("Succesfully wrote .gitignore to %s", gitignorePath); + git_repository_free(repo); // we free to be able to open it in gitBackupUpdate() + char *date = getDateAndTime(); + char *commitMsg = malloc(37); + snprintf(commitMsg, 37, "Initial commit - %s", date); + gitBackupUpdate(path, vault, signatureName, signatureEmail, commitMsg, shouldDebug); + free(date); + free(commitMsg); + } else { // the git repo already exists + debug("%s is already a git repo. Skipping initialization...", fullPath); + git_repository_free(repo); + } + + free(fullPath); } void gitBackupUpdate(const char *path, const char *vault, const char *signatureName, const char *signatureEmail, const char *commitMsg, const int shouldDebug) { @@ -162,7 +163,7 @@ void gitBackupUpdate(const char *path, const char *vault, const char *signatureN git_oid commit_id; char *fullPath = malloc(PATH_MAX); - + error(fullPath == NULL, "program", "Failed to allocate path"); snprintf(fullPath, PATH_MAX, "%s/%s", path, vault); @@ -196,33 +197,16 @@ void gitBackupUpdate(const char *path, const char *vault, const char *signatureN } else { git_oid head_oid; - return_code = git_reference_name_to_id( - &head_oid, - repo, - "HEAD" - ); + return_code = git_reference_name_to_id(&head_oid, repo, "HEAD"); error(return_code, "program", "Failed to get HEAD\n%s", git_error_last()->message); - return_code = git_commit_lookup( - &head_commit, - repo, - &head_oid - ); + return_code = git_commit_lookup(&head_commit, repo, &head_oid); error(return_code, "program", "Failed to lookup HEAD commit\n%s", git_error_last()->message); - return_code = git_commit_tree( - &head_tree, - head_commit - ); + return_code = git_commit_tree(&head_tree, head_commit); error(return_code, "program", "Failed to get HEAD tree\n%s", git_error_last()->message); - return_code = git_diff_tree_to_index( - &diff, - repo, - head_tree, - index, - NULL - ); + return_code = git_diff_tree_to_index(&diff, repo, head_tree, index, NULL); error(return_code, "program", "Failed to create diff\n%s", git_error_last()->message); @@ -243,39 +227,14 @@ void gitBackupUpdate(const char *path, const char *vault, const char *signatureN error(return_code, "program", "Failed to lookup tree\n%s", git_error_last()->message); /* Author/committer */ - return_code = git_signature_now( - &signature, - signatureName, - signatureEmail - ); + return_code = git_signature_now(&signature, signatureName, signatureEmail); error(return_code, "program", "Failed to create signature\n%s", git_error_last()->message); /* Create commit */ if (git_repository_head_unborn(repo)) { - return_code = git_commit_create_v( - &commit_id, - repo, - "HEAD", - signature, - signature, - NULL, - commitMsg, - tree, - 0 - ); + return_code = git_commit_create_v(&commit_id, repo, "HEAD", signature, signature, NULL, commitMsg, tree, 0); } else { - return_code = git_commit_create_v( - &commit_id, - repo, - "HEAD", - signature, - signature, - NULL, - commitMsg, - tree, - 1, - head_commit - ); + return_code = git_commit_create_v(&commit_id, repo, "HEAD", signature, signature, NULL, commitMsg, tree, 1, head_commit); } error(return_code, "program", "Failed to create commit\n%s", git_error_last()->message); @@ -351,13 +310,13 @@ void initAppFilesAndDirs(const char *home, const int shouldDebug) { } char *getDateAndTime() { - char *return_value = malloc(20); - time_t now = time(NULL); - struct tm tm_now; - localtime_r(&now, &tm_now); + char *return_value = malloc(20); + time_t now = time(NULL); + struct tm tm_now; + localtime_r(&now, &tm_now); - strftime(return_value, sizeof(return_value), "%Y-%m-%%d %H:%M:%%S", &tm_now); - return return_value; + strftime(return_value, sizeof(return_value), "%Y-%m-%%d %H:%M:%%S", &tm_now); + return return_value; } void handleBackups(char **sourceDirectoryArray, const int sourceNumber, char **destinationDirectoryArray, const char *homeDir, const int interval, const char **rsyncArguments, const int rsyncArgumentsNumber, diff --git a/src/utils.h b/src/utils.h index 207aee9..c064e52 100644 --- a/src/utils.h +++ b/src/utils.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -17,7 +18,6 @@ #include #include #include -#include #ifndef VERSION #define VERSION "dev" From 5f37e48030ad1ffd29d280940f7e4e195e5f0faf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Rivera?= Date: Fri, 14 Aug 2026 17:46:49 +0200 Subject: [PATCH 6/6] CI: add libgit2 dependency --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9ad3995..f8628f4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,6 +28,7 @@ jobs: pkg-config \ libcjson-dev \ libncurses-dev \ + libgit2-dev \ git \ neovim - name: Prepare environnement