Compare commits

...
Author SHA1 Message Date
tomasr edc979dccb Merge (#63) from tomasriveral/git-backup
feat: git version control for backup
2026-08-14 17:48:15 +02:00
tomasr 5f37e48030 CI: add libgit2 dependency 2026-08-14 17:46:49 +02:00
tomasr df0301fdcd fix: clang format 2026-08-14 17:44:59 +02:00
tomasr 518d4fd09c Merge branch 'main' into git-backup 2026-08-14 17:43:17 +02:00
tomasr a83da3e1aa 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
2026-08-14 17:36:48 +02:00
tomasr eb0d6b6e5d docs: document git backup 2026-08-14 17:33:20 +02:00
tomasr 82d016fdd7 feat: git version control for backup 2026-08-14 17:28:15 +02:00
tomasr 2e03edac2b flake: add libgit2 dependency 2026-08-14 15:14:53 +02:00
tomasr 689c3074c0 Merge pull request #60 from tomasriveral/flake-update/2026-08-10 2026-08-10 09:13:26 +02:00
github-actions[bot] e54933e5fa chore: weekly flake update 2026-08-10 04:45:43 +00:00
tomasr 3f9471531d Merge pull request #58 from tomasriveral/flake-update/2026-08-03 2026-08-03 11:11:57 +02:00
github-actions[bot] ab1928551a chore: weekly flake update 2026-08-03 06:31:05 +00:00
tomasr e75c8e5815 Merge pull request #57 from tomasriveral/flake-update/2026-07-27 2026-07-27 13:06:13 +02:00
github-actions[bot] 693e85d974 chore: weekly flake update 2026-07-27 06:37:15 +00:00
tomasr b9f71f580a Merge pull request #55 from tomasriveral/flake-update/2026-07-13 2026-07-13 19:24:40 -05:00
github-actions[bot] c6aac80951 chore: weekly flake update 2026-07-13 06:26:42 +00:00
tomasr b395fe1072 Merge pull request #52 from tomasriveral/flake-update/2026-06-22 2026-06-23 01:43:20 +00:00
github-actions[bot] 27cc3de27d chore: weekly flake update 2026-06-22 09:18:09 +00:00
tomasr 18538f3250 Merge pull request #51 from tomasriveral/flake-update/2026-06-15 2026-06-15 09:34:33 +00:00
github-actions[bot] 6c7cc413da chore: weekly flake update 2026-06-15 09:31:17 +00:00
tomasr e670ef935f Merge pull request #50 from tomasriveral/flake-update/2026-06-08
[Automated] Weekly flake update 2026-06-08
2026-06-08 08:53:32 +00:00
github-actions[bot] 29d86955f4 chore: weekly flake update 2026-06-08 08:19:19 +00:00
tomasr 0f9ec32bfc Merge (#49) from tomasriveral/release/v2.3.0
[Automatic] Release PR v2.3.0
2026-06-02 14:41:02 +02:00
github-actions[bot] 5ad8f2f99e chore(release): v2.3.0 2026-06-02 12:40:33 +00:00
tomasr c6e590e57c Merge (#48) from tomasriveral/vault-indicator
feat: vault indicator
2026-06-02 14:39:23 +02:00
tomasr d47bdb82eb feat: vault indicator 2026-06-02 14:37:39 +02:00
tomasr 50748eeac0 Merge (#47) from tomasriveral/ncurse-quick-selection
feat: ncurses quick select
2026-06-02 14:32:43 +02:00
tomasr 19a8f7e07f feat: ncurses quick select 2026-06-02 14:30:16 +02:00
tomasr 1c17c62835 Merge (#46) from tomasriveral/journal-return-to-vault
feat: button return to note selection from journal entry selection
2026-06-02 14:05:49 +02:00
10 changed files with 324 additions and 26 deletions

No files matched your search

+1
View File
@@ -28,6 +28,7 @@ jobs:
pkg-config \ pkg-config \
libcjson-dev \ libcjson-dev \
libncurses-dev \ libncurses-dev \
libgit2-dev \
git \ git \
neovim neovim
- name: Prepare environnement - name: Prepare environnement
+17 -5
View File
@@ -41,6 +41,7 @@ Before building NoteWrapper, you must install the following dependencies:
* `sed` * `sed`
* `ripgrep` * `ripgrep`
* `fzf` * `fzf`
* `libgit2`
You must also have a [supported editor (and their associated plugin if needed)](#editor-support) installed: You must also have a [supported editor (and their associated plugin if needed)](#editor-support) installed:
@@ -157,11 +158,13 @@ NoteWrapper relies on certain editor features, so not all functionality is suppo
### Features requiring editor support: ### Features requiring editor support:
* **Bufferless rendering**: updates the rendered view while typing (without saving) * **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 * **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 * **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). 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).
@@ -200,7 +203,12 @@ Edit `~/.config/notewrapper/config.json`. If it does not exist, it will be creat
"/other/paths/": "path/to/backup2" "/other/paths/": "path/to/backup2"
}, },
"interval": "weekly", "interval": "weekly",
"rsyncArgs": ["-Lqah", "--update"] "rsyncArgs": ["-Lqah", "--update"],
"git": {
"enable": false,
"name": "Example Example",
"email": "mail@example.com"
}
} }
} }
``` ```
@@ -218,6 +226,8 @@ Edit `~/.config/notewrapper/config.json`. If it does not exist, it will be creat
* `backup.directory`: backup's destination for each directory * `backup.directory`: backup's destination for each directory
* `backup.interval`: backup frequency (`daily`, `weekly`, `monthly`, or integer) * `backup.interval`: backup frequency (`daily`, `weekly`, `monthly`, or integer)
* `backup.rsyncArgs`: arguments passed to `rsync` * `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: Note:
* Directories must end with `/`. * Directories must end with `/`.
@@ -228,6 +238,8 @@ Note:
* `journalRegex` must match if the file name ends with `.md` and if it doesn't. * `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`. * 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 `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.
--- ---
Generated
+3 -3
View File
@@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1779560665, "lastModified": 1786106723,
"narHash": "sha256-tpyBcxPpcQb8ukyNF7DoCwfSY3VPsxHoYwj00Cayv5o=", "narHash": "sha256-zDSUbpoeo/9ZmD2+wXnzxoo1+uhL8vxc0b8yuYMKYq0=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "64c08a7ca051951c8eae34e3e3cb1e202fe36786", "rev": "f13ff45afd1bb73e640eaa08a7066dbed07e3238",
"type": "github" "type": "github"
}, },
"original": { "original": {
+3 -1
View File
@@ -13,7 +13,7 @@
in { in {
packages.default = pkgs.stdenv.mkDerivation (finalAttrs: { packages.default = pkgs.stdenv.mkDerivation (finalAttrs: {
pname = "notewrapper"; pname = "notewrapper";
version = "v2.2.1"; version = "v2.3.0";
src = ./.; src = ./.;
@@ -30,6 +30,7 @@
ripgrep ripgrep
fzf fzf
gnused gnused
libgit2
]; ];
buildPhase = '' buildPhase = ''
@@ -51,6 +52,7 @@
valgrind valgrind
clang-tools clang-tools
gnumake gnumake
libgit2
]; ];
}; };
+2 -2
View File
@@ -4,9 +4,9 @@ VERSION := $(shell git describe --tags --always --dirty)
CFLAGS := -Wall -Wextra -Werror -O2 \ CFLAGS := -Wall -Wextra -Werror -O2 \
-DVERSION=\"$(VERSION)\" \ -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 SRC := src/main.c src/ui.c src/utils.c src/notes.c
TARGET := notewrapper TARGET := notewrapper
+67 -12
View File
@@ -96,7 +96,14 @@ int main(int argc, char *argv[]) {
debug("Parsing the JSON config"); debug("Parsing the JSON config");
cJSON *json = cJSON_Parse(data); cJSON *json = cJSON_Parse(data);
if (!json) { if (!json) {
const char *error_ptr = cJSON_GetErrorPtr();
if (error_ptr) {
error(1, "program", "JSON parse error near: %.30s", error_ptr);
}
free(data); free(data);
} }
error(!json, "program", "JSON parse error"); error(!json, "program", "JSON parse error");
@@ -210,6 +217,9 @@ int main(int argc, char *argv[]) {
char **backupDirectoriesArray = malloc(numDirectories * sizeof(char *)); char **backupDirectoriesArray = malloc(numDirectories * sizeof(char *));
char **rsyncArgs = NULL; char **rsyncArgs = NULL;
int rsyncArgsNumber = 0; int rsyncArgsNumber = 0;
int gitEnabled = 0;
char *gitSignatureName = NULL;
char *gitSignatureEmail = NULL;
cJSON *backupJSON = cJSON_GetObjectItem(json, "backup"); cJSON *backupJSON = cJSON_GetObjectItem(json, "backup");
if (backupJSON && cJSON_IsObject(backupJSON)) { if (backupJSON && cJSON_IsObject(backupJSON)) {
cJSON *doesBackupJSON = cJSON_GetObjectItem(backupJSON, "enable"); cJSON *doesBackupJSON = cJSON_GetObjectItem(backupJSON, "enable");
@@ -285,11 +295,36 @@ int main(int argc, char *argv[]) {
"value is from an unexpected type", "value is from an unexpected type",
configPath); 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 { } else {
error(1, "user", debug("git in %s is disabled", configPath);
"%s did not contained a enable value inside the backup section or the value is " }
"from an unexpected type", } else {
configPath); 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);
} }
// handle rsyncs array of arguments. // handle rsyncs array of arguments.
cJSON *rsyncArgsJSON = cJSON_GetObjectItem(backupJSON, "rsyncArgs"); cJSON *rsyncArgsJSON = cJSON_GetObjectItem(backupJSON, "rsyncArgs");
@@ -314,9 +349,7 @@ int main(int argc, char *argv[]) {
configPath); configPath);
} }
} else { } else {
debug("In %s, \"backup\" wasn't set or we encountered a abnormal type. Defaulting to " debug("In %s, \"backup\" wasn't set or we encountered a abnormal type. Defaulting to {\"enable\": false}.", configPath);
"{\"enable\": false}.",
configPath);
} }
backup_config_end: backup_config_end:
@@ -541,6 +574,12 @@ backup_config_end:
initscr(); // initialize ncurses 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; int shouldExit = 0;
while (!shouldExit) { while (!shouldExit) {
// this loop is the vault selector // this loop is the vault selector
@@ -598,7 +637,8 @@ backup_config_end:
*backupMessage = 0; // we reset it to show the message only once *backupMessage = 0; // we reset it to show the message only once
vaultSelected = ncursesSelect(vaultsArray, "Select vault to open (Use arrows or WASD, Enter to select):", vaultsCount, extraOptions, " ", "Or select an option below", "", shouldDebug); vaultSelected = ncursesSelect(vaultsArray, "Select vault to open (Use arrows or WASD, Enter to select or use 1-9 to quick select or use 1-9 to quick select):", vaultsCount, extraOptions, " ",
"Or select an option below", "", shouldDebug);
// now that we won't use vaultsArray in this iteration of the loop, we should free it and // now that we won't use vaultsArray in this iteration of the loop, we should free it and
// all its elements. (As this is memory in the heap and not the stack and thus is our // all its elements. (As this is memory in the heap and not the stack and thus is our
@@ -620,6 +660,11 @@ backup_config_end:
int shouldChangeVault = 0; int shouldChangeVault = 0;
// we must find the directory from which the vault comes again. // we must find the directory from which the vault comes again.
char *notesDirectoryString = getDirectoryFromVault(vaultSelected, vaultsArray, vaultsCount, vaultsCountForEachDirectory, directoriesArray, numDirectories, shouldDebug); 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) { while (!shouldExit && !shouldChangeVault) {
// this loop is the note selector // this loop is the note selector
int filesCount = 0; int filesCount = 0;
@@ -666,8 +711,9 @@ backup_config_end:
goto note_creation; goto note_creation;
} }
} }
noteSelected = char vaultMessage[PATH_MAX];
ncursesSelect(filesArray, "Select note or journal to open (Use arrows or WASD, Enter to select):", filesCount, extraNotesOptions, " ", "Or select an option below", "", shouldDebug); snprintf(vaultMessage, PATH_MAX, "%s: Select note or journal to open (Use arrows or WASD, Enter to select or use 1-9 to quick select):", vaultSelected);
noteSelected = ncursesSelect(filesArray, vaultMessage, filesCount, extraNotesOptions, " ", "Or select an option below", "", shouldDebug);
// now that we won't use filesArray in this iteration of the loop, we should free it // now that we won't use filesArray in this iteration of the loop, we should free it
// and all its elements. (As this is memory in the heap and not the stack and thus // and all its elements. (As this is memory in the heap and not the stack and thus
// is our responsability to manage) // is our responsability to manage)
@@ -720,7 +766,16 @@ backup_config_end:
openEditor(fullPath, editorToOpen, shouldRender, shouldJumpToEnd, shouldDebug); openEditor(fullPath, editorToOpen, shouldRender, shouldJumpToEnd, shouldDebug);
free(fullPath); free(fullPath);
} }
free(returnNoteSelection); 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) { } else if (strcmp(noteSelected, "Create new note") == 0) {
note_creation: note_creation:
noteSelected = createNewNote(notesDirectoryString, vaultSelected, bypassSelectionNote, bypassSelectionNoteValue, journalRegex, shouldDebug); noteSelected = createNewNote(notesDirectoryString, vaultSelected, bypassSelectionNote, bypassSelectionNoteValue, journalRegex, shouldDebug);
@@ -741,7 +796,7 @@ backup_config_end:
const char *yesNo[] = {"No, go back to note selection.", "Yes."}; const char *yesNo[] = {"No, go back to note selection.", "Yes."};
char *answer = ncursesSelect((char **)yesNo, char *answer = ncursesSelect((char **)yesNo,
"Are you sure you want to delete the entire vault? This can " "Are you sure you want to delete the entire vault? This can "
"not be undone (Use arrows or WASD, Enter to select):", "not be undone (Use arrows or WASD, Enter to select or use 1-9 to quick select):",
1, 1, " ", "", "", shouldDebug); 1, 1, " ", "", "", shouldDebug);
debug("You answered: %s for deleting the vault %s", answer, vaultSelected); debug("You answered: %s for deleting the vault %s", answer, vaultSelected);
if (strcmp(answer, "Yes.") == 0) { if (strcmp(answer, "Yes.") == 0) {
+2 -1
View File
@@ -260,7 +260,8 @@ char *updateJournal(char *path, char *journal, char *timeFormat, int *journalWas
// element // element
// we must now select to create new entry or to enter in old one // we must now select to create new entry or to enter in old one
char *selectedOption = ncursesSelect(entryArray, "Create new entry or acces old entry (Use arrows or WASD, Enter to select):", extraOptions, entryCount - extraOptions, " ", " ", "", char *selectedOption =
ncursesSelect(entryArray, "Create new entry or acces old entry (Use arrows or WASD, Enter to select or use 1-9 to quick select):", extraOptions, entryCount - extraOptions, " ", " ", "",
shouldDebug); // the "Create new entry for the journal %s" will be the only options. All shouldDebug); // the "Create new entry for the journal %s" will be the only options. All
// other will be extraOptions. This is made so that "Create [...] %s" will // other will be extraOptions. This is made so that "Create [...] %s" will
// always be on top // always be on top
+65 -1
View File
@@ -6,7 +6,8 @@ void createNewVault(char **directoriesArray, int directoryCount, char **vaultsAr
int emptyWarning = 0; // set to 1 later if inpted empty name int emptyWarning = 0; // set to 1 later if inpted empty name
input_screen: input_screen:
// choose in which directory the vault will be created // choose in which directory the vault will be created
char *dirToVault = ncursesSelect(directoriesArray, "Select a directory, in which the vault will remain (Use arrows or WASD, Enter to select)", directoryCount, 0, "", "", " ", shouldDebug); char *dirToVault =
ncursesSelect(directoriesArray, "Select a directory, in which the vault will remain (Use arrows or WASD, Enter to select or use 1-9 to quick select)", directoryCount, 0, "", "", " ", shouldDebug);
char *vaultName = malloc(PATH_MAX); char *vaultName = malloc(PATH_MAX);
if (!bypass) { // if won't bypass (if -v or --vault weren't set) if (!bypass) { // if won't bypass (if -v or --vault weren't set)
echo(); echo();
@@ -309,6 +310,69 @@ char *ncursesSelect(char **options, char *optionsText, int optionsNumber, int ex
highlight = 0; highlight = 0;
} }
break; break;
case '1':
if (optionsNumber + extraOptionsNumber >= 1) {
debug("Quickselected option 1.");
highlight = 0;
goto end_loop;
}
break;
case '2':
if (optionsNumber + extraOptionsNumber >= 1) {
debug("Quickselected option 2.");
highlight = 1;
goto end_loop;
}
break;
case '3':
if (optionsNumber + extraOptionsNumber >= 1) {
debug("Quickselected option 3.");
highlight = 2;
goto end_loop;
}
break;
case '4':
if (optionsNumber + extraOptionsNumber >= 1) {
debug("Quickselected option 4.");
highlight = 3;
goto end_loop;
}
break;
case '5':
if (optionsNumber + extraOptionsNumber >= 1) {
debug("Quickselected option 5.");
highlight = 4;
goto end_loop;
}
break;
case '6':
if (optionsNumber + extraOptionsNumber >= 1) {
debug("Quickselected option 6.");
highlight = 5;
goto end_loop;
}
break;
case '7':
if (optionsNumber + extraOptionsNumber >= 1) {
debug("Quickselected option 7.");
highlight = 6;
goto end_loop;
}
break;
case '8':
if (optionsNumber + extraOptionsNumber >= 1) {
debug("Quickselected option 8.");
highlight = 7;
goto end_loop;
}
break;
case '9':
if (optionsNumber + extraOptionsNumber >= 1) {
debug("Quickselected option 9.");
highlight = 8;
goto end_loop;
}
break;
case 10: //\n case 10: //\n
goto end_loop; goto end_loop;
} }
+154 -1
View File
@@ -113,6 +113,144 @@ 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) { void initAppFilesAndDirs(const char *home, const int shouldDebug) {
// sometimes .cache and .config doesn't exist. Such as with github actions machines // sometimes .cache and .config doesn't exist. Such as with github actions machines
char config_dir[PATH_MAX]; char config_dir[PATH_MAX];
@@ -159,13 +297,28 @@ void initAppFilesAndDirs(const char *home, const int shouldDebug) {
" \"~/Documents/\": \"path/to/backup/\"\n" " \"~/Documents/\": \"path/to/backup/\"\n"
" },\n" " },\n"
" \"interval\": \"weekly\",\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"
"}\n"); "}\n");
fclose(w); 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, 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) { const int forceBackup, int *backupMessage, const int shouldDebug) {
int shouldBackup = 0; int shouldBackup = 0;
+10
View File
@@ -6,6 +6,7 @@
#include <dirent.h> #include <dirent.h>
#include <errno.h> #include <errno.h>
#include <ftw.h> #include <ftw.h>
#include <git2.h>
#include <limits.h> #include <limits.h>
#include <ncurses.h> #include <ncurses.h>
#include <pwd.h> #include <pwd.h>
@@ -17,6 +18,7 @@
#include <sys/wait.h> #include <sys/wait.h>
#include <time.h> #include <time.h>
#include <unistd.h> #include <unistd.h>
#ifndef VERSION #ifndef VERSION
#define VERSION "dev" #define VERSION "dev"
#endif #endif
@@ -85,4 +87,12 @@ will be added in the function.
backupMessage will be set to 1 if a backup was launched.*/ 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, 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); 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 #endif