summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAargh Rai <aargh.rai+git@gmail.com>2026-10-20 11:20:15 +0530
committerAargh Rai <aargh.rai+git@gmail.com>2026-10-20 11:20:15 +0530
commit0f3997d6ff2d25c0941824ec3daa3c8e767bd73e (patch)
treed6110910cfa976b5bfd57139e3810d2924bbba9e /src
parent790131205281884585d4418ae7093636d6e61c97 (diff)
wrote the entire thing using libgit2
Diffstat (limited to 'src')
-rw-r--r--src/backup.c173
-rw-r--r--src/backup_streamer.c54
-rw-r--r--src/config.c24
-rw-r--r--src/ignore.c66
-rw-r--r--[-rwxr-xr-x]src/lib.h61
-rw-r--r--[-rwxr-xr-x]src/main.c143
-rw-r--r--src/recursive_reader.c21
-rw-r--r--src/tmp_repo.c72
8 files changed, 220 insertions, 394 deletions
diff --git a/src/backup.c b/src/backup.c
deleted file mode 100644
index 060777c..0000000
--- a/src/backup.c
+++ /dev/null
@@ -1,173 +0,0 @@
-#include "lib.h"
-#include <dirent.h>
-#include <regex.h>
-
-int check_path_type(const char* path) {
- struct stat path_stat;
- if (stat(path, &path_stat) != 0) {
- return path_type_unknown;
- }
- if (S_ISDIR(path_stat.st_mode)) {
- return path_type_directory;
- }
- return path_type_file;
-}
-
-int follows_ignore_rules(char* path, char* ignore_line) {
- char lastbitofthepath[PATH_MAX];
- int k = 0;
- for (int i = 0; path[i] != 0; i++) {
- if (path[i] == '/') {
- lastbitofthepath[k] = 0;
- k = 0;
- continue;
- }
- lastbitofthepath[k++] = path[i];
- }
- if (k != 0 && lastbitofthepath[k] != 0) {
- lastbitofthepath[k] = 0;
- }
-
- regex_t re;
- int ret;
-
- if (regcomp(&re, ignore_line, REG_EXTENDED) != 0) {
- return 0;
- }
-
- ret = regexec(&re, lastbitofthepath, (size_t) 0, NULL, 0);
- regfree(&re);
-
- if (ret == 0) {
- return 1;
- } else {
- return 0;
- }
-}
-
-int should_ignore(char* path, char* ignore_instructions, int VERBOSE) {
- int i = 0, k = 0;
- char line[PATH_MAX];
- while (ignore_instructions[i] != 0) {
- if (ignore_instructions[i] != '\n') {
- line[k++] = ignore_instructions[i];
- i++;
- continue;
- }
- line[k] = 0;
- k = 0;
- if (!follows_ignore_rules(path, line)) {
- i++;
- continue;
- }
- if (VERBOSE) {
- printf("Ignoring %s due to ignore rule %s\n", path, line);
- }
- return 1;
- }
- return 0;
-}
-
-void copy_to_backup(char* source_path, int VERBOSE) {
- char destination_path[PATH_MAX];
- int index_of_first_slash = -1;
- for (int i = 0; i < PATH_MAX; i++) {
- if (source_path[i] != '/') continue;
- index_of_first_slash = i;
- break;
- }
-
- if (index_of_first_slash == -1) {
- // I DON'T EVEN KNOW HOW TO HANDLE THIS
- fprintf(stderr, "Weird path issue encountered, failed to copy to source\nPath encountered: %s", source_path);
- return;
- }
-
- int i = 0;
- for (; i < index_of_first_slash; i++) {
- destination_path[i] = source_path[i];
- }
- destination_path[i++] = '.';
- destination_path[i++] = 'b';
- destination_path[i] = 'k';
- while (++i < PATH_MAX) {
- destination_path[i] = source_path[i - 3];
- }
-
- if (VERBOSE) {
- printf("%s -> %s\n", source_path, destination_path);
- }
-
- verify_parent_dirs_exist(destination_path, VERBOSE);
-
- char command[2 * PATH_MAX + 10];
- snprintf(command, 2 * PATH_MAX + 10, "cp \"%s\" \"%s\"", source_path, destination_path);
- system(command);
- if (VERBOSE) {
- printf("Executing: %s\n", command);
- }
-}
-
-void push(char* directories, int cursor, const char* to_add) {
- int i = 0;
- for (; to_add[i] != 0; i++) {
- directories[cursor * PATH_MAX + i] = to_add[i];
- }
- while (i < PATH_MAX) {
- directories[cursor * PATH_MAX + i++] = 0;
- }
-}
-
-void backup(char* target, char* ignore_instructions, int VERBOSE) {
- int file_type = check_path_type(target);
- if (file_type != path_type_directory) {
- fprintf(stderr, "Input not a directory\n");
- return;
- }
-
- int cursor = 0;
- char* directories = malloc(PATH_MAX * PATH_MAX * sizeof(*directories));
- push(directories, cursor, target);
- while (cursor != -1) {
- char* path = &directories[(cursor--) * PATH_MAX];
- DIR* dir_ptr = opendir(path);
- if (dir_ptr == NULL) {
- fprintf(stderr, "Couldn't open the directory: %s\n", path);
- continue;
- }
-
- struct dirent* entry_ptr;
- char full_path[PATH_MAX];
- int i = 0;
- for (; path[i] != 0; i++) {
- full_path[i] = path[i];
- }
- full_path[i++] = '/';
- while ((entry_ptr = readdir(dir_ptr)) != NULL) {
- if (
- strcmp(entry_ptr->d_name, ".") == 0 ||
- strcmp(entry_ptr->d_name, "..") == 0
- ) continue;
- int j = 0;
- for (; entry_ptr->d_name[j] != 0; j++) {
- full_path[i + j] = entry_ptr->d_name[j];
- }
- full_path[i + j] = 0;
-
- if (VERBOSE) {
- printf("Looking at: %s\n", full_path);
- }
- if (should_ignore(full_path, ignore_instructions, VERBOSE)) {
- continue;
- }
-
- if (entry_ptr->d_type == DT_REG) {
- copy_to_backup(full_path, VERBOSE);
- continue;
- }
- push(directories, ++cursor, full_path);
- }
- closedir(dir_ptr);
- }
- free(directories);
-}
diff --git a/src/backup_streamer.c b/src/backup_streamer.c
new file mode 100644
index 0000000..3b5929b
--- /dev/null
+++ b/src/backup_streamer.c
@@ -0,0 +1,54 @@
+#include <unistd.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <string.h>
+
+#include "lib.h"
+
+struct backup_streamer backup_streamer_init(char *cwd) {
+ struct backup_streamer output;
+ output.cwd_length = strnlen(cwd, PATH_MAX);
+ char *end = stpncpy(output.to_prefix, cwd, PATH_MAX - 3);
+ *(end++) = '.';
+ *(end++) = 'b';
+ *(end++) = 'k';
+ *(end++) = 0;
+ return output;
+}
+
+void get_backup_destination(struct backup_streamer *streamer, char* from) {
+ char *cursor = stpncpy(streamer->cursor, streamer->to_prefix, PATH_MAX);
+ from += streamer->cwd_length;
+ int n = (cursor - streamer->cursor) - 2;
+ strncpy(cursor, from, PATH_MAX - n);
+}
+
+int is_dir(char *path) {
+ struct stat info;
+ if (stat(path, &info) != 0) return -1;
+ return S_ISDIR(info.st_mode);
+}
+
+void backup_file(char *from, char *to) {
+ ensure_parents_exist(to);
+ pid_t pid;
+ if ((pid = fork()) == 0) {
+ char *argv[] = {"/usr/bin/cp", from, to, NULL};
+ execv(argv[0], argv);
+ }
+ assert(waitpid(pid, NULL, 0) != -1);
+}
+
+void ensure_parents_exist(char *path) {
+ int n = strnlen(path, PATH_MAX);
+ for (int i = 1; i < n; i++) {
+ if (path[i] != '/') continue;
+ path[i] = 0;
+ if (is_dir(path) == -1) {
+ debugf("mkdir %s\n", path);
+ assert(mkdir(path, 0755) != -1);
+ }
+ path[i] = '/';
+ }
+}
diff --git a/src/config.c b/src/config.c
deleted file mode 100644
index ab0eb05..0000000
--- a/src/config.c
+++ /dev/null
@@ -1,24 +0,0 @@
-#include "lib.h"
-#include <stdio.h>
-#include <stdlib.h>
-
-char config[PATH_MAX] = {0};
-char* get_default_config() {
- if (config[0] != 0) return config;
-
- const char* xdg_config_home = getenv("XDG_CONFIG_HOME");
- const char* home = getenv("HOME");
-
- if (home == NULL) {
- fprintf(stderr, "HOME environment variable not set, failed to determine the config path");
- return NULL;
- }
-
- if (xdg_config_home != NULL && xdg_config_home[0] != 0) {
- snprintf(config, PATH_MAX, "%s/backbencher/.backupignore", xdg_config_home);
- } else {
- snprintf(config, PATH_MAX, "%s/.config/backbencher/.backupignore", home);
- }
-
- return config;
-}
diff --git a/src/ignore.c b/src/ignore.c
deleted file mode 100644
index fbfb8ed..0000000
--- a/src/ignore.c
+++ /dev/null
@@ -1,66 +0,0 @@
-#include "lib.h"
-
-int _;
-
-void mkdir_if_not_exist(char* path, int VERBOSE) {
- if (mkdir(path, S_IRWXU) < 0) return;
- if (VERBOSE) {
- printf("(Created to create directory %s)", path);
- }
-}
-
-void verify_parent_dirs_exist(char* file_path, int VERBOSE) {
- for (int i = 0; file_path[i] != 0; i++) {
- if (file_path[i] == '/') file_path[i] = 0;
- if (file_path[i] != 0) continue;
- mkdir_if_not_exist(file_path, VERBOSE);
- file_path[i] = '/';
- }
-}
-
-char* get_ignore_file(char* ignore_file, int VERBOSE) {
- verify_parent_dirs_exist(ignore_file, VERBOSE);
- FILE* file = fopen(ignore_file, "r");
- char* default_config;
-
- if (file != 0) {
- float current_pointer = ftell(file);
- fseek(file, 0, SEEK_END);
- long int file_size = ftell(file);
- fseek(file, current_pointer, SEEK_SET);
-
- char* contents = malloc(file_size * sizeof(*contents));
- _ = fread(contents, 1, file_size, file);
- fclose(file);
-
- return contents;
- }
-
- if (strcmp(ignore_file, (default_config = get_default_config())) != 0) {
- return NULL;
- }
-
- // mainly used for first time running
- if (default_config == NULL) {
- return NULL;
- }
-
- file = fopen(default_config, "w");
- if (file == 0) {
- return NULL;
- }
-
- int content_length = strlen(DEFAULT_CONFIG_CONTENTS);
- _ = fwrite(DEFAULT_CONFIG_CONTENTS, 1, content_length, file);
- fclose(file);
- if (VERBOSE) {
- printf("Creating %s\n", default_config);
- }
-
- char* contents = malloc(content_length * sizeof(char));
- for (int i = 0; i < content_length; i++) {
- contents[i] = DEFAULT_CONFIG_CONTENTS[i];
- }
- return contents;
-}
-
diff --git a/src/lib.h b/src/lib.h
index 999e7d7..6c098c7 100755..100644
--- a/src/lib.h
+++ b/src/lib.h
@@ -1,24 +1,47 @@
-#include <unistd.h>
-#include <string.h>
-#include <stdio.h>
-#include <sys/stat.h>
-#include <dirent.h>
+#ifndef LIB_H_
+#define LIB_H_
+
+#include <git2.h>
+#include <linux/limits.h>
#include <stdio.h>
-#include <stdlib.h>
-#define NULL_STR (char*)0
-#define DEFAULT_CONFIG_CONTENTS "^node_modules$\n^target$\n^build$\n^dist$\n^\\.next$\n^\\.output$\n^\\.vercel$\n^\\.wrangler$\n^\\.venv$\n^\\.astro$\n^\\.devenv$\n^__pycache__$"
+#define debugf(...) if (debug) (void)printf("[debug] " __VA_ARGS__)
+
+int is_git_metadata_dir(char *path);
+
+struct tmp_repo {
+ git_repository* repo;
+ char path[70];
+};
+struct tmp_repo tmp_repo_init();
+void tmp_repo_deinit(struct tmp_repo repo);
+
+int check_for_backupignore();
+void copy_over_backupignore(struct tmp_repo repo);
+
+int is_ignored(struct tmp_repo repo, char *path);
+
+struct recursive_reader {
+ FILE *pfile;
+ char *line;
+ size_t capacity;
+};
+struct recursive_reader recursive_reader_init();
+int recursive_reader_read(struct recursive_reader *reader);
+void recursive_reader_deinit(struct recursive_reader reader);
+
+struct backup_streamer {
+ int cwd_length;
+ char to_prefix[PATH_MAX];
+ char cursor[PATH_MAX];
+};
+struct backup_streamer backup_streamer_init(char *cwd);
+void get_backup_destination(struct backup_streamer *streamer, char* from);
-typedef enum {
- path_type_unknown,
- path_type_directory,
- path_type_file
-} path_type;
+int is_dir(char *path);
+void backup_file(char *from, char *to);
+void ensure_parents_exist(char *path);
-char* get_default_config();
-char* shift(int* argc, char*** argv);
-char* get_ignore_file(char* ignore_file, int VERBOSE);
-void mkdir_if_not_exist(char* path, int VERBOSE);
-void verify_parent_dirs_exist(char* file_path, int VERBOSE);
+int debug;
-void backup(char* target, char* ignore_instructions, int VERBOSE);
+#endif // LIB_H_
diff --git a/src/main.c b/src/main.c
index 7d33850..1d419fa 100755..100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,125 +1,44 @@
-#include "lib.h"
+#include <unistd.h>
+#include <assert.h>
-#define VERSION "1.0"
+#include "lib.h"
-void help() {
- printf(
-"Backup your files\n"
-"Usage: Backbencher [OPTIONS] [input]\n"
-"Arguments:\n"
-"\t[input]... The path of the folder to backup\n"
-"Options:\n"
-"\t-i --ignore-file <filename> Specify which files to exclude\n\t\t(default = ~/.config/backbencher/.backupignore)\n"
-"\t-h --help \tPrint help\n"
-"\t-v --verbose \tVerbose logging\n"
-"\t-V --version \tPrint version\n"
- );
-}
+int main(int argc, char **argv) {
+ (void)argv;
-void assign(char* to, char* from) {
- int i = 0;
- for (; from[i] != 0; i++) {
- to[i] = from[i];
- }
- to[i] = 0;
-}
+ debug = argc > 1;
+ if (check_for_backupignore()) return 1;
+ debugf(".backupignore found\n");
-int source_main(int argc, char** argv) {
- shift(&argc, &argv);
+ struct tmp_repo repo = tmp_repo_init();
+ assert(repo.repo != NULL);
+ debugf("%s initialized\n", repo.path);
- char* ignore_file = get_default_config();
- char* target = NULL_STR;
- int VERBOSE = 0;
+ copy_over_backupignore(repo);
+ debugf("ignore file loaded\n");
- while (1) {
- char* segment = shift(&argc, &argv);
- if (segment == NULL_STR) {
- break;
- }
- else if (segment[0] == '-' && segment[1] == '-') {
- if (strcmp(&segment[2], "ignore-file") == 0) {
- assign(&ignore_file[0], shift(&argc, &argv));
- if (ignore_file == NULL_STR) {
- fprintf(stderr, "Missing <filename> for --ignore-file\n");
- return 1;
- }
- }
- else if (strcmp(&segment[2], "help") == 0) {
- help();
- return 0;
- }
- else if (strcmp(&segment[2], "verbose") == 0) {
- VERBOSE = 1;
- }
- else if (strcmp(&segment[2], "version") == 0) {
- printf(VERSION);
- printf("\n");
- return 0;
- }
- } else if (segment[0] == '-') {
- if (segment[1] == 'i') {
- assign(&ignore_file[0], shift(&argc, &argv));
- if (ignore_file == NULL_STR) {
- fprintf(stderr, "Missing <filename> for --ignore-file\n");
- return 1;
- }
- }
- else if (segment[1] == 'h') {
- help();
- return 0;
- }
- else if (segment[1] == 'v') {
- VERBOSE = 1;
- }
- else if (segment[1] == 'V') {
- printf(VERSION);
- printf("\n");
- return 0;
- }
- } else {
- target = segment;
- }
- }
+ char *cwd = getcwd(NULL, 0);
+ assert(cwd != NULL);
+ struct backup_streamer streamer = backup_streamer_init(cwd);
+ free(cwd);
- if (target == NULL_STR) {
- fprintf(stderr, "Missing [input] - The folder to backup\n");
- return 1;
- }
+ struct recursive_reader reader = recursive_reader_init();
+ while (recursive_reader_read(&reader) != -1) {
+ if (is_ignored(repo, reader.line)) continue;
+ if (is_dir(reader.line)) continue;
- if (VERBOSE) {
- printf("Target folder: %s\n", target);
- printf("Using ignore file: %s\n", ignore_file);
- printf("Using verbosity: %d\n", VERBOSE);
+ get_backup_destination(&streamer, reader.line);
+ debugf("cp %s %s\n", reader.line, streamer.cursor);
+ backup_file(reader.line, streamer.cursor);
}
- char* ignore_instructions = get_ignore_file(ignore_file, VERBOSE);
- if (ignore_instructions == NULL) {
- fprintf(stderr, "I don't have permissions to read the ignore file\n");
- return 1;
- }
- if (VERBOSE) {
- printf("Ignoring rules:\n%s\n", ignore_instructions);
- }
-
- backup(target, ignore_instructions, VERBOSE);
-
- free(ignore_instructions);
+ recursive_reader_deinit(reader);
+ tmp_repo_deinit(repo);
+ git_repository_free(repo.repo);
+ git_libgit2_shutdown();
return 0;
}
-int main(int argc, char** argv) {
- int result = source_main(argc, argv);
- if (result != 0) {
- printf("\n");
- help();
- }
- return result;
-}
-
-char* shift(int* argc, char*** argv) {
- if (argc == 0) return NULL_STR;
- --(*argc);
- char* output = **argv;
- ++(*argv);
- return output;
-}
+#include "tmp_repo.c"
+#include "recursive_reader.c"
+#include "backup_streamer.c"
diff --git a/src/recursive_reader.c b/src/recursive_reader.c
new file mode 100644
index 0000000..0a32131
--- /dev/null
+++ b/src/recursive_reader.c
@@ -0,0 +1,21 @@
+#include <assert.h>
+
+#include "lib.h"
+
+struct recursive_reader recursive_reader_init() {
+ FILE *pipe = popen("find \"$PWD\" -print", "r");
+ assert(pipe != NULL);
+ return (struct recursive_reader) { pipe, NULL, 0 };
+}
+
+int recursive_reader_read(struct recursive_reader *reader) {
+ int n = getline(&reader->line, &reader->capacity, reader->pfile);
+ reader->line[n - 1] = 0;
+ return n;
+}
+
+void recursive_reader_deinit(struct recursive_reader reader) {
+ free(reader.line);
+ int status = pclose(reader.pfile);
+ assert(status != -1);
+}
diff --git a/src/tmp_repo.c b/src/tmp_repo.c
new file mode 100644
index 0000000..ff4536c
--- /dev/null
+++ b/src/tmp_repo.c
@@ -0,0 +1,72 @@
+#include <unistd.h>
+#include <sys/random.h>
+#include <sys/wait.h>
+#include <assert.h>
+#include <string.h>
+
+#include "lib.h"
+
+struct tmp_repo tmp_repo_init() {
+ unsigned char rng_bytes[64];
+ assert(getrandom(rng_bytes, sizeof(rng_bytes), 0) == sizeof(rng_bytes));
+
+ char repo_path[70] = "/tmp/";
+ for (int i = 0; i < 64; i++) {
+ repo_path[5 + i] = (char)('a' + rng_bytes[i] % 26);
+ }
+ repo_path[69] = '\0';
+
+ git_libgit2_init();
+
+ git_repository *repo = NULL;
+ assert(git_repository_init(&repo, repo_path, 0) == 0);
+ struct tmp_repo output;
+ memcpy(output.path, repo_path, 70);
+ output.repo = repo;
+ return output;
+}
+
+void tmp_repo_deinit(struct tmp_repo repo) {
+ pid_t pid;
+ if ((pid = fork()) == 0) {
+ char *argv[] = {"/usr/bin/rm", "-rf", repo.path, NULL};
+ execv(argv[0], argv);
+ }
+ assert(waitpid(pid, NULL, 0) != -1);
+}
+
+int check_for_backupignore() {
+ FILE *file = fopen(".backupignore", "r");
+ if (file == NULL) {
+ fprintf(stderr, "Missing .backupignore file in the current directory\n");
+ return 1;
+ } else fclose(file);
+ return 0;
+}
+
+void copy_over_backupignore(struct tmp_repo repo) {
+ char cp_destination[81];
+ snprintf(cp_destination, 81, "%s/.gitignore", repo.path);
+
+ pid_t pid;
+ if ((pid = fork()) == 0) {
+ char *argv[] = {"/usr/bin/cp", ".backupignore", cp_destination, NULL};
+ execv(argv[0], argv);
+ }
+ assert(waitpid(pid, NULL, 0) != -1);
+}
+
+int is_ignored(struct tmp_repo repo, char *path) {
+ if (is_git_metadata_dir(path)) return 0;
+ int ignored;
+ assert(git_ignore_path_is_ignored(&ignored, repo.repo, path) >= 0);
+ return ignored;
+}
+
+int is_git_metadata_dir(char *path) {
+ int n = strnlen(path, PATH_MAX);
+ for (int i = 0; i < n - 6; i++) {
+ if (strncmp(path + i, "/.git/", 6) == 0) return 1;
+ }
+ return 0;
+}