#include "common.h"
#include <zlib.h>
#include "git2/repository.h"
#include "git2/indexer.h"
#include "git2/sys/odb_backend.h"
#include "delta.h"
#include "futils.h"
#include "hash.h"
#include "midx.h"
#include "mwindow.h"
#include "odb.h"
#include "pack.h"
#include "git2/odb_backend.h"
#define FRESHEN_FREQUENCY 2
struct pack_backend {
git_odb_backend parent;
git_midx_file *midx;
git_vector midx_packs;
git_vector packs;
struct git_pack_file *last_found;
char *pack_folder;
};
struct pack_writepack {
struct git_odb_writepack parent;
git_indexer *indexer;
};
static int packfile_sort__cb(const void *a_, const void *b_);
static int packfile_load__cb(void *_data, git_buf *path);
static int packfile_byname_search_cmp(const void *path, const void *pack_entry);
static int pack_entry_find(struct git_pack_entry *e,
struct pack_backend *backend, const git_oid *oid);
static int pack_entry_find_prefix(
struct git_pack_entry *e,
struct pack_backend *backend,
const git_oid *short_oid,
size_t len);
static int packfile_byname_search_cmp(const void *path_, const void *p_)
{
const git_buf *path = (const git_buf *)path_;
const struct git_pack_file *p = (const struct git_pack_file *)p_;
return strncmp(p->pack_name, git_buf_cstr(path), git_buf_len(path));
}
static int packfile_sort__cb(const void *a_, const void *b_)
{
const struct git_pack_file *a = a_;
const struct git_pack_file *b = b_;
int st;
st = a->pack_local - b->pack_local;
if (st)
return -st;
if (a->mtime < b->mtime)
return 1;
else if (a->mtime == b->mtime)
return 0;
return -1;
}
static int packfile_load__cb(void *data, git_buf *path)
{
struct pack_backend *backend = data;
struct git_pack_file *pack;
const char *path_str = git_buf_cstr(path);
git_buf index_prefix = GIT_BUF_INIT;
size_t cmp_len = git_buf_len(path);
int error;
if (cmp_len <= strlen(".idx") || git__suffixcmp(path_str, ".idx") != 0)
return 0;
cmp_len -= strlen(".idx");
git_buf_attach_notowned(&index_prefix, path_str, cmp_len);
if (git_vector_search2(NULL, &backend->midx_packs, packfile_byname_search_cmp, &index_prefix) == 0)
return 0;
if (git_vector_search2(NULL, &backend->packs, packfile_byname_search_cmp, &index_prefix) == 0)
return 0;
error = git_mwindow_get_pack(&pack, path->ptr);
if (error == GIT_ENOTFOUND) {
git_error_clear();
return 0;
}
if (!error)
error = git_vector_insert(&backend->packs, pack);
return error;
}
static int pack_entry_find(struct git_pack_entry *e, struct pack_backend *backend, const git_oid *oid)
{
struct git_pack_file *last_found = backend->last_found, *p;
git_midx_entry midx_entry;
size_t i;
if (backend->midx &&
git_midx_entry_find(&midx_entry, backend->midx, oid, GIT_OID_HEXSZ) == 0 &&
midx_entry.pack_index < git_vector_length(&backend->midx_packs)) {
e->offset = midx_entry.offset;
git_oid_cpy(&e->sha1, &midx_entry.sha1);
e->p = git_vector_get(&backend->midx_packs, midx_entry.pack_index);
return 0;
}
if (last_found &&
git_pack_entry_find(e, last_found, oid, GIT_OID_HEXSZ) == 0)
return 0;
git_vector_foreach(&backend->packs, i, p) {
if (p == last_found)
continue;
if (git_pack_entry_find(e, p, oid, GIT_OID_HEXSZ) == 0) {
backend->last_found = p;
return 0;
}
}
return git_odb__error_notfound(
"failed to find pack entry", oid, GIT_OID_HEXSZ);
}
static int pack_entry_find_prefix(
struct git_pack_entry *e,
struct pack_backend *backend,
const git_oid *short_oid,
size_t len)
{
int error;
size_t i;
git_oid found_full_oid = {{0}};
bool found = false;
struct git_pack_file *last_found = backend->last_found, *p;
git_midx_entry midx_entry;
if (backend->midx) {
error = git_midx_entry_find(&midx_entry, backend->midx, short_oid, len);
if (error == GIT_EAMBIGUOUS)
return error;
if (!error && midx_entry.pack_index < git_vector_length(&backend->midx_packs)) {
e->offset = midx_entry.offset;
git_oid_cpy(&e->sha1, &midx_entry.sha1);
e->p = git_vector_get(&backend->midx_packs, midx_entry.pack_index);
git_oid_cpy(&found_full_oid, &e->sha1);
found = true;
}
}
if (last_found) {
error = git_pack_entry_find(e, last_found, short_oid, len);
if (error == GIT_EAMBIGUOUS)
return error;
if (!error) {
if (found && git_oid_cmp(&e->sha1, &found_full_oid))
return git_odb__error_ambiguous("found multiple pack entries");
git_oid_cpy(&found_full_oid, &e->sha1);
found = true;
}
}
git_vector_foreach(&backend->packs, i, p) {
if (p == last_found)
continue;
error = git_pack_entry_find(e, p, short_oid, len);
if (error == GIT_EAMBIGUOUS)
return error;
if (!error) {
if (found && git_oid_cmp(&e->sha1, &found_full_oid))
return git_odb__error_ambiguous("found multiple pack entries");
git_oid_cpy(&found_full_oid, &e->sha1);
found = true;
backend->last_found = p;
}
}
if (!found)
return git_odb__error_notfound("no matching pack entry for prefix",
short_oid, len);
else
return 0;
}
static int remove_multi_pack_index(struct pack_backend *backend)
{
size_t i, j = git_vector_length(&backend->packs);
struct pack_backend *p;
int error = git_vector_size_hint(
&backend->packs,
j + git_vector_length(&backend->midx_packs));
if (error < 0)
return error;
git_vector_foreach(&backend->midx_packs, i, p)
git_vector_set(NULL, &backend->packs, j++, p);
git_vector_clear(&backend->midx_packs);
git_midx_free(backend->midx);
backend->midx = NULL;
return 0;
}
static int process_multi_pack_index_pack(
struct pack_backend *backend,
size_t i,
const char *packfile_name)
{
int error;
struct git_pack_file *pack;
size_t found_position;
git_buf pack_path = GIT_BUF_INIT, index_prefix = GIT_BUF_INIT;
error = git_buf_joinpath(&pack_path, backend->pack_folder, packfile_name);
if (error < 0)
return error;
if (git_buf_len(&pack_path) <= strlen(".idx") || git__suffixcmp(git_buf_cstr(&pack_path), ".idx") != 0)
return git_odb__error_notfound("midx file contained a non-index", NULL, 0);
git_buf_attach_notowned(&index_prefix, git_buf_cstr(&pack_path), git_buf_len(&pack_path) - strlen(".idx"));
if (git_vector_search2(&found_position, &backend->packs, packfile_byname_search_cmp, &index_prefix) == 0) {
git_buf_dispose(&pack_path);
git_vector_set(NULL, &backend->midx_packs, i, git_vector_get(&backend->packs, found_position));
git_vector_remove(&backend->packs, found_position);
return 0;
}
error = git_mwindow_get_pack(&pack, git_buf_cstr(&pack_path));
git_buf_dispose(&pack_path);
if (error < 0)
return error;
git_vector_set(NULL, &backend->midx_packs, i, pack);
return 0;
}
static int refresh_multi_pack_index(struct pack_backend *backend)
{
int error;
git_buf midx_path = GIT_BUF_INIT;
const char *packfile_name;
size_t i;
error = git_buf_joinpath(&midx_path, backend->pack_folder, "multi-pack-index");
if (error < 0)
return error;
if (backend->midx) {
if (!git_midx_needs_refresh(backend->midx, git_buf_cstr(&midx_path))) {
git_buf_dispose(&midx_path);
return 0;
}
error = remove_multi_pack_index(backend);
if (error < 0) {
git_buf_dispose(&midx_path);
return error;
}
}
error = git_midx_open(&backend->midx, git_buf_cstr(&midx_path));
git_buf_dispose(&midx_path);
if (error < 0)
return error;
git_vector_resize_to(&backend->midx_packs, git_vector_length(&backend->midx->packfile_names));
git_vector_foreach(&backend->midx->packfile_names, i, packfile_name) {
error = process_multi_pack_index_pack(backend, i, packfile_name);
if (error < 0) {
git_vector_resize_to(&backend->midx_packs, i);
remove_multi_pack_index(backend);
return error;
}
}
return 0;
}
static int pack_backend__refresh(git_odb_backend *backend_)
{
int error;
struct stat st;
git_buf path = GIT_BUF_INIT;
struct pack_backend *backend = (struct pack_backend *)backend_;
if (backend->pack_folder == NULL)
return 0;
if (p_stat(backend->pack_folder, &st) < 0 || !S_ISDIR(st.st_mode))
return git_odb__error_notfound("failed to refresh packfiles", NULL, 0);
if (refresh_multi_pack_index(backend) < 0) {
git_error_clear();
}
git_buf_sets(&path, backend->pack_folder);
error = git_path_direach(&path, 0, packfile_load__cb, backend);
git_buf_dispose(&path);
git_vector_sort(&backend->packs);
return error;
}
static int pack_backend__read_header(
size_t *len_p, git_object_t *type_p,
struct git_odb_backend *backend, const git_oid *oid)
{
struct git_pack_entry e;
int error;
GIT_ASSERT_ARG(len_p);
GIT_ASSERT_ARG(type_p);
GIT_ASSERT_ARG(backend);
GIT_ASSERT_ARG(oid);
if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0)
return error;
return git_packfile_resolve_header(len_p, type_p, e.p, e.offset);
}
static int pack_backend__freshen(
git_odb_backend *backend, const git_oid *oid)
{
struct git_pack_entry e;
time_t now;
int error;
if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0)
return error;
now = time(NULL);
if (e.p->last_freshen > now - FRESHEN_FREQUENCY)
return 0;
if ((error = git_futils_touch(e.p->pack_name, &now)) < 0)
return error;
e.p->last_freshen = now;
return 0;
}
static int pack_backend__read(
void **buffer_p, size_t *len_p, git_object_t *type_p,
git_odb_backend *backend, const git_oid *oid)
{
struct git_pack_entry e;
git_rawobj raw = {NULL};
int error;
if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0 ||
(error = git_packfile_unpack(&raw, e.p, &e.offset)) < 0)
return error;
*buffer_p = raw.data;
*len_p = raw.len;
*type_p = raw.type;
return 0;
}
static int pack_backend__read_prefix(
git_oid *out_oid,
void **buffer_p,
size_t *len_p,
git_object_t *type_p,
git_odb_backend *backend,
const git_oid *short_oid,
size_t len)
{
int error = 0;
if (len < GIT_OID_MINPREFIXLEN)
error = git_odb__error_ambiguous("prefix length too short");
else if (len >= GIT_OID_HEXSZ) {
error = pack_backend__read(buffer_p, len_p, type_p, backend, short_oid);
if (!error)
git_oid_cpy(out_oid, short_oid);
} else {
struct git_pack_entry e;
git_rawobj raw = {NULL};
if ((error = pack_entry_find_prefix(
&e, (struct pack_backend *)backend, short_oid, len)) == 0 &&
(error = git_packfile_unpack(&raw, e.p, &e.offset)) == 0)
{
*buffer_p = raw.data;
*len_p = raw.len;
*type_p = raw.type;
git_oid_cpy(out_oid, &e.sha1);
}
}
return error;
}
static int pack_backend__exists(git_odb_backend *backend, const git_oid *oid)
{
struct git_pack_entry e;
return pack_entry_find(&e, (struct pack_backend *)backend, oid) == 0;
}
static int pack_backend__exists_prefix(
git_oid *out, git_odb_backend *backend, const git_oid *short_id, size_t len)
{
int error;
struct pack_backend *pb = (struct pack_backend *)backend;
struct git_pack_entry e = {0};
error = pack_entry_find_prefix(&e, pb, short_id, len);
git_oid_cpy(out, &e.sha1);
return error;
}
static int pack_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb cb, void *data)
{
int error;
struct git_pack_file *p;
struct pack_backend *backend;
unsigned int i;
GIT_ASSERT_ARG(_backend);
GIT_ASSERT_ARG(cb);
backend = (struct pack_backend *)_backend;
if ((error = pack_backend__refresh(_backend)) != 0)
return error;
if (backend->midx && (error = git_midx_foreach_entry(backend->midx, cb, data)) != 0)
return error;
git_vector_foreach(&backend->packs, i, p) {
if ((error = git_pack_foreach_entry(p, cb, data)) != 0)
return error;
}
return 0;
}
static int pack_backend__writepack_append(struct git_odb_writepack *_writepack, const void *data, size_t size, git_indexer_progress *stats)
{
struct pack_writepack *writepack = (struct pack_writepack *)_writepack;
GIT_ASSERT_ARG(writepack);
return git_indexer_append(writepack->indexer, data, size, stats);
}
static int pack_backend__writepack_commit(struct git_odb_writepack *_writepack, git_indexer_progress *stats)
{
struct pack_writepack *writepack = (struct pack_writepack *)_writepack;
GIT_ASSERT_ARG(writepack);
return git_indexer_commit(writepack->indexer, stats);
}
static void pack_backend__writepack_free(struct git_odb_writepack *_writepack)
{
struct pack_writepack *writepack;
if (!_writepack)
return;
writepack = (struct pack_writepack *)_writepack;
git_indexer_free(writepack->indexer);
git__free(writepack);
}
static int pack_backend__writepack(struct git_odb_writepack **out,
git_odb_backend *_backend,
git_odb *odb,
git_indexer_progress_cb progress_cb,
void *progress_payload)
{
git_indexer_options opts = GIT_INDEXER_OPTIONS_INIT;
struct pack_backend *backend;
struct pack_writepack *writepack;
GIT_ASSERT_ARG(out);
GIT_ASSERT_ARG(_backend);
*out = NULL;
opts.progress_cb = progress_cb;
opts.progress_cb_payload = progress_payload;
backend = (struct pack_backend *)_backend;
writepack = git__calloc(1, sizeof(struct pack_writepack));
GIT_ERROR_CHECK_ALLOC(writepack);
if (git_indexer_new(&writepack->indexer,
backend->pack_folder, 0, odb, &opts) < 0) {
git__free(writepack);
return -1;
}
writepack->parent.backend = _backend;
writepack->parent.append = pack_backend__writepack_append;
writepack->parent.commit = pack_backend__writepack_commit;
writepack->parent.free = pack_backend__writepack_free;
*out = (git_odb_writepack *)writepack;
return 0;
}
static int get_idx_path(
git_buf *idx_path,
struct pack_backend *backend,
struct git_pack_file *p)
{
size_t path_len;
int error;
error = git_path_prettify(idx_path, p->pack_name, backend->pack_folder);
if (error < 0)
return error;
path_len = git_buf_len(idx_path);
if (path_len <= strlen(".pack") || git__suffixcmp(git_buf_cstr(idx_path), ".pack") != 0)
return git_odb__error_notfound("packfile does not end in .pack", NULL, 0);
path_len -= strlen(".pack");
error = git_buf_splice(idx_path, path_len, strlen(".pack"), ".idx", strlen(".idx"));
if (error < 0)
return error;
return 0;
}
static int pack_backend__writemidx(git_odb_backend *_backend)
{
struct pack_backend *backend;
git_midx_writer *w = NULL;
struct git_pack_file *p;
size_t i;
int error = 0;
GIT_ASSERT_ARG(_backend);
backend = (struct pack_backend *)_backend;
error = git_midx_writer_new(&w, backend->pack_folder);
if (error < 0)
return error;
git_vector_foreach(&backend->midx_packs, i, p) {
git_buf idx_path = GIT_BUF_INIT;
error = get_idx_path(&idx_path, backend, p);
if (error < 0)
goto cleanup;
error = git_midx_writer_add(w, git_buf_cstr(&idx_path));
git_buf_dispose(&idx_path);
if (error < 0)
goto cleanup;
}
git_vector_foreach(&backend->packs, i, p) {
git_buf idx_path = GIT_BUF_INIT;
error = get_idx_path(&idx_path, backend, p);
if (error < 0)
goto cleanup;
error = git_midx_writer_add(w, git_buf_cstr(&idx_path));
git_buf_dispose(&idx_path);
if (error < 0)
goto cleanup;
}
error = remove_multi_pack_index(backend);
if (error < 0)
goto cleanup;
error = git_midx_writer_commit(w);
if (error < 0)
goto cleanup;
error = refresh_multi_pack_index(backend);
cleanup:
git_midx_writer_free(w);
return error;
}
static void pack_backend__free(git_odb_backend *_backend)
{
struct pack_backend *backend;
struct git_pack_file *p;
size_t i;
if (!_backend)
return;
backend = (struct pack_backend *)_backend;
git_vector_foreach(&backend->midx_packs, i, p)
git_mwindow_put_pack(p);
git_vector_foreach(&backend->packs, i, p)
git_mwindow_put_pack(p);
git_midx_free(backend->midx);
git_vector_free(&backend->midx_packs);
git_vector_free(&backend->packs);
git__free(backend->pack_folder);
git__free(backend);
}
static int pack_backend__alloc(struct pack_backend **out, size_t initial_size)
{
struct pack_backend *backend = git__calloc(1, sizeof(struct pack_backend));
GIT_ERROR_CHECK_ALLOC(backend);
if (git_vector_init(&backend->midx_packs, 0, NULL) < 0) {
git__free(backend);
return -1;
}
if (git_vector_init(&backend->packs, initial_size, packfile_sort__cb) < 0) {
git_vector_free(&backend->midx_packs);
git__free(backend);
return -1;
}
backend->parent.version = GIT_ODB_BACKEND_VERSION;
backend->parent.read = &pack_backend__read;
backend->parent.read_prefix = &pack_backend__read_prefix;
backend->parent.read_header = &pack_backend__read_header;
backend->parent.exists = &pack_backend__exists;
backend->parent.exists_prefix = &pack_backend__exists_prefix;
backend->parent.refresh = &pack_backend__refresh;
backend->parent.foreach = &pack_backend__foreach;
backend->parent.writepack = &pack_backend__writepack;
backend->parent.writemidx = &pack_backend__writemidx;
backend->parent.freshen = &pack_backend__freshen;
backend->parent.free = &pack_backend__free;
*out = backend;
return 0;
}
int git_odb_backend_one_pack(git_odb_backend **backend_out, const char *idx)
{
struct pack_backend *backend = NULL;
struct git_pack_file *packfile = NULL;
if (pack_backend__alloc(&backend, 1) < 0)
return -1;
if (git_mwindow_get_pack(&packfile, idx) < 0 ||
git_vector_insert(&backend->packs, packfile) < 0)
{
pack_backend__free((git_odb_backend *)backend);
return -1;
}
*backend_out = (git_odb_backend *)backend;
return 0;
}
int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir)
{
int error = 0;
struct pack_backend *backend = NULL;
git_buf path = GIT_BUF_INIT;
if (pack_backend__alloc(&backend, 8) < 0)
return -1;
if (!(error = git_buf_joinpath(&path, objects_dir, "pack")) &&
git_path_isdir(git_buf_cstr(&path)))
{
backend->pack_folder = git_buf_detach(&path);
error = pack_backend__refresh((git_odb_backend *)backend);
}
if (error < 0) {
pack_backend__free((git_odb_backend *)backend);
backend = NULL;
}
*backend_out = (git_odb_backend *)backend;
git_buf_dispose(&path);
return error;
}