merk/src/object.c

427 lines
12 KiB
C

#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;
}