diff --git a/include/object.h b/include/object.h index 65432e6..bfce57c 100644 --- a/include/object.h +++ b/include/object.h @@ -24,9 +24,12 @@ #include "hash.h" #include "file.h" #include "tree.h" -#include "utilities.h" +#include "common.h" char* get_object(char*, size_t*); void* parse_object(char*, ObjectType, size_t*); +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*, Changes*); #endif // OBJECT_H \ No newline at end of file diff --git a/include/utilities.h b/include/utilities.h index e42aa53..a6b1209 100644 --- a/include/utilities.h +++ b/include/utilities.h @@ -9,11 +9,6 @@ #include "myers.h" #include "action_list.h" -#define IS_ALPHA(c) ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) -#define IS_DIGIT(c) (c >= '0' && c <= '9') -#define IS_ALNUM(c) (IS_ALPHA(c) || IS_DIGIT(c)) -#define IS_PUNCT(c) ((c >= 33 && c <= 47) || (c >= 58 && c <= 64) || (c >= 91 && c <= 96) || (c >= 123 && c <= 126)) - #define RESET "\033[0m" #define RED_BG "\033[41m" #define GREEN_BG "\033[42m" @@ -29,10 +24,6 @@ typedef enum { typedef List StringBuffer; -typedef struct { - uint32_t insertions; - uint32_t deletions; -} Changes; StringBuffer* string_buffer_new(); int string_buffer_push(StringBuffer*, char*); @@ -42,15 +33,12 @@ char* find_root(char*); void walk(char*, char*, char*, FileInfoBuffer*, int, char*); char* get_repo_path(char*, char*); int is_in_repo(char*, char*); -void visualize_diff(File*, File*, ActionList*); int cut_path(char*, char*); void combine_path(char*, char*); PathType get_path_type(const char*); char* get_file_content(char*); char* get_file_content_with_size(char*, size_t*); int create_default_config_file(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*, Changes*); + #endif // UTILITIES_H diff --git a/src/object.c b/src/object.c index 343afa6..e121b63 100644 --- a/src/object.c +++ b/src/object.c @@ -114,4 +114,377 @@ void* parse_object(char* hash, ObjectType type, size_t* size_out) { default: free(content); return NULL; } +} + +static int mkdir_recursive(const char *path, mode_t mode) { + char *path_copy = strdup(path); + if (!path_copy) return -1; + + char *p = path_copy; + + if (*p == '/') p++; + + while (*p) { + while (*p && *p != '/') p++; + + char saved = *p; + *p = '\0'; + + if (mkdir(path_copy, mode) == -1 && errno != EEXIST) { + free(path_copy); + return -1; + } + + *p = saved; + if (*p) p++; + } + + free(path_copy); + return 0; +} + +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); + + for (size_t idx = 0; idx < diff->len; idx++) { + Action action = diff->actions[idx]; + if (action.type == DELETE) { + // 4 = OPBIT SPACE ... SPACE ... SPACE + buffer_size += + 4 + + snprintf(NULL, 0, "%d", action.line_original) + + snprintf(NULL, 0, "%d", action.line_changed); + continue; + } + // 6 = OPBIT SPACE ... SPACE ... SPACE ... SPACE ... SPACE + buffer_size += + 6 + + snprintf(NULL, 0, "%d", action.line_original) + + snprintf(NULL, 0, "%d", action.line_changed) + + snprintf(NULL, 0, "%zu", strlen(modified_file->content[action.line_changed])) + + strlen(modified_file->content[action.line_changed]); + } + + char tmp[buffer_size]; + size_t offset = 0; + + offset += snprintf(tmp, sizeof(tmp), "%s %zu ", basefile_hash, diff->len); + + for (size_t idx = 0; idx < diff->len; idx++) { + Action action = diff->actions[idx]; + size_t remaining = sizeof(tmp) - offset; + + if (action.type == DELETE) { + 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(modified_file->content[action.line_changed]), + modified_file->content[action.line_changed] + ); + } + + char id[2+snprintf(NULL, 0, "%d", diff_id)+strlen(path)]; + snprintf(id, sizeof(id), "%s %d", path, diff_id); + char hash[41]; + if (tree) object_hash(TreeDiffObject, id, hash); + else object_hash(FileDiffObject, id, hash); + + char dir_path[PATH_MAX]; + char file_path[PATH_MAX]; + snprintf(dir_path, sizeof(dir_path), "%s/.merk/objects/%.2s", root, hash); + mkdir(dir_path, 0755); + snprintf(file_path, sizeof(file_path), "%s/%s", dir_path, hash+2); + + if (access(file_path, F_OK) == 0) { + return 1; + } + + FILE* fp = fopen(file_path, "wb"); + if (!fp) { + perror("ERROR: cannot open path in save_diff!\n"); + return 0; + } + + // Ensure the directory structure exists + if (mkdir_recursive(dir_path, 0755) < 0 && errno != EEXIST) { + perror("ERROR: failed to create directory structure in save_diff!"); + return 0; + } + + uLong originalLen = strlen(tmp) + 1; + uLong compressedLen = compressBound(originalLen); + Bytef* compressed = malloc(compressedLen); + if (!compressed) { + fclose(fp); + return 0; + } + + if (compress(compressed, &compressedLen, (const Bytef*)tmp, originalLen) != Z_OK) { + perror("ERROR: compression failed in save_diff!"); + free(compressed); + fclose(fp); + return 0; + } + + fprintf(fp, "%lu ", (unsigned long)originalLen); + + fwrite(compressed, 1, compressedLen, fp); + + fclose(fp); + + chmod(file_path, S_IRUSR | S_IRGRP | S_IROTH); + + return 1; +} + +int read_diff(char* content, char* basefile_hash, ActionList* diff_out) { + if (!content || !basefile_hash || !diff_out) { + return 0; + } + + size_t idx = 0; + strncpy(basefile_hash, content, 40); + basefile_hash[40] = '\0'; + idx += 41; + + while (content[idx] && IS_DIGIT(content[idx])) { + idx++; + } + + if (idx == 41) { + perror("ERROR: no length found at start of diff!"); + return 0; + } + + char* number_of_actions = calloc(idx - 41 + 1, sizeof(char)); + if (!number_of_actions) { + return 0; + } + + memcpy(number_of_actions, content + 41, idx - 41); + number_of_actions[idx - 41] = '\0'; + + char* end; + long actions = strtol(number_of_actions, &end, 10); + free(number_of_actions); + + if (end == number_of_actions || *end != '\0' || actions <= 0) { + perror("ERROR: invalid number of actions in read_diff!"); + return 0; + } + + size_t action_idx = 0; + while (content[idx] && action_idx < (size_t)actions) { + idx++; + + Action action = {0}; + + if (content[idx] == '0') { + idx++; + if (content[idx] != ' ') { + perror("ERROR: expected space after opbit in read_diff!"); + return 0; + } + idx++; + + char* endptr; + action.line_original = strtol(content + idx, &endptr, 10); + if (endptr == content + idx || *endptr != ' ') { + perror("ERROR: failed to parse line_original in read_diff!"); + return 0; + } + idx = endptr - content + 1; + + action.line_changed = strtol(content + idx, &endptr, 10); + if (endptr == content + idx || (*endptr != ' ' && *endptr != '\0')) { + perror("ERROR: failed to parse line_changed in read_diff!"); + return 0; + } + idx = endptr - content; + } else if (content[idx] == '1') { + idx++; + if (content[idx] != ' ') { + perror("ERROR: expected space after opbit in read_diff!"); + return 0; + } + idx++; + + char* endptr; + action.line_original = strtol(content + idx, &endptr, 10); + if (endptr == content + idx || *endptr != ' ') { + perror("ERROR: failed to parse line_original in read_diff!"); + return 0; + } + idx = endptr - content + 1; + + action.line_changed = strtol(content + idx, &endptr, 10); + if (endptr == content + idx || *endptr != ' ') { + perror("ERROR: failed to parse line_changed in read_diff!"); + return 0; + } + idx = endptr - content + 1; + + size_t content_length = strtol(content + idx, &endptr, 10); + if (endptr == content + idx || *endptr != ' ') { + perror("ERROR: failed to parse content length in read_diff!"); + return 0; + } + idx = endptr - content + 1; + + action.content = strndup(content + idx, content_length); + if (!action.content) { + perror("ERROR: failed to allocate memory for action content in read_diff!"); + return 0; + } + idx += content_length; + } else { + perror("ERROR: unknown action type in read_diff!"); + return 0; + } + + add_action(diff_out, action); + + action_idx++; + } + + return 1; +} + +int save_file_diff(char* path, char* root, size_t diff_id, char* basefile_hash, Changes* changes) { + char basefile_location[2+strlen(root)+strlen("/.merk/objects/")+strlen(basefile_hash)]; + snprintf(basefile_location, sizeof(basefile_location), "%s/.merk/objects/%.2s/%s", root, basefile_hash, basefile_hash+2); + + size_t compressed_size; + unsigned char* compressed_data = (unsigned char*)get_file_content_with_size(basefile_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 at start of save_file_diff!"); + 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') { + perror("ERROR: invalid length in base_file_list"); + free(size_str); + free(compressed_data); + return 0; + } + + size_t offset = strlen(size_str) + 1; + free(size_str); + + char* basefile_content = calloc(size+1, sizeof(char)); + if (!basefile_content) { + free(compressed_data); + return 0; + } + + uLongf dest_len = (uLongf)size; + + int result = uncompress((unsigned char*)basefile_content, &dest_len, compressed_data+offset, compressed_size); + free(compressed_data); + + if (result != Z_OK) { + perror("ERROR: decompression of the base file list failed!"); + free(basefile_content); + return 0; + } + + basefile_content[dest_len] = '\0'; + + File* basefile = from_string(basefile_content); + char* modified_file_path = realpath(path, NULL); + File* modified_file = new_file(modified_file_path); + + ActionList* diff = myers_diff(basefile, modified_file, 0, 0); + if (diff->len == 0) { + free(basefile_content); + return 1; + } + + + 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; + } + } + + save_diff(diff, path, root, diff_id, basefile_hash, modified_file, 0); + + free(basefile_content); + return 1; +} + +File* apply_diff(File* basefile, ActionList* diff) { + if (!basefile || !diff) return NULL; + + File* copy = copy_file(basefile); + if (!copy) return NULL; + + sort_action_list(diff); + + for (size_t i = 0; i < diff->len; i++) { + Action* action = &diff->actions[i]; + int success = 0; + + switch (action->type) { + case INSERT: + success = insert_line(copy, action->content, action->line_changed); + if (!success) { + fprintf(stderr, "ERROR: Failed to insert line at index %zu\n", action->line_changed); + free_file(copy); + return NULL; + } + break; + + case DELETE: + success = delete_line(copy, action->line_original); + if (!success) { + fprintf(stderr, "ERROR: Failed to delete line at index %zu\n", action->line_original); + free_file(copy); + return NULL; + } + break; + + default: + fprintf(stderr, "ERROR: Unknown action type %d\n", action->type); + free_file(copy); + return NULL; + } + } + + return copy; } \ No newline at end of file diff --git a/src/utilities.c b/src/utilities.c index 54dccfd..9bfe709 100644 --- a/src/utilities.c +++ b/src/utilities.c @@ -350,48 +350,6 @@ PathType get_path_type(const char* path) { return PT_OTHER; } -char* get_file_content(char* path) { - if (!path) return NULL; - - FILE* file = fopen(path, "rb"); - if (!file) { perror("ERROR: could not open file in get_file_content!"); return NULL; } - if (fseek(file, 0, SEEK_END) != 0) { fclose(file); return NULL; } - long file_size = ftell(file); - if (file_size < 0) { perror("ERROR: file size is negative in get_file_content!"); fclose(file); return NULL; } - if (fseek(file, 0, SEEK_SET) != 0) { fclose(file); return NULL; } - size_t n = (size_t)file_size; - char* buf = (char*)calloc(n + 1, 1); - if (!buf) { fclose(file); return NULL; } - - size_t got = fread(buf, 1, n, file); - fclose(file); - - buf[got] = '\0'; - - return buf; -} - -char* get_file_content_with_size(char* path, size_t* size) { - if (!path || !size) return NULL; - - FILE* file = fopen(path, "rb"); - if (!file) { perror("ERROR: could not open file in get_file_content_with_size!"); return NULL; } - if (fseek(file, 0, SEEK_END) != 0) { fclose(file); return NULL; } - long file_size = ftell(file); - if (file_size < 0) { perror("ERROR: file size is negative in get_file_content_with_size!"); fclose(file); return NULL; } - if (fseek(file, 0, SEEK_SET) != 0) { fclose(file); return NULL; } - size_t n = (size_t)file_size; - - char* buf = (char*)malloc(n); - if (!buf) { fclose(file); return NULL; } - - size_t got = fread(buf, 1, n, file); - fclose(file); - - *size = got; - return buf; -} - // Create directory recursively static int mkdir_recursive(const char *path, mode_t mode) { char *path_copy = strdup(path); @@ -448,350 +406,4 @@ int create_default_config_file(char* config_path) { fclose(fp); return 0; -} - -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); - - for (size_t idx = 0; idx < diff->len; idx++) { - Action action = diff->actions[idx]; - if (action.type == DELETE) { - // 4 = OPBIT SPACE ... SPACE ... SPACE - buffer_size += - 4 + - snprintf(NULL, 0, "%d", action.line_original) + - snprintf(NULL, 0, "%d", action.line_changed); - continue; - } - // 6 = OPBIT SPACE ... SPACE ... SPACE ... SPACE ... SPACE - buffer_size += - 6 + - snprintf(NULL, 0, "%d", action.line_original) + - snprintf(NULL, 0, "%d", action.line_changed) + - snprintf(NULL, 0, "%zu", strlen(modified_file->content[action.line_changed])) + - strlen(modified_file->content[action.line_changed]); - } - - char tmp[buffer_size]; - size_t offset = 0; - - offset += snprintf(tmp, sizeof(tmp), "%s %zu ", basefile_hash, diff->len); - - for (size_t idx = 0; idx < diff->len; idx++) { - Action action = diff->actions[idx]; - size_t remaining = sizeof(tmp) - offset; - - if (action.type == DELETE) { - 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(modified_file->content[action.line_changed]), - modified_file->content[action.line_changed] - ); - } - - char id[2+snprintf(NULL, 0, "%d", diff_id)+strlen(path)]; - snprintf(id, sizeof(id), "%s %d", path, diff_id); - char hash[41]; - if (tree) object_hash(TreeDiffObject, id, hash); - else object_hash(FileDiffObject, id, hash); - - char dir_path[PATH_MAX]; - char file_path[PATH_MAX]; - snprintf(dir_path, sizeof(dir_path), "%s/.merk/objects/%.2s", root, hash); - mkdir(dir_path, 0755); - snprintf(file_path, sizeof(file_path), "%s/%s", dir_path, hash+2); - - if (access(file_path, F_OK) == 0) { - return 1; - } - - FILE* fp = fopen(file_path, "wb"); - if (!fp) { - perror("ERROR: cannot open path in save_diff!\n"); - return 0; - } - - // Ensure the directory structure exists - if (mkdir_recursive(dir_path, 0755) < 0 && errno != EEXIST) { - perror("ERROR: failed to create directory structure in save_diff!"); - return 0; - } - - uLong originalLen = strlen(tmp) + 1; - uLong compressedLen = compressBound(originalLen); - Bytef* compressed = malloc(compressedLen); - if (!compressed) { - fclose(fp); - return 0; - } - - if (compress(compressed, &compressedLen, (const Bytef*)tmp, originalLen) != Z_OK) { - perror("ERROR: compression failed in save_diff!"); - free(compressed); - fclose(fp); - return 0; - } - - fprintf(fp, "%lu ", (unsigned long)originalLen); - - fwrite(compressed, 1, compressedLen, fp); - - fclose(fp); - - chmod(file_path, S_IRUSR | S_IRGRP | S_IROTH); - - return 1; -} - -int read_diff(char* content, char* basefile_hash, ActionList* diff_out) { - if (!content || !basefile_hash || !diff_out) { - return 0; - } - - size_t idx = 0; - strncpy(basefile_hash, content, 40); - basefile_hash[40] = '\0'; - idx += 41; - - while (content[idx] && IS_DIGIT(content[idx])) { - idx++; - } - - if (idx == 41) { - perror("ERROR: no length found at start of diff!"); - return 0; - } - - char* number_of_actions = calloc(idx - 41 + 1, sizeof(char)); - if (!number_of_actions) { - return 0; - } - - memcpy(number_of_actions, content + 41, idx - 41); - number_of_actions[idx - 41] = '\0'; - - char* end; - long actions = strtol(number_of_actions, &end, 10); - free(number_of_actions); - - if (end == number_of_actions || *end != '\0' || actions <= 0) { - perror("ERROR: invalid number of actions in read_diff!"); - return 0; - } - - size_t action_idx = 0; - while (content[idx] && action_idx < (size_t)actions) { - idx++; - - Action action = {0}; - - if (content[idx] == '0') { - idx++; - if (content[idx] != ' ') { - perror("ERROR: expected space after opbit in read_diff!"); - return 0; - } - idx++; - - char* endptr; - action.line_original = strtol(content + idx, &endptr, 10); - if (endptr == content + idx || *endptr != ' ') { - perror("ERROR: failed to parse line_original in read_diff!"); - return 0; - } - idx = endptr - content + 1; - - action.line_changed = strtol(content + idx, &endptr, 10); - if (endptr == content + idx || (*endptr != ' ' && *endptr != '\0')) { - perror("ERROR: failed to parse line_changed in read_diff!"); - return 0; - } - idx = endptr - content; - } else if (content[idx] == '1') { - idx++; - if (content[idx] != ' ') { - perror("ERROR: expected space after opbit in read_diff!"); - return 0; - } - idx++; - - char* endptr; - action.line_original = strtol(content + idx, &endptr, 10); - if (endptr == content + idx || *endptr != ' ') { - perror("ERROR: failed to parse line_original in read_diff!"); - return 0; - } - idx = endptr - content + 1; - - action.line_changed = strtol(content + idx, &endptr, 10); - if (endptr == content + idx || *endptr != ' ') { - perror("ERROR: failed to parse line_changed in read_diff!"); - return 0; - } - idx = endptr - content + 1; - - size_t content_length = strtol(content + idx, &endptr, 10); - if (endptr == content + idx || *endptr != ' ') { - perror("ERROR: failed to parse content length in read_diff!"); - return 0; - } - idx = endptr - content + 1; - - action.content = strndup(content + idx, content_length); - if (!action.content) { - perror("ERROR: failed to allocate memory for action content in read_diff!"); - return 0; - } - idx += content_length; - } else { - perror("ERROR: unknown action type in read_diff!"); - return 0; - } - - add_action(diff_out, action); - - action_idx++; - } - - return 1; -} - -int save_file_diff(char* path, char* root, size_t diff_id, char* basefile_hash, Changes* changes) { - char basefile_location[2+strlen(root)+strlen("/.merk/objects/")+strlen(basefile_hash)]; - snprintf(basefile_location, sizeof(basefile_location), "%s/.merk/objects/%.2s/%s", root, basefile_hash, basefile_hash+2); - - size_t compressed_size; - unsigned char* compressed_data = (unsigned char*)get_file_content_with_size(basefile_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 at start of save_file_diff!"); - 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') { - perror("ERROR: invalid length in base_file_list"); - free(size_str); - free(compressed_data); - return 0; - } - - size_t offset = strlen(size_str) + 1; - free(size_str); - - char* basefile_content = calloc(size+1, sizeof(char)); - if (!basefile_content) { - free(compressed_data); - return 0; - } - - uLongf dest_len = (uLongf)size; - - int result = uncompress((unsigned char*)basefile_content, &dest_len, compressed_data+offset, compressed_size); - free(compressed_data); - - if (result != Z_OK) { - perror("ERROR: decompression of the base file list failed!"); - free(basefile_content); - return 0; - } - - basefile_content[dest_len] = '\0'; - - File* basefile = from_string(basefile_content); - char* modified_file_path = realpath(path, NULL); - File* modified_file = new_file(modified_file_path); - - ActionList* diff = myers_diff(basefile, modified_file, 0, 0); - if (diff->len == 0) { - free(basefile_content); - return 1; - } - - - 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; - } - } - - save_diff(diff, path, root, diff_id, basefile_hash, modified_file, 0); - - free(basefile_content); - return 1; -} - -File* apply_diff(File* basefile, ActionList* diff) { - if (!basefile || !diff) return NULL; - - File* copy = copy_file(basefile); - if (!copy) return NULL; - - sort_action_list(diff); - - for (size_t i = 0; i < diff->len; i++) { - Action* action = &diff->actions[i]; - int success = 0; - - switch (action->type) { - case INSERT: - success = insert_line(copy, action->content, action->line_changed); - if (!success) { - fprintf(stderr, "ERROR: Failed to insert line at index %zu\n", action->line_changed); - free_file(copy); - return NULL; - } - break; - - case DELETE: - success = delete_line(copy, action->line_original); - if (!success) { - fprintf(stderr, "ERROR: Failed to delete line at index %zu\n", action->line_original); - free_file(copy); - return NULL; - } - break; - - default: - fprintf(stderr, "ERROR: Unknown action type %d\n", action->type); - free_file(copy); - return NULL; - } - } - - return copy; } \ No newline at end of file