From f19209ba0aa392f12724a687f104876e41fe23c3 Mon Sep 17 00:00:00 2001 From: lisk77 Date: Mon, 15 Sep 2025 03:23:49 +0200 Subject: [PATCH] feat(commit): added structs and functionality for the commit log --- include/commit.h | 31 ++++ src/commit.c | 466 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 497 insertions(+) create mode 100644 include/commit.h create mode 100644 src/commit.c diff --git a/include/commit.h b/include/commit.h new file mode 100644 index 0000000..7c7cec3 --- /dev/null +++ b/include/commit.h @@ -0,0 +1,31 @@ +#ifndef COMMIT_H +#define COMMIT_H + +#include + +#include "utilities.h" + +typedef struct { + char* name; + char* email; + char* timestamp; +} Author; + +typedef struct { + char* hash; + char* tree_hash; + Author* authors; + size_t authors_count; + Author committer; + char* message; +} Commit; + +typedef List CommitLog; + +CommitLog* commit_log_new(); +void commit_log_push(CommitLog*, Commit); +void commit_log_free(CommitLog*); +int read_commit_log(CommitLog*, char*); +int write_commit_log(CommitLog*, char*); + +#endif // COMMIT_H diff --git a/src/commit.c b/src/commit.c new file mode 100644 index 0000000..25ca833 --- /dev/null +++ b/src/commit.c @@ -0,0 +1,466 @@ +#include "commit.h" + +CommitLog* commit_log_new() { + return list_new(sizeof(Commit)); +} + +void commit_log_push(CommitLog* log, Commit commit) { + if (!log) return; + list_push(log, &commit); +} + +void commit_log_free(CommitLog* log) { + if (log) { + for (size_t i = 0; i < log->len; i++) { + Commit* commit = (Commit*)log->items + i; + free(commit->hash); + free(commit->tree_hash); + if (commit->authors) { + for (size_t j = 0; j < commit->authors_count; j++) { + free(commit->authors[j].name); + free(commit->authors[j].email); + } + free(commit->authors); + } + free(commit->committer.name); + free(commit->committer.email); + free(commit->message); + } + free(log->items); + free(log); + } +} + +int read_commit_log(CommitLog* log, char* commit_log_path) { + size_t compressed_size; + unsigned char* compressed_data = (unsigned char*)get_file_content_with_size(commit_log_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 commit log!"); + 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_commit_log!"); + free(size_str); + free(compressed_data); + return 0; + } + free(size_str); + + if (idx < compressed_size && compressed_data[idx] == ' ') { + idx++; + } + + char* commit_log_data = calloc(original_size + 1, sizeof(char)); + if (!commit_log_data) { + free(compressed_data); + return 0; + } + + uLongf dest_len = (uLongf)original_size; + + int result = uncompress((unsigned char*)commit_log_data, &dest_len, compressed_data + idx, compressed_size - idx); + free(compressed_data); + + if (result != Z_OK) { + perror("ERROR: decompression of the commit log failed!"); + free(commit_log_data); + return 0; + } + + commit_log_data[dest_len] = '\0'; + + size_t data_len = strlen(commit_log_data); + if (data_len == 0) { + free(commit_log_data); + return 1; + } + + idx = 0; + + while (idx < data_len && IS_DIGIT(commit_log_data[idx])) { + idx++; + } + + if (idx == 0) { + perror("ERROR: no commit count found at start of commit log!"); + free(commit_log_data); + return 0; + } + + char* count_str = calloc(idx + 1, sizeof(char)); + memcpy(count_str, commit_log_data, idx); + count_str[idx] = '\0'; + + long commit_count = strtol(count_str, &end, 10); + if (end == count_str || *end != '\0') { + perror("ERROR: invalid commit count in commit log!"); + free(count_str); + free(commit_log_data); + return 0; + } + free(count_str); + + if (idx < data_len && commit_log_data[idx] == ' ') { + idx++; + } + + for (int i = 0; i < commit_count; i++) { + Commit commit = {0}; + + commit.hash = calloc(41, sizeof(char)); + memcpy(commit.hash, commit_log_data + idx, 40); + commit.hash[40] = '\0'; + idx += 40; + + if (idx < data_len && commit_log_data[idx] == ' ') { + idx++; + } + + commit.tree_hash = calloc(41, sizeof(char)); + memcpy(commit.tree_hash, commit_log_data + idx, 40); + commit.tree_hash[40] = '\0'; + idx += 40; + + if (idx < data_len && commit_log_data[idx] == ' ') { + idx++; + } + + size_t authors_count_start = idx; + while (idx < data_len && IS_DIGIT(commit_log_data[idx])) { + idx++; + } + + if (idx == authors_count_start) { + perror("ERROR: no authors count found in commit log!"); + free(commit_log_data); + return 0; + } + + size_t authors_count_size = idx - authors_count_start; + char* authors_count_str = calloc(authors_count_size + 1, sizeof(char)); + memcpy(authors_count_str, commit_log_data + authors_count_start, authors_count_size); + authors_count_str[authors_count_size] = '\0'; + + long authors_count = strtol(authors_count_str, &end, 10); + if (end == authors_count_str || *end != '\0') { + perror("ERROR: invalid authors count in commit log!"); + free(authors_count_str); + free(commit_log_data); + return 0; + } + free(authors_count_str); + + commit.authors_count = authors_count; + commit.authors = calloc(authors_count, sizeof(Author)); + if (!commit.authors) { + free(commit_log_data); + return 0; + } + + if (idx < data_len && commit_log_data[idx] == ' ') { + idx++; + } + + for (size_t a = 0; a < authors_count; a++) { + size_t name_len_start = idx; + while (idx < data_len && IS_DIGIT(commit_log_data[idx])) { + idx++; + } + size_t name_len_size = idx - name_len_start; + char* name_len_str = calloc(name_len_size + 1, sizeof(char)); + memcpy(name_len_str, commit_log_data + name_len_start, name_len_size); + long name_len = strtol(name_len_str, &end, 10); + free(name_len_str); + + if (idx < data_len && commit_log_data[idx] == ' ') { + idx++; + } + + commit.authors[a].name = calloc(name_len + 1, sizeof(char)); + memcpy(commit.authors[a].name, commit_log_data + idx, name_len); + idx += name_len; + + if (idx < data_len && commit_log_data[idx] == ' ') { + idx++; + } + + size_t email_len_start = idx; + while (idx < data_len && IS_DIGIT(commit_log_data[idx])) { + idx++; + } + size_t email_len_size = idx - email_len_start; + char* email_len_str = calloc(email_len_size + 1, sizeof(char)); + memcpy(email_len_str, commit_log_data + email_len_start, email_len_size); + long email_len = strtol(email_len_str, &end, 10); + free(email_len_str); + + if (idx < data_len && commit_log_data[idx] == ' ') { + idx++; + } + + commit.authors[a].email = calloc(email_len + 1, sizeof(char)); + memcpy(commit.authors[a].email, commit_log_data + idx, email_len); + idx += email_len; + + if (idx < data_len && commit_log_data[idx] == ' ') { + idx++; + } + + size_t timestamp_start = idx; + while (idx < data_len && commit_log_data[idx] != ' ') { + idx++; + } + + size_t timestamp_len = idx - timestamp_start; + + if (timestamp_len <= 0 || timestamp_start + timestamp_len > data_len) { + fprintf(stderr, "ERROR: Invalid timestamp length or start index in commit log!\n"); + free(commit_log_data); + return 0; + } + + commit.authors[a].timestamp = calloc(timestamp_len + 1, sizeof(char)); + if (!commit.authors[a].timestamp) { + fprintf(stderr, "ERROR: Memory allocation failed for timestamp!\n"); + free(commit_log_data); + return 0; + } + memcpy(commit.authors[a].timestamp, commit_log_data + timestamp_start, timestamp_len); + commit.authors[a].timestamp[timestamp_len] = '\0'; + + + if (idx < data_len && commit_log_data[idx] == ' ') { + idx++; + } + + } + + size_t name_len_start = idx; + while (idx < data_len && IS_DIGIT(commit_log_data[idx])) { + idx++; + } + + size_t name_len_size = idx - name_len_start; + char* name_len_str = calloc(name_len_size + 1, sizeof(char)); + memcpy(name_len_str, commit_log_data + name_len_start, name_len_size); + long name_len = strtol(name_len_str, &end, 10); + free(name_len_str); + + if (idx < data_len && commit_log_data[idx] == ' ') { + idx++; + } + + commit.committer.name = calloc(name_len + 1, sizeof(char)); + memcpy(commit.committer.name, commit_log_data + idx, name_len); + idx += name_len; + + if (idx < data_len && commit_log_data[idx] == ' ') { + idx++; + } + + size_t email_len_start = idx; + while (idx < data_len && IS_DIGIT(commit_log_data[idx])) { + idx++; + } + size_t email_len_size = idx - email_len_start; + char* email_len_str = calloc(email_len_size + 1, sizeof(char)); + memcpy(email_len_str, commit_log_data + email_len_start, email_len_size); + long email_len = strtol(email_len_str, &end, 10); + free(email_len_str); + + if (idx < data_len && commit_log_data[idx] == ' ') { + idx++; + } + + commit.committer.email = calloc(email_len + 1, sizeof(char)); + memcpy(commit.committer.email, commit_log_data + idx, email_len); + idx += email_len; + + if (idx < data_len && commit_log_data[idx] == ' ') { + idx++; + } + + size_t timestamp_start = idx; + while (idx < data_len && commit_log_data[idx] != ' ') { + idx++; + } + size_t timestamp_len = idx - timestamp_start; + + commit.committer.timestamp = calloc(timestamp_len + 1, sizeof(char)); + if (!commit.committer.timestamp) { + fprintf(stderr, "ERROR: Memory allocation failed for committer timestamp!\n"); + free(commit_log_data); + return 0; + } + memcpy(commit.committer.timestamp, commit_log_data + timestamp_start, timestamp_len); + commit.committer.timestamp[timestamp_len] = '\0'; + + if (idx < data_len && commit_log_data[idx] == ' ') { + idx++; + } + + size_t message_len_start = idx; + while (idx < data_len && IS_DIGIT(commit_log_data[idx])) { + idx++; + } + + size_t message_len_size = idx - message_len_start; + char* message_len_str = calloc(message_len_size + 1, sizeof(char)); + memcpy(message_len_str, commit_log_data + message_len_start, message_len_size); + long message_len = strtol(message_len_str, &end, 10); + free(message_len_str); + + if (idx < data_len && commit_log_data[idx] == ' ') { + idx++; + } + + commit.message = calloc(message_len + 1, sizeof(char)); + memcpy(commit.message, commit_log_data + idx, message_len); + commit.message[message_len] = '\0'; + idx += message_len; + + if (i < commit_count - 1 && idx < data_len && commit_log_data[idx] == ' ') { + idx++; + } + + commit_log_push(log, commit); + } + + free(commit_log_data); + return 1; +} + +int write_commit_log(CommitLog* log, char* commit_log_path) { + if (!log) return 0; + + size_t buffer_size = 1; + buffer_size += snprintf(NULL, 0, "%zu ", log->len); + + for (size_t i = 0; i < log->len; i++) { + Commit* commit = (Commit*)log->items + i; + + buffer_size += 40 + 1; + buffer_size += 40 + 1; + + buffer_size += snprintf(NULL, 0, "%zu ", commit->authors_count); + + for (size_t a = 0; a < commit->authors_count; a++) { + size_t name_len = strlen(commit->authors[a].name); + size_t email_len = strlen(commit->authors[a].email); + + buffer_size += snprintf(NULL, 0, "%zu ", name_len); + buffer_size += name_len + 1; + buffer_size += snprintf(NULL, 0, "%zu ", email_len); + buffer_size += email_len + 1; + buffer_size += strlen(commit->authors[a].timestamp) + 1; + } + + size_t committer_name_len = strlen(commit->committer.name); + size_t committer_email_len = strlen(commit->committer.email); + + buffer_size += snprintf(NULL, 0, "%zu ", committer_name_len); + buffer_size += committer_name_len + 1; + buffer_size += snprintf(NULL, 0, "%zu ", committer_email_len); + buffer_size += committer_email_len + 1; + buffer_size += strlen(commit->committer.timestamp) + 1; + + size_t message_len = strlen(commit->message); + buffer_size += snprintf(NULL, 0, "%zu ", message_len); + buffer_size += message_len + 1; + } + + char tmp[buffer_size]; + size_t offset = 0; + + offset += snprintf(tmp + offset, sizeof(tmp) - offset, "%zu ", log->len); + + for (size_t i = 0; i < log->len; i++) { + Commit* commit = (Commit*)log->items + i; + size_t remaining = sizeof(tmp) - offset; + + offset += snprintf(tmp + offset, remaining, "%s ", commit->hash); + + remaining = sizeof(tmp) - offset; + offset += snprintf(tmp + offset, remaining, "%s ", commit->tree_hash); + + remaining = sizeof(tmp) - offset; + offset += snprintf(tmp + offset, remaining, "%zu ", commit->authors_count); + + for (size_t a = 0; a < commit->authors_count; a++) { + remaining = sizeof(tmp) - offset; + size_t name_len = strlen(commit->authors[a].name); + size_t email_len = strlen(commit->authors[a].email); + + offset += snprintf(tmp + offset, remaining, "%zu %s %zu %s %s ", + name_len, commit->authors[a].name, + email_len, commit->authors[a].email, + commit->authors[a].timestamp); + } + + remaining = sizeof(tmp) - offset; + size_t committer_name_len = strlen(commit->committer.name); + size_t committer_email_len = strlen(commit->committer.email); + + offset += snprintf(tmp + offset, remaining, "%zu %s %zu %s %s ", + committer_name_len, commit->committer.name, + committer_email_len, commit->committer.email, + commit->committer.timestamp); + + remaining = sizeof(tmp) - offset; + size_t message_len = strlen(commit->message); + offset += snprintf(tmp + offset, remaining, "%zu %s ", message_len, commit->message); + } + + FILE* fp = fopen(commit_log_path, "wb"); + if (!fp) { + perror("ERROR: cannot open path in write_commit_log!\n"); + return 0; + } + + uLong originalLen = strlen(tmp); + 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 write_commit_log!"); + free(compressed); + fclose(fp); + return 0; + } + + fprintf(fp, "%lu ", originalLen); + fwrite(compressed, sizeof(Bytef), compressedLen, fp); + + fclose(fp); + free(compressed); + + return 1; +}