#include "object.h" char* get_object(char* hash, size_t* size_out) { char* root = find_root(NULL); if (!root) { perror("ERROR: unable to find root directory of the repository!"); return NULL; } char dir_path[PATH_MAX]; char file_path[PATH_MAX]; snprintf(dir_path, sizeof(dir_path), "%s/.merk/objects/%.2s", root, hash); snprintf(file_path, sizeof(file_path), "%s/%s", dir_path, hash+2); size_t compressed_size; unsigned char* compressed_data = (unsigned char*)get_file_content_with_size(file_path, &compressed_size); if (!compressed_data || compressed_size == 0) { free(compressed_data); return NULL; } 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 object!"); free(compressed_data); return NULL; } char* size_str = calloc(idx + 1, sizeof(char)); if (!size_str) { free(compressed_data); return NULL; } 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 get_object!"); free(size_str); free(compressed_data); return NULL; } free(size_str); if (idx < compressed_size && compressed_data[idx] == ' ') { idx++; } char* decompressed = malloc(original_size + 1); if (!decompressed) { free(compressed_data); return NULL; } uLongf decompressed_size = original_size; if (uncompress((Bytef*)decompressed, &decompressed_size, compressed_data + idx, compressed_size - idx) != Z_OK) { perror("ERROR: decompression failed in get_object!"); free(decompressed); free(compressed_data); return NULL; } free(compressed_data); decompressed[decompressed_size] = '\0'; if (size_out) { *size_out = decompressed_size; } return decompressed; } void* parse_object(char* hash, ObjectType type, size_t* size_out, char* meta_hash) { size_t line_count; char* content = get_object(hash, &line_count); if (!content) return NULL; if (line_count == 0) { free(content); return NULL; } switch (type) { case BaseFileObject: { File* file = from_string(content); if (!file) { free(content); return NULL; } free(content); return file; } case TreeObject: { FileInfoBuffer* buffer = file_info_buffer_new(); if (!buffer) { free(content); return NULL; } if (read_tree(buffer, hash) < 0) { file_info_buffer_free(buffer); free(content); return NULL; } free(content); return buffer; } case FileDiffObject: { ActionList* diff = new_list(); if (!read_diff(content, meta_hash, diff)) { free(content); return NULL; } return diff; } 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, char* branch) { 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(action.content)) + strlen(action.content); } 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(action.content), action.content ); } size_t id_len = snprintf(NULL, 0, "%s %zu %s", path, diff_id, branch) + 1; char* id = calloc(id_len, sizeof(char)); if (!id) { return 0; } snprintf(id, id_len, "%s %zu %s", path, diff_id, branch); char hash[41]; object_hash(FileDiffObject, id, hash); free(id); 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 || !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); if (end == number_of_actions || *end != '\0' || actions < 0) { perror("ERROR: invalid number of actions in read_diff!"); free(number_of_actions); return 0; } size_t action_idx = 0; while (content[idx] && action_idx < (size_t)actions) { idx++; Action action; 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; action.content = NULL; action.type = DELETE; } 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; action.type = INSERT; } 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, ActionList* diff) { 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; }