694 lines
16 KiB
C
694 lines
16 KiB
C
#include <assert.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <openssl/comp.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <zlib.h>
|
|
|
|
#include "fs.h"
|
|
#include "includes.h"
|
|
#include "tree.h"
|
|
#include "objects.h"
|
|
#include "utils.h"
|
|
#include "commit.h"
|
|
|
|
int local_repo_exist()
|
|
{
|
|
struct stat buffer;
|
|
return stat(LOCAL_REPO, &buffer) == 0;
|
|
}
|
|
|
|
int index_exist()
|
|
{
|
|
struct stat buffer;
|
|
return stat(INDEX_FILE, &buffer) == 0;
|
|
}
|
|
|
|
int heads_dir_exist()
|
|
{
|
|
struct stat buffer;
|
|
return stat(HEADS_DIR, &buffer) == 0;
|
|
}
|
|
|
|
int head_file_exist(size_t *head_size)
|
|
{
|
|
struct stat buffer;
|
|
int result = stat(HEAD_FILE, &buffer) == 0;
|
|
if(head_size != NULL)
|
|
*head_size = buffer.st_size;
|
|
return result;
|
|
}
|
|
|
|
int init_repo()
|
|
{
|
|
if(local_repo_exist())
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
mkdir(LOCAL_REPO, DEFAULT_DIR_MODE);
|
|
|
|
struct stat buffer = {0};
|
|
if (stat(OBJECTS_DIR, &buffer) != 0)
|
|
mkdir(OBJECTS_DIR, DEFAULT_DIR_MODE);
|
|
|
|
if (!index_exist())
|
|
{
|
|
int fd = open(INDEX_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
|
FILE *index = fdopen(fd, "w");
|
|
|
|
fprintf(index, "0\n");
|
|
fclose(index);
|
|
}
|
|
|
|
if (!head_file_exist(NULL))
|
|
{
|
|
open(HEAD_FILE, O_CREAT, 0644);
|
|
}
|
|
|
|
if (stat(REFS_DIR, &buffer) != 0)
|
|
{
|
|
mkdir(REFS_DIR, DEFAULT_DIR_MODE);
|
|
}
|
|
|
|
if (!heads_dir_exist())
|
|
{
|
|
mkdir(HEADS_DIR, DEFAULT_DIR_MODE);
|
|
}
|
|
}
|
|
|
|
int blob_from_file(char *filename, struct object *object)
|
|
{
|
|
FILE* file = fopen(filename, "r");
|
|
if (file == NULL) {
|
|
error_print("File %s not found", filename);
|
|
return FILE_NOT_FOUND;
|
|
}
|
|
|
|
struct stat file_info;
|
|
if (stat(filename, &file_info) != 0)
|
|
return -1;
|
|
|
|
object->object_type = str_to_object_type("blob");
|
|
object->size = file_info.st_size;
|
|
object->content = realloc(object->content, object->size);
|
|
fread(object->content, 1, object->size, file);
|
|
fclose(file);
|
|
|
|
return FS_OK;
|
|
}
|
|
|
|
int write_object(struct object *obj)
|
|
{
|
|
if(!local_repo_exist())
|
|
{
|
|
return REPO_NOT_INITIALIZED;
|
|
}
|
|
int result = FS_OK;
|
|
|
|
struct stat buffer;
|
|
if (stat(OBJECTS_DIR, &buffer) != 0)
|
|
{
|
|
mkdir(OBJECTS_DIR, DEFAULT_DIR_MODE);
|
|
}
|
|
|
|
DIR *objects_dir = opendir(OBJECTS_DIR);
|
|
int objects_dir_fd = dirfd(objects_dir);
|
|
|
|
char checksum[DIGEST_LENGTH * 2];
|
|
hash_object(obj, checksum);
|
|
|
|
int save_file_fd = openat(objects_dir_fd, checksum, O_CREAT | O_WRONLY | O_TRUNC, DEFAULT_FILE_MODE);
|
|
if(save_file_fd == -1) {
|
|
if (errno == EACCES)
|
|
{
|
|
debug_print("Object %s already exists", checksum);
|
|
defer(OBJECT_ALREADY_EXIST);
|
|
}
|
|
defer(FS_ERROR);
|
|
}
|
|
FILE* save_file = fdopen(save_file_fd, "w");
|
|
|
|
size_t data_size = object_size(obj);
|
|
uLong comp_size = compressBound(data_size);
|
|
char *compressed = malloc(comp_size);
|
|
|
|
int res = compress_object(obj, compressed, &comp_size);
|
|
if (res != Z_OK)
|
|
{
|
|
defer(COMPRESSION_ERROR);
|
|
}
|
|
fwrite(compressed, comp_size, 1, save_file);
|
|
free(compressed);
|
|
fclose(save_file);
|
|
|
|
defer:
|
|
closedir(objects_dir);
|
|
return result;
|
|
}
|
|
|
|
int read_object(char *checksum, struct object *obj)
|
|
{
|
|
if(!local_repo_exist())
|
|
{
|
|
return REPO_NOT_INITIALIZED;
|
|
}
|
|
int result = FS_OK;
|
|
|
|
struct stat buffer;
|
|
if (stat(OBJECTS_DIR, &buffer) != 0)
|
|
{
|
|
error_print("Object dir does not exist");
|
|
return OBJECT_DOES_NOT_EXIST;
|
|
}
|
|
|
|
DIR *objects_dir = opendir(OBJECTS_DIR);
|
|
int objects_dir_fd = dirfd(objects_dir);
|
|
|
|
fstatat(objects_dir_fd, checksum, &buffer, 0);
|
|
char file_content[buffer.st_size];
|
|
int save_file_fd = openat(objects_dir_fd, checksum, O_RDONLY, DEFAULT_FILE_MODE);
|
|
if (save_file_fd == -1)
|
|
{
|
|
if(errno == EACCES)
|
|
{
|
|
error_print("Object %s does not exist", checksum);
|
|
defer(OBJECT_DOES_NOT_EXIST);
|
|
}
|
|
error_print("Cannot open file %s", checksum);
|
|
defer(FS_ERROR);
|
|
}
|
|
|
|
FILE *save_file = fdopen(save_file_fd, "r");
|
|
fread(file_content, 1, buffer.st_size, save_file);
|
|
fclose(save_file);
|
|
|
|
result = uncompress_object(obj, file_content, buffer.st_size);
|
|
|
|
defer:
|
|
closedir(objects_dir);
|
|
return result;
|
|
}
|
|
|
|
int remove_object(char *checksum)
|
|
{
|
|
if(!local_repo_exist())
|
|
{
|
|
return REPO_NOT_INITIALIZED;
|
|
}
|
|
int result = FS_OK;
|
|
|
|
char path[strlen(OBJECTS_DIR) + strlen(checksum) + 2];
|
|
sprintf(path, "%s/%s", OBJECTS_DIR, checksum);
|
|
|
|
remove(path);
|
|
}
|
|
|
|
int create_dir(char *dir)
|
|
{
|
|
struct stat buffer = {0};
|
|
int result = FS_OK;
|
|
|
|
if(stat(dir, &buffer) == 0)
|
|
{
|
|
if (!S_ISREG(buffer.st_mode))
|
|
{
|
|
result = remove(dir);
|
|
if (result != 0)
|
|
return FS_ERROR;
|
|
}
|
|
} else {
|
|
result = mkdir(dir, DEFAULT_DIR_MODE);
|
|
if (result != 0)
|
|
return FS_ERROR;
|
|
}
|
|
}
|
|
|
|
void remove_dir(char *dir)
|
|
{
|
|
char cmd[strlen(dir) + strlen("rm -rf ") + 1];
|
|
sprintf(cmd, "rm -rf %s", dir);
|
|
|
|
FILE *p = popen(cmd, "r");
|
|
pclose(p);
|
|
}
|
|
|
|
int init_tmp_diff_dir(char* dir)
|
|
{
|
|
int dirlen = 0;
|
|
if (*dir != '\0')
|
|
dirlen = strlen(dir);
|
|
char path_a[dirlen + 8], path_b[dirlen + 8];
|
|
sprintf(path_a, "/tmp/a/%s", dir);
|
|
sprintf(path_b, "/tmp/b/%s", dir);
|
|
|
|
if (create_dir(path_a) != FS_OK)
|
|
return FS_ERROR;
|
|
if (create_dir(path_b) != FS_OK)
|
|
return FS_ERROR;
|
|
|
|
|
|
return FS_OK;
|
|
}
|
|
|
|
int tmp_dump(struct object *obj, char* filename)
|
|
{
|
|
if (obj == NULL)
|
|
{
|
|
open(filename, O_CREAT, 0644);
|
|
return FS_OK;
|
|
}
|
|
|
|
FILE *file = fopen(filename, "w");
|
|
fwrite(obj->content, obj->size, 1, file);
|
|
fclose(file);
|
|
|
|
return FS_OK;
|
|
}
|
|
|
|
int dump_tree(char *cwd, struct tree *tree)
|
|
{
|
|
struct entry *current = tree->first_entry;
|
|
while(current != NULL)
|
|
{
|
|
size_t cwd_len = 0;
|
|
if (*cwd != '\0')
|
|
cwd_len = strlen(cwd);
|
|
size_t filename_size = cwd_len + 2 + strlen(current->filename);
|
|
char filename[filename_size];
|
|
sprintf(filename, "%s/%s", cwd, current->filename);
|
|
|
|
struct object obj = {0};
|
|
read_object(current->checksum, &obj);
|
|
|
|
if(current->type == BLOB)
|
|
{
|
|
tmp_dump(&obj, filename);
|
|
} else if (current->type == TREE) {
|
|
struct tree subtree = {0};
|
|
get_tree(obj.content, &subtree);
|
|
|
|
create_dir(filename);
|
|
dump_tree(filename, &subtree);
|
|
|
|
free_tree(&subtree);
|
|
}
|
|
|
|
free_object(&obj);
|
|
current = current->next;
|
|
}
|
|
|
|
return FS_OK;
|
|
}
|
|
|
|
int load_tree(char* checksum, struct tree *tree)
|
|
{
|
|
struct object object;
|
|
int res = read_object(checksum, &object);
|
|
if (res != FS_OK)
|
|
return res;
|
|
|
|
if (object.object_type != TREE)
|
|
{
|
|
error_print("Object %s is not a tree", checksum);
|
|
return WRONG_OBJECT_TYPE;
|
|
}
|
|
|
|
get_tree(object.content, tree);
|
|
free_object(&object);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int load_index(struct tree *index)
|
|
{
|
|
if(!local_repo_exist() || !index_exist())
|
|
{
|
|
return REPO_NOT_INITIALIZED;
|
|
}
|
|
|
|
FILE* index_file = fopen(INDEX_FILE, "r");
|
|
struct stat buffer;
|
|
stat(INDEX_FILE, &buffer);
|
|
|
|
char* file_content = calloc(buffer.st_size + 1, sizeof(char));
|
|
fread(file_content, buffer.st_size, 1, index_file);
|
|
fclose(index_file);
|
|
|
|
get_tree(file_content, index);
|
|
|
|
free(file_content);
|
|
|
|
return FS_OK;
|
|
}
|
|
|
|
int save_index(struct tree *tree)
|
|
{
|
|
if(!local_repo_exist())
|
|
{
|
|
return REPO_NOT_INITIALIZED;
|
|
}
|
|
|
|
FILE *index_file = fopen(INDEX_FILE, "w");
|
|
if(index_exist == NULL)
|
|
{
|
|
return FS_ERROR;
|
|
}
|
|
|
|
struct object object;
|
|
tree_to_object(tree, &object);
|
|
|
|
fwrite(object.content, object.size, 1, index_file);
|
|
fclose(index_file);
|
|
free_object(&object);
|
|
}
|
|
|
|
int get_head_commit_checksum(char* checksum)
|
|
{
|
|
size_t head_size = 0;
|
|
if(!local_repo_exist() || !head_file_exist(&head_size) || !heads_dir_exist)
|
|
{
|
|
return REPO_NOT_INITIALIZED;
|
|
}
|
|
|
|
if(head_size == 0) return NO_CURRENT_HEAD;
|
|
|
|
FILE *head_file = NULL;
|
|
head_file = fopen(HEAD_FILE, "r");
|
|
char head_path[head_size + 1];
|
|
memset(head_path, 0, head_size + 1);
|
|
fread(head_path, head_size, 1, head_file);
|
|
fclose(head_file);
|
|
|
|
struct stat buffer = {0};
|
|
if (stat(head_path, &buffer) != 0) return NO_CURRENT_HEAD;
|
|
|
|
memset(checksum, 0, buffer.st_size + 1);
|
|
head_file = fopen(head_path, "r");
|
|
fread(checksum, buffer.st_size, 1, head_file);
|
|
fclose(head_file);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int get_last_commit(struct object *commit)
|
|
{
|
|
char commit_checksum[DIGEST_LENGTH * 2 + 1];
|
|
if (get_head_commit_checksum(commit_checksum) == NO_CURRENT_HEAD)
|
|
return FS_OK;
|
|
|
|
int res = read_object(commit_checksum, commit);
|
|
if (res != 0) return FS_ERROR;
|
|
|
|
if (commit->object_type != COMMIT) return WRONG_OBJECT_TYPE;
|
|
|
|
return FS_OK;
|
|
}
|
|
|
|
int update_current_branch_head(char *new_head)
|
|
{
|
|
size_t head_size = 0;
|
|
if(!local_repo_exist() || !head_file_exist(&head_size) || !heads_dir_exist)
|
|
{
|
|
return REPO_NOT_INITIALIZED;
|
|
}
|
|
|
|
FILE *file;
|
|
|
|
if(head_size != 0) {
|
|
FILE *head_file = fopen(HEAD_FILE, "r");
|
|
char branch[head_size];
|
|
fread(branch, head_size, 1, head_file);
|
|
fclose(head_file);
|
|
file = fopen(branch, "w");
|
|
} else
|
|
{
|
|
FILE *head_file = fopen(HEAD_FILE, "w");
|
|
fprintf(head_file, "%s/master", HEADS_DIR);
|
|
fclose(head_file);
|
|
file = fopen(HEADS_DIR"/master", "w");
|
|
}
|
|
|
|
fwrite(new_head, strlen(new_head), 1, file);
|
|
fclose(file);
|
|
return 0;
|
|
}
|
|
|
|
int branch_exist(char *branch)
|
|
{
|
|
if(!local_repo_exist() || !heads_dir_exist())
|
|
return REPO_NOT_INITIALIZED;
|
|
|
|
DIR *dir = opendir(HEADS_DIR);
|
|
struct dirent *ent;
|
|
if (dir != NULL)
|
|
{
|
|
while((ent = readdir(dir)) != NULL)
|
|
{
|
|
if (strcmp(ent->d_name, branch) == 0)
|
|
{
|
|
closedir(dir);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
closedir(dir);
|
|
} else
|
|
return FS_ERROR;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int new_branch(char* branch_name)
|
|
{
|
|
if(branch_exist(branch_name))
|
|
return BRANCH_ALREADY_EXIST;
|
|
|
|
char path[strlen(HEADS_DIR) + strlen(branch_name) + 2];
|
|
sprintf(path, "%s/%s", HEADS_DIR, branch_name);
|
|
|
|
char old_head[DIGEST_LENGTH * 2 + 1];
|
|
get_head_commit_checksum(old_head);
|
|
|
|
FILE *branch_head = fopen(path, "w");
|
|
fprintf(branch_head, "%s", old_head);
|
|
fclose(branch_head);
|
|
|
|
FILE *head_file = fopen(HEAD_FILE, "w");
|
|
fprintf(head_file, "%s", path);
|
|
fclose(head_file);
|
|
|
|
return FS_OK;
|
|
}
|
|
|
|
int reset_to(char* commit_checksum)
|
|
{
|
|
int res = diff_commit_with_working_tree(commit_checksum, 0);
|
|
if(res != FS_OK)
|
|
return res;
|
|
|
|
FILE *p = popen("patch -p0 -R < "LOCAL_REPO"/last.diff > /dev/null", "w");
|
|
pclose(p);
|
|
|
|
return FS_OK;
|
|
}
|
|
|
|
int checkout_branch(char *branch)
|
|
{
|
|
if(!branch_exist(branch))
|
|
{
|
|
return BRANCH_DOES_NOT_EXIST;
|
|
}
|
|
|
|
char branch_path[strlen(HEADS_DIR) + strlen(branch) + 2];
|
|
sprintf(branch_path, "%s/%s", HEADS_DIR, branch);
|
|
|
|
char commit_checksum[DIGEST_LENGTH * 2 + 1] = {0};
|
|
FILE *branch_head = fopen(branch_path, "r");
|
|
fread(commit_checksum, DIGEST_LENGTH * 2, 1, branch_head);
|
|
fclose(branch_head);
|
|
|
|
debug_print("Checking out on %s", commit_checksum);
|
|
reset_to(commit_checksum);
|
|
|
|
FILE *head_file = fopen(HEAD_FILE, "w");
|
|
fprintf(head_file, "%s", branch_path);
|
|
fclose(head_file);
|
|
}
|
|
|
|
int is_file_ignored(char *filename)
|
|
{
|
|
struct stat buffer = {0};
|
|
if (stat(IGNORE_FILE, &buffer) != 0)
|
|
{
|
|
return 0;
|
|
}
|
|
char content[buffer.st_size + 1];
|
|
memset(content, 0, buffer.st_size + 1);
|
|
FILE *ignore_file = fopen(IGNORE_FILE, "r");
|
|
fread(content, buffer.st_size, 1, ignore_file);
|
|
fclose(ignore_file);
|
|
|
|
char *current_line = strtok(content, "\n");
|
|
|
|
while(current_line != NULL)
|
|
{
|
|
if(strncmp(current_line, filename, strlen(current_line)) == 0)
|
|
{
|
|
return 1;
|
|
}
|
|
char alt[strlen(current_line) + 3];
|
|
sprintf(alt, "./%s", current_line);
|
|
if(strncmp(alt, filename, strlen(alt)) == 0)
|
|
return 1;
|
|
current_line = strtok(NULL, "\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int add_file_to_index(struct tree *index, char *filename)
|
|
{
|
|
if (is_file_ignored(filename)) {
|
|
return 0;
|
|
}
|
|
struct stat st = {0};
|
|
if (stat(filename, &st) != 0)
|
|
{
|
|
return FILE_NOT_FOUND;
|
|
}
|
|
if (!S_ISREG(st.st_mode))
|
|
{
|
|
DIR *dp;
|
|
struct dirent *ep;
|
|
dp = opendir(filename);
|
|
if (dp != NULL)
|
|
{
|
|
while ((ep = readdir(dp)) != NULL)
|
|
{
|
|
if (strcmp(ep->d_name, "..") != 0 && strcmp(ep->d_name, ".") != 0)
|
|
{
|
|
char path[strlen(ep->d_name) + strlen(filename) + 2];
|
|
if (strcmp(filename, "./") == 0)
|
|
sprintf(path, "%s", ep->d_name);
|
|
else if (strcmp(filename, ".") == 0)
|
|
sprintf(path, "%s", ep->d_name);
|
|
else if(filename[strlen(filename) - 1] == '/')
|
|
sprintf(path, "%s%s", filename, ep->d_name);
|
|
else
|
|
sprintf(path, "%s/%s", filename, ep->d_name);
|
|
add_file_to_index(index, path);
|
|
}
|
|
}
|
|
|
|
closedir(dp);
|
|
return 0;
|
|
}
|
|
} else {
|
|
if (add_to_index(index, filename) == FILE_NOT_FOUND)
|
|
return FILE_NOT_FOUND;
|
|
}
|
|
}
|
|
|
|
int remove_file_from_index(struct tree *index, char *filename)
|
|
{
|
|
if (is_file_ignored(filename))
|
|
return 0;
|
|
|
|
struct stat st = {0};
|
|
if (stat(filename, &st) != 0)
|
|
return FILE_NOT_FOUND;
|
|
if (!S_ISREG(st.st_mode))
|
|
{
|
|
DIR *dp;
|
|
struct dirent *ep;
|
|
dp = opendir(filename);
|
|
if (dp != NULL)
|
|
{
|
|
while ((ep = readdir(dp)) != NULL)
|
|
{
|
|
if (strcmp(ep->d_name, "..") != 0 && strcmp(ep->d_name, ".") != 0)
|
|
{
|
|
char path[strlen(ep->d_name) + strlen(filename) + 2];
|
|
if (strcmp(filename, "./") == 0)
|
|
sprintf(path, "%s", ep->d_name);
|
|
else if(filename[strlen(filename) - 1] == '/')
|
|
sprintf(path, "%s%s", filename, ep->d_name);
|
|
else
|
|
sprintf(path, "%s/%s", filename, ep->d_name);
|
|
remove_file_from_index(index, path);
|
|
}
|
|
}
|
|
|
|
closedir(dp);
|
|
return 0;
|
|
}
|
|
} else {
|
|
remove_from_index(index, filename, 1);
|
|
}
|
|
}
|
|
|
|
int dump_log()
|
|
{
|
|
struct object current_obj = {0};
|
|
get_last_commit(¤t_obj);
|
|
|
|
if (current_obj.size == 0)
|
|
return 0;
|
|
|
|
struct commit current = {0};
|
|
commit_from_object(¤t, ¤t_obj);
|
|
|
|
FILE *log_file = fopen(LOG_FILE, "w");
|
|
char checksum[DIGEST_LENGTH * 2 + 1];
|
|
hash_object(¤t_obj, checksum);
|
|
fprintf(log_file, "commit %s HEAD\n", checksum);
|
|
fprintf(log_file, "Author: %s\n", current.author);
|
|
fprintf(log_file, "Tree: %s\n", current.tree);
|
|
fprintf(log_file, "\n");
|
|
|
|
while (strcmp(current.parent, " ") != 0)
|
|
{
|
|
free_object(¤t_obj);
|
|
read_object(current.parent, ¤t_obj);
|
|
free_commit(¤t);
|
|
commit_from_object(¤t, ¤t_obj);
|
|
|
|
checksum[DIGEST_LENGTH * 2 + 1];
|
|
hash_object(¤t_obj, checksum);
|
|
fprintf(log_file, "commit %s\n", checksum);
|
|
fprintf(log_file, "Author: %s\n", current.author);
|
|
fprintf(log_file, "Tree: %s\n", current.tree);
|
|
fprintf(log_file, "\n");
|
|
}
|
|
fclose(log_file);
|
|
return 0;
|
|
}
|
|
|
|
int dump_branches()
|
|
{
|
|
if(!heads_dir_exist())
|
|
{
|
|
return REPO_NOT_INITIALIZED;
|
|
}
|
|
|
|
DIR *heads_dir = opendir(HEADS_DIR);
|
|
struct dirent *ep;
|
|
FILE *dump = fopen(TMP"/branches", "w");
|
|
if (heads_dir != NULL)
|
|
{
|
|
while ((ep = readdir(heads_dir)) != NULL)
|
|
{
|
|
if (strcmp(ep->d_name, "..") != 0 && strcmp(ep->d_name, ".") != 0)
|
|
fprintf(dump, "%s\n", ep->d_name);
|
|
}
|
|
fclose(dump);
|
|
closedir(heads_dir);
|
|
return 0;
|
|
} else
|
|
return FS_ERROR;
|
|
} |