diff --git a/include/commands.h b/include/commands.h deleted file mode 100644 index c7d8176..0000000 --- a/include/commands.h +++ /dev/null @@ -1,21 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "base_file_buffer.h" -#include "config.h" -#include "commit.h" -#include "hash.h" -#include "object.h" -#include "utilities.h" - -void usage(int); -int init(); -int diff(char*, char*); -int status(); -int commit(int, char**); -int commit_log(); diff --git a/include/hash.h b/include/hash.h index a0d1025..cbaa80b 100644 --- a/include/hash.h +++ b/include/hash.h @@ -10,6 +10,7 @@ typedef enum { BaseFileObject, FileDiffObject, TreeObject, + TreeDiffObject, LogObject } ObjectType; diff --git a/include/object.h b/include/object.h index 7e92fa4..655d22d 100644 --- a/include/object.h +++ b/include/object.h @@ -28,9 +28,9 @@ char* get_object(char*, size_t*); void* parse_object(char*, ObjectType, size_t*, char*); -int save_diff(ActionList*, char*, char*, size_t, char*); +int save_diff(ActionList*, char*, char*, size_t, char*, File*, int); int read_diff(char*, char*, ActionList*); int save_file_diff(char*, char*, size_t, char*, ActionList*); File* apply_diff(File*, ActionList*); -#endif // OBJECT_H +#endif // OBJECT_H \ No newline at end of file diff --git a/include/tree.h b/include/tree.h index aa48e7a..ac50552 100644 --- a/include/tree.h +++ b/include/tree.h @@ -15,5 +15,6 @@ #define IS_PUNCT(c) ((c >= 33 && c <= 47) || (c >= 58 && c <= 64) || (c >= 91 && c <= 96) || (c >= 123 && c <= 126)) void snapshot_tree(FileInfoBuffer*, char*); +int save_tree_diff(FileInfoBuffer*, char*, char*, size_t, char*, char*); int read_tree(FileInfoBuffer*, char*); #endif // TREE_H diff --git a/include/utilities.h b/include/utilities.h index 56f6b0d..a6b1209 100644 --- a/include/utilities.h +++ b/include/utilities.h @@ -22,24 +22,13 @@ typedef enum { PT_ERROR } PathType; -typedef struct { - char* key; - char* value; -} KeyValuePair; - typedef List StringBuffer; -typedef List FlatMap; + StringBuffer* string_buffer_new(); int string_buffer_push(StringBuffer*, char*); void string_buffer_sort(StringBuffer*); char* string_buffer_search(StringBuffer*, char*); -FlatMap* flat_map_new(); -int flat_map_put(FlatMap*, const char*, const char*); -char* flat_map_get(FlatMap*, const char*); -void flat_map_free(FlatMap*); -void flat_map_sort(FlatMap*); -KeyValuePair* flat_map_search(FlatMap*, const char*); char* find_root(char*); void walk(char*, char*, char*, FileInfoBuffer*, int, char*); char* get_repo_path(char*, char*); diff --git a/src/base_file_buffer.c b/src/base_file_buffer.c index f30b5dd..ada2e98 100644 --- a/src/base_file_buffer.c +++ b/src/base_file_buffer.c @@ -248,9 +248,9 @@ int write_base_file_list(BaseFileBuffer* buffer, char* info_file) { for (size_t idx = 0; idx < buffer->len; idx++) { BaseFileInfo* info = (BaseFileInfo*)buffer->items + idx; - buffer_size += 3 + - strlen(info->name) + - snprintf(NULL, 0, "%zu", info->base_num) + + buffer_size += 3 + + strlen(info->name) + + snprintf(NULL, 0, "%zu", info->base_num) + snprintf(NULL, 0, "%zu", info->diff_num); } @@ -262,7 +262,7 @@ int write_base_file_list(BaseFileBuffer* buffer, char* info_file) { for (size_t idx = 0; idx < buffer->len; idx++) { size_t remaining = sizeof(tmp) - offset; BaseFileInfo* info = (BaseFileInfo*)buffer->items + idx; - offset += snprintf(tmp + offset, remaining, "%s %zu %zu ", + offset += snprintf(tmp + offset, remaining, "%s %zu %zu ", info->name, info->base_num, info->diff_num); } @@ -276,7 +276,7 @@ int write_base_file_list(BaseFileBuffer* buffer, char* info_file) { fclose(fp); return 0; } - + if (compress(compressed, &compressedLen, (const Bytef*)tmp, originalLen) != Z_OK) { perror("ERROR: compression failed in snapshot_tree!"); free(compressed); diff --git a/src/commands.c b/src/commands.c deleted file mode 100644 index f05c059..0000000 --- a/src/commands.c +++ /dev/null @@ -1,760 +0,0 @@ -#include "../include/commands.h" - -void usage(int exitcode) { - printf("usage: merk []\n\ - \n init Initializes a repository in the current directory\ - \n diff Displays the difference between the given two files\ - \n status Displays a list of modified and untracked files in the repository\ - \n commit Record changes made to the repository\n\ - \n log Displays the list of commits on the current branch\n" - ); - exit(exitcode); -} - -int init() { - 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 1; - } - } - - 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; -} - -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; -} - -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[3 + snprintf(NULL, 0, "%lu", base_file->base_num) + strlen(file_name) + strlen(branch)]; - snprintf(id, sizeof(id), "%s %lu %s", file_name, base_file->base_num, branch); - 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[3 + snprintf(NULL, 0, "%zu", prev_diff) + strlen(file_name) + strlen(branch)]; - snprintf(id2, sizeof(id2), "%s %zu %s", file_name, prev_diff, branch); - 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", (char*)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", (char*)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", (char*)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 %lu commits\n", changes->insertions, changes->deletions, branch, log->len); - - file_info_buffer_free(files); - free(root); - return 1; -} - -int commit(int argc, char** argv) { - char* commit_message = NULL; - int file_start_index = 2; - - if (argc >= 4 && strcmp(argv[2], "-m") == 0) { - if (argv[3][0] == '-') { - printf("ERROR: Invalid commit message provided after -m flag!\n"); - exit(1); - } - commit_message = strdup(argv[3]); - if (!commit_message) { - fprintf(stderr, "ERROR: Memory allocation failed\n"); - exit(1); - } - file_start_index = 4; - } else if (argc == 3 && strcmp(argv[2], "-m") == 0) { - printf("ERROR: Missing commit message after -m flag!\n"); - exit(1); - } - - for (int i = file_start_index; i < argc; i++) { - if (strcmp(argv[i], "-m") == 0) { - printf("ERROR: Unexpected -m flag in file list!\n"); - exit(1); - } - } - - Config config = {0}; - - if (load_config(&config) < 0) { - free(commit_message); - return 1; - } - - if (validate_config(&config) < 0) { - free_config(&config); - free(commit_message); - return 1; - } - - char tmp_path[PATH_MAX] = ".merk/BRANCH"; - char branch_path[PATH_MAX]; - realpath(tmp_path, branch_path); - - char* branch = get_file_content(branch_path); - if (!branch) { - free_config(&config); - free(commit_message); - return 1; - } - - char tmp_path2[PATH_MAX]; - char info_path[PATH_MAX]; - snprintf(tmp_path2, sizeof(tmp_path2), ".merk/info/%s", branch); - realpath(tmp_path2, info_path); - - BaseFileBuffer* base_files = base_file_buffer_new(); - if (!base_files) { - free(branch); - free_config(&config); - free(commit_message); - return 1; - } - - read_base_file_list(base_files, info_path); - - StringBuffer* files = string_buffer_new(); - if (!files) { - base_file_buffer_free(base_files); - free(branch); - free_config(&config); - free(commit_message); - return 1; - } - - char* root = find_root(NULL); - if (!root) { - list_free(files); - base_file_buffer_free(base_files); - free(branch); - free_config(&config); - free(commit_message); - return 1; - } - - for (int i = file_start_index; i < argc; i++) { - char* real = realpath(argv[i], NULL); - if (!real) { - fprintf(stderr, "ERROR: Cannot resolve path: %s\n", argv[i]); - list_free(files); - base_file_buffer_free(base_files); - free(branch); - free_config(&config); - free(commit_message); - return 1; - } - - if (get_path_type(real) == PT_FILE && is_in_repo(root, real) == 0) { - char* repo_path = get_repo_path(root, real); - if (repo_path) { - string_buffer_push(files, repo_path); - free(repo_path); - } - } - else { - fprintf(stderr, "ERROR: %s is not a file or not inside the repo!\n", real); - free(real); - list_free(files); - base_file_buffer_free(base_files); - free(branch); - free(root); - free_config(&config); - free(commit_message); - return 1; - } - free(real); - } - - if (!commit_message) { - printf("Enter commit message: "); - fflush(stdout); - - char buffer[1024]; - if (fgets(buffer, sizeof(buffer), stdin) != NULL) { - size_t len = strlen(buffer); - if (len > 0 && buffer[len-1] == '\n') { - buffer[len-1] = '\0'; - } - - if (strlen(buffer) == 0) { - printf("ERROR: Empty commit message!\n"); - list_free(files); - base_file_buffer_free(base_files); - free(branch); - free(root); - free_config(&config); - return 1; - } - - commit_message = strdup(buffer); - if (!commit_message) { - fprintf(stderr, "ERROR: Memory allocation failed\n"); - list_free(files); - base_file_buffer_free(base_files); - free(branch); - free(root); - free_config(&config); - return 1; - } - } else { - printf("ERROR: Failed to read commit message!\n"); - list_free(files); - base_file_buffer_free(base_files); - free(branch); - free(root); - free_config(&config); - return 1; - } - } - - FlatMap* file_hash_map = flat_map_new(); - Changes* changes = calloc(1, sizeof(Changes)); - if (!changes) { - free(commit_message); - list_free(files); - base_file_buffer_free(base_files); - free(branch); - free(root); - free_config(&config); - return 1; - } - - changes->insertions = 0; - changes->deletions = 0; - - for (size_t idx = 0; idx < files->len; idx++) { - BaseFileInfo* base_file = base_file_buffer_search(base_files, files->items[idx]); - if (!base_file) { - printf("this is a basefile as it should be\n"); - BaseFileInfo info = (BaseFileInfo){.base_num = 0, .diff_num = 0, .name = strdup(files->items[idx])}; - base_file_buffer_push(base_files, info); - // Compress the files content and put it into the object database - File* modified_file = new_file(files->items[idx]); - if (!modified_file) { - free(commit_message); - list_free(files); - base_file_buffer_free(base_files); - free(branch); - free(root); - free_config(&config); - free(changes); - return 1; - } - changes->insertions += modified_file->lines; - - char file_hash[41]; - snapshot_file(files->items[idx], root, 0, file_hash); - flat_map_put(file_hash_map, files->items[idx], file_hash); - continue; - } - - char base_file_hash[41]; - char id[3 + snprintf(NULL, 0, "%lu", base_file->base_num) + strlen(files->items[idx]) + strlen(branch)]; - snprintf(id, sizeof(id), "%s %lu %s", (char*)files->items[idx], base_file->base_num, branch); - object_hash(BaseFileObject, id, base_file_hash); - File* basefile = parse_object(base_file_hash, BaseFileObject, NULL, NULL); - if (!basefile) { - free(commit_message); - list_free(files); - base_file_buffer_free(base_files); - free(branch); - free(root); - free_config(&config); - free(changes); - return 1; - } - - File* modified_file = new_file(files->items[idx]); - if (!modified_file) { - free_file(basefile); - free(commit_message); - list_free(files); - base_file_buffer_free(base_files); - free(branch); - free(root); - free_config(&config); - free(changes); - return 1; - } - - ActionList* diff = myers_diff(basefile, modified_file, 0, 0); - if (!diff) { - free_file(basefile); - free_file(modified_file); - free(commit_message); - list_free(files); - base_file_buffer_free(base_files); - free(branch); - free(root); - free_config(&config); - free(changes); - return 1; - } - - if (base_file->diff_num == 0) { - for (size_t idx = 0; idx < diff->len; idx++) { - Action action = diff->actions[idx]; - if (action.type == INSERT) { - changes->insertions += 1; - } else if (action.type == DELETE) { - changes->deletions += 1; - } - } - } - - if (base_file->diff_num > 0) { - size_t prev_diff = base_file->diff_num - 1; - char prev_diff_hash[41]; - char id2[3 + snprintf(NULL, 0, "%zu", prev_diff) + strlen(files->items[idx]) + strlen(branch)]; - snprintf(id2, sizeof(id2), "%s %zu %s", (char*)files->items[idx], prev_diff, branch); - object_hash(FileDiffObject, id2, prev_diff_hash); - char hash[41]; - ActionList* last_diff = parse_object(prev_diff_hash, FileDiffObject, NULL, hash); - if (!last_diff) { - free_action_list(diff); - free_file(modified_file); - free_file(basefile); - free(commit_message); - list_free(files); - base_file_buffer_free(base_files); - free(branch); - free(root); - free_config(&config); - free(changes); - return 1; - } - - File* last_version = apply_diff(basefile, last_diff); - if (!last_version) { - free_action_list(last_diff); - free_action_list(diff); - free_file(modified_file); - free_file(basefile); - free(commit_message); - list_free(files); - base_file_buffer_free(base_files); - free(branch); - free(root); - free_config(&config); - free(changes); - return 1; - } - - ActionList* modified_diff = myers_diff(last_version, modified_file, 0, 0); - if (!modified_diff) { - free_action_list(last_diff); - free_action_list(diff); - free_file(modified_file); - free_file(basefile); - free(commit_message); - list_free(files); - base_file_buffer_free(base_files); - free(branch); - free(root); - free_config(&config); - free(changes); - return 1; - } - - for (size_t idx = 0; idx < modified_diff->len; idx++) { - Action action = modified_diff->actions[idx]; - if (action.type == INSERT) { - changes->insertions += 1; - } else if (action.type == DELETE) { - changes->deletions += 1; - } - } - } - - if (diff->len > 200) { - char new_base_hash[41]; - snapshot_file(files->items[idx], root, base_file->base_num+1, new_base_hash); - - flat_map_put(file_hash_map, files->items[idx], new_base_hash); - - base_file_buffer_remove(base_files, files->items[idx]); - BaseFileInfo new_info = (BaseFileInfo){ - .base_num = base_file->base_num + 1, - .diff_num = base_file->diff_num, - .name = strdup(files->items[idx]) - }; - base_file_buffer_push(base_files, new_info); - continue; - } - else { - save_diff(diff, files->items[idx], root, base_file->diff_num, base_file_hash); - - char diff_hash[41]; - char id[3 + snprintf(NULL, 0, "%lu", base_file->diff_num) + strlen(files->items[idx]) + strlen(branch)]; - snprintf(id, sizeof(id), "%s %lu %s", (char*)files->items[idx], base_file->diff_num, branch); - object_hash(BaseFileObject, id, diff_hash); - - flat_map_put(file_hash_map, files->items[idx], diff_hash); - - base_file_buffer_remove(base_files, files->items[idx]); - BaseFileInfo new_info = (BaseFileInfo){ - .base_num = base_file->base_num, - .diff_num = base_file->diff_num + 1, - .name = strdup(files->items[idx]) - }; - base_file_buffer_push(base_files, new_info); - } - } - - base_file_buffer_sort(base_files); - write_base_file_list(base_files, info_path); - FileInfoBuffer* tree = basefilebuffer_to_fileinfobuffer(base_files); - if (!tree) { - free(commit_message); - list_free(files); - base_file_buffer_free(base_files); - free(branch); - free(root); - free_config(&config); - return 1; - } - - for (size_t idx = 0; idx < tree->len; idx++) { - char* hash = flat_map_get(file_hash_map, ((FileInfo*)tree->items + idx)->name); - if (hash) ((FileInfo*)tree->items + idx)->hash = strdup(hash); - } - - char tree_hash[41]; - snapshot_tree(tree, tree_hash); - - char log_path[PATH_MAX]; - snprintf(log_path, sizeof(log_path), "%s/.merk/logs/branches/%s", root, branch); - - CommitLog* log = commit_log_new(); - - if (!log) { - file_info_buffer_free(tree); - free(commit_message); - list_free(files); - base_file_buffer_free(base_files); - free(branch); - free(root); - free_config(&config); - return 1; - } - - read_commit_log(log, log_path); - - char parent_hash[41]; - if (log->len != 0) { - snprintf(parent_hash, sizeof(parent_hash), "%s", ((Commit*)log->items + (log->len - 1))->hash); - } - - char timestamp[20]; - snprintf(timestamp, sizeof(timestamp), "%ld", time(NULL)); - - Author author = { - .name = strdup(config.user.name), - .email = strdup(config.user.email), - .timestamp = strdup(timestamp) - }; - - if (log->len != 0) { - char parent_hash[41]; - snprintf(parent_hash, sizeof(parent_hash), "%s", ((Commit*)log->items + (log->len - 1))->hash); - } - - char commit_hash_content[4096]; - int offset = 0; - offset += snprintf(commit_hash_content + offset, sizeof(commit_hash_content) - offset, "tree %s\n", tree_hash); - if (log->len != 0) { - offset += snprintf(commit_hash_content + offset, sizeof(commit_hash_content) - offset, "parent %s\n", parent_hash); - } - offset += snprintf(commit_hash_content + offset, sizeof(commit_hash_content) - offset, "author %s <%s> %s\n", author.name, author.email, author.timestamp); - offset += snprintf(commit_hash_content + offset, sizeof(commit_hash_content) - offset, "committer %s <%s> %s\n", author.name, author.email, author.timestamp); - offset += snprintf(commit_hash_content + offset, sizeof(commit_hash_content) - offset, "message %s\n", commit_message); - - char commit_hash[41]; - object_hash(LogObject, commit_hash_content, commit_hash); - - Commit commit = { - .hash = commit_hash, - .tree_hash = strdup(tree_hash), - .authors = &author, - .authors_count = 1, - .committer = author, - .message = strdup(commit_message) - }; - - commit_log_push(log, commit); - if (!write_commit_log(log, log_path)) { - commit_log_free(log); - file_info_buffer_free(tree); - free(commit_message); - list_free(files); - base_file_buffer_free(base_files); - free(branch); - free(root); - free_config(&config); - return 1; - } - - char ref_path[PATH_MAX]; - snprintf(ref_path, sizeof(ref_path), "%s/.merk/refs/branches/%s", root, branch); - - int ref = open(ref_path, O_WRONLY | O_CREAT | O_TRUNC, 0666); - if (ref < 0) return -1; - write(ref, commit_hash, 40); - close(ref); - - printf("(\x1b[33;1m%.7s\x1b[0m on \x1b[39;1;4m%s\x1b[0m \x1b[32;1m%d+\x1b[0m \x1b[31;1m%d-\x1b[0m) %s\n", - commit_hash, - branch, - changes->insertions, - changes->deletions, - commit_message - ); - - list_free(files); - base_file_buffer_free(base_files); - free(branch); - free(root); - free_config(&config); - free(commit_message); - return 1; -} - -int commit_log() { - CommitLog* log = commit_log_new(); - if (!log) return 0; - - char* root = find_root(NULL); - if (!root) { - return 0; - } - - 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 log_path[PATH_MAX]; - snprintf(log_path, sizeof(log_path), "%s/.merk/logs/branches/%s", root, branch); - - 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 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; -} diff --git a/src/hash.c b/src/hash.c index 2545276..27c3d90 100644 --- a/src/hash.c +++ b/src/hash.c @@ -23,6 +23,7 @@ void object_hash(ObjectType obj_type, char* content, char* hex_hash) { case BaseFileObject: header_len = snprintf(header, sizeof(header), "basefile %zu", content_len); break; case FileDiffObject: header_len = snprintf(header, sizeof(header), "filediff %zu", content_len); break; case TreeObject: header_len = snprintf(header, sizeof(header), "tree %zu", content_len); break; + case TreeDiffObject: header_len = snprintf(header, sizeof(header), "treediff %zu", content_len); break; case LogObject: header_len = snprintf(header, sizeof(header), "log %zu", content_len); break; default: header_len = 0; break; } diff --git a/src/main.c b/src/main.c index 08124cb..90785a3 100644 --- a/src/main.c +++ b/src/main.c @@ -1,30 +1,93 @@ -#include "../include/commands.h" +#include +#include +#include +#include +#include +#include +#include + +#include "base_file_buffer.h" +#include "config.h" +#include "commit.h" +#include "hash.h" +#include "object.h" + +static void usage(int exitcode) { + printf("usage: merk []\n\ + \n init Initializes a repository in the current directory\ + \n diff Displays the difference between the given two files\ + \n status Displays a list of modified and untracked files in the repository\ + \n commit Record changes made to the repository\n"); + exit(exitcode); +} int main(int argc, char** argv) { if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) { - usage(0); + 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); + 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("ERROR: too many agruments given!\n"); printf("Usage: merk init"); exit(1); } - init(); + 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 @@ -35,9 +98,28 @@ int main(int argc, char** argv) { exit(1); } - diff(argv[2], argv[3]); - } + 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) { @@ -46,9 +128,121 @@ int main(int argc, char** argv) { exit(1); } - 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; + } + + ActionList* diff = myers_diff(basefile, 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); + } + } + } + + 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) { @@ -57,22 +251,508 @@ int main(int argc, char** argv) { exit(1); } - commit(argc, argv); - } + char* commit_message = NULL; + int file_start_index = 2; - else if (strcmp(subcmd, "log") == 0) { - if (argc != 2) { - printf("ERROR: too many arguments given!\n"); - printf("Usage: merk log"); + if (argc >= 4 && strcmp(argv[2], "-m") == 0) { + if (argv[3][0] == '-') { + printf("ERROR: Invalid commit message provided after -m flag!\n"); + exit(1); + } + commit_message = strdup(argv[3]); + if (!commit_message) { + fprintf(stderr, "ERROR: Memory allocation failed\n"); + exit(1); + } + file_start_index = 4; + } else if (argc == 3 && strcmp(argv[2], "-m") == 0) { + printf("ERROR: Missing commit message after -m flag!\n"); exit(1); } - commit_log(); + // 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"); + exit(1); + } + } + + Config config = {0}; + + if (load_config(&config) < 0) { + free(commit_message); + return 1; + } + + if (validate_config(&config) < 0) { + free_config(&config); + free(commit_message); + return 1; + } + + char tmp_path[PATH_MAX] = ".merk/BRANCH"; + char branch_path[PATH_MAX]; + realpath(tmp_path, branch_path); + + char* branch = get_file_content(branch_path); + if (!branch) { + free_config(&config); + free(commit_message); + return 1; + } + + char tmp_path2[PATH_MAX]; + char info_path[PATH_MAX]; + snprintf(tmp_path2, sizeof(tmp_path2), ".merk/info/%s", branch); + realpath(tmp_path2, info_path); + + BaseFileBuffer* base_files = base_file_buffer_new(); + if (!base_files) { + free(branch); + free_config(&config); + free(commit_message); + return 1; + } + + read_base_file_list(base_files, info_path); + + StringBuffer* files = string_buffer_new(); + if (!files) { + base_file_buffer_free(base_files); + free(branch); + free_config(&config); + free(commit_message); + return 1; + } + + char* root = find_root(NULL); + if (!root) { + list_free(files); + base_file_buffer_free(base_files); + free(branch); + free_config(&config); + free(commit_message); + return 1; + } + + for (int i = file_start_index; i < argc; i++) { + char* real = realpath(argv[i], NULL); + if (!real) { + fprintf(stderr, "ERROR: Cannot resolve path: %s\n", argv[i]); + list_free(files); + base_file_buffer_free(base_files); + free(branch); + free_config(&config); + free(commit_message); + return 1; + } + + if (get_path_type(real) == PT_FILE && is_in_repo(root, real) == 0) { + char* repo_path = get_repo_path(root, real); + if (repo_path) { + string_buffer_push(files, repo_path); + free(repo_path); + } + } + else { + fprintf(stderr, "ERROR: %s is not a file or not inside the repo!\n", real); + free(real); + list_free(files); + base_file_buffer_free(base_files); + free(branch); + free(root); + free_config(&config); + free(commit_message); + return 1; + } + free(real); + } + + if (!commit_message) { + printf("Enter commit message: "); + fflush(stdout); + + char buffer[1024]; + if (fgets(buffer, sizeof(buffer), stdin) != NULL) { + size_t len = strlen(buffer); + if (len > 0 && buffer[len-1] == '\n') { + buffer[len-1] = '\0'; + } + + if (strlen(buffer) == 0) { + printf("ERROR: Empty commit message!\n"); + list_free(files); + base_file_buffer_free(base_files); + free(branch); + free(root); + free_config(&config); + return 1; + } + + commit_message = strdup(buffer); + if (!commit_message) { + fprintf(stderr, "ERROR: Memory allocation failed\n"); + list_free(files); + base_file_buffer_free(base_files); + free(branch); + free(root); + free_config(&config); + return 1; + } + } else { + printf("ERROR: Failed to read commit message!\n"); + list_free(files); + base_file_buffer_free(base_files); + free(branch); + free(root); + free_config(&config); + return 1; + } + } + + Changes* changes = calloc(1, sizeof(Changes)); + if (!changes) { + free(commit_message); + list_free(files); + base_file_buffer_free(base_files); + free(branch); + free(root); + free_config(&config); + return 1; + } + + changes->insertions = 0; + changes->deletions = 0; + + for (size_t idx = 0; idx < files->len; idx++) { + BaseFileInfo* base_file = base_file_buffer_search(base_files, files->items[idx]); + if (!base_file) { + BaseFileInfo info = (BaseFileInfo){.base_num = 0, .diff_num = 0, .name = strdup(files->items[idx])}; + base_file_buffer_push(base_files, info); + // Compress the files content and put it into the object database + File* modified_file = new_file(files->items[idx]); + if (!modified_file) { + free(commit_message); + list_free(files); + base_file_buffer_free(base_files); + free(branch); + free(root); + free_config(&config); + free(changes); + return 1; + } + changes->insertions += modified_file->lines; + + char file_hash[41]; + snapshot_file(files->items[idx], root, 0, file_hash); + continue; + } + + char base_file_hash[41]; + char id[2 + snprintf(NULL, 0, "%d", base_file->base_num) + strlen(files->items[idx])]; + snprintf(id, sizeof(id), "%s %d", files->items[idx], base_file->base_num); + object_hash(BaseFileObject, id, base_file_hash); + File* basefile = parse_object(base_file_hash, BaseFileObject, NULL, NULL); + if (!basefile) { + free(commit_message); + list_free(files); + base_file_buffer_free(base_files); + free(branch); + free(root); + free_config(&config); + free(changes); + return 1; + } + + File* modified_file = new_file(files->items[idx]); + if (!modified_file) { + free_file(basefile); + free(commit_message); + list_free(files); + base_file_buffer_free(base_files); + free(branch); + free(root); + free_config(&config); + free(changes); + return 1; + } + + ActionList* diff = myers_diff(basefile, modified_file, 0, 0); + if (!diff) { + free_file(basefile); + free_file(modified_file); + free(commit_message); + list_free(files); + base_file_buffer_free(base_files); + free(branch); + free(root); + free_config(&config); + free(changes); + return 1; + } + + if (base_file->diff_num == 0) { + for (size_t idx = 0; idx < diff->len; idx++) { + Action action = diff->actions[idx]; + if (action.type == INSERT) { + changes->insertions += 1; + } else if (action.type == DELETE) { + changes->deletions += 1; + } + } + } + + 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(files->items[idx])]; + snprintf(id2, sizeof(id2), "%s %zu", files->items[idx], 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) { + free_action_list(diff); + free_file(modified_file); + free_file(basefile); + free(commit_message); + list_free(files); + base_file_buffer_free(base_files); + free(branch); + free(root); + free_config(&config); + free(changes); + return 1; + } + + File* last_version = apply_diff(basefile, last_diff); + if (!last_version) { + free_action_list(last_diff); + free_action_list(diff); + free_file(modified_file); + free_file(basefile); + free(commit_message); + list_free(files); + base_file_buffer_free(base_files); + free(branch); + free(root); + free_config(&config); + free(changes); + return 1; + } + + ActionList* modified_diff = myers_diff(last_version, modified_file, 0, 0); + if (!modified_diff) { + free_action_list(last_diff); + free_action_list(diff); + free_file(modified_file); + free_file(basefile); + free(commit_message); + list_free(files); + base_file_buffer_free(base_files); + free(branch); + free(root); + free_config(&config); + free(changes); + return 1; + } + + for (size_t idx = 0; idx < modified_diff->len; idx++) { + Action action = modified_diff->actions[idx]; + if (action.type == INSERT) { + changes->insertions += 1; + } else if (action.type == DELETE) { + changes->deletions += 1; + } + } + } + + save_diff(diff, files->items[idx], root, base_file->diff_num, base_file_hash, NULL, 0); + + base_file_buffer_remove(base_files, files->items[idx]); + BaseFileInfo new_info = (BaseFileInfo){ + .base_num = base_file->base_num, + .diff_num = base_file->diff_num + 1, + .name = strdup(files->items[idx]) + }; + base_file_buffer_push(base_files, new_info); + } + + base_file_buffer_sort(base_files); + write_base_file_list(base_files, info_path); + FileInfoBuffer* tree = basefilebuffer_to_fileinfobuffer(base_files); + if (!tree) { + free(commit_message); + list_free(files); + base_file_buffer_free(base_files); + free(branch); + free(root); + free_config(&config); + return 1; + } + + for (size_t idx = 0; idx < tree->len; idx++) { + FileInfo* info = (FileInfo*)tree->items + idx; + BaseFileInfo* base_info = base_file_buffer_search(base_files, info->name); + char hash[41]; + if (base_info->diff_num == 0) { + char id[2 + snprintf(NULL, 0, "%d", base_info->base_num) + strlen(info->name)]; + snprintf(id, sizeof(id), "%s %d", info->name, base_info->base_num); + object_hash(BaseFileObject, id, hash); + } else { + char id[2 + snprintf(NULL, 0, "%d", base_info->diff_num-1) + strlen(info->name)]; + snprintf(id, sizeof(id), "%s %d", info->name, base_info->diff_num-1); + object_hash(FileDiffObject, id, hash); + } + info->hash = strdup(hash); + } + + char tree_hash[41]; + snapshot_tree(tree, tree_hash); + + char log_path[PATH_MAX]; + snprintf(log_path, sizeof(log_path), "%s/.merk/logs/branches/%s", root, branch); + + CommitLog* log = commit_log_new(); + + if (!log) { + file_info_buffer_free(tree); + free(commit_message); + list_free(files); + base_file_buffer_free(base_files); + free(branch); + free(root); + free_config(&config); + return 1; + } + + read_commit_log(log, log_path); + + char parent_hash[41]; + if (log->len != 0) { + snprintf(parent_hash, sizeof(parent_hash), "%s", ((Commit*)log->items + (log->len - 1))->hash); + } + + char timestamp[20]; + snprintf(timestamp, sizeof(timestamp), "%ld", time(NULL)); + + Author author = { + .name = strdup(config.user.name), + .email = strdup(config.user.email), + .timestamp = strdup(timestamp) + }; + + if (log->len != 0) { + char parent_hash[41]; + snprintf(parent_hash, sizeof(parent_hash), "%s", ((Commit*)log->items + (log->len - 1))->hash); + } + + char commit_hash_content[4096]; + int offset = 0; + offset += snprintf(commit_hash_content + offset, sizeof(commit_hash_content) - offset, "tree %s\n", tree_hash); + if (log->len != 0) { + offset += snprintf(commit_hash_content + offset, sizeof(commit_hash_content) - offset, "parent %s\n", parent_hash); + } + offset += snprintf(commit_hash_content + offset, sizeof(commit_hash_content) - offset, "author %s <%s> %s\n", author.name, author.email, author.timestamp); + offset += snprintf(commit_hash_content + offset, sizeof(commit_hash_content) - offset, "committer %s <%s> %s\n", author.name, author.email, author.timestamp); + offset += snprintf(commit_hash_content + offset, sizeof(commit_hash_content) - offset, "message %s\n", commit_message); + + char commit_hash[41]; + object_hash(LogObject, commit_hash_content, commit_hash); + + Commit commit = { + .hash = commit_hash, + .tree_hash = strdup(tree_hash), + .authors = &author, + .authors_count = 1, + .committer = author, + .message = strdup(commit_message) + }; + + commit_log_push(log, commit); + if (!write_commit_log(log, log_path)) { + commit_log_free(log); + file_info_buffer_free(tree); + free(commit_message); + list_free(files); + base_file_buffer_free(base_files); + free(branch); + free(root); + free_config(&config); + return 1; + } + + char ref_path[PATH_MAX]; + snprintf(ref_path, sizeof(ref_path), "%s/.merk/refs/branches/%s", root, branch); + + int ref = open(ref_path, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (ref < 0) return -1; + write(ref, commit_hash, 40); + close(ref); + + printf("(\x1b[33;1m%.7s\x1b[0m on \x1b[39;1;4m%s\x1b[0m \x1b[32;1m%d+\x1b[0m \x1b[31;1m%d-\x1b[0m) %s\n", + commit_hash, + branch, + changes->insertions, + changes->deletions, + commit_message + ); + + list_free(files); + base_file_buffer_free(base_files); + free(branch); + free(root); + free_config(&config); + free(commit_message); } + 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 = 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); + + read_commit_log(log, log_path); + + 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)); + + printf("\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", + commit->hash, + commit->message, + commit->committer.name, + commit->committer.email, + commit->committer.email, + human_readable_time + ); + } + + free(branch); + free(root); + commit_log_free(log); + } else if (strcmp(subcmd, "test") == 0) {} - - else { usage(1); } + else { + usage(1); + } return 0; } + diff --git a/src/object.c b/src/object.c index e4ed47a..223f547 100644 --- a/src/object.c +++ b/src/object.c @@ -17,7 +17,7 @@ char* get_object(char* hash, size_t* size_out) { free(compressed_data); return NULL; } - + size_t idx = 0; while (idx < compressed_size && IS_DIGIT(compressed_data[idx])) { @@ -152,7 +152,7 @@ static int mkdir_recursive(const char *path, mode_t mode) { return 0; } -int save_diff(ActionList* diff, char* path, char* root, size_t diff_id, char* basefile_hash) { +int save_diff(ActionList* diff, char* path, char* root, size_t diff_id, char* basefile_hash, File* modified_file, int tree) { size_t buffer_size = 41; buffer_size += 1 + snprintf(NULL, 0, "%d", diff->len); @@ -186,20 +186,20 @@ int save_diff(ActionList* diff, char* path, char* root, size_t diff_id, char* ba size_t remaining = sizeof(tmp) - offset; if (action.type == DELETE) { - offset += snprintf(tmp + offset, - remaining, - "0 %zu %zu ", - action.line_original, + offset += snprintf(tmp + offset, + remaining, + "0 %zu %zu ", + action.line_original, action.line_changed ); continue; } - offset += snprintf(tmp + offset, remaining, - "1 %zu %zu %zu %s ", - action.line_original, - action.line_changed, - strlen(action.content), + offset += snprintf(tmp + offset, remaining, + "1 %zu %zu %zu %s ", + action.line_original, + action.line_changed, + strlen(action.content), action.content ); } @@ -207,7 +207,8 @@ int save_diff(ActionList* diff, char* path, char* root, size_t diff_id, char* ba char id[2+snprintf(NULL, 0, "%d", diff_id)+strlen(path)]; snprintf(id, sizeof(id), "%s %d", path, diff_id); char hash[41]; - object_hash(FileDiffObject, id, hash); + if (tree) object_hash(TreeDiffObject, id, hash); + else object_hash(FileDiffObject, id, hash); char dir_path[PATH_MAX]; char file_path[PATH_MAX]; @@ -287,12 +288,13 @@ int read_diff(char* content, char* basefile_hash, ActionList* diff_out) { char* end; long actions = strtol(number_of_actions, &end, 10); - if (end == number_of_actions || *end != '\0' || actions < 0) { + if (end == number_of_actions || *end != '\0' || actions <= 0) { perror("ERROR: invalid number of actions in read_diff!"); - free(number_of_actions); return 0; } + free(number_of_actions); + size_t action_idx = 0; while (content[idx] && action_idx < (size_t)actions) { idx++; @@ -374,7 +376,7 @@ int read_diff(char* content, char* basefile_hash, ActionList* diff_out) { } int save_file_diff(char* path, char* root, size_t diff_id, char* basefile_hash, ActionList* diff) { - + return 1; } @@ -418,4 +420,4 @@ File* apply_diff(File* basefile, ActionList* diff) { } return copy; -} +} \ No newline at end of file diff --git a/src/tree.c b/src/tree.c index c23542f..67d6fc8 100644 --- a/src/tree.c +++ b/src/tree.c @@ -34,7 +34,7 @@ void snapshot_tree(FileInfoBuffer* tree, char* hash) { FileInfo* file_info = (FileInfo*)tree->items + idx; size_t remaining = buffer_size - offset; - int written = snprintf(tmp + offset, remaining, "%o %s %s\n", + int written = snprintf(tmp + offset, remaining, "%o %s %s\n", file_info->mode, file_info->name, file_info->hash); if (written < 0 || (size_t)written >= remaining) { @@ -63,7 +63,7 @@ void snapshot_tree(FileInfoBuffer* tree, char* hash) { } FILE* fp = fopen(file_path, "wb"); - if (!fp) { + if (!fp) { perror("ERROR: cannot open path in snapshot_tree!"); free(tmp); file_info_buffer_free(tree); @@ -81,7 +81,7 @@ void snapshot_tree(FileInfoBuffer* tree, char* hash) { free(root); return; } - + if (compress(compressed, &compressedLen, (const Bytef*)tmp, originalLen) != Z_OK) { perror("ERROR: compression failed in snapshot_tree!"); free(compressed); @@ -98,13 +98,325 @@ void snapshot_tree(FileInfoBuffer* tree, char* hash) { fclose(fp); chmod(file_path, S_IRUSR | S_IRGRP | S_IROTH); - + free(compressed); free(tmp); file_info_buffer_free(tree); free(root); } +int save_tree_diff(FileInfoBuffer* current_tree, char* root, char* branch_name, size_t diff_id, char* base_tree_hash, char* hash) { + if (!current_tree || !root || !branch_name || !base_tree_hash || !hash) { + return 0; + } + + char base_tree_location[PATH_MAX]; + snprintf(base_tree_location, sizeof(base_tree_location), "%s/.merk/objects/%.2s/%s", root, base_tree_hash, base_tree_hash+2); + + size_t compressed_size; + unsigned char* compressed_data = (unsigned char*)get_file_content_with_size(base_tree_location, &compressed_size); + if (!compressed_data) { + return 0; + } + + size_t idx = 0; + while (idx < compressed_size && IS_DIGIT(compressed_data[idx])) { + idx++; + } + + if (idx == 0) { + perror("ERROR: no length found in base tree file!"); + free(compressed_data); + return 0; + } + + char* size_str = calloc(idx + 1, sizeof(char)); + if (!size_str) { + free(compressed_data); + return 0; + } + + memcpy(size_str, compressed_data, idx); + size_str[idx] = '\0'; + + char* end; + long size = strtol(size_str, &end, 10); + if (end == size_str || *end != '\0' || size <= 0) { + perror("ERROR: invalid length in base tree file"); + free(size_str); + free(compressed_data); + return 0; + } + + size_t offset = strlen(size_str) + 1; + free(size_str); + + char* base_tree_content = calloc(size + 1, sizeof(char)); + if (!base_tree_content) { + free(compressed_data); + return 0; + } + + uLongf dest_len = (uLongf)size; + int result = uncompress((unsigned char*)base_tree_content, &dest_len, compressed_data + offset, compressed_size - offset); + free(compressed_data); + + if (result != Z_OK) { + perror("ERROR: decompression of base tree failed!"); + free(base_tree_content); + return 0; + } + + base_tree_content[dest_len] = '\0'; + + File* base_tree_file = from_string(base_tree_content); + if (!base_tree_file) { + free(base_tree_content); + return 0; + } + + size_t buffer_size = 0; + for (size_t i = 0; i < current_tree->len; i++) { + FileInfo* file_info = (FileInfo*)current_tree->items + i; + buffer_size += snprintf(NULL, 0, "%o %s %s\n", file_info->mode, file_info->name, file_info->hash); + } + buffer_size += 1; + + char* current_tree_content = malloc(buffer_size); + if (!current_tree_content) { + free(base_tree_content); + free(base_tree_file->content[0]); + free(base_tree_file->content); + free(base_tree_file); + return 0; + } + + size_t content_offset = 0; + for (size_t i = 0; i < current_tree->len; i++) { + FileInfo* file_info = (FileInfo*)current_tree->items + i; + size_t remaining = buffer_size - content_offset; + int written = snprintf(current_tree_content + content_offset, remaining, "%o %s %s\n", + file_info->mode, file_info->name, file_info->hash); + if (written < 0 || (size_t)written >= remaining) { + perror("ERROR: buffer overflow in current tree content!"); + free(current_tree_content); + free(base_tree_content); + free(base_tree_file->content[0]); + free(base_tree_file->content); + free(base_tree_file); + return 0; + } + content_offset += (size_t)written; + } + current_tree_content[content_offset] = '\0'; + + File* current_tree_file = from_string(current_tree_content); + if (!current_tree_file) { + free(current_tree_content); + free(base_tree_content); + free(base_tree_file->content[0]); + free(base_tree_file->content); + free(base_tree_file); + return 0; + } + + ActionList* diff = myers_diff(base_tree_file, current_tree_file, 0, 0); + + if (!diff || diff->len == 0) { + strcpy(hash, base_tree_hash); + + free(current_tree_content); + free(base_tree_content); + free(base_tree_file->content[0]); + free(base_tree_file->content); + free(base_tree_file); + free(current_tree_file->content[0]); + free(current_tree_file->content); + free(current_tree_file); + if (diff) { + free(diff->actions); + free(diff); + } + return 1; + } + + size_t diff_buffer_size = strlen(base_tree_hash) + 1; + diff_buffer_size += snprintf(NULL, 0, "%zu ", diff->len); + + for (size_t i = 0; i < diff->len; i++) { + Action action = diff->actions[i]; + if (action.type == DELETE) { + diff_buffer_size += snprintf(NULL, 0, "0 %zu %zu ", action.line_original, action.line_changed); + } else { + diff_buffer_size += snprintf(NULL, 0, "1 %zu %zu %s ", + action.line_original, action.line_changed, current_tree_file->content[action.line_changed]); + } + } + diff_buffer_size += 1; + + char* diff_content = malloc(diff_buffer_size); + if (!diff_content) { + perror("ERROR: memory allocation failed for diff content!"); + free(current_tree_content); + free(base_tree_content); + free(base_tree_file->content[0]); + free(base_tree_file->content); + free(base_tree_file); + free(current_tree_file->content[0]); + free(current_tree_file->content); + free(current_tree_file); + free(diff->actions); + free(diff); + return 0; + } + + size_t diff_offset = 0; + diff_offset += snprintf(diff_content, diff_buffer_size, "%s %zu ", base_tree_hash, diff->len); + + for (size_t i = 0; i < diff->len; i++) { + Action action = diff->actions[i]; + size_t remaining = diff_buffer_size - diff_offset; + + if (action.type == DELETE) { + int written = snprintf(diff_content + diff_offset, remaining, "0 %zu %zu ", + action.line_original, action.line_changed); + if (written < 0 || (size_t)written >= remaining) { + perror("ERROR: buffer overflow in diff content!"); + free(diff_content); + free(current_tree_content); + free(base_tree_content); + free(base_tree_file->content[0]); + free(base_tree_file->content); + free(base_tree_file); + free(current_tree_file->content[0]); + free(current_tree_file->content); + free(current_tree_file); + free(diff->actions); + free(diff); + return 0; + } + diff_offset += (size_t)written; + } else { + int written = snprintf(diff_content + diff_offset, remaining, "1 %zu %zu %s ", + action.line_original, action.line_changed, current_tree_file->content[action.line_changed]); + if (written < 0 || (size_t)written >= remaining) { + perror("ERROR: buffer overflow in diff content!"); + free(diff_content); + free(current_tree_content); + free(base_tree_content); + free(base_tree_file->content[0]); + free(base_tree_file->content); + free(base_tree_file); + free(current_tree_file->content[0]); + free(current_tree_file->content); + free(current_tree_file); + free(diff->actions); + free(diff); + return 0; + } + diff_offset += (size_t)written; + } + } + + object_hash(TreeDiffObject, diff_content, hash); + + char dir_path[PATH_MAX]; + char file_path[PATH_MAX]; + snprintf(dir_path, sizeof(dir_path), "%s/.merk/objects/%.2s", root, hash); + if (mkdir(dir_path, 0755) != 0 && errno != EEXIST) { + perror("ERROR: failed to create directory!"); + free(diff_content); + free(current_tree_content); + free(base_tree_content); + free(base_tree_file->content[0]); + free(base_tree_file->content); + free(base_tree_file); + free(current_tree_file->content[0]); + free(current_tree_file->content); + free(current_tree_file); + free(diff->actions); + free(diff); + return 0; + } + snprintf(file_path, sizeof(file_path), "%s/%s", dir_path, hash+2); + + FILE* fp = fopen(file_path, "wb"); + if (!fp) { + perror("ERROR: cannot open file for writing!"); + free(diff_content); + free(current_tree_content); + free(base_tree_content); + free(base_tree_file->content[0]); + free(base_tree_file->content); + free(base_tree_file); + free(current_tree_file->content[0]); + free(current_tree_file->content); + free(current_tree_file); + free(diff->actions); + free(diff); + return 0; + } + + uLong originalLen = strlen(diff_content) + 1; + uLong compressedLen = compressBound(originalLen); + Bytef* compressed = malloc(compressedLen); + if (!compressed) { + perror("ERROR: memory allocation failed for compression!"); + fclose(fp); + free(diff_content); + free(current_tree_content); + free(base_tree_content); + free(base_tree_file->content[0]); + free(base_tree_file->content); + free(base_tree_file); + free(current_tree_file->content[0]); + free(current_tree_file->content); + free(current_tree_file); + free(diff->actions); + free(diff); + return 0; + } + + if (compress(compressed, &compressedLen, (const Bytef*)diff_content, originalLen) != Z_OK) { + perror("ERROR: compression failed!"); + free(compressed); + fclose(fp); + free(diff_content); + free(current_tree_content); + free(base_tree_content); + free(base_tree_file->content[0]); + free(base_tree_file->content); + free(base_tree_file); + free(current_tree_file->content[0]); + free(current_tree_file->content); + free(current_tree_file); + free(diff->actions); + free(diff); + return 0; + } + + fprintf(fp, "%lu ", (unsigned long)originalLen); + fwrite(compressed, 1, compressedLen, fp); + fclose(fp); + + chmod(file_path, S_IRUSR | S_IRGRP | S_IROTH); + + free(compressed); + free(diff_content); + free(current_tree_content); + free(base_tree_content); + free(base_tree_file->content[0]); + free(base_tree_file->content); + free(base_tree_file); + free(current_tree_file->content[0]); + free(current_tree_file->content); + free(current_tree_file); + free(diff->actions); + free(diff); + + return 1; +} + int read_tree(FileInfoBuffer* tree_out, char* hash) { if (!tree_out || !hash) { return 0; diff --git a/src/utilities.c b/src/utilities.c index 8f52b4c..9bfe709 100644 --- a/src/utilities.c +++ b/src/utilities.c @@ -32,64 +32,6 @@ char* string_buffer_search(StringBuffer* buffer, char* path) { return (char*)list_binary_search(buffer, path, compare_strings); } -FlatMap* flat_map_new() { - return list_new(sizeof(KeyValuePair)); -} - -int flat_map_put(FlatMap* map, const char* key, const char* value) { - if (!map || !key || !value) return 0; - - KeyValuePair kv = {strdup(key), strdup(value)}; - int res = list_push(map, &kv); - flat_map_sort(map); - return res; -} - -char* flat_map_get(FlatMap* map, const char* key) { - if (!map || !key) return NULL; - - KeyValuePair* kv = flat_map_search(map, key); - return kv ? kv->value : NULL; -} - -static int compare_kv(const void* a, const void* b) { - const KeyValuePair* kv1 = (const KeyValuePair*)a; - const KeyValuePair* kv2 = (const KeyValuePair*)b; - return strcmp(kv1->key, kv2->key); -} - -void flat_map_sort(FlatMap* map) { - if (!map || map->len <= 1) { - return; - } - - qsort(map->items, map->len, sizeof(KeyValuePair), compare_kv); -} - -static int compare_kv_with_key(const void* key_ptr, const void* element_ptr) { - const char* key = (const char*)key_ptr; - const KeyValuePair* kv = (const KeyValuePair*)element_ptr; - return strcmp(key, kv->key); -} - -KeyValuePair* flat_map_search(FlatMap* map, const char* key) { - if (!map || !key) return NULL; - return (KeyValuePair*)list_binary_search(map, key, compare_kv_with_key); -} - -void flat_map_free(FlatMap* map) { - if (!map) return; - - for (size_t i = 0; i < map->len; i++) { - KeyValuePair* kv = &((KeyValuePair*)map->items)[i]; - free(kv->key); - free(kv->value); - } - - free(map->items); - free(map); -} - static int is_dot_or_dotdot(const char* s) { return (s[0] == '.' && (s[1] == '\0' || (s[1] == '.' && s[2] == '\0'))); } @@ -464,4 +406,4 @@ int create_default_config_file(char* config_path) { fclose(fp); return 0; -} +} \ No newline at end of file