feat(cli): added commiting and displaying the commit log
This commit is contained in:
parent
cfb24bd3a2
commit
8cbf82a535
1 changed files with 373 additions and 18 deletions
389
src/main.c
389
src/main.c
|
|
@ -4,12 +4,16 @@
|
|||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "action_list.h"
|
||||
#include "file.h"
|
||||
#include "myers.h"
|
||||
#include "tree.h"
|
||||
#include "base_file_buffer.h"
|
||||
#include "config.h"
|
||||
#include "commit.h"
|
||||
#include "hash.h"
|
||||
|
||||
static void usage(int exitcode) {
|
||||
printf("usage: merk <command> [<args>]\n\
|
||||
|
|
@ -30,7 +34,9 @@ int main(int argc, char **argv) {
|
|||
if (strcmp(subcmd, "diff") != 0 &&
|
||||
strcmp(subcmd, "init") != 0 &&
|
||||
strcmp(subcmd, "status") != 0 &&
|
||||
strcmp(subcmd, "commit") != 0
|
||||
strcmp(subcmd, "commit") != 0 &&
|
||||
strcmp(subcmd, "log") != 0 &&
|
||||
strcmp(subcmd, "test") != 0
|
||||
) {
|
||||
fprintf(stderr, "ERROR: Unknown subcommand: '%s'\n", subcmd);
|
||||
usage(2);
|
||||
|
|
@ -65,6 +71,8 @@ int main(int argc, char **argv) {
|
|||
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;
|
||||
|
|
@ -73,6 +81,16 @@ int main(int argc, char **argv) {
|
|||
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
|
||||
|
|
@ -126,40 +144,56 @@ int main(int argc, char **argv) {
|
|||
|
||||
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();
|
||||
|
||||
for (size_t idx = 0; idx < files->len; idx++) {
|
||||
char* file_name = ((FileInfo*)files->items + idx)->name;
|
||||
// This if is just for debugging purposes right now and will be
|
||||
// changed later to the actual implementation
|
||||
if (base_file_buffer_search(tracked_list, file_name)) {
|
||||
string_buffer_push(modified_files, file_name);
|
||||
continue;
|
||||
}
|
||||
if (base_file_buffer_search(tracked_list, file_name)) continue;
|
||||
string_buffer_push(untracked_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]);
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
|
||||
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 (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("\nOn branch \x1b[39;1;4m%s\x1b[0m with 0 commits\n", branch);
|
||||
printf("\nOn branch \x1b[39;1;4m%s\x1b[0m with %d commits\n", branch, log->len);
|
||||
|
||||
file_info_buffer_free(files);
|
||||
free(root);
|
||||
|
|
@ -167,40 +201,361 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
// Commits changes to the repo
|
||||
else if (strcmp(subcmd, "commit") == 0) {
|
||||
if (argc == 2) {
|
||||
if (argc < 3) {
|
||||
printf("ERROR: too little arguments given!\n");
|
||||
printf("Usage: merk commit [-m <COMMIT MESSAGE>] <FILE> ...");
|
||||
printf("Usage: merk commit [-m <COMMIT MESSAGE>] <FILE> ...\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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 relative[PATH_MAX] = "";
|
||||
char* root = find_root(relative);
|
||||
if (!root) {
|
||||
free_config(&config);
|
||||
free(commit_message);
|
||||
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);
|
||||
free_config(&config);
|
||||
free(commit_message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char info_path[PATH_MAX];
|
||||
snprintf(info_path, sizeof(info_path), "%s/.merk/info/%s", root, branch);
|
||||
char* base_file_list = get_file_content(info_path);
|
||||
|
||||
for (size_t i = 2; i < argc; i++) {
|
||||
BaseFileBuffer* base_files = base_file_buffer_new();
|
||||
if (!base_files) {
|
||||
free(branch);
|
||||
free(root);
|
||||
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(root);
|
||||
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(root);
|
||||
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 {
|
||||
perror("ERROR: given arguments were not all files!");
|
||||
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(base_file_list);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
StringBuffer* file_hashes = string_buffer_new();
|
||||
if (!file_hashes) {
|
||||
free(commit_message);
|
||||
list_free(files);
|
||||
base_file_buffer_free(base_files);
|
||||
free(branch);
|
||||
free(base_file_list);
|
||||
free(root);
|
||||
free_config(&config);
|
||||
return 1;
|
||||
}
|
||||
|
||||
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
|
||||
char file_hash[41];
|
||||
snapshot_file(files->items[idx], root, 0, file_hash);
|
||||
string_buffer_push(file_hashes, file_hash);
|
||||
continue;
|
||||
}
|
||||
size_t base_num = base_file->base_num;
|
||||
size_t diff_num = base_file->diff_num + 1;
|
||||
base_file_buffer_remove(base_files, files->items[idx]);
|
||||
BaseFileInfo new_info = (BaseFileInfo){.base_num = base_num, .diff_num = diff_num, .name = strdup(files->items[idx])};
|
||||
base_file_buffer_push(base_files, new_info);
|
||||
// Take the diff and put it into the object database
|
||||
char basefile_hash[41];
|
||||
char id[2 + snprintf(NULL, 0, "%d", base_num) + strlen(files->items[idx])];
|
||||
snprintf(id, sizeof(id), "%s %d", files->items[idx], base_num);
|
||||
object_hash(BaseFileObject, id, basefile_hash);
|
||||
|
||||
char diff_hash[41];
|
||||
save_file_diff(files->items[idx], root, base_num, basefile_hash, diff_hash);
|
||||
string_buffer_push(file_hashes, diff_hash);
|
||||
}
|
||||
|
||||
base_file_buffer_sort(base_files);
|
||||
write_base_file_list(base_files, info_path);
|
||||
FileInfoBuffer* tree = basefilebuffer_to_fileinfobuffer(base_files);
|
||||
if (!tree) {
|
||||
list_free(file_hashes);
|
||||
free(commit_message);
|
||||
list_free(files);
|
||||
base_file_buffer_free(base_files);
|
||||
free(branch);
|
||||
free(root);
|
||||
free_config(&config);
|
||||
return 1;
|
||||
}
|
||||
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);
|
||||
list_free(file_hashes);
|
||||
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);
|
||||
list_free(file_hashes);
|
||||
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);
|
||||
|
||||
printf("this is the ref path: %s\n", ref_path);
|
||||
|
||||
int ref = open(ref_path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
||||
if (ref < 0) return -1;
|
||||
write(ref, commit_hash, 40);
|
||||
close(ref);
|
||||
|
||||
// Cleanup
|
||||
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 {
|
||||
usage(1);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue