Support index v4 by novalis · Pull Request #3837 · libgit2/libgit2 · GitHub
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 CHANGELOG.md
25 changes: 25 additions & 0 deletions include/git2/index.h
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,31 @@ GIT_EXTERN(int) git_index_caps(const git_index *index);
*/
GIT_EXTERN(int) git_index_set_caps(git_index *index, int caps);

/**
* Get index on-disk version.
*
* Valid return values are 2, 3, or 4. If 3 is returned, an index
* with version 2 may be written instead, if the extension data in
* version 3 is not necessary.
*
* @param index An existing index object
* @return the index version
*/
GIT_EXTERN(unsigned int) git_index_version(git_index *index);

/**
* Set index on-disk version.
*
* Valid values are 2, 3, or 4. If 2 is given, git_index_write may
* write an index with version 3 instead, if necessary to accurately
* represent the index.
*
* @param index An existing index object
* @param version The new version number
* @return 0 on success, -1 on failure
*/
GIT_EXTERN(int) git_index_set_version(git_index *index, unsigned int version);

/**
* Update the contents of an existing index object in memory by reading
* from the hard disk.
Expand Down
147 changes: 118 additions & 29 deletions src/index.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "blob.h"
#include "idxmap.h"
#include "diff.h"
#include "varint.h"

#include "git2/odb.h"
#include "git2/oid.h"
Expand Down Expand Up @@ -65,8 +66,11 @@ static int index_apply_to_wd_diff(git_index *index, int action, const git_strarr
static const size_t INDEX_FOOTER_SIZE = GIT_OID_RAWSZ;
static const size_t INDEX_HEADER_SIZE = 12;

static const unsigned int INDEX_VERSION_NUMBER = 2;
static const unsigned int INDEX_VERSION_NUMBER_DEFAULT = 2;
static const unsigned int INDEX_VERSION_NUMBER_LB = 2;
static const unsigned int INDEX_VERSION_NUMBER_EXT = 3;
static const unsigned int INDEX_VERSION_NUMBER_COMP = 4;
static const unsigned int INDEX_VERSION_NUMBER_UB = 4;

static const unsigned int INDEX_HEADER_SIG = 0x44495243;
static const char INDEX_EXT_TREECACHE_SIG[] = {'T', 'R', 'E', 'E'};
Expand Down Expand Up @@ -434,6 +438,7 @@ int git_index_open(git_index **index_out, const char *index_path)
index->entries_search = git_index_entry_srch;
index->entries_search_path = index_entry_srch_path;
index->reuc_search = reuc_srch;
index->version = INDEX_VERSION_NUMBER_DEFAULT;

if (index_path != NULL && (error = git_index_read(index, true)) < 0)
goto fail;
Expand Down Expand Up @@ -747,6 +752,28 @@ static int truncate_racily_clean(git_index *index)
return 0;
}

unsigned git_index_version(git_index *index)
{
assert(index);

return index->version;
}

int git_index_set_version(git_index *index, unsigned int version)
{
assert(index);

if (version < INDEX_VERSION_NUMBER_LB ||
version > INDEX_VERSION_NUMBER_UB) {
giterr_set(GITERR_INDEX, "Invalid version number");
return -1;
}

index->version = version;

return 0;
}

int git_index_write(git_index *index)
{
git_indexwriter writer = GIT_INDEXWRITER_INIT;
Expand Down Expand Up @@ -2262,12 +2289,15 @@ static size_t read_entry(
git_index_entry **out,
git_index *index,
const void *buffer,
size_t buffer_size)
size_t buffer_size,
const char **last)
{
size_t path_length, entry_size;
const char *path_ptr;
struct entry_short source;
git_index_entry entry = {{0}};
bool compressed = index->version >= INDEX_VERSION_NUMBER_COMP;
char *tmp_path = NULL;

if (INDEX_FOOTER_SIZE + minimal_entry_size > buffer_size)
return 0;
Expand Down Expand Up @@ -2302,33 +2332,56 @@ static size_t read_entry(
} else
path_ptr = (const char *) buffer + offsetof(struct entry_short, path);

path_length = entry.flags & GIT_IDXENTRY_NAMEMASK;

/* if this is a very long string, we must find its
* real length without overflowing */
if (path_length == 0xFFF) {
const char *path_end;
if (!compressed) {
path_length = entry.flags & GIT_IDXENTRY_NAMEMASK;

path_end = memchr(path_ptr, '\0', buffer_size);
if (path_end == NULL)
return 0;
/* if this is a very long string, we must find its
* real length without overflowing */
if (path_length == 0xFFF) {
const char *path_end;

path_length = path_end - path_ptr;
}
path_end = memchr(path_ptr, '\0', buffer_size);
if (path_end == NULL)
return 0;

if (entry.flags & GIT_IDXENTRY_EXTENDED)
entry_size = long_entry_size(path_length);
else
entry_size = short_entry_size(path_length);
path_length = path_end - path_ptr;
}

if (INDEX_FOOTER_SIZE + entry_size > buffer_size)
return 0;
if (entry.flags & GIT_IDXENTRY_EXTENDED)
entry_size = long_entry_size(path_length);
else
entry_size = short_entry_size(path_length);

entry.path = (char *)path_ptr;
if (INDEX_FOOTER_SIZE + entry_size > buffer_size)
return 0;

if (index_entry_dup(out, index, &entry) < 0)
entry.path = (char *)path_ptr;
} else {
size_t varint_len;
size_t shared = git_decode_varint((const unsigned char *)path_ptr,
&varint_len);
size_t len = strlen(path_ptr + varint_len);
size_t last_len = strlen(*last);
size_t tmp_path_len;

if (varint_len == 0)
return index_error_invalid("incorrect prefix length");

GITERR_CHECK_ALLOC_ADD(&tmp_path_len, shared, len + 1);
tmp_path = git__malloc(tmp_path_len);
GITERR_CHECK_ALLOC(tmp_path);
memcpy(tmp_path, last, last_len);
memcpy(tmp_path + last_len, path_ptr + varint_len, len);
entry_size = long_entry_size(shared + len);
entry.path = tmp_path;
}

if (index_entry_dup(out, index, &entry) < 0) {
git__free(tmp_path);
return 0;
}

git__free(tmp_path);
return entry_size;
}

Expand All @@ -2341,8 +2394,8 @@ static int read_header(struct index_header *dest, const void *buffer)
return index_error_invalid("incorrect header signature");

dest->version = ntohl(source->version);
if (dest->version != INDEX_VERSION_NUMBER_EXT &&
dest->version != INDEX_VERSION_NUMBER)
if (dest->version < INDEX_VERSION_NUMBER_LB ||
dest->version > INDEX_VERSION_NUMBER_UB)
return index_error_invalid("incorrect header version");

dest->entry_count = ntohl(source->entry_count);
Expand Down Expand Up @@ -2395,6 +2448,8 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size)
unsigned int i;
struct index_header header = { 0 };
git_oid checksum_calculated, checksum_expected;
const char **last = NULL;
const char *empty = "";

#define seek_forward(_increase) { \
if (_increase >= buffer_size) { \
Expand All @@ -2415,6 +2470,10 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size)
if ((error = read_header(&header, buffer)) < 0)
return error;

index->version = header.version;
if (index->version >= INDEX_VERSION_NUMBER_COMP)
last = &empty;

seek_forward(INDEX_HEADER_SIZE);

assert(!index->entries.length);
Expand All @@ -2427,7 +2486,7 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size)
/* Parse all the entries */
for (i = 0; i < header.entry_count && buffer_size > INDEX_FOOTER_SIZE; ++i) {
git_index_entry *entry;
size_t entry_size = read_entry(&entry, index, buffer, buffer_size);
size_t entry_size = read_entry(&entry, index, buffer, buffer_size, last);

/* 0 bytes read means an object corruption */
if (entry_size == 0) {
Expand Down Expand Up @@ -2518,15 +2577,31 @@ static bool is_index_extended(git_index *index)
return (extended > 0);
}

static int write_disk_entry(git_filebuf *file, git_index_entry *entry)
static int write_disk_entry(git_filebuf *file, git_index_entry *entry, const char **last)
{
void *mem = NULL;
struct entry_short *ondisk;
size_t path_len, disk_size;
char *path;
const char *path_start = entry->path;
size_t same_len = 0;

path_len = ((struct entry_internal *)entry)->pathlen;

if (last) {
const char *last_c = *last;

while (*path_start == *last_c) {
if (!*path_start || !*last_c)
break;
++path_start;
++last_c;
++same_len;
}
path_len -= same_len;
*last = entry->path;
}

if (entry->flags & GIT_IDXENTRY_EXTENDED)
disk_size = long_entry_size(path_len);
else
Expand Down Expand Up @@ -2574,7 +2649,12 @@ static int write_disk_entry(git_filebuf *file, git_index_entry *entry)
else
path = ondisk->path;

memcpy(path, entry->path, path_len);
if (last) {
path += git_encode_varint((unsigned char *) path,
disk_size,
path_len - same_len);
}
memcpy(path, path_start, path_len);

return 0;
}
Expand All @@ -2585,6 +2665,8 @@ static int write_entries(git_index *index, git_filebuf *file)
size_t i;
git_vector case_sorted, *entries;
git_index_entry *entry;
const char **last = NULL;
const char *empty = "";

/* If index->entries is sorted case-insensitively, then we need
* to re-sort it case-sensitively before writing */
Expand All @@ -2596,8 +2678,11 @@ static int write_entries(git_index *index, git_filebuf *file)
entries = &index->entries;
}

if (index->version >= INDEX_VERSION_NUMBER_COMP)
last = &empty;

git_vector_foreach(entries, i, entry)
if ((error = write_disk_entry(file, entry)) < 0)
if ((error = write_disk_entry(file, entry, last)) < 0)
break;

if (index->ignore_case)
Expand Down Expand Up @@ -2762,8 +2847,12 @@ static int write_index(git_oid *checksum, git_index *index, git_filebuf *file)

assert(index && file);

is_extended = is_index_extended(index);
index_version_number = is_extended ? INDEX_VERSION_NUMBER_EXT : INDEX_VERSION_NUMBER;
if (index->version <= INDEX_VERSION_NUMBER_EXT) {
is_extended = is_index_extended(index);
index_version_number = is_extended ? INDEX_VERSION_NUMBER_EXT : INDEX_VERSION_NUMBER_LB;
} else {
index_version_number = index->version;
}

header.signature = htonl(INDEX_HEADER_SIG);
header.version = htonl(index_version_number);
Expand Down
2 changes: 2 additions & 0 deletions src/index.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ struct git_index {
git_vector_cmp entries_search;
git_vector_cmp entries_search_path;
git_vector_cmp reuc_search;

unsigned int version;
};

struct git_index_conflict_iterator {
Expand Down
44 changes: 44 additions & 0 deletions src/varint.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* 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 "varint.h"

uintmax_t git_decode_varint(const unsigned char *bufp, size_t *varint_len)
{
const unsigned char *buf = bufp;
unsigned char c = *buf++;
uintmax_t val = c & 127;
while (c & 128) {
val += 1;
if (!val || MSB(val, 7)) {
/* This is not a valid varint_len, so it signals
the error */
*varint_len = 0;
return 0; /* overflow */
}
c = *buf++;
val = (val << 7) + (c & 127);
}
*varint_len = buf - bufp;
return val;
}

int git_encode_varint(unsigned char *buf, size_t bufsize, uintmax_t value)
{
unsigned char varint[16];
unsigned pos = sizeof(varint) - 1;
varint[pos] = value & 127;
while (value >>= 7)
varint[--pos] = 128 | (--value & 127);
if (buf) {
if (bufsize < pos)
return -1;
memcpy(buf, varint + pos, sizeof(varint) - pos);
}
return sizeof(varint) - pos;
}
15 changes: 15 additions & 0 deletions src/varint.h
Loading