Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,8 @@ add_executable(test_many_commits tests/test_many_commits.c)
target_link_libraries(test_many_commits bup_odb ${LIBGIT2_LIBRARIES})
add_test(NAME test_many_commits COMMAND test_many_commits)
set_tests_properties(test_many_commits PROPERTIES WORKING_DIRECTORY ${CMAKE_BINARY_DIR})

add_executable(test_repack_fsck tests/test_repack_fsck.c)
target_link_libraries(test_repack_fsck bup_odb ${LIBGIT2_LIBRARIES})
add_test(NAME test_repack_fsck COMMAND test_repack_fsck)
set_tests_properties(test_repack_fsck PROPERTIES WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
271 changes: 271 additions & 0 deletions src/git2.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <git2/pack.h>
#include <dirent.h>
#include <unistd.h>

static int cmd_hash_object(const char *file)
{
Expand Down Expand Up @@ -257,6 +260,260 @@ static int cmd_commit(const char *repo_path, const char *message)
return ret;
}

static int walk_tree(git_repository *repo, git_tree *tree)
{
size_t count = git_tree_entrycount(tree);
for (size_t i = 0; i < count; i++) {
const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
git_object *obj = NULL;
int ret = git_tree_entry_to_object(&obj, repo, entry);
if (ret < 0)
return ret;
if (git_object_type(obj) == GIT_OBJECT_TREE) {
ret = walk_tree(repo, (git_tree *)obj);
git_object_free(obj);
if (ret < 0)
return ret;
} else {
git_object_free(obj);
}
}
return 0;
}

static int cmd_fsck(const char *repo_path)
{
git_repository *repo = NULL;
int ret = git_repository_open(&repo, repo_path);
if (ret < 0)
return ret;

git_odb *odb = NULL;
git_repository_odb(&odb, repo);

git_revwalk *walk = NULL;
ret = git_revwalk_new(&walk, repo);
if (ret < 0)
goto out;
git_revwalk_push_head(walk);

git_oid oid;
while ((ret = git_revwalk_next(&oid, walk)) == 0) {
git_commit *commit = NULL;
if (git_commit_lookup(&commit, repo, &oid) < 0) {
ret = -1;
break;
}
git_tree *tree = NULL;
if (git_commit_tree(&tree, commit) < 0) {
git_commit_free(commit);
ret = -1;
break;
}
ret = walk_tree(repo, tree);
git_tree_free(tree);
git_commit_free(commit);
if (ret < 0)
break;
}

if (ret == GIT_ITEROVER)
ret = 0;

git_revwalk_free(walk);
out:
git_odb_free(odb);
git_repository_free(repo);
return ret;
}

typedef struct {
git_oid *oids;
size_t count;
size_t cap;
} oid_list;

static int oid_list_add(oid_list *list, const git_oid *oid)
{
for (size_t i = 0; i < list->count; i++)
if (git_oid_cmp(&list->oids[i], oid) == 0)
return 0;
if (list->count == list->cap) {
size_t new_cap = list->cap ? list->cap * 2 : 32;
git_oid *tmp = realloc(list->oids, new_cap * sizeof(git_oid));
if (!tmp)
return -1;
list->oids = tmp;
list->cap = new_cap;
}
git_oid_cpy(&list->oids[list->count++], oid);
return 0;
}

static int collect_tree_oids(git_repository *repo, git_tree *tree, oid_list *list)
{
size_t count = git_tree_entrycount(tree);
for (size_t i = 0; i < count; i++) {
const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
const git_oid *oid = git_tree_entry_id(entry);
if (oid_list_add(list, oid) < 0)
return -1;
if (git_tree_entry_type(entry) == GIT_OBJECT_TREE) {
git_object *obj = NULL;
if (git_tree_entry_to_object(&obj, repo, entry) < 0)
return -1;
int ret = collect_tree_oids(repo, (git_tree *)obj, list);
git_object_free(obj);
if (ret < 0)
return ret;
}
}
return 0;
}

static int collect_reachable_oids(git_repository *repo, oid_list *list)
{
git_revwalk *walk = NULL;
int ret = git_revwalk_new(&walk, repo);
if (ret < 0)
return ret;
git_revwalk_push_head(walk);

git_oid oid;
while ((ret = git_revwalk_next(&oid, walk)) == 0) {
if (oid_list_add(list, &oid) < 0)
break;
git_commit *commit = NULL;
if (git_commit_lookup(&commit, repo, &oid) < 0) {
ret = -1;
break;
}
git_tree *tree = NULL;
if (git_commit_tree(&tree, commit) < 0) {
git_commit_free(commit);
ret = -1;
break;
}
ret = collect_tree_oids(repo, tree, list);
git_tree_free(tree);
git_commit_free(commit);
if (ret < 0)
break;
}
git_revwalk_free(walk);
return ret == GIT_ITEROVER ? 0 : ret;
}

static void remove_loose_objects(const char *repo_path)
{
git_repository *repo = NULL;
if (git_repository_open(&repo, repo_path) < 0)
return;

git_odb *odb = NULL;
if (git_repository_odb(&odb, repo) < 0) {
git_repository_free(repo);
return;
}

oid_list keep = {0};
if (collect_reachable_oids(repo, &keep) < 0) {
git_odb_free(odb);
git_repository_free(repo);
free(keep.oids);
return;
}

char objdir[512];
snprintf(objdir, sizeof(objdir), "%s/.git/objects", repo_path);
DIR *d = opendir(objdir);
if (!d) {
git_odb_free(odb);
git_repository_free(repo);
free(keep.oids);
return;
}

struct dirent *ent;
char path[512];
char file[512];
char hex[41];
git_oid oid;

while ((ent = readdir(d))) {
if (strlen(ent->d_name) != 2)
continue;
snprintf(path, sizeof(path), "%s/%s", objdir, ent->d_name);
DIR *sd = opendir(path);
if (!sd)
continue;
struct dirent *ent2;
while ((ent2 = readdir(sd))) {
if (!strcmp(ent2->d_name, ".") || !strcmp(ent2->d_name, ".."))
continue;
snprintf(file, sizeof(file), "%s/%s", path, ent2->d_name);
snprintf(hex, sizeof(hex), "%s%s", ent->d_name, ent2->d_name);
if (git_oid_fromstr(&oid, hex) == 0) {
int keep_obj = 0;
for (size_t i = 0; i < keep.count; i++) {
if (git_oid_cmp(&keep.oids[i], &oid) == 0) {
keep_obj = 1;
break;
}
}
if (keep_obj)
unlink(file);
}
}
closedir(sd);
rmdir(path); /* ignore failure if not empty */
}

closedir(d);
free(keep.oids);
git_odb_free(odb);
git_repository_free(repo);
}

static int cmd_repack(const char *repo_path)
{
git_repository *repo = NULL;
int ret = git_repository_open(&repo, repo_path);
if (ret < 0)
return ret;

git_packbuilder *pb = NULL;
ret = git_packbuilder_new(&pb, repo);
if (ret < 0)
goto out_repo;

git_revwalk *walk = NULL;
ret = git_revwalk_new(&walk, repo);
if (ret < 0)
goto out_pb;
git_revwalk_push_head(walk);
ret = git_packbuilder_insert_walk(pb, walk);
git_revwalk_free(walk);
if (ret < 0)
goto out_pb;

ret = git_packbuilder_write(pb, NULL, 0, NULL, NULL);
git_packbuilder_free(pb);
pb = NULL;
git_repository_free(repo);
repo = NULL;
if (ret == 0)
remove_loose_objects(repo_path);

out_pb:
if (pb)
git_packbuilder_free(pb);
out_repo:
if (repo)
git_repository_free(repo);
return ret;
}

int main(int argc, char **argv)
{
git_libgit2_init();
Expand Down Expand Up @@ -320,6 +577,20 @@ int main(int argc, char **argv)
} else {
ret = cmd_show(repo_path, argv[arg]);
}
} else if (strcmp(cmd, "repack") == 0) {
if (!repo_path) {
fprintf(stderr, "repack requires -C <repo>\n");
ret = 1;
} else {
ret = cmd_repack(repo_path);
}
} else if (strcmp(cmd, "fsck") == 0) {
if (!repo_path) {
fprintf(stderr, "fsck requires -C <repo>\n");
ret = 1;
} else {
ret = cmd_fsck(repo_path);
}
} else {
fprintf(stderr, "Unknown command %s\n", cmd);
ret = 1;
Expand Down
Loading