diff --git a/include/utilities.h b/include/utilities.h index a96ba22..27ef475 100644 --- a/include/utilities.h +++ b/include/utilities.h @@ -6,8 +6,7 @@ #include #include -#include "file.h" -#include "action_list.h" +#include "myers.h" #define IS_ALPHA(c) ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) #define IS_DIGIT(c) (c >= '0' && c <= '9') @@ -65,6 +64,10 @@ 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, char*); +int read_diff(char*, char*, size_t, char*, ActionList*); +int save_file_diff(char*, char*, size_t, char*, char*); +File* apply_diff(File*, ActionList*); +void sort_action_list(ActionList*); #endif // UTILITIES_H diff --git a/src/utilities.c b/src/utilities.c index ad65027..67c43e0 100644 --- a/src/utilities.c +++ b/src/utilities.c @@ -590,3 +590,557 @@ 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, char* hash) { + 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); + 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; + } + + // Check if the file already exists + + + // 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* path, char* root, size_t diff_id, char* basefile_hash, ActionList* diff_out) { + size_t compressed_size; + unsigned char* compressed_data = (unsigned char*)get_file_content_with_size(path, &compressed_size); + + if (!compressed_data || compressed_size == 0) { + free(compressed_data); + return 1; + } + + 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 base file list!"); + 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 original_size = strtol(size_str, &end, 10); + if (end == size_str || *end != '\0') { + perror("ERROR: invalid length in read_diff!"); + free(size_str); + free(compressed_data); + return 0; + } + free(size_str); + + if (idx < compressed_size && compressed_data[idx] == ' ') { + idx++; + } + + char* diff_list = calloc(original_size + 1, sizeof(char)); + if (!diff_list) { + free(compressed_data); + return 0; + } + + uLongf dest_len = (uLongf)original_size; + + int result = uncompress((unsigned char*)diff_list, &dest_len, compressed_data + idx, compressed_size - idx); + free(compressed_data); + + if (result != Z_OK) { + perror("ERROR: decompression of the diff failed!"); + free(diff_list); + return 0; + } + + diff_list[dest_len] = '\0'; + + printf("Decompressed diff content: %s\n", diff_list); + + idx = 0; + strncpy(basefile_hash, diff_list, 40); + basefile_hash[40] = '\0'; + idx += 41; + + while (idx < dest_len && IS_DIGIT(diff_list[idx])) { + idx++; + } + + if (idx == 41) { + perror("ERROR: no length found at start of diff!"); + free(diff_list); + return 0; + } + + char* number_of_actions = calloc(idx + 1, sizeof(char)); + if (!number_of_actions) { + free(diff_list); + return 0; + } + + memcpy(number_of_actions, diff_list + 41, idx-41); + number_of_actions[idx] = '\0'; + + long actions = strtol(number_of_actions, &end, 10); + if (end == number_of_actions || *end != '\0') { + perror("ERROR: invalid length in read_diff!"); + free(number_of_actions); + free(diff_list); + return 0; + } + + free(number_of_actions); + + if (actions <= 0) { + free(diff_list); + return 1; + } + + size_t action_idx = 0; + while (idx < dest_len && action_idx < (size_t)actions) { + idx++; + + if (diff_list[idx] == '0') { + idx++; + if (diff_list[idx] == ' ') idx++; + else { + perror("ERROR: expected space after opbit in read_diff!"); + free(diff_list); + return 0; + } + size_t start_idx = idx; + + while (idx < dest_len && IS_DIGIT(diff_list[idx])) { + idx++; + } + if (idx == dest_len) { + perror("ERROR: unexpected end of diff in read_diff!"); + free(diff_list); + return 0; + } + char* line_original_str = calloc(idx + 1, sizeof(char)); + if (!line_original_str) { + free(diff_list); + return 0; + } + memcpy(line_original_str, diff_list + start_idx, idx - start_idx); + line_original_str[idx - start_idx] = '\0'; + char* end2; + long line_original = strtol(line_original_str, &end2, 10); + if (end2 == line_original_str || *end2 != '\0') { + perror("ERROR: invalid line number in read_diff!"); + free(line_original_str); + free(diff_list); + return 0; + } + free(line_original_str); + + if (diff_list[idx] == ' ') idx++; + else { + perror("ERROR: expected space after line original in read_diff!"); + free(diff_list); + return 0; + } + start_idx = idx; + while (idx < dest_len && IS_DIGIT(diff_list[idx])) { + idx++; + } + if (idx == dest_len) { + perror("ERROR: unexpected end of diff in read_diff!"); + free(diff_list); + return 0; + } + char* line_changed_str = calloc(idx + 1, sizeof(char)); + if (!line_changed_str) { + free(diff_list); + return 0; + } + memcpy(line_changed_str, diff_list + start_idx, idx - start_idx); + line_changed_str[idx - start_idx] = '\0'; + long line_changed = strtol(line_changed_str, &end2, 10); + if (end2 == line_changed_str || *end2 != '\0') { + perror("ERROR: invalid line number in read_diff!"); + free(line_changed_str); + free(diff_list); + return 0; + } + free(line_changed_str); + + Action action = {.type=DELETE, .line_original=line_original, .line_changed=line_changed, .content=NULL}; + add_action(diff_out, action); + action_idx++; + } + else if (diff_list[idx] == '1') { + idx++; + if (diff_list[idx] == ' ') idx++; + else { + perror("ERROR: expected space after opbit in read_diff!"); + free(diff_list); + return 0; + } + size_t start_idx = idx; + + while (idx < dest_len && IS_DIGIT(diff_list[idx])) { + idx++; + } + if (idx == dest_len) { + perror("ERROR: unexpected end of diff in read_diff!"); + free(diff_list); + return 0; + } + char* line_original_str = calloc(idx + 1, sizeof(char)); + if (!line_original_str) { + free(diff_list); + return 0; + } + memcpy(line_original_str, diff_list + start_idx, idx - start_idx); + line_original_str[idx - start_idx] = '\0'; + char* end2; + long line_original = strtol(line_original_str, &end2, 10); + if (end2 == line_original_str || *end2 != '\0') { + perror("ERROR: invalid line number in read_diff!"); + free(line_original_str); + free(diff_list); + return 0; + } + free(line_original_str); + + if (diff_list[idx] == ' ') idx++; + else { + perror("ERROR: expected space after line original in read_diff!"); + free(diff_list); + return 0; + } + start_idx = idx; + while (idx < dest_len && IS_DIGIT(diff_list[idx])) { + idx++; + } + if (idx == dest_len) { + perror("ERROR: unexpected end of diff in read_diff!"); + free(diff_list); + return 0; + } + char* line_changed_str = calloc(idx + 1, sizeof(char)); + if (!line_changed_str) { + free(diff_list); + return 0; + } + memcpy(line_changed_str, diff_list + start_idx, idx - start_idx); + line_changed_str[idx - start_idx] = '\0'; + long line_changed = strtol(line_changed_str, &end2, 10); + if (end2 == line_changed_str || *end2 != '\0') { + perror("ERROR: invalid line number in read_diff!"); + free(line_changed_str); + free(diff_list); + return 0; + } + free(line_changed_str); + + if (diff_list[idx] == ' ') idx++; + else { + perror("ERROR: expected space after line changed in read_diff!"); + free(diff_list); + return 0; + } + + start_idx = idx; + while (idx < dest_len && IS_DIGIT(diff_list[idx])) { + idx++; + } + if (idx == dest_len) { + perror("ERROR: unexpected end of diff in read_diff!"); + free(diff_list); + return 0; + } + char* content_len_str = calloc(idx + 1, sizeof(char)); + if (!content_len_str) { + free(diff_list); + return 0; + } + memcpy(content_len_str, diff_list + start_idx, idx - start_idx); + content_len_str[idx - start_idx] = '\0'; + long content_len = strtol(content_len_str, &end2, 10); + if (end2 == content_len_str || *end2 != '\0') { + perror("ERROR: invalid line number in read_diff!"); + free(content_len_str); + free(diff_list); + return 0; + } + free(content_len_str); + + if (diff_list[idx] == ' ') idx++; + else { + perror("ERROR: expected space after line original in read_diff!"); + free(diff_list); + return 0; + } + + if (idx + content_len > dest_len) { + perror("ERROR: content length exceeds diff length in read_diff!"); + free(diff_list); + return 0; + } + + char* content = calloc(content_len + 1, sizeof(char)); + if (!content) { + free(diff_list); + return 0; + } + memcpy(content, diff_list + idx, content_len); + content[content_len] = '\0'; + + idx += content_len; + + Action action = {.type=INSERT, .line_original=line_original, .line_changed=line_changed, .content=content}; + add_action(diff_out, action); + action_idx++; + } + else { + perror("ERROR: invalid opbit in read_diff!"); + free(diff_list); + return 0; + } + } + + return 1; +} + + +int save_file_diff(char* path, char* root, size_t diff_id, char* basefile_hash, char* hash) { + 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) { + hash = NULL; + free(basefile_content); + return 1; + } + + save_diff(diff, path, root, diff_id, basefile_hash, modified_file, 0, hash); + + 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; + + 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_changed); + if (!success) { + fprintf(stderr, "ERROR: Failed to delete line at index %zu\n", action->line_changed); + free_file(copy); + return NULL; + } + break; + + default: + fprintf(stderr, "ERROR: Unknown action type %d\n", action->type); + free_file(copy); + return NULL; + } + } + + return copy; +} + +int compare_actions(const void* a, const void* b) { + const Action* action1 = (const Action*)a; + const Action* action2 = (const Action*)b; + + if (action1->type != action2->type) { + return (action1->type == DELETE) ? -1 : 1; + } + + return 0; +} + +void sort_action_list(ActionList* actions) { + if (!actions || actions->len <= 1) return; + + qsort(actions->actions, actions->len, sizeof(Action), compare_actions); +} \ No newline at end of file