From bb961f04378db2ccee831a7c9e053d43e5f07339 Mon Sep 17 00:00:00 2001 From: lisk77 Date: Sun, 21 Sep 2025 15:49:24 +0200 Subject: [PATCH] refactor(cli): moved all commands to seperate functions --- src/main.c | 636 ++++++++++++++++++++++++++++------------------------- 1 file changed, 332 insertions(+), 304 deletions(-) diff --git a/src/main.c b/src/main.c index 9340dda..c08f0b9 100644 --- a/src/main.c +++ b/src/main.c @@ -21,264 +21,221 @@ static void usage(int exitcode) { exit(exitcode); } -int main(int argc, char** argv) { - if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) { - usage(0); +static int init() { + if (mkdir(".merk", 0755) == 0) { + printf("Initialized merk repository\n"); } - const char* subcmd = argv[1]; - - if (strcmp(subcmd, "diff") != 0 && - strcmp(subcmd, "init") != 0 && - strcmp(subcmd, "status") != 0 && - strcmp(subcmd, "commit") != 0 && - strcmp(subcmd, "log") != 0 && - strcmp(subcmd, "test") != 0 - ) { - fprintf(stderr, "ERROR: Unknown subcommand: '%s'\n", subcmd); - usage(2); - } - - // Initializes a merk repository in the current directory - if (strcmp(subcmd, "init") == 0) { - if (argc != 2) { - printf("ERROR: too many agruments given!\n"); - printf("Usage: merk init"); - exit(1); - } - - if (mkdir(".merk", 0755) == 0) { - printf("Initialized merk repository\n"); - } - - if (errno == EEXIST) { - struct stat st; - if (stat(".merk", &st) == 0 && S_ISDIR(st.st_mode)) { - printf("This directory is already a merk repository\n"); - return 0; - } - } - - int branch = open(".merk/BRANCH", O_WRONLY | O_CREAT, 0666); - if (branch < 0) return -1; - write(branch, "main", 4); - close(branch); - - mkdir(".merk/objects", 0755); - mkdir(".merk/refs", 0755); - mkdir(".merk/refs/branches", 0755); - mkdir(".merk/info", 0755); - mkdir(".merk/logs", 0755); - mkdir(".merk/logs/branches", 0755); - - int main_branch = open(".merk/refs/branches/main", O_WRONLY | O_CREAT, 0666); - if (main_branch < 0) return -1; - close(main_branch); - - int main_info = open(".merk/info/main", O_WRONLY | O_CREAT, 0666); - if (main_info < 0) return -1; - close(main_info); - - int main_log = open(".merk/logs/branches/main", O_WRONLY | O_CREAT, 0666); - if (main_log < 0) return -1; - close(main_log); - - CommitLog* empty = commit_log_new(); - if (!empty) { perror("ERROR: failed to create commit log for branch main!"); return -1; } - if (!write_commit_log(empty, ".merk/logs/branches/main")) { perror("ERROR: failed to write initial main commit log!"); commit_log_free(empty); return -1; } - - commit_log_free(empty); - } - - // Prints out a visual representation of the diff of the files provided - else if (strcmp(subcmd, "diff") == 0) { - if (argc != 4) { - printf("ERROR: too many/little arguments given!\n"); - printf("Usage: merk diff "); - exit(1); - } - - File* file1; - switch (get_path_type(argv[2])) { - case PT_FILE: file1 = new_file(argv[2]); break; - case PT_DIR: file1 = NULL; printf("ERROR: first path is a directory and no file!"); break; - case PT_NOEXIST: file1 = NULL; printf("ERROR: first path does not exist!"); break; - default: file1 = NULL; printf("ERROR: unknown first path!"); - } - if (!file1) exit(1); - - File* file2; - switch (get_path_type(argv[3])) { - case PT_FILE: file2 = new_file(argv[3]); break; - case PT_DIR: file2 = NULL; printf("ERROR: second path is a directory and no file!"); break; - case PT_NOEXIST: file2 = NULL; printf("ERROR: second path does not exist!"); break; - default: file2 = NULL; printf("ERROR: unknown second path!"); - } - if (!file2) exit(1); - - ActionList* actions = myers_diff(file1, file2, 0, 0); - if (!actions) printf("ERROR: something went wrong while taking the diff!"); - else visualize_diff(file1, file2, actions); - } - // Gives a list of untracked files in the repo as well as meta information - else if (strcmp(subcmd, "status") == 0) { - if (argc != 2) { - printf("ERROR: too many arguments given!\n"); - printf("Usage: merk status"); - exit(1); - } - - FileInfoBuffer* files = file_info_buffer_new(); - char relative[PATH_MAX] = ""; - char* root = find_root(relative); - if (!root) { - list_free(files); + if (errno == EEXIST) { + struct stat st; + if (stat(".merk", &st) == 0 && S_ISDIR(st.st_mode)) { + printf("This directory is already a merk repository\n"); return 1; } - - walk(root, relative, relative, files, 0, root); - file_info_buffer_sort(files); - - char branch_path[PATH_MAX]; - snprintf(branch_path, sizeof(branch_path), "%s/.merk/BRANCH", root); - char* branch = get_file_content(branch_path); - char info_path[PATH_MAX]; - snprintf(info_path, sizeof(info_path), "%s/.merk/info/%s", root, branch); - - char log_path[PATH_MAX]; - snprintf(log_path, sizeof(log_path), "%s/.merk/logs/branches/%s", root, branch); - - CommitLog* log = commit_log_new(); - read_commit_log(log, log_path); - char* last_tree_hash = NULL; - if (log->len != 0) { - last_tree_hash = ((Commit*)log->items + (log->len))->tree_hash; - } - - BaseFileBuffer* tracked_list = base_file_buffer_new(); - read_base_file_list(tracked_list, info_path); - - StringBuffer* modified_files = string_buffer_new(); - StringBuffer* deleted_files = string_buffer_new(); - StringBuffer* untracked_files = string_buffer_new(); - - Changes* changes = calloc(1, sizeof(Changes)); - if (!changes) { - printf("ERROR: unable to allocate memory for changes!\n"); - return 1; - } - - changes->insertions = 0; - changes->deletions = 0; - - for (size_t idx = 0; idx < files->len; idx++) { - char* file_name = ((FileInfo*)files->items + idx)->name; - if (base_file_buffer_search(tracked_list, file_name)) continue; - string_buffer_push(untracked_files, file_name); - } - - for (size_t idx = 0; idx < files->len; idx++) { - char* file_name = ((FileInfo*)files->items + idx)->name; - if (base_file_buffer_search(tracked_list, file_name)) { - BaseFileInfo* base_file = base_file_buffer_search(tracked_list, file_name); - char base_file_hash[41]; - char id[2 + snprintf(NULL, 0, "%d", base_file->base_num) + strlen(file_name)]; - snprintf(id, sizeof(id), "%s %d", file_name, base_file->base_num); - object_hash(BaseFileObject, id, base_file_hash); - File* basefile = parse_object(base_file_hash, BaseFileObject, NULL, NULL); - if (!basefile) { - printf("ERROR: unable to parse base file %s!\n", file_name); - return 1; - } - - File* modified_file = new_file(file_name); - if (!modified_file) { - printf("ERROR: unable to read modified file %s!\n", file_name); - return 1; - } - - File* comparison_file = basefile; - - // If there are previous diffs, we need to compare against the last committed version - if (base_file->diff_num > 0) { - size_t prev_diff = base_file->diff_num - 1; - char prev_diff_hash[41]; - char id2[2 + snprintf(NULL, 0, "%zu", prev_diff) + strlen(file_name)]; - snprintf(id2, sizeof(id2), "%s %zu", file_name, prev_diff); - object_hash(FileDiffObject, id2, prev_diff_hash); - char hash[41]; - ActionList* last_diff = parse_object(prev_diff_hash, FileDiffObject, NULL, hash); - if (last_diff) { - File* last_version = apply_diff(basefile, last_diff); - if (last_version) { - comparison_file = last_version; - } - free_action_list(last_diff); - } - } - - ActionList* diff = myers_diff(comparison_file, modified_file, 0, 0); - if (!diff) { - printf("ERROR: unable to compute diff for file %s!\n", file_name); - return 1; - } - - if (diff->len != 0) { - for (size_t idx = 0; idx < diff->len; idx++) { - Action action = diff->actions[idx]; - if (action.type == DELETE) { - changes->deletions++; - } else if (action.type == INSERT) { - changes->insertions++; - } - } - string_buffer_push(modified_files, file_name); - } - - free_action_list(diff); - free_file(modified_file); - if (comparison_file != basefile) { - free_file(comparison_file); - } - free_file(basefile); - } - } - - for (size_t idx = 0; idx < tracked_list->len; idx++) { - char* file_name = ((BaseFileInfo*)tracked_list->items + idx)->name; - if (file_info_buffer_search(files, file_name)) continue; - else string_buffer_push(deleted_files, file_name); - } - - for (size_t idx = 0; idx < modified_files->len; idx++) { - printf("\x1b[36;1mM\x1b[0m \x1b[36m%s\x1b[0m\n", modified_files->items[idx]); - } - - for (size_t idx = 0; idx < deleted_files->len; idx++) { - printf("\x1b[31;4mD\x1b[0m \x1b[31;9m%s\x1b[0m\n", deleted_files->items[idx]); - } - - if (modified_files->len != 0 || deleted_files->len != 0) printf("\n"); - - for (size_t idx = 0; idx < untracked_files->len; idx++) { - printf("\x1b[31;1mU\x1b[0m \x1b[31m%s\x1b[0m\n", untracked_files->items[idx]); - } - - printf("\n\x1b[32;1m%d+\x1b[0m \x1b[31;1m%d-\x1b[0m on branch \x1b[39;1;4m%s\x1b[0m with %d commits\n", changes->insertions, changes->deletions, branch, log->len); - - file_info_buffer_free(files); - free(root); - return 0; } - // Commits changes to the repo - else if (strcmp(subcmd, "commit") == 0) { - if (argc < 3) { - printf("ERROR: too little arguments given!\n"); - printf("Usage: merk commit [-m ] ...\n"); - exit(1); - } - char* commit_message = NULL; + int branch = open(".merk/BRANCH", O_WRONLY | O_CREAT, 0666); + if (branch < 0) return 0; + write(branch, "main", 4); + close(branch); + + mkdir(".merk/objects", 0755); + mkdir(".merk/refs", 0755); + mkdir(".merk/refs/branches", 0755); + mkdir(".merk/info", 0755); + mkdir(".merk/logs", 0755); + mkdir(".merk/logs/branches", 0755); + + int main_branch = open(".merk/refs/branches/main", O_WRONLY | O_CREAT, 0666); + if (main_branch < 0) return 0; + close(main_branch); + + int main_info = open(".merk/info/main", O_WRONLY | O_CREAT, 0666); + if (main_info < 0) return 0; + close(main_info); + + int main_log = open(".merk/logs/branches/main", O_WRONLY | O_CREAT, 0666); + if (main_log < 0) return 0; + close(main_log); + + CommitLog* empty = commit_log_new(); + if (!empty) { perror("ERROR: failed to create commit log for branch main!"); return 0; } + if (!write_commit_log(empty, ".merk/logs/branches/main")) { perror("ERROR: failed to write initial main commit log!"); commit_log_free(empty); return 0; } + + commit_log_free(empty); + return 1; +} + +static int diff(char* file1_path, char* file2_path) { + File* file1; + switch (get_path_type(file1_path)) { + case PT_FILE: file1 = new_file(file1_path); break; + case PT_DIR: file1 = NULL; printf("ERROR: first path is a directory and no file!"); break; + case PT_NOEXIST: file1 = NULL; printf("ERROR: first path does not exist!"); break; + default: file1 = NULL; printf("ERROR: unknown first path!"); + } + if (!file1) exit(1); + + File* file2; + switch (get_path_type(file2_path)) { + case PT_FILE: file2 = new_file(file2_path); break; + case PT_DIR: file2 = NULL; printf("ERROR: second path is a directory and no file!"); break; + case PT_NOEXIST: file2 = NULL; printf("ERROR: second path does not exist!"); break; + default: file2 = NULL; printf("ERROR: unknown second path!"); + } + if (!file2) exit(1); + + ActionList* actions = myers_diff(file1, file2, 0, 0); + if (!actions) printf("ERROR: something went wrong while taking the diff!"); + else visualize_diff(file1, file2, actions); + return 1; +} + +static int status() { + FileInfoBuffer* files = file_info_buffer_new(); + char relative[PATH_MAX] = ""; + char* root = find_root(relative); + if (!root) { + list_free(files); + return 1; + } + + walk(root, relative, relative, files, 0, root); + file_info_buffer_sort(files); + + char branch_path[PATH_MAX]; + snprintf(branch_path, sizeof(branch_path), "%s/.merk/BRANCH", root); + char* branch = get_file_content(branch_path); + char info_path[PATH_MAX]; + snprintf(info_path, sizeof(info_path), "%s/.merk/info/%s", root, branch); + + char log_path[PATH_MAX]; + snprintf(log_path, sizeof(log_path), "%s/.merk/logs/branches/%s", root, branch); + + CommitLog* log = commit_log_new(); + read_commit_log(log, log_path); + char* last_tree_hash = NULL; + if (log->len != 0) { + last_tree_hash = ((Commit*)log->items + (log->len))->tree_hash; + } + + BaseFileBuffer* tracked_list = base_file_buffer_new(); + read_base_file_list(tracked_list, info_path); + + StringBuffer* modified_files = string_buffer_new(); + StringBuffer* deleted_files = string_buffer_new(); + StringBuffer* untracked_files = string_buffer_new(); + + Changes* changes = calloc(1, sizeof(Changes)); + if (!changes) { + printf("ERROR: unable to allocate memory for changes!\n"); + return 1; + } + + changes->insertions = 0; + changes->deletions = 0; + + for (size_t idx = 0; idx < files->len; idx++) { + char* file_name = ((FileInfo*)files->items + idx)->name; + if (base_file_buffer_search(tracked_list, file_name)) continue; + string_buffer_push(untracked_files, file_name); + } + + for (size_t idx = 0; idx < files->len; idx++) { + char* file_name = ((FileInfo*)files->items + idx)->name; + if (base_file_buffer_search(tracked_list, file_name)) { + BaseFileInfo* base_file = base_file_buffer_search(tracked_list, file_name); + char base_file_hash[41]; + char id[2 + snprintf(NULL, 0, "%d", base_file->base_num) + strlen(file_name)]; + snprintf(id, sizeof(id), "%s %d", file_name, base_file->base_num); + object_hash(BaseFileObject, id, base_file_hash); + File* basefile = parse_object(base_file_hash, BaseFileObject, NULL, NULL); + if (!basefile) { + printf("ERROR: unable to parse base file %s!\n", file_name); + return 1; + } + + File* modified_file = new_file(file_name); + if (!modified_file) { + printf("ERROR: unable to read modified file %s!\n", file_name); + return 1; + } + + File* comparison_file = basefile; + + if (base_file->diff_num > 0) { + size_t prev_diff = base_file->diff_num - 1; + char prev_diff_hash[41]; + char id2[2 + snprintf(NULL, 0, "%zu", prev_diff) + strlen(file_name)]; + snprintf(id2, sizeof(id2), "%s %zu", file_name, prev_diff); + object_hash(FileDiffObject, id2, prev_diff_hash); + char hash[41]; + ActionList* last_diff = parse_object(prev_diff_hash, FileDiffObject, NULL, hash); + if (last_diff) { + File* last_version = apply_diff(basefile, last_diff); + if (last_version) { + comparison_file = last_version; + } + free_action_list(last_diff); + } + } + + ActionList* diff = myers_diff(comparison_file, modified_file, 0, 0); + if (!diff) { + printf("ERROR: unable to compute diff for file %s!\n", file_name); + return 1; + } + + if (diff->len != 0) { + for (size_t idx = 0; idx < diff->len; idx++) { + Action action = diff->actions[idx]; + if (action.type == DELETE) { + changes->deletions++; + } else if (action.type == INSERT) { + changes->insertions++; + } + } + string_buffer_push(modified_files, file_name); + } + + free_action_list(diff); + free_file(modified_file); + if (comparison_file != basefile) { + free_file(comparison_file); + } + free_file(basefile); + } + } + + for (size_t idx = 0; idx < tracked_list->len; idx++) { + char* file_name = ((BaseFileInfo*)tracked_list->items + idx)->name; + if (file_info_buffer_search(files, file_name)) continue; + else string_buffer_push(deleted_files, file_name); + } + + for (size_t idx = 0; idx < modified_files->len; idx++) { + printf("\x1b[36;1mM\x1b[0m \x1b[36m%s\x1b[0m\n", modified_files->items[idx]); + } + + for (size_t idx = 0; idx < deleted_files->len; idx++) { + printf("\x1b[31;4mD\x1b[0m \x1b[31;9m%s\x1b[0m\n", deleted_files->items[idx]); + } + + if (modified_files->len != 0 || deleted_files->len != 0) printf("\n"); + + for (size_t idx = 0; idx < untracked_files->len; idx++) { + printf("\x1b[31;1mU\x1b[0m \x1b[31m%s\x1b[0m\n", untracked_files->items[idx]); + } + + printf("\n\x1b[32;1m%d+\x1b[0m \x1b[31;1m%d-\x1b[0m on branch \x1b[39;1;4m%s\x1b[0m with %d commits\n", changes->insertions, changes->deletions, branch, log->len); + + file_info_buffer_free(files); + free(root); + return 1; +} + +static int commit(int argc, char** argv) { + char* commit_message = NULL; int file_start_index = 2; if (argc >= 4 && strcmp(argv[2], "-m") == 0) { @@ -297,7 +254,6 @@ int main(int argc, char** argv) { exit(1); } - // Ensure files to commit are correctly identified for (int i = file_start_index; i < argc; i++) { if (strcmp(argv[i], "-m") == 0) { printf("ERROR: Unexpected -m flag in file list!\n"); @@ -731,67 +687,139 @@ int main(int argc, char** argv) { free(root); free_config(&config); free(commit_message); + return 1; +} + +static int commit_log() { + CommitLog* log = commit_log_new(); + if (!log) return 0; + + char* root = find_root(NULL); + if (!root) { + return 0; } - else if (strcmp(subcmd, "log") == 0) { - CommitLog* log = commit_log_new(); - if (!log) return 1; - char* root = find_root(NULL); - if (!root) { - return 1; - } + char branch_path[PATH_MAX]; + snprintf(branch_path, sizeof(branch_path), "%s/.merk/BRANCH", root); - char branch_path[PATH_MAX]; - snprintf(branch_path, sizeof(branch_path), "%s/.merk/BRANCH", root); + char* branch = get_file_content(branch_path); + if (!branch) { + free(root); + return 0; + } - char* branch = get_file_content(branch_path); - if (!branch) { - free(root); - return 1; - } + char log_path[PATH_MAX]; + snprintf(log_path, sizeof(log_path), "%s/.merk/logs/branches/%s", root, branch); - char log_path[PATH_MAX]; - snprintf(log_path, sizeof(log_path), "%s/.merk/logs/branches/%s", root, branch); + read_commit_log(log, log_path); - read_commit_log(log, log_path); - - char ref_path[PATH_MAX]; - snprintf(ref_path, sizeof(ref_path), "%s/.merk/refs/branches/%s", root, branch); - - int ref = open(ref_path, O_RDONLY); - if (ref < 0) { - free(branch); - free(root); - commit_log_free(log); - return -1; - } - char head[41] = ""; - read(ref, head, 40); - close(ref); - - for (size_t i = log->len; i != 0; i--) { - Commit* commit = (Commit*)log->items + i - 1; - char human_readable_time[32]; - time_t timestamp = atol(commit->committer.timestamp); - strftime(human_readable_time, sizeof(human_readable_time), "%Y-%m-%d %H:%M:%S %z", localtime(×tamp)); - - char* marker = (strcmp(commit->hash, head) == 0) ? "\x1b[32m@\x1b[0m" : "\x1b[34m◯\x1b[0m"; - - printf("%s \x1b[33;1m%.7s\x1b[0m: %s\n│ %s <\x1b[38;5;240m\x1b]8;;mailto:%s\x1b\\%s\x1b]8;;\x1b\\\x1b[0m> (\x1b[36m%s\x1b[0m)\n│\n", - marker, - commit->hash, - commit->message, - commit->committer.name, - commit->committer.email, - commit->committer.email, - human_readable_time - ); - } - if (log->len != 0) printf("┴\n"); + char ref_path[PATH_MAX]; + snprintf(ref_path, sizeof(ref_path), "%s/.merk/refs/branches/%s", root, branch); + int ref = open(ref_path, O_RDONLY); + if (ref < 0) { free(branch); free(root); commit_log_free(log); + return 0; + } + char head[41] = ""; + read(ref, head, 40); + close(ref); + + for (size_t i = log->len; i != 0; i--) { + Commit* commit = (Commit*)log->items + i - 1; + char human_readable_time[32]; + time_t timestamp = atol(commit->committer.timestamp); + strftime(human_readable_time, sizeof(human_readable_time), "%Y-%m-%d %H:%M:%S %z", localtime(×tamp)); + + char* marker = (strcmp(commit->hash, head) == 0) ? "\x1b[32m@\x1b[0m" : "\x1b[34m◯\x1b[0m"; + + printf("%s \x1b[33;1m%.7s\x1b[0m: %s\n│ %s <\x1b[38;5;240m\x1b]8;;mailto:%s\x1b\\%s\x1b]8;;\x1b\\\x1b[0m> (\x1b[36m%s\x1b[0m)\n│\n", + marker, + commit->hash, + commit->message, + commit->committer.name, + commit->committer.email, + commit->committer.email, + human_readable_time + ); + } + if (log->len != 0) printf("┴\n"); + + free(branch); + free(root); + commit_log_free(log); + return 1; +} + +int main(int argc, char** argv) { + if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) { + usage(0); + } + + const char* subcmd = argv[1]; + + if (strcmp(subcmd, "diff") != 0 && + strcmp(subcmd, "init") != 0 && + strcmp(subcmd, "status") != 0 && + strcmp(subcmd, "commit") != 0 && + strcmp(subcmd, "log") != 0 && + strcmp(subcmd, "test") != 0 + ) { + fprintf(stderr, "ERROR: Unknown subcommand: '%s'\n", subcmd); + usage(2); + } + + // Initializes a merk repository in the current directory + if (strcmp(subcmd, "init") == 0) { + if (argc != 2) { + printf("ERROR: too many arguments given!\n"); + printf("Usage: merk init"); + exit(1); + } + + init(); + } + + // Prints out a visual representation of the diff of the files provided + else if (strcmp(subcmd, "diff") == 0) { + if (argc != 4) { + printf("ERROR: too many/little arguments given!\n"); + printf("Usage: merk diff "); + exit(1); + } + + diff(argv[2], argv[3]); + } + // Gives a list of untracked files in the repo as well as meta information + else if (strcmp(subcmd, "status") == 0) { + if (argc != 2) { + printf("ERROR: too many arguments given!\n"); + printf("Usage: merk status"); + exit(1); + } + + status(); + } + // Commits changes to the repo + else if (strcmp(subcmd, "commit") == 0) { + if (argc < 3) { + printf("ERROR: too little arguments given!\n"); + printf("Usage: merk commit [-m ] ...\n"); + exit(1); + } + + commit(argc, argv); + } + else if (strcmp(subcmd, "log") == 0) { + if (argc != 2) { + printf("ERROR: too many arguments given!\n"); + printf("Usage: merk log"); + exit(1); + } + + commit_log(); } else if (strcmp(subcmd, "test") == 0) {} else {