Compare commits
No commits in common. "8aa95da2a1e6b95dd610293c37ed9120278c1456" and "cba5d43039d5db5496fd013452bf151b878995c6" have entirely different histories.
8aa95da2a1
...
cba5d43039
12 changed files with 1050 additions and 903 deletions
|
|
@ -1,21 +0,0 @@
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
#include "base_file_buffer.h"
|
|
||||||
#include "config.h"
|
|
||||||
#include "commit.h"
|
|
||||||
#include "hash.h"
|
|
||||||
#include "object.h"
|
|
||||||
#include "utilities.h"
|
|
||||||
|
|
||||||
void usage(int);
|
|
||||||
int init();
|
|
||||||
int diff(char*, char*);
|
|
||||||
int status();
|
|
||||||
int commit(int, char**);
|
|
||||||
int commit_log();
|
|
||||||
|
|
@ -10,6 +10,7 @@ typedef enum {
|
||||||
BaseFileObject,
|
BaseFileObject,
|
||||||
FileDiffObject,
|
FileDiffObject,
|
||||||
TreeObject,
|
TreeObject,
|
||||||
|
TreeDiffObject,
|
||||||
LogObject
|
LogObject
|
||||||
} ObjectType;
|
} ObjectType;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,9 @@
|
||||||
|
|
||||||
char* get_object(char*, size_t*);
|
char* get_object(char*, size_t*);
|
||||||
void* parse_object(char*, ObjectType, size_t*, char*);
|
void* parse_object(char*, ObjectType, size_t*, char*);
|
||||||
int save_diff(ActionList*, char*, char*, size_t, char*);
|
int save_diff(ActionList*, char*, char*, size_t, char*, File*, int);
|
||||||
int read_diff(char*, char*, ActionList*);
|
int read_diff(char*, char*, ActionList*);
|
||||||
int save_file_diff(char*, char*, size_t, char*, ActionList*);
|
int save_file_diff(char*, char*, size_t, char*, ActionList*);
|
||||||
File* apply_diff(File*, ActionList*);
|
File* apply_diff(File*, ActionList*);
|
||||||
|
|
||||||
#endif // OBJECT_H
|
#endif // OBJECT_H
|
||||||
|
|
@ -15,5 +15,6 @@
|
||||||
#define IS_PUNCT(c) ((c >= 33 && c <= 47) || (c >= 58 && c <= 64) || (c >= 91 && c <= 96) || (c >= 123 && c <= 126))
|
#define IS_PUNCT(c) ((c >= 33 && c <= 47) || (c >= 58 && c <= 64) || (c >= 91 && c <= 96) || (c >= 123 && c <= 126))
|
||||||
|
|
||||||
void snapshot_tree(FileInfoBuffer*, char*);
|
void snapshot_tree(FileInfoBuffer*, char*);
|
||||||
|
int save_tree_diff(FileInfoBuffer*, char*, char*, size_t, char*, char*);
|
||||||
int read_tree(FileInfoBuffer*, char*);
|
int read_tree(FileInfoBuffer*, char*);
|
||||||
#endif // TREE_H
|
#endif // TREE_H
|
||||||
|
|
|
||||||
|
|
@ -22,24 +22,13 @@ typedef enum {
|
||||||
PT_ERROR
|
PT_ERROR
|
||||||
} PathType;
|
} PathType;
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char* key;
|
|
||||||
char* value;
|
|
||||||
} KeyValuePair;
|
|
||||||
|
|
||||||
typedef List StringBuffer;
|
typedef List StringBuffer;
|
||||||
typedef List FlatMap;
|
|
||||||
|
|
||||||
StringBuffer* string_buffer_new();
|
StringBuffer* string_buffer_new();
|
||||||
int string_buffer_push(StringBuffer*, char*);
|
int string_buffer_push(StringBuffer*, char*);
|
||||||
void string_buffer_sort(StringBuffer*);
|
void string_buffer_sort(StringBuffer*);
|
||||||
char* string_buffer_search(StringBuffer*, char*);
|
char* string_buffer_search(StringBuffer*, char*);
|
||||||
FlatMap* flat_map_new();
|
|
||||||
int flat_map_put(FlatMap*, const char*, const char*);
|
|
||||||
char* flat_map_get(FlatMap*, const char*);
|
|
||||||
void flat_map_free(FlatMap*);
|
|
||||||
void flat_map_sort(FlatMap*);
|
|
||||||
KeyValuePair* flat_map_search(FlatMap*, const char*);
|
|
||||||
char* find_root(char*);
|
char* find_root(char*);
|
||||||
void walk(char*, char*, char*, FileInfoBuffer*, int, char*);
|
void walk(char*, char*, char*, FileInfoBuffer*, int, char*);
|
||||||
char* get_repo_path(char*, char*);
|
char* get_repo_path(char*, char*);
|
||||||
|
|
|
||||||
|
|
@ -248,9 +248,9 @@ int write_base_file_list(BaseFileBuffer* buffer, char* info_file) {
|
||||||
|
|
||||||
for (size_t idx = 0; idx < buffer->len; idx++) {
|
for (size_t idx = 0; idx < buffer->len; idx++) {
|
||||||
BaseFileInfo* info = (BaseFileInfo*)buffer->items + idx;
|
BaseFileInfo* info = (BaseFileInfo*)buffer->items + idx;
|
||||||
buffer_size += 3 +
|
buffer_size += 3 +
|
||||||
strlen(info->name) +
|
strlen(info->name) +
|
||||||
snprintf(NULL, 0, "%zu", info->base_num) +
|
snprintf(NULL, 0, "%zu", info->base_num) +
|
||||||
snprintf(NULL, 0, "%zu", info->diff_num);
|
snprintf(NULL, 0, "%zu", info->diff_num);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -262,7 +262,7 @@ int write_base_file_list(BaseFileBuffer* buffer, char* info_file) {
|
||||||
for (size_t idx = 0; idx < buffer->len; idx++) {
|
for (size_t idx = 0; idx < buffer->len; idx++) {
|
||||||
size_t remaining = sizeof(tmp) - offset;
|
size_t remaining = sizeof(tmp) - offset;
|
||||||
BaseFileInfo* info = (BaseFileInfo*)buffer->items + idx;
|
BaseFileInfo* info = (BaseFileInfo*)buffer->items + idx;
|
||||||
offset += snprintf(tmp + offset, remaining, "%s %zu %zu ",
|
offset += snprintf(tmp + offset, remaining, "%s %zu %zu ",
|
||||||
info->name, info->base_num, info->diff_num);
|
info->name, info->base_num, info->diff_num);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -276,7 +276,7 @@ int write_base_file_list(BaseFileBuffer* buffer, char* info_file) {
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (compress(compressed, &compressedLen, (const Bytef*)tmp, originalLen) != Z_OK) {
|
if (compress(compressed, &compressedLen, (const Bytef*)tmp, originalLen) != Z_OK) {
|
||||||
perror("ERROR: compression failed in snapshot_tree!");
|
perror("ERROR: compression failed in snapshot_tree!");
|
||||||
free(compressed);
|
free(compressed);
|
||||||
|
|
|
||||||
760
src/commands.c
760
src/commands.c
|
|
@ -1,760 +0,0 @@
|
||||||
#include "../include/commands.h"
|
|
||||||
|
|
||||||
void usage(int exitcode) {
|
|
||||||
printf("usage: merk <command> [<args>]\n\
|
|
||||||
\n init Initializes a repository in the current directory\
|
|
||||||
\n diff Displays the difference between the given two files\
|
|
||||||
\n status Displays a list of modified and untracked files in the repository\
|
|
||||||
\n commit Record changes made to the repository\n\
|
|
||||||
\n log Displays the list of commits on the current branch\n"
|
|
||||||
);
|
|
||||||
exit(exitcode);
|
|
||||||
}
|
|
||||||
|
|
||||||
int init() {
|
|
||||||
if (mkdir(".merk", 0755) == 0) {
|
|
||||||
printf("Initialized merk repository\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errno == EEXIST) {
|
|
||||||
struct stat st;
|
|
||||||
if (stat(".merk", &st) == 0 && S_ISDIR(st.st_mode)) {
|
|
||||||
printf("This directory is already a merk repository\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int branch = open(".merk/BRANCH", O_WRONLY | O_CREAT, 0666);
|
|
||||||
if (branch < 0) return 0;
|
|
||||||
write(branch, "main", 4);
|
|
||||||
close(branch);
|
|
||||||
|
|
||||||
mkdir(".merk/objects", 0755);
|
|
||||||
mkdir(".merk/refs", 0755);
|
|
||||||
mkdir(".merk/refs/branches", 0755);
|
|
||||||
mkdir(".merk/info", 0755);
|
|
||||||
mkdir(".merk/logs", 0755);
|
|
||||||
mkdir(".merk/logs/branches", 0755);
|
|
||||||
|
|
||||||
int main_branch = open(".merk/refs/branches/main", O_WRONLY | O_CREAT, 0666);
|
|
||||||
if (main_branch < 0) return 0;
|
|
||||||
close(main_branch);
|
|
||||||
|
|
||||||
int main_info = open(".merk/info/main", O_WRONLY | O_CREAT, 0666);
|
|
||||||
if (main_info < 0) return 0;
|
|
||||||
close(main_info);
|
|
||||||
|
|
||||||
int main_log = open(".merk/logs/branches/main", O_WRONLY | O_CREAT, 0666);
|
|
||||||
if (main_log < 0) return 0;
|
|
||||||
close(main_log);
|
|
||||||
|
|
||||||
CommitLog* empty = commit_log_new();
|
|
||||||
if (!empty) { perror("ERROR: failed to create commit log for branch main!"); return 0; }
|
|
||||||
if (!write_commit_log(empty, ".merk/logs/branches/main")) { perror("ERROR: failed to write initial main commit log!"); commit_log_free(empty); return 0; }
|
|
||||||
|
|
||||||
commit_log_free(empty);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int diff(char* file1_path, char* file2_path) {
|
|
||||||
File* file1;
|
|
||||||
switch (get_path_type(file1_path)) {
|
|
||||||
case PT_FILE: file1 = new_file(file1_path); break;
|
|
||||||
case PT_DIR: file1 = NULL; printf("ERROR: first path is a directory and no file!"); break;
|
|
||||||
case PT_NOEXIST: file1 = NULL; printf("ERROR: first path does not exist!"); break;
|
|
||||||
default: file1 = NULL; printf("ERROR: unknown first path!");
|
|
||||||
}
|
|
||||||
if (!file1) exit(1);
|
|
||||||
|
|
||||||
File* file2;
|
|
||||||
switch (get_path_type(file2_path)) {
|
|
||||||
case PT_FILE: file2 = new_file(file2_path); break;
|
|
||||||
case PT_DIR: file2 = NULL; printf("ERROR: second path is a directory and no file!"); break;
|
|
||||||
case PT_NOEXIST: file2 = NULL; printf("ERROR: second path does not exist!"); break;
|
|
||||||
default: file2 = NULL; printf("ERROR: unknown second path!");
|
|
||||||
}
|
|
||||||
if (!file2) exit(1);
|
|
||||||
|
|
||||||
ActionList* actions = myers_diff(file1, file2, 0, 0);
|
|
||||||
if (!actions) printf("ERROR: something went wrong while taking the diff!");
|
|
||||||
else visualize_diff(file1, file2, actions);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int status() {
|
|
||||||
FileInfoBuffer* files = file_info_buffer_new();
|
|
||||||
char relative[PATH_MAX] = "";
|
|
||||||
char* root = find_root(relative);
|
|
||||||
if (!root) {
|
|
||||||
list_free(files);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
walk(root, relative, relative, files, 0, root);
|
|
||||||
file_info_buffer_sort(files);
|
|
||||||
|
|
||||||
char branch_path[PATH_MAX];
|
|
||||||
snprintf(branch_path, sizeof(branch_path), "%s/.merk/BRANCH", root);
|
|
||||||
char* branch = get_file_content(branch_path);
|
|
||||||
char info_path[PATH_MAX];
|
|
||||||
snprintf(info_path, sizeof(info_path), "%s/.merk/info/%s", root, branch);
|
|
||||||
|
|
||||||
char log_path[PATH_MAX];
|
|
||||||
snprintf(log_path, sizeof(log_path), "%s/.merk/logs/branches/%s", root, branch);
|
|
||||||
|
|
||||||
CommitLog* log = commit_log_new();
|
|
||||||
read_commit_log(log, log_path);
|
|
||||||
char* last_tree_hash = NULL;
|
|
||||||
if (log->len != 0) {
|
|
||||||
last_tree_hash = ((Commit*)log->items + (log->len))->tree_hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
BaseFileBuffer* tracked_list = base_file_buffer_new();
|
|
||||||
read_base_file_list(tracked_list, info_path);
|
|
||||||
|
|
||||||
StringBuffer* modified_files = string_buffer_new();
|
|
||||||
StringBuffer* deleted_files = string_buffer_new();
|
|
||||||
StringBuffer* untracked_files = string_buffer_new();
|
|
||||||
|
|
||||||
Changes* changes = calloc(1, sizeof(Changes));
|
|
||||||
if (!changes) {
|
|
||||||
printf("ERROR: unable to allocate memory for changes!\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
changes->insertions = 0;
|
|
||||||
changes->deletions = 0;
|
|
||||||
|
|
||||||
for (size_t idx = 0; idx < files->len; idx++) {
|
|
||||||
char* file_name = ((FileInfo*)files->items + idx)->name;
|
|
||||||
if (base_file_buffer_search(tracked_list, file_name)) continue;
|
|
||||||
string_buffer_push(untracked_files, file_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t idx = 0; idx < files->len; idx++) {
|
|
||||||
char* file_name = ((FileInfo*)files->items + idx)->name;
|
|
||||||
if (base_file_buffer_search(tracked_list, file_name)) {
|
|
||||||
BaseFileInfo* base_file = base_file_buffer_search(tracked_list, file_name);
|
|
||||||
char base_file_hash[41];
|
|
||||||
char id[3 + snprintf(NULL, 0, "%lu", base_file->base_num) + strlen(file_name) + strlen(branch)];
|
|
||||||
snprintf(id, sizeof(id), "%s %lu %s", file_name, base_file->base_num, branch);
|
|
||||||
object_hash(BaseFileObject, id, base_file_hash);
|
|
||||||
File* basefile = parse_object(base_file_hash, BaseFileObject, NULL, NULL);
|
|
||||||
if (!basefile) {
|
|
||||||
printf("ERROR: unable to parse base file %s!\n", file_name);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
File* modified_file = new_file(file_name);
|
|
||||||
if (!modified_file) {
|
|
||||||
printf("ERROR: unable to read modified file %s!\n", file_name);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
File* comparison_file = basefile;
|
|
||||||
|
|
||||||
if (base_file->diff_num > 0) {
|
|
||||||
size_t prev_diff = base_file->diff_num - 1;
|
|
||||||
char prev_diff_hash[41];
|
|
||||||
char id2[3 + snprintf(NULL, 0, "%zu", prev_diff) + strlen(file_name) + strlen(branch)];
|
|
||||||
snprintf(id2, sizeof(id2), "%s %zu %s", file_name, prev_diff, branch);
|
|
||||||
object_hash(FileDiffObject, id2, prev_diff_hash);
|
|
||||||
char hash[41];
|
|
||||||
ActionList* last_diff = parse_object(prev_diff_hash, FileDiffObject, NULL, hash);
|
|
||||||
if (last_diff) {
|
|
||||||
File* last_version = apply_diff(basefile, last_diff);
|
|
||||||
if (last_version) {
|
|
||||||
comparison_file = last_version;
|
|
||||||
}
|
|
||||||
free_action_list(last_diff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ActionList* diff = myers_diff(comparison_file, modified_file, 0, 0);
|
|
||||||
if (!diff) {
|
|
||||||
printf("ERROR: unable to compute diff for file %s!\n", file_name);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (diff->len != 0) {
|
|
||||||
for (size_t idx = 0; idx < diff->len; idx++) {
|
|
||||||
Action action = diff->actions[idx];
|
|
||||||
if (action.type == DELETE) {
|
|
||||||
changes->deletions++;
|
|
||||||
} else if (action.type == INSERT) {
|
|
||||||
changes->insertions++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
string_buffer_push(modified_files, file_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
free_action_list(diff);
|
|
||||||
free_file(modified_file);
|
|
||||||
if (comparison_file != basefile) {
|
|
||||||
free_file(comparison_file);
|
|
||||||
}
|
|
||||||
free_file(basefile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t idx = 0; idx < tracked_list->len; idx++) {
|
|
||||||
char* file_name = ((BaseFileInfo*)tracked_list->items + idx)->name;
|
|
||||||
if (file_info_buffer_search(files, file_name)) continue;
|
|
||||||
else string_buffer_push(deleted_files, file_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t idx = 0; idx < modified_files->len; idx++) {
|
|
||||||
printf("\x1b[36;1mM\x1b[0m \x1b[36m%s\x1b[0m\n", (char*)modified_files->items[idx]);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t idx = 0; idx < deleted_files->len; idx++) {
|
|
||||||
printf("\x1b[31;4mD\x1b[0m \x1b[31;9m%s\x1b[0m\n", (char*)deleted_files->items[idx]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modified_files->len != 0 || deleted_files->len != 0) printf("\n");
|
|
||||||
|
|
||||||
for (size_t idx = 0; idx < untracked_files->len; idx++) {
|
|
||||||
printf("\x1b[31;1mU\x1b[0m \x1b[31m%s\x1b[0m\n", (char*)untracked_files->items[idx]);
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("\n\x1b[32;1m%d+\x1b[0m \x1b[31;1m%d-\x1b[0m on branch \x1b[39;1;4m%s\x1b[0m with %lu commits\n", changes->insertions, changes->deletions, branch, log->len);
|
|
||||||
|
|
||||||
file_info_buffer_free(files);
|
|
||||||
free(root);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int commit(int argc, char** argv) {
|
|
||||||
char* commit_message = NULL;
|
|
||||||
int file_start_index = 2;
|
|
||||||
|
|
||||||
if (argc >= 4 && strcmp(argv[2], "-m") == 0) {
|
|
||||||
if (argv[3][0] == '-') {
|
|
||||||
printf("ERROR: Invalid commit message provided after -m flag!\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
commit_message = strdup(argv[3]);
|
|
||||||
if (!commit_message) {
|
|
||||||
fprintf(stderr, "ERROR: Memory allocation failed\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
file_start_index = 4;
|
|
||||||
} else if (argc == 3 && strcmp(argv[2], "-m") == 0) {
|
|
||||||
printf("ERROR: Missing commit message after -m flag!\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = file_start_index; i < argc; i++) {
|
|
||||||
if (strcmp(argv[i], "-m") == 0) {
|
|
||||||
printf("ERROR: Unexpected -m flag in file list!\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Config config = {0};
|
|
||||||
|
|
||||||
if (load_config(&config) < 0) {
|
|
||||||
free(commit_message);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (validate_config(&config) < 0) {
|
|
||||||
free_config(&config);
|
|
||||||
free(commit_message);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
char tmp_path[PATH_MAX] = ".merk/BRANCH";
|
|
||||||
char branch_path[PATH_MAX];
|
|
||||||
realpath(tmp_path, branch_path);
|
|
||||||
|
|
||||||
char* branch = get_file_content(branch_path);
|
|
||||||
if (!branch) {
|
|
||||||
free_config(&config);
|
|
||||||
free(commit_message);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
char tmp_path2[PATH_MAX];
|
|
||||||
char info_path[PATH_MAX];
|
|
||||||
snprintf(tmp_path2, sizeof(tmp_path2), ".merk/info/%s", branch);
|
|
||||||
realpath(tmp_path2, info_path);
|
|
||||||
|
|
||||||
BaseFileBuffer* base_files = base_file_buffer_new();
|
|
||||||
if (!base_files) {
|
|
||||||
free(branch);
|
|
||||||
free_config(&config);
|
|
||||||
free(commit_message);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
read_base_file_list(base_files, info_path);
|
|
||||||
|
|
||||||
StringBuffer* files = string_buffer_new();
|
|
||||||
if (!files) {
|
|
||||||
base_file_buffer_free(base_files);
|
|
||||||
free(branch);
|
|
||||||
free_config(&config);
|
|
||||||
free(commit_message);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* root = find_root(NULL);
|
|
||||||
if (!root) {
|
|
||||||
list_free(files);
|
|
||||||
base_file_buffer_free(base_files);
|
|
||||||
free(branch);
|
|
||||||
free_config(&config);
|
|
||||||
free(commit_message);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = file_start_index; i < argc; i++) {
|
|
||||||
char* real = realpath(argv[i], NULL);
|
|
||||||
if (!real) {
|
|
||||||
fprintf(stderr, "ERROR: Cannot resolve path: %s\n", argv[i]);
|
|
||||||
list_free(files);
|
|
||||||
base_file_buffer_free(base_files);
|
|
||||||
free(branch);
|
|
||||||
free_config(&config);
|
|
||||||
free(commit_message);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (get_path_type(real) == PT_FILE && is_in_repo(root, real) == 0) {
|
|
||||||
char* repo_path = get_repo_path(root, real);
|
|
||||||
if (repo_path) {
|
|
||||||
string_buffer_push(files, repo_path);
|
|
||||||
free(repo_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
fprintf(stderr, "ERROR: %s is not a file or not inside the repo!\n", real);
|
|
||||||
free(real);
|
|
||||||
list_free(files);
|
|
||||||
base_file_buffer_free(base_files);
|
|
||||||
free(branch);
|
|
||||||
free(root);
|
|
||||||
free_config(&config);
|
|
||||||
free(commit_message);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
free(real);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!commit_message) {
|
|
||||||
printf("Enter commit message: ");
|
|
||||||
fflush(stdout);
|
|
||||||
|
|
||||||
char buffer[1024];
|
|
||||||
if (fgets(buffer, sizeof(buffer), stdin) != NULL) {
|
|
||||||
size_t len = strlen(buffer);
|
|
||||||
if (len > 0 && buffer[len-1] == '\n') {
|
|
||||||
buffer[len-1] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strlen(buffer) == 0) {
|
|
||||||
printf("ERROR: Empty commit message!\n");
|
|
||||||
list_free(files);
|
|
||||||
base_file_buffer_free(base_files);
|
|
||||||
free(branch);
|
|
||||||
free(root);
|
|
||||||
free_config(&config);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
commit_message = strdup(buffer);
|
|
||||||
if (!commit_message) {
|
|
||||||
fprintf(stderr, "ERROR: Memory allocation failed\n");
|
|
||||||
list_free(files);
|
|
||||||
base_file_buffer_free(base_files);
|
|
||||||
free(branch);
|
|
||||||
free(root);
|
|
||||||
free_config(&config);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
printf("ERROR: Failed to read commit message!\n");
|
|
||||||
list_free(files);
|
|
||||||
base_file_buffer_free(base_files);
|
|
||||||
free(branch);
|
|
||||||
free(root);
|
|
||||||
free_config(&config);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FlatMap* file_hash_map = flat_map_new();
|
|
||||||
Changes* changes = calloc(1, sizeof(Changes));
|
|
||||||
if (!changes) {
|
|
||||||
free(commit_message);
|
|
||||||
list_free(files);
|
|
||||||
base_file_buffer_free(base_files);
|
|
||||||
free(branch);
|
|
||||||
free(root);
|
|
||||||
free_config(&config);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
changes->insertions = 0;
|
|
||||||
changes->deletions = 0;
|
|
||||||
|
|
||||||
for (size_t idx = 0; idx < files->len; idx++) {
|
|
||||||
BaseFileInfo* base_file = base_file_buffer_search(base_files, files->items[idx]);
|
|
||||||
if (!base_file) {
|
|
||||||
printf("this is a basefile as it should be\n");
|
|
||||||
BaseFileInfo info = (BaseFileInfo){.base_num = 0, .diff_num = 0, .name = strdup(files->items[idx])};
|
|
||||||
base_file_buffer_push(base_files, info);
|
|
||||||
// Compress the files content and put it into the object database
|
|
||||||
File* modified_file = new_file(files->items[idx]);
|
|
||||||
if (!modified_file) {
|
|
||||||
free(commit_message);
|
|
||||||
list_free(files);
|
|
||||||
base_file_buffer_free(base_files);
|
|
||||||
free(branch);
|
|
||||||
free(root);
|
|
||||||
free_config(&config);
|
|
||||||
free(changes);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
changes->insertions += modified_file->lines;
|
|
||||||
|
|
||||||
char file_hash[41];
|
|
||||||
snapshot_file(files->items[idx], root, 0, file_hash);
|
|
||||||
flat_map_put(file_hash_map, files->items[idx], file_hash);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
char base_file_hash[41];
|
|
||||||
char id[3 + snprintf(NULL, 0, "%lu", base_file->base_num) + strlen(files->items[idx]) + strlen(branch)];
|
|
||||||
snprintf(id, sizeof(id), "%s %lu %s", (char*)files->items[idx], base_file->base_num, branch);
|
|
||||||
object_hash(BaseFileObject, id, base_file_hash);
|
|
||||||
File* basefile = parse_object(base_file_hash, BaseFileObject, NULL, NULL);
|
|
||||||
if (!basefile) {
|
|
||||||
free(commit_message);
|
|
||||||
list_free(files);
|
|
||||||
base_file_buffer_free(base_files);
|
|
||||||
free(branch);
|
|
||||||
free(root);
|
|
||||||
free_config(&config);
|
|
||||||
free(changes);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
File* modified_file = new_file(files->items[idx]);
|
|
||||||
if (!modified_file) {
|
|
||||||
free_file(basefile);
|
|
||||||
free(commit_message);
|
|
||||||
list_free(files);
|
|
||||||
base_file_buffer_free(base_files);
|
|
||||||
free(branch);
|
|
||||||
free(root);
|
|
||||||
free_config(&config);
|
|
||||||
free(changes);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ActionList* diff = myers_diff(basefile, modified_file, 0, 0);
|
|
||||||
if (!diff) {
|
|
||||||
free_file(basefile);
|
|
||||||
free_file(modified_file);
|
|
||||||
free(commit_message);
|
|
||||||
list_free(files);
|
|
||||||
base_file_buffer_free(base_files);
|
|
||||||
free(branch);
|
|
||||||
free(root);
|
|
||||||
free_config(&config);
|
|
||||||
free(changes);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (base_file->diff_num == 0) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (base_file->diff_num > 0) {
|
|
||||||
size_t prev_diff = base_file->diff_num - 1;
|
|
||||||
char prev_diff_hash[41];
|
|
||||||
char id2[3 + snprintf(NULL, 0, "%zu", prev_diff) + strlen(files->items[idx]) + strlen(branch)];
|
|
||||||
snprintf(id2, sizeof(id2), "%s %zu %s", (char*)files->items[idx], prev_diff, branch);
|
|
||||||
object_hash(FileDiffObject, id2, prev_diff_hash);
|
|
||||||
char hash[41];
|
|
||||||
ActionList* last_diff = parse_object(prev_diff_hash, FileDiffObject, NULL, hash);
|
|
||||||
if (!last_diff) {
|
|
||||||
free_action_list(diff);
|
|
||||||
free_file(modified_file);
|
|
||||||
free_file(basefile);
|
|
||||||
free(commit_message);
|
|
||||||
list_free(files);
|
|
||||||
base_file_buffer_free(base_files);
|
|
||||||
free(branch);
|
|
||||||
free(root);
|
|
||||||
free_config(&config);
|
|
||||||
free(changes);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
File* last_version = apply_diff(basefile, last_diff);
|
|
||||||
if (!last_version) {
|
|
||||||
free_action_list(last_diff);
|
|
||||||
free_action_list(diff);
|
|
||||||
free_file(modified_file);
|
|
||||||
free_file(basefile);
|
|
||||||
free(commit_message);
|
|
||||||
list_free(files);
|
|
||||||
base_file_buffer_free(base_files);
|
|
||||||
free(branch);
|
|
||||||
free(root);
|
|
||||||
free_config(&config);
|
|
||||||
free(changes);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ActionList* modified_diff = myers_diff(last_version, modified_file, 0, 0);
|
|
||||||
if (!modified_diff) {
|
|
||||||
free_action_list(last_diff);
|
|
||||||
free_action_list(diff);
|
|
||||||
free_file(modified_file);
|
|
||||||
free_file(basefile);
|
|
||||||
free(commit_message);
|
|
||||||
list_free(files);
|
|
||||||
base_file_buffer_free(base_files);
|
|
||||||
free(branch);
|
|
||||||
free(root);
|
|
||||||
free_config(&config);
|
|
||||||
free(changes);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t idx = 0; idx < modified_diff->len; idx++) {
|
|
||||||
Action action = modified_diff->actions[idx];
|
|
||||||
if (action.type == INSERT) {
|
|
||||||
changes->insertions += 1;
|
|
||||||
} else if (action.type == DELETE) {
|
|
||||||
changes->deletions += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (diff->len > 200) {
|
|
||||||
char new_base_hash[41];
|
|
||||||
snapshot_file(files->items[idx], root, base_file->base_num+1, new_base_hash);
|
|
||||||
|
|
||||||
flat_map_put(file_hash_map, files->items[idx], new_base_hash);
|
|
||||||
|
|
||||||
base_file_buffer_remove(base_files, files->items[idx]);
|
|
||||||
BaseFileInfo new_info = (BaseFileInfo){
|
|
||||||
.base_num = base_file->base_num + 1,
|
|
||||||
.diff_num = base_file->diff_num,
|
|
||||||
.name = strdup(files->items[idx])
|
|
||||||
};
|
|
||||||
base_file_buffer_push(base_files, new_info);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
save_diff(diff, files->items[idx], root, base_file->diff_num, base_file_hash);
|
|
||||||
|
|
||||||
char diff_hash[41];
|
|
||||||
char id[3 + snprintf(NULL, 0, "%lu", base_file->diff_num) + strlen(files->items[idx]) + strlen(branch)];
|
|
||||||
snprintf(id, sizeof(id), "%s %lu %s", (char*)files->items[idx], base_file->diff_num, branch);
|
|
||||||
object_hash(BaseFileObject, id, diff_hash);
|
|
||||||
|
|
||||||
flat_map_put(file_hash_map, files->items[idx], diff_hash);
|
|
||||||
|
|
||||||
base_file_buffer_remove(base_files, files->items[idx]);
|
|
||||||
BaseFileInfo new_info = (BaseFileInfo){
|
|
||||||
.base_num = base_file->base_num,
|
|
||||||
.diff_num = base_file->diff_num + 1,
|
|
||||||
.name = strdup(files->items[idx])
|
|
||||||
};
|
|
||||||
base_file_buffer_push(base_files, new_info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
base_file_buffer_sort(base_files);
|
|
||||||
write_base_file_list(base_files, info_path);
|
|
||||||
FileInfoBuffer* tree = basefilebuffer_to_fileinfobuffer(base_files);
|
|
||||||
if (!tree) {
|
|
||||||
free(commit_message);
|
|
||||||
list_free(files);
|
|
||||||
base_file_buffer_free(base_files);
|
|
||||||
free(branch);
|
|
||||||
free(root);
|
|
||||||
free_config(&config);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t idx = 0; idx < tree->len; idx++) {
|
|
||||||
char* hash = flat_map_get(file_hash_map, ((FileInfo*)tree->items + idx)->name);
|
|
||||||
if (hash) ((FileInfo*)tree->items + idx)->hash = strdup(hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
char tree_hash[41];
|
|
||||||
snapshot_tree(tree, tree_hash);
|
|
||||||
|
|
||||||
char log_path[PATH_MAX];
|
|
||||||
snprintf(log_path, sizeof(log_path), "%s/.merk/logs/branches/%s", root, branch);
|
|
||||||
|
|
||||||
CommitLog* log = commit_log_new();
|
|
||||||
|
|
||||||
if (!log) {
|
|
||||||
file_info_buffer_free(tree);
|
|
||||||
free(commit_message);
|
|
||||||
list_free(files);
|
|
||||||
base_file_buffer_free(base_files);
|
|
||||||
free(branch);
|
|
||||||
free(root);
|
|
||||||
free_config(&config);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
read_commit_log(log, log_path);
|
|
||||||
|
|
||||||
char parent_hash[41];
|
|
||||||
if (log->len != 0) {
|
|
||||||
snprintf(parent_hash, sizeof(parent_hash), "%s", ((Commit*)log->items + (log->len - 1))->hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
char timestamp[20];
|
|
||||||
snprintf(timestamp, sizeof(timestamp), "%ld", time(NULL));
|
|
||||||
|
|
||||||
Author author = {
|
|
||||||
.name = strdup(config.user.name),
|
|
||||||
.email = strdup(config.user.email),
|
|
||||||
.timestamp = strdup(timestamp)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (log->len != 0) {
|
|
||||||
char parent_hash[41];
|
|
||||||
snprintf(parent_hash, sizeof(parent_hash), "%s", ((Commit*)log->items + (log->len - 1))->hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
char commit_hash_content[4096];
|
|
||||||
int offset = 0;
|
|
||||||
offset += snprintf(commit_hash_content + offset, sizeof(commit_hash_content) - offset, "tree %s\n", tree_hash);
|
|
||||||
if (log->len != 0) {
|
|
||||||
offset += snprintf(commit_hash_content + offset, sizeof(commit_hash_content) - offset, "parent %s\n", parent_hash);
|
|
||||||
}
|
|
||||||
offset += snprintf(commit_hash_content + offset, sizeof(commit_hash_content) - offset, "author %s <%s> %s\n", author.name, author.email, author.timestamp);
|
|
||||||
offset += snprintf(commit_hash_content + offset, sizeof(commit_hash_content) - offset, "committer %s <%s> %s\n", author.name, author.email, author.timestamp);
|
|
||||||
offset += snprintf(commit_hash_content + offset, sizeof(commit_hash_content) - offset, "message %s\n", commit_message);
|
|
||||||
|
|
||||||
char commit_hash[41];
|
|
||||||
object_hash(LogObject, commit_hash_content, commit_hash);
|
|
||||||
|
|
||||||
Commit commit = {
|
|
||||||
.hash = commit_hash,
|
|
||||||
.tree_hash = strdup(tree_hash),
|
|
||||||
.authors = &author,
|
|
||||||
.authors_count = 1,
|
|
||||||
.committer = author,
|
|
||||||
.message = strdup(commit_message)
|
|
||||||
};
|
|
||||||
|
|
||||||
commit_log_push(log, commit);
|
|
||||||
if (!write_commit_log(log, log_path)) {
|
|
||||||
commit_log_free(log);
|
|
||||||
file_info_buffer_free(tree);
|
|
||||||
free(commit_message);
|
|
||||||
list_free(files);
|
|
||||||
base_file_buffer_free(base_files);
|
|
||||||
free(branch);
|
|
||||||
free(root);
|
|
||||||
free_config(&config);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
char ref_path[PATH_MAX];
|
|
||||||
snprintf(ref_path, sizeof(ref_path), "%s/.merk/refs/branches/%s", root, branch);
|
|
||||||
|
|
||||||
int ref = open(ref_path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
|
||||||
if (ref < 0) return -1;
|
|
||||||
write(ref, commit_hash, 40);
|
|
||||||
close(ref);
|
|
||||||
|
|
||||||
printf("(\x1b[33;1m%.7s\x1b[0m on \x1b[39;1;4m%s\x1b[0m \x1b[32;1m%d+\x1b[0m \x1b[31;1m%d-\x1b[0m) %s\n",
|
|
||||||
commit_hash,
|
|
||||||
branch,
|
|
||||||
changes->insertions,
|
|
||||||
changes->deletions,
|
|
||||||
commit_message
|
|
||||||
);
|
|
||||||
|
|
||||||
list_free(files);
|
|
||||||
base_file_buffer_free(base_files);
|
|
||||||
free(branch);
|
|
||||||
free(root);
|
|
||||||
free_config(&config);
|
|
||||||
free(commit_message);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int commit_log() {
|
|
||||||
CommitLog* log = commit_log_new();
|
|
||||||
if (!log) return 0;
|
|
||||||
|
|
||||||
char* root = find_root(NULL);
|
|
||||||
if (!root) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
char branch_path[PATH_MAX];
|
|
||||||
snprintf(branch_path, sizeof(branch_path), "%s/.merk/BRANCH", root);
|
|
||||||
|
|
||||||
char* branch = get_file_content(branch_path);
|
|
||||||
if (!branch) {
|
|
||||||
free(root);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
char log_path[PATH_MAX];
|
|
||||||
snprintf(log_path, sizeof(log_path), "%s/.merk/logs/branches/%s", root, branch);
|
|
||||||
|
|
||||||
read_commit_log(log, log_path);
|
|
||||||
|
|
||||||
char ref_path[PATH_MAX];
|
|
||||||
snprintf(ref_path, sizeof(ref_path), "%s/.merk/refs/branches/%s", root, branch);
|
|
||||||
|
|
||||||
int ref = open(ref_path, O_RDONLY);
|
|
||||||
if (ref < 0) {
|
|
||||||
free(branch);
|
|
||||||
free(root);
|
|
||||||
commit_log_free(log);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
char head[41] = "";
|
|
||||||
read(ref, head, 40);
|
|
||||||
close(ref);
|
|
||||||
|
|
||||||
for (size_t i = log->len; i != 0; i--) {
|
|
||||||
Commit* commit = (Commit*)log->items + i - 1;
|
|
||||||
char human_readable_time[32];
|
|
||||||
time_t timestamp = atol(commit->committer.timestamp);
|
|
||||||
strftime(human_readable_time, sizeof(human_readable_time), "%Y-%m-%d %H:%M:%S %z", localtime(×tamp));
|
|
||||||
|
|
||||||
char* marker = (strcmp(commit->hash, head) == 0) ? "\x1b[32m@\x1b[0m" : "\x1b[34m◯\x1b[0m";
|
|
||||||
|
|
||||||
printf("%s \x1b[33;1m%.7s\x1b[0m: %s\n│ %s <\x1b[38;5;240m\x1b]8;;mailto:%s\x1b\\%s\x1b]8;;\x1b\\\x1b[0m> (\x1b[36m%s\x1b[0m)\n│\n",
|
|
||||||
marker,
|
|
||||||
commit->hash,
|
|
||||||
commit->message,
|
|
||||||
commit->committer.name,
|
|
||||||
commit->committer.email,
|
|
||||||
commit->committer.email,
|
|
||||||
human_readable_time
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (log->len != 0) printf("┴\n");
|
|
||||||
|
|
||||||
free(branch);
|
|
||||||
free(root);
|
|
||||||
commit_log_free(log);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
@ -23,6 +23,7 @@ void object_hash(ObjectType obj_type, char* content, char* hex_hash) {
|
||||||
case BaseFileObject: header_len = snprintf(header, sizeof(header), "basefile %zu", content_len); break;
|
case BaseFileObject: header_len = snprintf(header, sizeof(header), "basefile %zu", content_len); break;
|
||||||
case FileDiffObject: header_len = snprintf(header, sizeof(header), "filediff %zu", content_len); break;
|
case FileDiffObject: header_len = snprintf(header, sizeof(header), "filediff %zu", content_len); break;
|
||||||
case TreeObject: header_len = snprintf(header, sizeof(header), "tree %zu", content_len); break;
|
case TreeObject: header_len = snprintf(header, sizeof(header), "tree %zu", content_len); break;
|
||||||
|
case TreeDiffObject: header_len = snprintf(header, sizeof(header), "treediff %zu", content_len); break;
|
||||||
case LogObject: header_len = snprintf(header, sizeof(header), "log %zu", content_len); break;
|
case LogObject: header_len = snprintf(header, sizeof(header), "log %zu", content_len); break;
|
||||||
default: header_len = 0; break;
|
default: header_len = 0; break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
728
src/main.c
728
src/main.c
|
|
@ -1,30 +1,93 @@
|
||||||
#include "../include/commands.h"
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#include "base_file_buffer.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "commit.h"
|
||||||
|
#include "hash.h"
|
||||||
|
#include "object.h"
|
||||||
|
|
||||||
|
static void usage(int exitcode) {
|
||||||
|
printf("usage: merk <command> [<args>]\n\
|
||||||
|
\n init Initializes a repository in the current directory\
|
||||||
|
\n diff Displays the difference between the given two files\
|
||||||
|
\n status Displays a list of modified and untracked files in the repository\
|
||||||
|
\n commit Record changes made to the repository\n");
|
||||||
|
exit(exitcode);
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) {
|
if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) {
|
||||||
usage(0);
|
usage(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* subcmd = argv[1];
|
const char* subcmd = argv[1];
|
||||||
|
|
||||||
if (strcmp(subcmd, "diff") != 0
|
if (strcmp(subcmd, "diff") != 0 &&
|
||||||
&& strcmp(subcmd, "init") != 0
|
strcmp(subcmd, "init") != 0 &&
|
||||||
&& strcmp(subcmd, "status") != 0
|
strcmp(subcmd, "status") != 0 &&
|
||||||
&& strcmp(subcmd, "commit") != 0
|
strcmp(subcmd, "commit") != 0 &&
|
||||||
&& strcmp(subcmd, "log") != 0
|
strcmp(subcmd, "log") != 0 &&
|
||||||
&& strcmp(subcmd, "test") != 0 ) {
|
strcmp(subcmd, "test") != 0
|
||||||
fprintf(stderr, "ERROR: Unknown subcommand: '%s'\n", subcmd); usage(2);
|
) {
|
||||||
|
fprintf(stderr, "ERROR: Unknown subcommand: '%s'\n", subcmd);
|
||||||
|
usage(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initializes a merk repository in the current directory
|
// Initializes a merk repository in the current directory
|
||||||
if (strcmp(subcmd, "init") == 0) {
|
if (strcmp(subcmd, "init") == 0) {
|
||||||
if (argc != 2) {
|
if (argc != 2) {
|
||||||
printf("ERROR: too many arguments given!\n");
|
printf("ERROR: too many agruments given!\n");
|
||||||
printf("Usage: merk init");
|
printf("Usage: merk init");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
init();
|
if (mkdir(".merk", 0755) == 0) {
|
||||||
|
printf("Initialized merk repository\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errno == EEXIST) {
|
||||||
|
struct stat st;
|
||||||
|
if (stat(".merk", &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||||
|
printf("This directory is already a merk repository\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int branch = open(".merk/BRANCH", O_WRONLY | O_CREAT, 0666);
|
||||||
|
if (branch < 0) return -1;
|
||||||
|
write(branch, "main", 4);
|
||||||
|
close(branch);
|
||||||
|
|
||||||
|
mkdir(".merk/objects", 0755);
|
||||||
|
mkdir(".merk/refs", 0755);
|
||||||
|
mkdir(".merk/refs/branches", 0755);
|
||||||
|
mkdir(".merk/info", 0755);
|
||||||
|
mkdir(".merk/logs", 0755);
|
||||||
|
mkdir(".merk/logs/branches", 0755);
|
||||||
|
|
||||||
|
int main_branch = open(".merk/refs/branches/main", O_WRONLY | O_CREAT, 0666);
|
||||||
|
if (main_branch < 0) return -1;
|
||||||
|
close(main_branch);
|
||||||
|
|
||||||
|
int main_info = open(".merk/info/main", O_WRONLY | O_CREAT, 0666);
|
||||||
|
if (main_info < 0) return -1;
|
||||||
|
close(main_info);
|
||||||
|
|
||||||
|
int main_log = open(".merk/logs/branches/main", O_WRONLY | O_CREAT, 0666);
|
||||||
|
if (main_log < 0) return -1;
|
||||||
|
close(main_log);
|
||||||
|
|
||||||
|
CommitLog* empty = commit_log_new();
|
||||||
|
if (!empty) { perror("ERROR: failed to create commit log for branch main!"); return -1; }
|
||||||
|
if (!write_commit_log(empty, ".merk/logs/branches/main")) { perror("ERROR: failed to write initial main commit log!"); commit_log_free(empty); return -1; }
|
||||||
|
|
||||||
|
commit_log_free(empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prints out a visual representation of the diff of the files provided
|
// Prints out a visual representation of the diff of the files provided
|
||||||
|
|
@ -35,9 +98,28 @@ int main(int argc, char** argv) {
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
diff(argv[2], argv[3]);
|
File* file1;
|
||||||
}
|
switch (get_path_type(argv[2])) {
|
||||||
|
case PT_FILE: file1 = new_file(argv[2]); break;
|
||||||
|
case PT_DIR: file1 = NULL; printf("ERROR: first path is a directory and no file!"); break;
|
||||||
|
case PT_NOEXIST: file1 = NULL; printf("ERROR: first path does not exist!"); break;
|
||||||
|
default: file1 = NULL; printf("ERROR: unknown first path!");
|
||||||
|
}
|
||||||
|
if (!file1) exit(1);
|
||||||
|
|
||||||
|
File* file2;
|
||||||
|
switch (get_path_type(argv[3])) {
|
||||||
|
case PT_FILE: file2 = new_file(argv[3]); break;
|
||||||
|
case PT_DIR: file2 = NULL; printf("ERROR: second path is a directory and no file!"); break;
|
||||||
|
case PT_NOEXIST: file2 = NULL; printf("ERROR: second path does not exist!"); break;
|
||||||
|
default: file2 = NULL; printf("ERROR: unknown second path!");
|
||||||
|
}
|
||||||
|
if (!file2) exit(1);
|
||||||
|
|
||||||
|
ActionList* actions = myers_diff(file1, file2, 0, 0);
|
||||||
|
if (!actions) printf("ERROR: something went wrong while taking the diff!");
|
||||||
|
else visualize_diff(file1, file2, actions);
|
||||||
|
}
|
||||||
// Gives a list of untracked files in the repo as well as meta information
|
// Gives a list of untracked files in the repo as well as meta information
|
||||||
else if (strcmp(subcmd, "status") == 0) {
|
else if (strcmp(subcmd, "status") == 0) {
|
||||||
if (argc != 2) {
|
if (argc != 2) {
|
||||||
|
|
@ -46,9 +128,121 @@ int main(int argc, char** argv) {
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
status();
|
FileInfoBuffer* files = file_info_buffer_new();
|
||||||
}
|
char relative[PATH_MAX] = "";
|
||||||
|
char* root = find_root(relative);
|
||||||
|
if (!root) {
|
||||||
|
list_free(files);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
walk(root, relative, relative, files, 0, root);
|
||||||
|
file_info_buffer_sort(files);
|
||||||
|
|
||||||
|
char branch_path[PATH_MAX];
|
||||||
|
snprintf(branch_path, sizeof(branch_path), "%s/.merk/BRANCH", root);
|
||||||
|
char* branch = get_file_content(branch_path);
|
||||||
|
char info_path[PATH_MAX];
|
||||||
|
snprintf(info_path, sizeof(info_path), "%s/.merk/info/%s", root, branch);
|
||||||
|
|
||||||
|
char log_path[PATH_MAX];
|
||||||
|
snprintf(log_path, sizeof(log_path), "%s/.merk/logs/branches/%s", root, branch);
|
||||||
|
|
||||||
|
CommitLog* log = commit_log_new();
|
||||||
|
read_commit_log(log, log_path);
|
||||||
|
char* last_tree_hash = NULL;
|
||||||
|
if (log->len != 0) {
|
||||||
|
last_tree_hash = ((Commit*)log->items + (log->len))->tree_hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseFileBuffer* tracked_list = base_file_buffer_new();
|
||||||
|
read_base_file_list(tracked_list, info_path);
|
||||||
|
|
||||||
|
StringBuffer* modified_files = string_buffer_new();
|
||||||
|
StringBuffer* deleted_files = string_buffer_new();
|
||||||
|
StringBuffer* untracked_files = string_buffer_new();
|
||||||
|
|
||||||
|
Changes* changes = calloc(1, sizeof(Changes));
|
||||||
|
if (!changes) {
|
||||||
|
printf("ERROR: unable to allocate memory for changes!\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
changes->insertions = 0;
|
||||||
|
changes->deletions = 0;
|
||||||
|
|
||||||
|
for (size_t idx = 0; idx < files->len; idx++) {
|
||||||
|
char* file_name = ((FileInfo*)files->items + idx)->name;
|
||||||
|
if (base_file_buffer_search(tracked_list, file_name)) continue;
|
||||||
|
string_buffer_push(untracked_files, file_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t idx = 0; idx < files->len; idx++) {
|
||||||
|
char* file_name = ((FileInfo*)files->items + idx)->name;
|
||||||
|
if (base_file_buffer_search(tracked_list, file_name)) {
|
||||||
|
BaseFileInfo* base_file = base_file_buffer_search(tracked_list, file_name);
|
||||||
|
char base_file_hash[41];
|
||||||
|
char id[2 + snprintf(NULL, 0, "%d", base_file->base_num) + strlen(file_name)];
|
||||||
|
snprintf(id, sizeof(id), "%s %d", file_name, base_file->base_num);
|
||||||
|
object_hash(BaseFileObject, id, base_file_hash);
|
||||||
|
File* basefile = parse_object(base_file_hash, BaseFileObject, NULL, NULL);
|
||||||
|
if (!basefile) {
|
||||||
|
printf("ERROR: unable to parse base file %s!\n", file_name);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
File* modified_file = new_file(file_name);
|
||||||
|
if (!modified_file) {
|
||||||
|
printf("ERROR: unable to read modified file %s!\n", file_name);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionList* diff = myers_diff(basefile, modified_file, 0, 0);
|
||||||
|
if (!diff) {
|
||||||
|
printf("ERROR: unable to compute diff for file %s!\n", file_name);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diff->len != 0) {
|
||||||
|
for (size_t idx = 0; idx < diff->len; idx++) {
|
||||||
|
Action action = diff->actions[idx];
|
||||||
|
if (action.type == DELETE) {
|
||||||
|
changes->deletions++;
|
||||||
|
} else if (action.type == INSERT) {
|
||||||
|
changes->insertions++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
string_buffer_push(modified_files, file_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t idx = 0; idx < tracked_list->len; idx++) {
|
||||||
|
char* file_name = ((BaseFileInfo*)tracked_list->items + idx)->name;
|
||||||
|
if (file_info_buffer_search(files, file_name)) continue;
|
||||||
|
else string_buffer_push(deleted_files, file_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t idx = 0; idx < modified_files->len; idx++) {
|
||||||
|
printf("\x1b[36;1mM\x1b[0m \x1b[36m%s\x1b[0m\n", modified_files->items[idx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t idx = 0; idx < deleted_files->len; idx++) {
|
||||||
|
printf("\x1b[31;4mD\x1b[0m \x1b[31;9m%s\x1b[0m\n", deleted_files->items[idx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modified_files->len != 0 || deleted_files->len != 0) printf("\n");
|
||||||
|
|
||||||
|
for (size_t idx = 0; idx < untracked_files->len; idx++) {
|
||||||
|
printf("\x1b[31;1mU\x1b[0m \x1b[31m%s\x1b[0m\n", untracked_files->items[idx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n\x1b[32;1m%d+\x1b[0m \x1b[31;1m%d-\x1b[0m on branch \x1b[39;1;4m%s\x1b[0m with %d commits\n", changes->insertions, changes->deletions, branch, log->len);
|
||||||
|
|
||||||
|
file_info_buffer_free(files);
|
||||||
|
free(root);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
// Commits changes to the repo
|
// Commits changes to the repo
|
||||||
else if (strcmp(subcmd, "commit") == 0) {
|
else if (strcmp(subcmd, "commit") == 0) {
|
||||||
if (argc < 3) {
|
if (argc < 3) {
|
||||||
|
|
@ -57,22 +251,508 @@ int main(int argc, char** argv) {
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
commit(argc, argv);
|
char* commit_message = NULL;
|
||||||
}
|
int file_start_index = 2;
|
||||||
|
|
||||||
else if (strcmp(subcmd, "log") == 0) {
|
if (argc >= 4 && strcmp(argv[2], "-m") == 0) {
|
||||||
if (argc != 2) {
|
if (argv[3][0] == '-') {
|
||||||
printf("ERROR: too many arguments given!\n");
|
printf("ERROR: Invalid commit message provided after -m flag!\n");
|
||||||
printf("Usage: merk log");
|
exit(1);
|
||||||
|
}
|
||||||
|
commit_message = strdup(argv[3]);
|
||||||
|
if (!commit_message) {
|
||||||
|
fprintf(stderr, "ERROR: Memory allocation failed\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
file_start_index = 4;
|
||||||
|
} else if (argc == 3 && strcmp(argv[2], "-m") == 0) {
|
||||||
|
printf("ERROR: Missing commit message after -m flag!\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
commit_log();
|
// Ensure files to commit are correctly identified
|
||||||
|
for (int i = file_start_index; i < argc; i++) {
|
||||||
|
if (strcmp(argv[i], "-m") == 0) {
|
||||||
|
printf("ERROR: Unexpected -m flag in file list!\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Config config = {0};
|
||||||
|
|
||||||
|
if (load_config(&config) < 0) {
|
||||||
|
free(commit_message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validate_config(&config) < 0) {
|
||||||
|
free_config(&config);
|
||||||
|
free(commit_message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char tmp_path[PATH_MAX] = ".merk/BRANCH";
|
||||||
|
char branch_path[PATH_MAX];
|
||||||
|
realpath(tmp_path, branch_path);
|
||||||
|
|
||||||
|
char* branch = get_file_content(branch_path);
|
||||||
|
if (!branch) {
|
||||||
|
free_config(&config);
|
||||||
|
free(commit_message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char tmp_path2[PATH_MAX];
|
||||||
|
char info_path[PATH_MAX];
|
||||||
|
snprintf(tmp_path2, sizeof(tmp_path2), ".merk/info/%s", branch);
|
||||||
|
realpath(tmp_path2, info_path);
|
||||||
|
|
||||||
|
BaseFileBuffer* base_files = base_file_buffer_new();
|
||||||
|
if (!base_files) {
|
||||||
|
free(branch);
|
||||||
|
free_config(&config);
|
||||||
|
free(commit_message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
read_base_file_list(base_files, info_path);
|
||||||
|
|
||||||
|
StringBuffer* files = string_buffer_new();
|
||||||
|
if (!files) {
|
||||||
|
base_file_buffer_free(base_files);
|
||||||
|
free(branch);
|
||||||
|
free_config(&config);
|
||||||
|
free(commit_message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* root = find_root(NULL);
|
||||||
|
if (!root) {
|
||||||
|
list_free(files);
|
||||||
|
base_file_buffer_free(base_files);
|
||||||
|
free(branch);
|
||||||
|
free_config(&config);
|
||||||
|
free(commit_message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = file_start_index; i < argc; i++) {
|
||||||
|
char* real = realpath(argv[i], NULL);
|
||||||
|
if (!real) {
|
||||||
|
fprintf(stderr, "ERROR: Cannot resolve path: %s\n", argv[i]);
|
||||||
|
list_free(files);
|
||||||
|
base_file_buffer_free(base_files);
|
||||||
|
free(branch);
|
||||||
|
free_config(&config);
|
||||||
|
free(commit_message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (get_path_type(real) == PT_FILE && is_in_repo(root, real) == 0) {
|
||||||
|
char* repo_path = get_repo_path(root, real);
|
||||||
|
if (repo_path) {
|
||||||
|
string_buffer_push(files, repo_path);
|
||||||
|
free(repo_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fprintf(stderr, "ERROR: %s is not a file or not inside the repo!\n", real);
|
||||||
|
free(real);
|
||||||
|
list_free(files);
|
||||||
|
base_file_buffer_free(base_files);
|
||||||
|
free(branch);
|
||||||
|
free(root);
|
||||||
|
free_config(&config);
|
||||||
|
free(commit_message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
free(real);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!commit_message) {
|
||||||
|
printf("Enter commit message: ");
|
||||||
|
fflush(stdout);
|
||||||
|
|
||||||
|
char buffer[1024];
|
||||||
|
if (fgets(buffer, sizeof(buffer), stdin) != NULL) {
|
||||||
|
size_t len = strlen(buffer);
|
||||||
|
if (len > 0 && buffer[len-1] == '\n') {
|
||||||
|
buffer[len-1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(buffer) == 0) {
|
||||||
|
printf("ERROR: Empty commit message!\n");
|
||||||
|
list_free(files);
|
||||||
|
base_file_buffer_free(base_files);
|
||||||
|
free(branch);
|
||||||
|
free(root);
|
||||||
|
free_config(&config);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
commit_message = strdup(buffer);
|
||||||
|
if (!commit_message) {
|
||||||
|
fprintf(stderr, "ERROR: Memory allocation failed\n");
|
||||||
|
list_free(files);
|
||||||
|
base_file_buffer_free(base_files);
|
||||||
|
free(branch);
|
||||||
|
free(root);
|
||||||
|
free_config(&config);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf("ERROR: Failed to read commit message!\n");
|
||||||
|
list_free(files);
|
||||||
|
base_file_buffer_free(base_files);
|
||||||
|
free(branch);
|
||||||
|
free(root);
|
||||||
|
free_config(&config);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Changes* changes = calloc(1, sizeof(Changes));
|
||||||
|
if (!changes) {
|
||||||
|
free(commit_message);
|
||||||
|
list_free(files);
|
||||||
|
base_file_buffer_free(base_files);
|
||||||
|
free(branch);
|
||||||
|
free(root);
|
||||||
|
free_config(&config);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
changes->insertions = 0;
|
||||||
|
changes->deletions = 0;
|
||||||
|
|
||||||
|
for (size_t idx = 0; idx < files->len; idx++) {
|
||||||
|
BaseFileInfo* base_file = base_file_buffer_search(base_files, files->items[idx]);
|
||||||
|
if (!base_file) {
|
||||||
|
BaseFileInfo info = (BaseFileInfo){.base_num = 0, .diff_num = 0, .name = strdup(files->items[idx])};
|
||||||
|
base_file_buffer_push(base_files, info);
|
||||||
|
// Compress the files content and put it into the object database
|
||||||
|
File* modified_file = new_file(files->items[idx]);
|
||||||
|
if (!modified_file) {
|
||||||
|
free(commit_message);
|
||||||
|
list_free(files);
|
||||||
|
base_file_buffer_free(base_files);
|
||||||
|
free(branch);
|
||||||
|
free(root);
|
||||||
|
free_config(&config);
|
||||||
|
free(changes);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
changes->insertions += modified_file->lines;
|
||||||
|
|
||||||
|
char file_hash[41];
|
||||||
|
snapshot_file(files->items[idx], root, 0, file_hash);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char base_file_hash[41];
|
||||||
|
char id[2 + snprintf(NULL, 0, "%d", base_file->base_num) + strlen(files->items[idx])];
|
||||||
|
snprintf(id, sizeof(id), "%s %d", files->items[idx], base_file->base_num);
|
||||||
|
object_hash(BaseFileObject, id, base_file_hash);
|
||||||
|
File* basefile = parse_object(base_file_hash, BaseFileObject, NULL, NULL);
|
||||||
|
if (!basefile) {
|
||||||
|
free(commit_message);
|
||||||
|
list_free(files);
|
||||||
|
base_file_buffer_free(base_files);
|
||||||
|
free(branch);
|
||||||
|
free(root);
|
||||||
|
free_config(&config);
|
||||||
|
free(changes);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
File* modified_file = new_file(files->items[idx]);
|
||||||
|
if (!modified_file) {
|
||||||
|
free_file(basefile);
|
||||||
|
free(commit_message);
|
||||||
|
list_free(files);
|
||||||
|
base_file_buffer_free(base_files);
|
||||||
|
free(branch);
|
||||||
|
free(root);
|
||||||
|
free_config(&config);
|
||||||
|
free(changes);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionList* diff = myers_diff(basefile, modified_file, 0, 0);
|
||||||
|
if (!diff) {
|
||||||
|
free_file(basefile);
|
||||||
|
free_file(modified_file);
|
||||||
|
free(commit_message);
|
||||||
|
list_free(files);
|
||||||
|
base_file_buffer_free(base_files);
|
||||||
|
free(branch);
|
||||||
|
free(root);
|
||||||
|
free_config(&config);
|
||||||
|
free(changes);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (base_file->diff_num == 0) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (base_file->diff_num > 0) {
|
||||||
|
size_t prev_diff = base_file->diff_num - 1;
|
||||||
|
char prev_diff_hash[41];
|
||||||
|
char id2[2 + snprintf(NULL, 0, "%zu", prev_diff) + strlen(files->items[idx])];
|
||||||
|
snprintf(id2, sizeof(id2), "%s %zu", files->items[idx], prev_diff);
|
||||||
|
object_hash(FileDiffObject, id2, prev_diff_hash);
|
||||||
|
char hash[41];
|
||||||
|
ActionList* last_diff = parse_object(prev_diff_hash, FileDiffObject, NULL, hash);
|
||||||
|
if (!last_diff) {
|
||||||
|
free_action_list(diff);
|
||||||
|
free_file(modified_file);
|
||||||
|
free_file(basefile);
|
||||||
|
free(commit_message);
|
||||||
|
list_free(files);
|
||||||
|
base_file_buffer_free(base_files);
|
||||||
|
free(branch);
|
||||||
|
free(root);
|
||||||
|
free_config(&config);
|
||||||
|
free(changes);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
File* last_version = apply_diff(basefile, last_diff);
|
||||||
|
if (!last_version) {
|
||||||
|
free_action_list(last_diff);
|
||||||
|
free_action_list(diff);
|
||||||
|
free_file(modified_file);
|
||||||
|
free_file(basefile);
|
||||||
|
free(commit_message);
|
||||||
|
list_free(files);
|
||||||
|
base_file_buffer_free(base_files);
|
||||||
|
free(branch);
|
||||||
|
free(root);
|
||||||
|
free_config(&config);
|
||||||
|
free(changes);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionList* modified_diff = myers_diff(last_version, modified_file, 0, 0);
|
||||||
|
if (!modified_diff) {
|
||||||
|
free_action_list(last_diff);
|
||||||
|
free_action_list(diff);
|
||||||
|
free_file(modified_file);
|
||||||
|
free_file(basefile);
|
||||||
|
free(commit_message);
|
||||||
|
list_free(files);
|
||||||
|
base_file_buffer_free(base_files);
|
||||||
|
free(branch);
|
||||||
|
free(root);
|
||||||
|
free_config(&config);
|
||||||
|
free(changes);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t idx = 0; idx < modified_diff->len; idx++) {
|
||||||
|
Action action = modified_diff->actions[idx];
|
||||||
|
if (action.type == INSERT) {
|
||||||
|
changes->insertions += 1;
|
||||||
|
} else if (action.type == DELETE) {
|
||||||
|
changes->deletions += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
save_diff(diff, files->items[idx], root, base_file->diff_num, base_file_hash, NULL, 0);
|
||||||
|
|
||||||
|
base_file_buffer_remove(base_files, files->items[idx]);
|
||||||
|
BaseFileInfo new_info = (BaseFileInfo){
|
||||||
|
.base_num = base_file->base_num,
|
||||||
|
.diff_num = base_file->diff_num + 1,
|
||||||
|
.name = strdup(files->items[idx])
|
||||||
|
};
|
||||||
|
base_file_buffer_push(base_files, new_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
base_file_buffer_sort(base_files);
|
||||||
|
write_base_file_list(base_files, info_path);
|
||||||
|
FileInfoBuffer* tree = basefilebuffer_to_fileinfobuffer(base_files);
|
||||||
|
if (!tree) {
|
||||||
|
free(commit_message);
|
||||||
|
list_free(files);
|
||||||
|
base_file_buffer_free(base_files);
|
||||||
|
free(branch);
|
||||||
|
free(root);
|
||||||
|
free_config(&config);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t idx = 0; idx < tree->len; idx++) {
|
||||||
|
FileInfo* info = (FileInfo*)tree->items + idx;
|
||||||
|
BaseFileInfo* base_info = base_file_buffer_search(base_files, info->name);
|
||||||
|
char hash[41];
|
||||||
|
if (base_info->diff_num == 0) {
|
||||||
|
char id[2 + snprintf(NULL, 0, "%d", base_info->base_num) + strlen(info->name)];
|
||||||
|
snprintf(id, sizeof(id), "%s %d", info->name, base_info->base_num);
|
||||||
|
object_hash(BaseFileObject, id, hash);
|
||||||
|
} else {
|
||||||
|
char id[2 + snprintf(NULL, 0, "%d", base_info->diff_num-1) + strlen(info->name)];
|
||||||
|
snprintf(id, sizeof(id), "%s %d", info->name, base_info->diff_num-1);
|
||||||
|
object_hash(FileDiffObject, id, hash);
|
||||||
|
}
|
||||||
|
info->hash = strdup(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
char tree_hash[41];
|
||||||
|
snapshot_tree(tree, tree_hash);
|
||||||
|
|
||||||
|
char log_path[PATH_MAX];
|
||||||
|
snprintf(log_path, sizeof(log_path), "%s/.merk/logs/branches/%s", root, branch);
|
||||||
|
|
||||||
|
CommitLog* log = commit_log_new();
|
||||||
|
|
||||||
|
if (!log) {
|
||||||
|
file_info_buffer_free(tree);
|
||||||
|
free(commit_message);
|
||||||
|
list_free(files);
|
||||||
|
base_file_buffer_free(base_files);
|
||||||
|
free(branch);
|
||||||
|
free(root);
|
||||||
|
free_config(&config);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
read_commit_log(log, log_path);
|
||||||
|
|
||||||
|
char parent_hash[41];
|
||||||
|
if (log->len != 0) {
|
||||||
|
snprintf(parent_hash, sizeof(parent_hash), "%s", ((Commit*)log->items + (log->len - 1))->hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
char timestamp[20];
|
||||||
|
snprintf(timestamp, sizeof(timestamp), "%ld", time(NULL));
|
||||||
|
|
||||||
|
Author author = {
|
||||||
|
.name = strdup(config.user.name),
|
||||||
|
.email = strdup(config.user.email),
|
||||||
|
.timestamp = strdup(timestamp)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (log->len != 0) {
|
||||||
|
char parent_hash[41];
|
||||||
|
snprintf(parent_hash, sizeof(parent_hash), "%s", ((Commit*)log->items + (log->len - 1))->hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
char commit_hash_content[4096];
|
||||||
|
int offset = 0;
|
||||||
|
offset += snprintf(commit_hash_content + offset, sizeof(commit_hash_content) - offset, "tree %s\n", tree_hash);
|
||||||
|
if (log->len != 0) {
|
||||||
|
offset += snprintf(commit_hash_content + offset, sizeof(commit_hash_content) - offset, "parent %s\n", parent_hash);
|
||||||
|
}
|
||||||
|
offset += snprintf(commit_hash_content + offset, sizeof(commit_hash_content) - offset, "author %s <%s> %s\n", author.name, author.email, author.timestamp);
|
||||||
|
offset += snprintf(commit_hash_content + offset, sizeof(commit_hash_content) - offset, "committer %s <%s> %s\n", author.name, author.email, author.timestamp);
|
||||||
|
offset += snprintf(commit_hash_content + offset, sizeof(commit_hash_content) - offset, "message %s\n", commit_message);
|
||||||
|
|
||||||
|
char commit_hash[41];
|
||||||
|
object_hash(LogObject, commit_hash_content, commit_hash);
|
||||||
|
|
||||||
|
Commit commit = {
|
||||||
|
.hash = commit_hash,
|
||||||
|
.tree_hash = strdup(tree_hash),
|
||||||
|
.authors = &author,
|
||||||
|
.authors_count = 1,
|
||||||
|
.committer = author,
|
||||||
|
.message = strdup(commit_message)
|
||||||
|
};
|
||||||
|
|
||||||
|
commit_log_push(log, commit);
|
||||||
|
if (!write_commit_log(log, log_path)) {
|
||||||
|
commit_log_free(log);
|
||||||
|
file_info_buffer_free(tree);
|
||||||
|
free(commit_message);
|
||||||
|
list_free(files);
|
||||||
|
base_file_buffer_free(base_files);
|
||||||
|
free(branch);
|
||||||
|
free(root);
|
||||||
|
free_config(&config);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char ref_path[PATH_MAX];
|
||||||
|
snprintf(ref_path, sizeof(ref_path), "%s/.merk/refs/branches/%s", root, branch);
|
||||||
|
|
||||||
|
int ref = open(ref_path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
||||||
|
if (ref < 0) return -1;
|
||||||
|
write(ref, commit_hash, 40);
|
||||||
|
close(ref);
|
||||||
|
|
||||||
|
printf("(\x1b[33;1m%.7s\x1b[0m on \x1b[39;1;4m%s\x1b[0m \x1b[32;1m%d+\x1b[0m \x1b[31;1m%d-\x1b[0m) %s\n",
|
||||||
|
commit_hash,
|
||||||
|
branch,
|
||||||
|
changes->insertions,
|
||||||
|
changes->deletions,
|
||||||
|
commit_message
|
||||||
|
);
|
||||||
|
|
||||||
|
list_free(files);
|
||||||
|
base_file_buffer_free(base_files);
|
||||||
|
free(branch);
|
||||||
|
free(root);
|
||||||
|
free_config(&config);
|
||||||
|
free(commit_message);
|
||||||
}
|
}
|
||||||
|
else if (strcmp(subcmd, "log") == 0) {
|
||||||
|
CommitLog* log = commit_log_new();
|
||||||
|
if (!log) return 1;
|
||||||
|
|
||||||
|
char* root = find_root(NULL);
|
||||||
|
if (!root) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char branch_path[PATH_MAX];
|
||||||
|
snprintf(branch_path, sizeof(branch_path), "%s/.merk/BRANCH", root);
|
||||||
|
|
||||||
|
char* branch = get_file_content(branch_path);
|
||||||
|
if (!branch) {
|
||||||
|
free(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char log_path[PATH_MAX];
|
||||||
|
snprintf(log_path, sizeof(log_path), "%s/.merk/logs/branches/%s", root, branch);
|
||||||
|
|
||||||
|
read_commit_log(log, log_path);
|
||||||
|
|
||||||
|
for (size_t i = log->len; i != 0; i--) {
|
||||||
|
Commit* commit = (Commit*)log->items + i - 1;
|
||||||
|
char human_readable_time[32];
|
||||||
|
time_t timestamp = atol(commit->committer.timestamp);
|
||||||
|
strftime(human_readable_time, sizeof(human_readable_time), "%Y-%m-%d %H:%M:%S %z", localtime(×tamp));
|
||||||
|
|
||||||
|
printf("\x1b[33;1m%.7s\x1b[0m: %s\n%s <\x1b[38;5;240m\x1b]8;;mailto:%s\x1b\\%s\x1b]8;;\x1b\\\x1b[0m> (\x1b[36m%s\x1b[0m)\n\n",
|
||||||
|
commit->hash,
|
||||||
|
commit->message,
|
||||||
|
commit->committer.name,
|
||||||
|
commit->committer.email,
|
||||||
|
commit->committer.email,
|
||||||
|
human_readable_time
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(branch);
|
||||||
|
free(root);
|
||||||
|
commit_log_free(log);
|
||||||
|
}
|
||||||
else if (strcmp(subcmd, "test") == 0) {}
|
else if (strcmp(subcmd, "test") == 0) {}
|
||||||
|
else {
|
||||||
else { usage(1); }
|
usage(1);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
34
src/object.c
34
src/object.c
|
|
@ -17,7 +17,7 @@ char* get_object(char* hash, size_t* size_out) {
|
||||||
free(compressed_data);
|
free(compressed_data);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
size_t idx = 0;
|
size_t idx = 0;
|
||||||
while (idx < compressed_size && IS_DIGIT(compressed_data[idx])) {
|
while (idx < compressed_size && IS_DIGIT(compressed_data[idx])) {
|
||||||
|
|
@ -152,7 +152,7 @@ static int mkdir_recursive(const char *path, mode_t mode) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int save_diff(ActionList* diff, char* path, char* root, size_t diff_id, char* basefile_hash) {
|
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;
|
size_t buffer_size = 41;
|
||||||
|
|
||||||
buffer_size += 1 + snprintf(NULL, 0, "%d", diff->len);
|
buffer_size += 1 + snprintf(NULL, 0, "%d", diff->len);
|
||||||
|
|
@ -186,20 +186,20 @@ int save_diff(ActionList* diff, char* path, char* root, size_t diff_id, char* ba
|
||||||
size_t remaining = sizeof(tmp) - offset;
|
size_t remaining = sizeof(tmp) - offset;
|
||||||
|
|
||||||
if (action.type == DELETE) {
|
if (action.type == DELETE) {
|
||||||
offset += snprintf(tmp + offset,
|
offset += snprintf(tmp + offset,
|
||||||
remaining,
|
remaining,
|
||||||
"0 %zu %zu ",
|
"0 %zu %zu ",
|
||||||
action.line_original,
|
action.line_original,
|
||||||
action.line_changed
|
action.line_changed
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
offset += snprintf(tmp + offset, remaining,
|
offset += snprintf(tmp + offset, remaining,
|
||||||
"1 %zu %zu %zu %s ",
|
"1 %zu %zu %zu %s ",
|
||||||
action.line_original,
|
action.line_original,
|
||||||
action.line_changed,
|
action.line_changed,
|
||||||
strlen(action.content),
|
strlen(action.content),
|
||||||
action.content
|
action.content
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -207,7 +207,8 @@ int save_diff(ActionList* diff, char* path, char* root, size_t diff_id, char* ba
|
||||||
char id[2+snprintf(NULL, 0, "%d", diff_id)+strlen(path)];
|
char id[2+snprintf(NULL, 0, "%d", diff_id)+strlen(path)];
|
||||||
snprintf(id, sizeof(id), "%s %d", path, diff_id);
|
snprintf(id, sizeof(id), "%s %d", path, diff_id);
|
||||||
char hash[41];
|
char hash[41];
|
||||||
object_hash(FileDiffObject, id, hash);
|
if (tree) object_hash(TreeDiffObject, id, hash);
|
||||||
|
else object_hash(FileDiffObject, id, hash);
|
||||||
|
|
||||||
char dir_path[PATH_MAX];
|
char dir_path[PATH_MAX];
|
||||||
char file_path[PATH_MAX];
|
char file_path[PATH_MAX];
|
||||||
|
|
@ -287,12 +288,13 @@ int read_diff(char* content, char* basefile_hash, ActionList* diff_out) {
|
||||||
char* end;
|
char* end;
|
||||||
long actions = strtol(number_of_actions, &end, 10);
|
long actions = strtol(number_of_actions, &end, 10);
|
||||||
|
|
||||||
if (end == number_of_actions || *end != '\0' || actions < 0) {
|
if (end == number_of_actions || *end != '\0' || actions <= 0) {
|
||||||
perror("ERROR: invalid number of actions in read_diff!");
|
perror("ERROR: invalid number of actions in read_diff!");
|
||||||
free(number_of_actions);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(number_of_actions);
|
||||||
|
|
||||||
size_t action_idx = 0;
|
size_t action_idx = 0;
|
||||||
while (content[idx] && action_idx < (size_t)actions) {
|
while (content[idx] && action_idx < (size_t)actions) {
|
||||||
idx++;
|
idx++;
|
||||||
|
|
@ -374,7 +376,7 @@ int read_diff(char* content, char* basefile_hash, ActionList* diff_out) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int save_file_diff(char* path, char* root, size_t diff_id, char* basefile_hash, ActionList* diff) {
|
int save_file_diff(char* path, char* root, size_t diff_id, char* basefile_hash, ActionList* diff) {
|
||||||
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
@ -418,4 +420,4 @@ File* apply_diff(File* basefile, ActionList* diff) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
320
src/tree.c
320
src/tree.c
|
|
@ -34,7 +34,7 @@ void snapshot_tree(FileInfoBuffer* tree, char* hash) {
|
||||||
FileInfo* file_info = (FileInfo*)tree->items + idx;
|
FileInfo* file_info = (FileInfo*)tree->items + idx;
|
||||||
size_t remaining = buffer_size - offset;
|
size_t remaining = buffer_size - offset;
|
||||||
|
|
||||||
int written = snprintf(tmp + offset, remaining, "%o %s %s\n",
|
int written = snprintf(tmp + offset, remaining, "%o %s %s\n",
|
||||||
file_info->mode, file_info->name, file_info->hash);
|
file_info->mode, file_info->name, file_info->hash);
|
||||||
|
|
||||||
if (written < 0 || (size_t)written >= remaining) {
|
if (written < 0 || (size_t)written >= remaining) {
|
||||||
|
|
@ -63,7 +63,7 @@ void snapshot_tree(FileInfoBuffer* tree, char* hash) {
|
||||||
}
|
}
|
||||||
|
|
||||||
FILE* fp = fopen(file_path, "wb");
|
FILE* fp = fopen(file_path, "wb");
|
||||||
if (!fp) {
|
if (!fp) {
|
||||||
perror("ERROR: cannot open path in snapshot_tree!");
|
perror("ERROR: cannot open path in snapshot_tree!");
|
||||||
free(tmp);
|
free(tmp);
|
||||||
file_info_buffer_free(tree);
|
file_info_buffer_free(tree);
|
||||||
|
|
@ -81,7 +81,7 @@ void snapshot_tree(FileInfoBuffer* tree, char* hash) {
|
||||||
free(root);
|
free(root);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (compress(compressed, &compressedLen, (const Bytef*)tmp, originalLen) != Z_OK) {
|
if (compress(compressed, &compressedLen, (const Bytef*)tmp, originalLen) != Z_OK) {
|
||||||
perror("ERROR: compression failed in snapshot_tree!");
|
perror("ERROR: compression failed in snapshot_tree!");
|
||||||
free(compressed);
|
free(compressed);
|
||||||
|
|
@ -98,13 +98,325 @@ void snapshot_tree(FileInfoBuffer* tree, char* hash) {
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
|
|
||||||
chmod(file_path, S_IRUSR | S_IRGRP | S_IROTH);
|
chmod(file_path, S_IRUSR | S_IRGRP | S_IROTH);
|
||||||
|
|
||||||
free(compressed);
|
free(compressed);
|
||||||
free(tmp);
|
free(tmp);
|
||||||
file_info_buffer_free(tree);
|
file_info_buffer_free(tree);
|
||||||
free(root);
|
free(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int save_tree_diff(FileInfoBuffer* current_tree, char* root, char* branch_name, size_t diff_id, char* base_tree_hash, char* hash) {
|
||||||
|
if (!current_tree || !root || !branch_name || !base_tree_hash || !hash) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char base_tree_location[PATH_MAX];
|
||||||
|
snprintf(base_tree_location, sizeof(base_tree_location), "%s/.merk/objects/%.2s/%s", root, base_tree_hash, base_tree_hash+2);
|
||||||
|
|
||||||
|
size_t compressed_size;
|
||||||
|
unsigned char* compressed_data = (unsigned char*)get_file_content_with_size(base_tree_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 in base tree file!");
|
||||||
|
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' || size <= 0) {
|
||||||
|
perror("ERROR: invalid length in base tree file");
|
||||||
|
free(size_str);
|
||||||
|
free(compressed_data);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t offset = strlen(size_str) + 1;
|
||||||
|
free(size_str);
|
||||||
|
|
||||||
|
char* base_tree_content = calloc(size + 1, sizeof(char));
|
||||||
|
if (!base_tree_content) {
|
||||||
|
free(compressed_data);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uLongf dest_len = (uLongf)size;
|
||||||
|
int result = uncompress((unsigned char*)base_tree_content, &dest_len, compressed_data + offset, compressed_size - offset);
|
||||||
|
free(compressed_data);
|
||||||
|
|
||||||
|
if (result != Z_OK) {
|
||||||
|
perror("ERROR: decompression of base tree failed!");
|
||||||
|
free(base_tree_content);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
base_tree_content[dest_len] = '\0';
|
||||||
|
|
||||||
|
File* base_tree_file = from_string(base_tree_content);
|
||||||
|
if (!base_tree_file) {
|
||||||
|
free(base_tree_content);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t buffer_size = 0;
|
||||||
|
for (size_t i = 0; i < current_tree->len; i++) {
|
||||||
|
FileInfo* file_info = (FileInfo*)current_tree->items + i;
|
||||||
|
buffer_size += snprintf(NULL, 0, "%o %s %s\n", file_info->mode, file_info->name, file_info->hash);
|
||||||
|
}
|
||||||
|
buffer_size += 1;
|
||||||
|
|
||||||
|
char* current_tree_content = malloc(buffer_size);
|
||||||
|
if (!current_tree_content) {
|
||||||
|
free(base_tree_content);
|
||||||
|
free(base_tree_file->content[0]);
|
||||||
|
free(base_tree_file->content);
|
||||||
|
free(base_tree_file);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t content_offset = 0;
|
||||||
|
for (size_t i = 0; i < current_tree->len; i++) {
|
||||||
|
FileInfo* file_info = (FileInfo*)current_tree->items + i;
|
||||||
|
size_t remaining = buffer_size - content_offset;
|
||||||
|
int written = snprintf(current_tree_content + content_offset, remaining, "%o %s %s\n",
|
||||||
|
file_info->mode, file_info->name, file_info->hash);
|
||||||
|
if (written < 0 || (size_t)written >= remaining) {
|
||||||
|
perror("ERROR: buffer overflow in current tree content!");
|
||||||
|
free(current_tree_content);
|
||||||
|
free(base_tree_content);
|
||||||
|
free(base_tree_file->content[0]);
|
||||||
|
free(base_tree_file->content);
|
||||||
|
free(base_tree_file);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
content_offset += (size_t)written;
|
||||||
|
}
|
||||||
|
current_tree_content[content_offset] = '\0';
|
||||||
|
|
||||||
|
File* current_tree_file = from_string(current_tree_content);
|
||||||
|
if (!current_tree_file) {
|
||||||
|
free(current_tree_content);
|
||||||
|
free(base_tree_content);
|
||||||
|
free(base_tree_file->content[0]);
|
||||||
|
free(base_tree_file->content);
|
||||||
|
free(base_tree_file);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionList* diff = myers_diff(base_tree_file, current_tree_file, 0, 0);
|
||||||
|
|
||||||
|
if (!diff || diff->len == 0) {
|
||||||
|
strcpy(hash, base_tree_hash);
|
||||||
|
|
||||||
|
free(current_tree_content);
|
||||||
|
free(base_tree_content);
|
||||||
|
free(base_tree_file->content[0]);
|
||||||
|
free(base_tree_file->content);
|
||||||
|
free(base_tree_file);
|
||||||
|
free(current_tree_file->content[0]);
|
||||||
|
free(current_tree_file->content);
|
||||||
|
free(current_tree_file);
|
||||||
|
if (diff) {
|
||||||
|
free(diff->actions);
|
||||||
|
free(diff);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t diff_buffer_size = strlen(base_tree_hash) + 1;
|
||||||
|
diff_buffer_size += snprintf(NULL, 0, "%zu ", diff->len);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < diff->len; i++) {
|
||||||
|
Action action = diff->actions[i];
|
||||||
|
if (action.type == DELETE) {
|
||||||
|
diff_buffer_size += snprintf(NULL, 0, "0 %zu %zu ", action.line_original, action.line_changed);
|
||||||
|
} else {
|
||||||
|
diff_buffer_size += snprintf(NULL, 0, "1 %zu %zu %s ",
|
||||||
|
action.line_original, action.line_changed, current_tree_file->content[action.line_changed]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
diff_buffer_size += 1;
|
||||||
|
|
||||||
|
char* diff_content = malloc(diff_buffer_size);
|
||||||
|
if (!diff_content) {
|
||||||
|
perror("ERROR: memory allocation failed for diff content!");
|
||||||
|
free(current_tree_content);
|
||||||
|
free(base_tree_content);
|
||||||
|
free(base_tree_file->content[0]);
|
||||||
|
free(base_tree_file->content);
|
||||||
|
free(base_tree_file);
|
||||||
|
free(current_tree_file->content[0]);
|
||||||
|
free(current_tree_file->content);
|
||||||
|
free(current_tree_file);
|
||||||
|
free(diff->actions);
|
||||||
|
free(diff);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t diff_offset = 0;
|
||||||
|
diff_offset += snprintf(diff_content, diff_buffer_size, "%s %zu ", base_tree_hash, diff->len);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < diff->len; i++) {
|
||||||
|
Action action = diff->actions[i];
|
||||||
|
size_t remaining = diff_buffer_size - diff_offset;
|
||||||
|
|
||||||
|
if (action.type == DELETE) {
|
||||||
|
int written = snprintf(diff_content + diff_offset, remaining, "0 %zu %zu ",
|
||||||
|
action.line_original, action.line_changed);
|
||||||
|
if (written < 0 || (size_t)written >= remaining) {
|
||||||
|
perror("ERROR: buffer overflow in diff content!");
|
||||||
|
free(diff_content);
|
||||||
|
free(current_tree_content);
|
||||||
|
free(base_tree_content);
|
||||||
|
free(base_tree_file->content[0]);
|
||||||
|
free(base_tree_file->content);
|
||||||
|
free(base_tree_file);
|
||||||
|
free(current_tree_file->content[0]);
|
||||||
|
free(current_tree_file->content);
|
||||||
|
free(current_tree_file);
|
||||||
|
free(diff->actions);
|
||||||
|
free(diff);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
diff_offset += (size_t)written;
|
||||||
|
} else {
|
||||||
|
int written = snprintf(diff_content + diff_offset, remaining, "1 %zu %zu %s ",
|
||||||
|
action.line_original, action.line_changed, current_tree_file->content[action.line_changed]);
|
||||||
|
if (written < 0 || (size_t)written >= remaining) {
|
||||||
|
perror("ERROR: buffer overflow in diff content!");
|
||||||
|
free(diff_content);
|
||||||
|
free(current_tree_content);
|
||||||
|
free(base_tree_content);
|
||||||
|
free(base_tree_file->content[0]);
|
||||||
|
free(base_tree_file->content);
|
||||||
|
free(base_tree_file);
|
||||||
|
free(current_tree_file->content[0]);
|
||||||
|
free(current_tree_file->content);
|
||||||
|
free(current_tree_file);
|
||||||
|
free(diff->actions);
|
||||||
|
free(diff);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
diff_offset += (size_t)written;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object_hash(TreeDiffObject, diff_content, hash);
|
||||||
|
|
||||||
|
char dir_path[PATH_MAX];
|
||||||
|
char file_path[PATH_MAX];
|
||||||
|
snprintf(dir_path, sizeof(dir_path), "%s/.merk/objects/%.2s", root, hash);
|
||||||
|
if (mkdir(dir_path, 0755) != 0 && errno != EEXIST) {
|
||||||
|
perror("ERROR: failed to create directory!");
|
||||||
|
free(diff_content);
|
||||||
|
free(current_tree_content);
|
||||||
|
free(base_tree_content);
|
||||||
|
free(base_tree_file->content[0]);
|
||||||
|
free(base_tree_file->content);
|
||||||
|
free(base_tree_file);
|
||||||
|
free(current_tree_file->content[0]);
|
||||||
|
free(current_tree_file->content);
|
||||||
|
free(current_tree_file);
|
||||||
|
free(diff->actions);
|
||||||
|
free(diff);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
snprintf(file_path, sizeof(file_path), "%s/%s", dir_path, hash+2);
|
||||||
|
|
||||||
|
FILE* fp = fopen(file_path, "wb");
|
||||||
|
if (!fp) {
|
||||||
|
perror("ERROR: cannot open file for writing!");
|
||||||
|
free(diff_content);
|
||||||
|
free(current_tree_content);
|
||||||
|
free(base_tree_content);
|
||||||
|
free(base_tree_file->content[0]);
|
||||||
|
free(base_tree_file->content);
|
||||||
|
free(base_tree_file);
|
||||||
|
free(current_tree_file->content[0]);
|
||||||
|
free(current_tree_file->content);
|
||||||
|
free(current_tree_file);
|
||||||
|
free(diff->actions);
|
||||||
|
free(diff);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uLong originalLen = strlen(diff_content) + 1;
|
||||||
|
uLong compressedLen = compressBound(originalLen);
|
||||||
|
Bytef* compressed = malloc(compressedLen);
|
||||||
|
if (!compressed) {
|
||||||
|
perror("ERROR: memory allocation failed for compression!");
|
||||||
|
fclose(fp);
|
||||||
|
free(diff_content);
|
||||||
|
free(current_tree_content);
|
||||||
|
free(base_tree_content);
|
||||||
|
free(base_tree_file->content[0]);
|
||||||
|
free(base_tree_file->content);
|
||||||
|
free(base_tree_file);
|
||||||
|
free(current_tree_file->content[0]);
|
||||||
|
free(current_tree_file->content);
|
||||||
|
free(current_tree_file);
|
||||||
|
free(diff->actions);
|
||||||
|
free(diff);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compress(compressed, &compressedLen, (const Bytef*)diff_content, originalLen) != Z_OK) {
|
||||||
|
perror("ERROR: compression failed!");
|
||||||
|
free(compressed);
|
||||||
|
fclose(fp);
|
||||||
|
free(diff_content);
|
||||||
|
free(current_tree_content);
|
||||||
|
free(base_tree_content);
|
||||||
|
free(base_tree_file->content[0]);
|
||||||
|
free(base_tree_file->content);
|
||||||
|
free(base_tree_file);
|
||||||
|
free(current_tree_file->content[0]);
|
||||||
|
free(current_tree_file->content);
|
||||||
|
free(current_tree_file);
|
||||||
|
free(diff->actions);
|
||||||
|
free(diff);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(fp, "%lu ", (unsigned long)originalLen);
|
||||||
|
fwrite(compressed, 1, compressedLen, fp);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
chmod(file_path, S_IRUSR | S_IRGRP | S_IROTH);
|
||||||
|
|
||||||
|
free(compressed);
|
||||||
|
free(diff_content);
|
||||||
|
free(current_tree_content);
|
||||||
|
free(base_tree_content);
|
||||||
|
free(base_tree_file->content[0]);
|
||||||
|
free(base_tree_file->content);
|
||||||
|
free(base_tree_file);
|
||||||
|
free(current_tree_file->content[0]);
|
||||||
|
free(current_tree_file->content);
|
||||||
|
free(current_tree_file);
|
||||||
|
free(diff->actions);
|
||||||
|
free(diff);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
int read_tree(FileInfoBuffer* tree_out, char* hash) {
|
int read_tree(FileInfoBuffer* tree_out, char* hash) {
|
||||||
if (!tree_out || !hash) {
|
if (!tree_out || !hash) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -32,64 +32,6 @@ char* string_buffer_search(StringBuffer* buffer, char* path) {
|
||||||
return (char*)list_binary_search(buffer, path, compare_strings);
|
return (char*)list_binary_search(buffer, path, compare_strings);
|
||||||
}
|
}
|
||||||
|
|
||||||
FlatMap* flat_map_new() {
|
|
||||||
return list_new(sizeof(KeyValuePair));
|
|
||||||
}
|
|
||||||
|
|
||||||
int flat_map_put(FlatMap* map, const char* key, const char* value) {
|
|
||||||
if (!map || !key || !value) return 0;
|
|
||||||
|
|
||||||
KeyValuePair kv = {strdup(key), strdup(value)};
|
|
||||||
int res = list_push(map, &kv);
|
|
||||||
flat_map_sort(map);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* flat_map_get(FlatMap* map, const char* key) {
|
|
||||||
if (!map || !key) return NULL;
|
|
||||||
|
|
||||||
KeyValuePair* kv = flat_map_search(map, key);
|
|
||||||
return kv ? kv->value : NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int compare_kv(const void* a, const void* b) {
|
|
||||||
const KeyValuePair* kv1 = (const KeyValuePair*)a;
|
|
||||||
const KeyValuePair* kv2 = (const KeyValuePair*)b;
|
|
||||||
return strcmp(kv1->key, kv2->key);
|
|
||||||
}
|
|
||||||
|
|
||||||
void flat_map_sort(FlatMap* map) {
|
|
||||||
if (!map || map->len <= 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
qsort(map->items, map->len, sizeof(KeyValuePair), compare_kv);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int compare_kv_with_key(const void* key_ptr, const void* element_ptr) {
|
|
||||||
const char* key = (const char*)key_ptr;
|
|
||||||
const KeyValuePair* kv = (const KeyValuePair*)element_ptr;
|
|
||||||
return strcmp(key, kv->key);
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyValuePair* flat_map_search(FlatMap* map, const char* key) {
|
|
||||||
if (!map || !key) return NULL;
|
|
||||||
return (KeyValuePair*)list_binary_search(map, key, compare_kv_with_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
void flat_map_free(FlatMap* map) {
|
|
||||||
if (!map) return;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < map->len; i++) {
|
|
||||||
KeyValuePair* kv = &((KeyValuePair*)map->items)[i];
|
|
||||||
free(kv->key);
|
|
||||||
free(kv->value);
|
|
||||||
}
|
|
||||||
|
|
||||||
free(map->items);
|
|
||||||
free(map);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int is_dot_or_dotdot(const char* s) {
|
static int is_dot_or_dotdot(const char* s) {
|
||||||
return (s[0] == '.' && (s[1] == '\0' || (s[1] == '.' && s[2] == '\0')));
|
return (s[0] == '.' && (s[1] == '\0' || (s[1] == '.' && s[2] == '\0')));
|
||||||
}
|
}
|
||||||
|
|
@ -464,4 +406,4 @@ int create_default_config_file(char* config_path) {
|
||||||
|
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue