libgit2/src/odb_loose.c
Russell Belfer 5dca201072 Update iterators for consistency across library
This updates all the `foreach()` type functions across the library
that take callbacks from the user to have a consistent behavior.
The rules are:

* A callback terminates the loop by returning any non-zero value
* Once the callback returns non-zero, it will not be called again
  (i.e. the loop stops all iteration regardless of state)
* If the callback returns non-zero, the parent fn returns GIT_EUSER
* Although the parent returns GIT_EUSER, no error will be set in
  the library and `giterr_last()` will return NULL if called.

This commit makes those changes across the library and adds tests
for most of the iteration APIs to make sure that they follow the
above rules.
2012-08-03 17:08:01 -07:00

939 lines
22 KiB
C

/*
* Copyright (C) 2009-2012 the libgit2 contributors
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
#include <zlib.h>
#include "git2/object.h"
#include "git2/oid.h"
#include "fileops.h"
#include "hash.h"
#include "odb.h"
#include "delta-apply.h"
#include "filebuf.h"
#include "git2/odb_backend.h"
#include "git2/types.h"
typedef struct { /* object header data */
git_otype type; /* object type */
size_t size; /* object size */
} obj_hdr;
typedef struct {
git_odb_stream stream;
git_filebuf fbuf;
} loose_writestream;
typedef struct loose_backend {
git_odb_backend parent;
int object_zlib_level; /** loose object zlib compression level. */
int fsync_object_files; /** loose object file fsync flag. */
char *objects_dir;
} loose_backend;
/* State structure for exploring directories,
* in order to locate objects matching a short oid.
*/
typedef struct {
size_t dir_len;
unsigned char short_oid[GIT_OID_HEXSZ]; /* hex formatted oid to match */
unsigned int short_oid_len;
int found; /* number of matching
* objects already found */
unsigned char res_oid[GIT_OID_HEXSZ]; /* hex formatted oid of
* the object found */
} loose_locate_object_state;
/***********************************************************
*
* MISCELANEOUS HELPER FUNCTIONS
*
***********************************************************/
static int object_file_name(git_buf *name, const char *dir, const git_oid *id)
{
git_buf_sets(name, dir);
/* expand length for 40 hex sha1 chars + 2 * '/' + '\0' */
if (git_buf_grow(name, git_buf_len(name) + GIT_OID_HEXSZ + 3) < 0)
return -1;
git_path_to_dir(name);
/* loose object filename: aa/aaa... (41 bytes) */
git_oid_pathfmt(name->ptr + git_buf_len(name), id);
name->size += GIT_OID_HEXSZ + 1;
name->ptr[name->size] = '\0';
return 0;
}
static size_t get_binary_object_header(obj_hdr *hdr, git_buf *obj)
{
unsigned char c;
unsigned char *data = (unsigned char *)obj->ptr;
size_t shift, size, used = 0;
if (git_buf_len(obj) == 0)
return 0;
c = data[used++];
hdr->type = (c >> 4) & 7;
size = c & 15;
shift = 4;
while (c & 0x80) {
if (git_buf_len(obj) <= used)
return 0;
if (sizeof(size_t) * 8 <= shift)
return 0;
c = data[used++];
size += (c & 0x7f) << shift;
shift += 7;
}
hdr->size = size;
return used;
}
static size_t get_object_header(obj_hdr *hdr, unsigned char *data)
{
char c, typename[10];
size_t size, used = 0;
/*
* type name string followed by space.
*/
while ((c = data[used]) != ' ') {
typename[used++] = c;
if (used >= sizeof(typename))
return 0;
}
typename[used] = 0;
if (used == 0)
return 0;
hdr->type = git_object_string2type(typename);
used++; /* consume the space */
/*
* length follows immediately in decimal (without
* leading zeros).
*/
size = data[used++] - '0';
if (size > 9)
return 0;
if (size) {
while ((c = data[used]) != '\0') {
size_t d = c - '0';
if (d > 9)
break;
used++;
size = size * 10 + d;
}
}
hdr->size = size;
/*
* the length must be followed by a zero byte
*/
if (data[used++] != '\0')
return 0;
return used;
}
/***********************************************************
*
* ZLIB RELATED FUNCTIONS
*
***********************************************************/
static void init_stream(z_stream *s, void *out, size_t len)
{
memset(s, 0, sizeof(*s));
s->next_out = out;
s->avail_out = (uInt)len;
}
static void set_stream_input(z_stream *s, void *in, size_t len)
{
s->next_in = in;
s->avail_in = (uInt)len;
}
static void set_stream_output(z_stream *s, void *out, size_t len)
{
s->next_out = out;
s->avail_out = (uInt)len;
}
static int start_inflate(z_stream *s, git_buf *obj, void *out, size_t len)
{
int status;
init_stream(s, out, len);
set_stream_input(s, obj->ptr, git_buf_len(obj));
if ((status = inflateInit(s)) < Z_OK)
return status;
return inflate(s, 0);
}
static int finish_inflate(z_stream *s)
{
int status = Z_OK;
while (status == Z_OK)
status = inflate(s, Z_FINISH);
inflateEnd(s);
if ((status != Z_STREAM_END) || (s->avail_in != 0)) {
giterr_set(GITERR_ZLIB, "Failed to finish ZLib inflation. Stream aborted prematurely");
return -1;
}
return 0;
}
static int is_zlib_compressed_data(unsigned char *data)
{
unsigned int w;
w = ((unsigned int)(data[0]) << 8) + data[1];
return (data[0] & 0x8F) == 0x08 && !(w % 31);
}
static int inflate_buffer(void *in, size_t inlen, void *out, size_t outlen)
{
z_stream zs;
int status = Z_OK;
memset(&zs, 0x0, sizeof(zs));
zs.next_out = out;
zs.avail_out = (uInt)outlen;
zs.next_in = in;
zs.avail_in = (uInt)inlen;
if (inflateInit(&zs) < Z_OK) {
giterr_set(GITERR_ZLIB, "Failed to inflate buffer");
return -1;
}
while (status == Z_OK)
status = inflate(&zs, Z_FINISH);
inflateEnd(&zs);
if (status != Z_STREAM_END /* || zs.avail_in != 0 */ ||
zs.total_out != outlen)
{
giterr_set(GITERR_ZLIB, "Failed to inflate buffer. Stream aborted prematurely");
return -1;
}
return 0;
}
static void *inflate_tail(z_stream *s, void *hb, size_t used, obj_hdr *hdr)
{
unsigned char *buf, *head = hb;
size_t tail;
/*
* allocate a buffer to hold the inflated data and copy the
* initial sequence of inflated data from the tail of the
* head buffer, if any.
*/
if ((buf = git__malloc(hdr->size + 1)) == NULL) {
inflateEnd(s);
return NULL;
}
tail = s->total_out - used;
if (used > 0 && tail > 0) {
if (tail > hdr->size)
tail = hdr->size;
memcpy(buf, head + used, tail);
}
used = tail;
/*
* inflate the remainder of the object data, if any
*/
if (hdr->size < used)
inflateEnd(s);
else {
set_stream_output(s, buf + used, hdr->size - used);
if (finish_inflate(s)) {
git__free(buf);
return NULL;
}
}
return buf;
}
/*
* At one point, there was a loose object format that was intended to
* mimic the format used in pack-files. This was to allow easy copying
* of loose object data into packs. This format is no longer used, but
* we must still read it.
*/
static int inflate_packlike_loose_disk_obj(git_rawobj *out, git_buf *obj)
{
unsigned char *in, *buf;
obj_hdr hdr;
size_t len, used;
/*
* read the object header, which is an (uncompressed)
* binary encoding of the object type and size.
*/
if ((used = get_binary_object_header(&hdr, obj)) == 0 ||
!git_object_typeisloose(hdr.type)) {
giterr_set(GITERR_ODB, "Failed to inflate loose object.");
return -1;
}
/*
* allocate a buffer and inflate the data into it
*/
buf = git__malloc(hdr.size + 1);
GITERR_CHECK_ALLOC(buf);
in = ((unsigned char *)obj->ptr) + used;
len = obj->size - used;
if (inflate_buffer(in, len, buf, hdr.size) < 0) {
git__free(buf);
return -1;
}
buf[hdr.size] = '\0';
out->data = buf;
out->len = hdr.size;
out->type = hdr.type;
return 0;
}
static int inflate_disk_obj(git_rawobj *out, git_buf *obj)
{
unsigned char head[64], *buf;
z_stream zs;
obj_hdr hdr;
size_t used;
/*
* check for a pack-like loose object
*/
if (!is_zlib_compressed_data((unsigned char *)obj->ptr))
return inflate_packlike_loose_disk_obj(out, obj);
/*
* inflate the initial part of the io buffer in order
* to parse the object header (type and size).
*/
if (start_inflate(&zs, obj, head, sizeof(head)) < Z_OK ||
(used = get_object_header(&hdr, head)) == 0 ||
!git_object_typeisloose(hdr.type))
{
giterr_set(GITERR_ODB, "Failed to inflate disk object.");
return -1;
}
/*
* allocate a buffer and inflate the object data into it
* (including the initial sequence in the head buffer).
*/
if ((buf = inflate_tail(&zs, head, used, &hdr)) == NULL)
return -1;
buf[hdr.size] = '\0';
out->data = buf;
out->len = hdr.size;
out->type = hdr.type;
return 0;
}
/***********************************************************
*
* ODB OBJECT READING & WRITING
*
* Backend for the public API; read headers and full objects
* from the ODB. Write raw data to the ODB.
*
***********************************************************/
static int read_loose(git_rawobj *out, git_buf *loc)
{
int error;
git_buf obj = GIT_BUF_INIT;
assert(out && loc);
if (git_buf_oom(loc))
return -1;
out->data = NULL;
out->len = 0;
out->type = GIT_OBJ_BAD;
if (!(error = git_futils_readbuffer(&obj, loc->ptr)))
error = inflate_disk_obj(out, &obj);
git_buf_free(&obj);
return error;
}
static int read_header_loose(git_rawobj *out, git_buf *loc)
{
int error = 0, z_return = Z_ERRNO, read_bytes;
git_file fd;
z_stream zs;
obj_hdr header_obj;
unsigned char raw_buffer[16], inflated_buffer[64];
assert(out && loc);
if (git_buf_oom(loc))
return -1;
out->data = NULL;
if ((fd = git_futils_open_ro(loc->ptr)) < 0)
return fd;
init_stream(&zs, inflated_buffer, sizeof(inflated_buffer));
z_return = inflateInit(&zs);
while (z_return == Z_OK) {
if ((read_bytes = p_read(fd, raw_buffer, sizeof(raw_buffer))) > 0) {
set_stream_input(&zs, raw_buffer, read_bytes);
z_return = inflate(&zs, 0);
} else
z_return = Z_STREAM_END;
}
if ((z_return != Z_STREAM_END && z_return != Z_BUF_ERROR)
|| get_object_header(&header_obj, inflated_buffer) == 0
|| git_object_typeisloose(header_obj.type) == 0)
{
giterr_set(GITERR_ZLIB, "Failed to read loose object header");
error = -1;
} else {
out->len = header_obj.size;
out->type = header_obj.type;
}
finish_inflate(&zs);
p_close(fd);
return error;
}
static int locate_object(
git_buf *object_location,
loose_backend *backend,
const git_oid *oid)
{
int error = object_file_name(object_location, backend->objects_dir, oid);
if (!error && !git_path_exists(object_location->ptr))
return GIT_ENOTFOUND;
return error;
}
/* Explore an entry of a directory and see if it matches a short oid */
static int fn_locate_object_short_oid(void *state, git_buf *pathbuf) {
loose_locate_object_state *sstate = (loose_locate_object_state *)state;
if (git_buf_len(pathbuf) - sstate->dir_len != GIT_OID_HEXSZ - 2) {
/* Entry cannot be an object. Continue to next entry */
return 0;
}
if (git_path_isdir(pathbuf->ptr) == false) {
/* We are already in the directory matching the 2 first hex characters,
* compare the first ncmp characters of the oids */
if (!memcmp(sstate->short_oid + 2,
(unsigned char *)pathbuf->ptr + sstate->dir_len,
sstate->short_oid_len - 2)) {
if (!sstate->found) {
sstate->res_oid[0] = sstate->short_oid[0];
sstate->res_oid[1] = sstate->short_oid[1];
memcpy(sstate->res_oid+2, pathbuf->ptr+sstate->dir_len, GIT_OID_HEXSZ-2);
}
sstate->found++;
}
}
if (sstate->found > 1)
return git_odb__error_ambiguous("multiple matches in loose objects");
return 0;
}
/* Locate an object matching a given short oid */
static int locate_object_short_oid(
git_buf *object_location,
git_oid *res_oid,
loose_backend *backend,
const git_oid *short_oid,
unsigned int len)
{
char *objects_dir = backend->objects_dir;
size_t dir_len = strlen(objects_dir);
loose_locate_object_state state;
int error;
/* prealloc memory for OBJ_DIR/xx/ */
if (git_buf_grow(object_location, dir_len + 5) < 0)
return -1;
git_buf_sets(object_location, objects_dir);
git_path_to_dir(object_location);
/* save adjusted position at end of dir so it can be restored later */
dir_len = git_buf_len(object_location);
/* Convert raw oid to hex formatted oid */
git_oid_fmt((char *)state.short_oid, short_oid);
/* Explore OBJ_DIR/xx/ where xx is the beginning of hex formatted short oid */
if (git_buf_printf(object_location, "%.2s/", state.short_oid) < 0)
return -1;
/* Check that directory exists */
if (git_path_isdir(object_location->ptr) == false)
return git_odb__error_notfound("no matching loose object for prefix", short_oid);
state.dir_len = git_buf_len(object_location);
state.short_oid_len = len;
state.found = 0;
/* Explore directory to find a unique object matching short_oid */
error = git_path_direach(
object_location, fn_locate_object_short_oid, &state);
if (error)
return error;
if (!state.found)
return git_odb__error_notfound("no matching loose object for prefix", short_oid);
/* Convert obtained hex formatted oid to raw */
error = git_oid_fromstr(res_oid, (char *)state.res_oid);
if (error)
return error;
/* Update the location according to the oid obtained */
git_buf_truncate(object_location, dir_len);
if (git_buf_grow(object_location, dir_len + GIT_OID_HEXSZ + 2) < 0)
return -1;
git_oid_pathfmt(object_location->ptr + dir_len, res_oid);
object_location->size += GIT_OID_HEXSZ + 1;
object_location->ptr[object_location->size] = '\0';
return 0;
}
/***********************************************************
*
* LOOSE BACKEND PUBLIC API
*
* Implement the git_odb_backend API calls
*
***********************************************************/
static int loose_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid)
{
git_buf object_path = GIT_BUF_INIT;
git_rawobj raw;
int error;
assert(backend && oid);
raw.len = 0;
raw.type = GIT_OBJ_BAD;
if (locate_object(&object_path, (loose_backend *)backend, oid) < 0)
error = git_odb__error_notfound("no matching loose object", oid);
else if ((error = read_header_loose(&raw, &object_path)) == 0) {
*len_p = raw.len;
*type_p = raw.type;
}
git_buf_free(&object_path);
return error;
}
static int loose_backend__read(void **buffer_p, size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid)
{
git_buf object_path = GIT_BUF_INIT;
git_rawobj raw;
int error = 0;
assert(backend && oid);
if (locate_object(&object_path, (loose_backend *)backend, oid) < 0)
error = git_odb__error_notfound("no matching loose object", oid);
else if ((error = read_loose(&raw, &object_path)) == 0) {
*buffer_p = raw.data;
*len_p = raw.len;
*type_p = raw.type;
}
git_buf_free(&object_path);
return error;
}
static int loose_backend__read_prefix(
git_oid *out_oid,
void **buffer_p,
size_t *len_p,
git_otype *type_p,
git_odb_backend *backend,
const git_oid *short_oid,
unsigned int len)
{
int error = 0;
if (len < GIT_OID_MINPREFIXLEN)
error = git_odb__error_ambiguous("prefix length too short");
else if (len >= GIT_OID_HEXSZ) {
/* We can fall back to regular read method */
error = loose_backend__read(buffer_p, len_p, type_p, backend, short_oid);
if (!error)
git_oid_cpy(out_oid, short_oid);
} else {
git_buf object_path = GIT_BUF_INIT;
git_rawobj raw;
assert(backend && short_oid);
if ((error = locate_object_short_oid(&object_path, out_oid,
(loose_backend *)backend, short_oid, len)) == 0 &&
(error = read_loose(&raw, &object_path)) == 0)
{
*buffer_p = raw.data;
*len_p = raw.len;
*type_p = raw.type;
}
git_buf_free(&object_path);
}
return error;
}
static int loose_backend__exists(git_odb_backend *backend, const git_oid *oid)
{
git_buf object_path = GIT_BUF_INIT;
int error;
assert(backend && oid);
error = locate_object(&object_path, (loose_backend *)backend, oid);
git_buf_free(&object_path);
return !error;
}
struct foreach_state {
size_t dir_len;
int (*cb)(git_oid *oid, void *data);
void *data;
int cb_error;
};
GIT_INLINE(int) filename_to_oid(git_oid *oid, const char *ptr)
{
int v, i = 0;
if (strlen(ptr) != 41)
return -1;
if (ptr[2] != '/') {
return -1;
}
v = (git__fromhex(ptr[i]) << 4) | git__fromhex(ptr[i+1]);
if (v < 0)
return -1;
oid->id[0] = (unsigned char) v;
ptr += 3;
for (i = 0; i < 38; i += 2) {
v = (git__fromhex(ptr[i]) << 4) | git__fromhex(ptr[i + 1]);
if (v < 0)
return -1;
oid->id[1 + i/2] = (unsigned char) v;
}
return 0;
}
static int foreach_object_dir_cb(void *_state, git_buf *path)
{
git_oid oid;
struct foreach_state *state = (struct foreach_state *) _state;
if (filename_to_oid(&oid, path->ptr + state->dir_len) < 0)
return 0;
if (state->cb(&oid, state->data)) {
state->cb_error = GIT_EUSER;
return -1;
}
return 0;
}
static int foreach_cb(void *_state, git_buf *path)
{
struct foreach_state *state = (struct foreach_state *) _state;
return git_path_direach(path, foreach_object_dir_cb, state);
}
static int loose_backend__foreach(git_odb_backend *_backend, int (*cb)(git_oid *oid, void *data), void *data)
{
char *objects_dir;
int error;
git_buf buf = GIT_BUF_INIT;
struct foreach_state state;
loose_backend *backend = (loose_backend *) _backend;
assert(backend && cb);
objects_dir = backend->objects_dir;
git_buf_sets(&buf, objects_dir);
git_path_to_dir(&buf);
memset(&state, 0, sizeof(state));
state.cb = cb;
state.data = data;
state.dir_len = git_buf_len(&buf);
error = git_path_direach(&buf, foreach_cb, &state);
git_buf_free(&buf);
return state.cb_error ? state.cb_error : error;
}
static int loose_backend__stream_fwrite(git_oid *oid, git_odb_stream *_stream)
{
loose_writestream *stream = (loose_writestream *)_stream;
loose_backend *backend = (loose_backend *)_stream->backend;
git_buf final_path = GIT_BUF_INIT;
int error = 0;
if (git_filebuf_hash(oid, &stream->fbuf) < 0 ||
object_file_name(&final_path, backend->objects_dir, oid) < 0 ||
git_futils_mkpath2file(final_path.ptr, GIT_OBJECT_DIR_MODE) < 0)
error = -1;
/*
* Don't try to add an existing object to the repository. This
* is what git does and allows us to sidestep the fact that
* we're not allowed to overwrite a read-only file on Windows.
*/
else if (git_path_exists(final_path.ptr) == true)
git_filebuf_cleanup(&stream->fbuf);
else
error = git_filebuf_commit_at(
&stream->fbuf, final_path.ptr, GIT_OBJECT_FILE_MODE);
git_buf_free(&final_path);
return error;
}
static int loose_backend__stream_write(git_odb_stream *_stream, const char *data, size_t len)
{
loose_writestream *stream = (loose_writestream *)_stream;
return git_filebuf_write(&stream->fbuf, data, len);
}
static void loose_backend__stream_free(git_odb_stream *_stream)
{
loose_writestream *stream = (loose_writestream *)_stream;
git_filebuf_cleanup(&stream->fbuf);
git__free(stream);
}
static int format_object_header(char *hdr, size_t n, size_t obj_len, git_otype obj_type)
{
const char *type_str = git_object_type2string(obj_type);
int len = snprintf(hdr, n, "%s %"PRIuZ, type_str, obj_len);
assert(len > 0); /* otherwise snprintf() is broken */
assert(((size_t)len) < n); /* otherwise the caller is broken! */
return len+1;
}
static int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_backend, size_t length, git_otype type)
{
loose_backend *backend;
loose_writestream *stream = NULL;
char hdr[64];
git_buf tmp_path = GIT_BUF_INIT;
int hdrlen;
assert(_backend);
backend = (loose_backend *)_backend;
*stream_out = NULL;
hdrlen = format_object_header(hdr, sizeof(hdr), length, type);
stream = git__calloc(1, sizeof(loose_writestream));
GITERR_CHECK_ALLOC(stream);
stream->stream.backend = _backend;
stream->stream.read = NULL; /* read only */
stream->stream.write = &loose_backend__stream_write;
stream->stream.finalize_write = &loose_backend__stream_fwrite;
stream->stream.free = &loose_backend__stream_free;
stream->stream.mode = GIT_STREAM_WRONLY;
if (git_buf_joinpath(&tmp_path, backend->objects_dir, "tmp_object") < 0 ||
git_filebuf_open(&stream->fbuf, tmp_path.ptr,
GIT_FILEBUF_HASH_CONTENTS |
GIT_FILEBUF_TEMPORARY |
(backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT)) < 0 ||
stream->stream.write((git_odb_stream *)stream, hdr, hdrlen) < 0)
{
git_filebuf_cleanup(&stream->fbuf);
git__free(stream);
stream = NULL;
}
git_buf_free(&tmp_path);
*stream_out = (git_odb_stream *)stream;
return !stream ? -1 : 0;
}
static int loose_backend__write(git_oid *oid, git_odb_backend *_backend, const void *data, size_t len, git_otype type)
{
int error = 0, header_len;
git_buf final_path = GIT_BUF_INIT;
char header[64];
git_filebuf fbuf = GIT_FILEBUF_INIT;
loose_backend *backend;
backend = (loose_backend *)_backend;
/* prepare the header for the file */
header_len = format_object_header(header, sizeof(header), len, type);
if (git_buf_joinpath(&final_path, backend->objects_dir, "tmp_object") < 0 ||
git_filebuf_open(&fbuf, final_path.ptr,
GIT_FILEBUF_HASH_CONTENTS |
GIT_FILEBUF_TEMPORARY |
(backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT)) < 0)
{
error = -1;
goto cleanup;
}
git_filebuf_write(&fbuf, header, header_len);
git_filebuf_write(&fbuf, data, len);
git_filebuf_hash(oid, &fbuf);
if (object_file_name(&final_path, backend->objects_dir, oid) < 0 ||
git_futils_mkpath2file(final_path.ptr, GIT_OBJECT_DIR_MODE) < 0 ||
git_filebuf_commit_at(&fbuf, final_path.ptr, GIT_OBJECT_FILE_MODE) < 0)
error = -1;
cleanup:
if (error < 0)
git_filebuf_cleanup(&fbuf);
git_buf_free(&final_path);
return error;
}
static void loose_backend__free(git_odb_backend *_backend)
{
loose_backend *backend;
assert(_backend);
backend = (loose_backend *)_backend;
git__free(backend->objects_dir);
git__free(backend);
}
int git_odb_backend_loose(
git_odb_backend **backend_out,
const char *objects_dir,
int compression_level,
int do_fsync)
{
loose_backend *backend;
backend = git__calloc(1, sizeof(loose_backend));
GITERR_CHECK_ALLOC(backend);
backend->objects_dir = git__strdup(objects_dir);
GITERR_CHECK_ALLOC(backend->objects_dir);
if (compression_level < 0)
compression_level = Z_BEST_SPEED;
backend->object_zlib_level = compression_level;
backend->fsync_object_files = do_fsync;
backend->parent.read = &loose_backend__read;
backend->parent.write = &loose_backend__write;
backend->parent.read_prefix = &loose_backend__read_prefix;
backend->parent.read_header = &loose_backend__read_header;
backend->parent.writestream = &loose_backend__stream;
backend->parent.exists = &loose_backend__exists;
backend->parent.foreach = &loose_backend__foreach;
backend->parent.free = &loose_backend__free;
*backend_out = (git_odb_backend *)backend;
return 0;
}