Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5625d86

Browse files
dturner-twnovalis
authored andcommitted
index: support index v4
Support reading and writing index v4. Index v4 uses a very simple compression scheme for pathnames, but is otherwise similar to index v3. Signed-off-by: David Turner <[email protected]>
1 parent aeb5ee5 commit 5625d86

File tree

5 files changed

+191
-29
lines changed

5 files changed

+191
-29
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ v0.24 + 1
1313
* Do not fail when deleting remotes in the presence of broken
1414
global configs which contain branches.
1515

16+
* Support for reading and writing git index v4 files
17+
1618
### API additions
1719

1820
* You can now get the user-agent used by libgit2 using the
@@ -49,6 +51,9 @@ v0.24 + 1
4951
* `git_diff_from_buffer` can create a `git_diff` object from the contents
5052
of a git-style patch file.
5153

54+
* `git_index_version()` and `git_index_set_version()` to get and set
55+
the index version
56+
5257
### API removals
5358

5459
* `git_blob_create_fromchunks()` has been removed in favour of

include/git2/index.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,31 @@ GIT_EXTERN(int) git_index_caps(const git_index *index);
251251
*/
252252
GIT_EXTERN(int) git_index_set_caps(git_index *index, int caps);
253253

254+
/**
255+
* Get index on-disk version.
256+
*
257+
* Valid return values are 2, 3, or 4. If 3 is returned, an index
258+
* with version 2 may be written instead, if the extension data in
259+
* version 3 is not necessary.
260+
*
261+
* @param index An existing index object
262+
* @return the index version
263+
*/
264+
GIT_EXTERN(unsigned int) git_index_version(git_index *index);
265+
266+
/**
267+
* Set index on-disk version.
268+
*
269+
* Valid values are 2, 3, or 4. If 2 is given, git_index_write may
270+
* write an index with version 3 instead, if necessary to accurately
271+
* represent the index.
272+
*
273+
* @param index An existing index object
274+
* @param version The new version number
275+
* @return 0 on success, -1 on failure
276+
*/
277+
GIT_EXTERN(int) git_index_set_version(git_index *index, unsigned int version);
278+
254279
/**
255280
* Update the contents of an existing index object in memory by reading
256281
* from the hard disk.

src/index.c

Lines changed: 118 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "blob.h"
2020
#include "idxmap.h"
2121
#include "diff.h"
22+
#include "varint.h"
2223

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

68-
static const unsigned int INDEX_VERSION_NUMBER = 2;
69+
static const unsigned int INDEX_VERSION_NUMBER_DEFAULT = 2;
70+
static const unsigned int INDEX_VERSION_NUMBER_LB = 2;
6971
static const unsigned int INDEX_VERSION_NUMBER_EXT = 3;
72+
static const unsigned int INDEX_VERSION_NUMBER_COMP = 4;
73+
static const unsigned int INDEX_VERSION_NUMBER_UB = 4;
7074

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

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

755+
unsigned git_index_version(git_index *index)
756+
{
757+
assert(index);
758+
759+
return index->version;
760+
}
761+
762+
int git_index_set_version(git_index *index, unsigned int version)
763+
{
764+
assert(index);
765+
766+
if (version < INDEX_VERSION_NUMBER_LB ||
767+
version > INDEX_VERSION_NUMBER_UB) {
768+
giterr_set(GITERR_INDEX, "Invalid version number");
769+
return -1;
770+
}
771+
772+
index->version = version;
773+
774+
return 0;
775+
}
776+
750777
int git_index_write(git_index *index)
751778
{
752779
git_indexwriter writer = GIT_INDEXWRITER_INIT;
@@ -2262,12 +2289,15 @@ static size_t read_entry(
22622289
git_index_entry **out,
22632290
git_index *index,
22642291
const void *buffer,
2265-
size_t buffer_size)
2292+
size_t buffer_size,
2293+
const char **last)
22662294
{
22672295
size_t path_length, entry_size;
22682296
const char *path_ptr;
22692297
struct entry_short source;
22702298
git_index_entry entry = {{0}};
2299+
bool compressed = index->version >= INDEX_VERSION_NUMBER_COMP;
2300+
char *tmp_path = NULL;
22712301

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

2305-
path_length = entry.flags & GIT_IDXENTRY_NAMEMASK;
2306-
2307-
/* if this is a very long string, we must find its
2308-
* real length without overflowing */
2309-
if (path_length == 0xFFF) {
2310-
const char *path_end;
2335+
if (!compressed) {
2336+
path_length = entry.flags & GIT_IDXENTRY_NAMEMASK;
23112337

2312-
path_end = memchr(path_ptr, '\0', buffer_size);
2313-
if (path_end == NULL)
2314-
return 0;
2338+
/* if this is a very long string, we must find its
2339+
* real length without overflowing */
2340+
if (path_length == 0xFFF) {
2341+
const char *path_end;
23152342

2316-
path_length = path_end - path_ptr;
2317-
}
2343+
path_end = memchr(path_ptr, '\0', buffer_size);
2344+
if (path_end == NULL)
2345+
return 0;
23182346

2319-
if (entry.flags & GIT_IDXENTRY_EXTENDED)
2320-
entry_size = long_entry_size(path_length);
2321-
else
2322-
entry_size = short_entry_size(path_length);
2347+
path_length = path_end - path_ptr;
2348+
}
23232349

2324-
if (INDEX_FOOTER_SIZE + entry_size > buffer_size)
2325-
return 0;
2350+
if (entry.flags & GIT_IDXENTRY_EXTENDED)
2351+
entry_size = long_entry_size(path_length);
2352+
else
2353+
entry_size = short_entry_size(path_length);
23262354

2327-
entry.path = (char *)path_ptr;
2355+
if (INDEX_FOOTER_SIZE + entry_size > buffer_size)
2356+
return 0;
23282357

2329-
if (index_entry_dup(out, index, &entry) < 0)
2358+
entry.path = (char *)path_ptr;
2359+
} else {
2360+
size_t varint_len;
2361+
size_t shared = git_decode_varint((const unsigned char *)path_ptr,
2362+
&varint_len);
2363+
size_t len = strlen(path_ptr + varint_len);
2364+
size_t last_len = strlen(*last);
2365+
size_t tmp_path_len;
2366+
2367+
if (varint_len == 0)
2368+
return index_error_invalid("incorrect prefix length");
2369+
2370+
GITERR_CHECK_ALLOC_ADD(&tmp_path_len, shared, len + 1);
2371+
tmp_path = git__malloc(tmp_path_len);
2372+
GITERR_CHECK_ALLOC(tmp_path);
2373+
memcpy(tmp_path, last, last_len);
2374+
memcpy(tmp_path + last_len, path_ptr + varint_len, len);
2375+
entry_size = long_entry_size(shared + len);
2376+
entry.path = tmp_path;
2377+
}
2378+
2379+
if (index_entry_dup(out, index, &entry) < 0) {
2380+
git__free(tmp_path);
23302381
return 0;
2382+
}
23312383

2384+
git__free(tmp_path);
23322385
return entry_size;
23332386
}
23342387

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

23432396
dest->version = ntohl(source->version);
2344-
if (dest->version != INDEX_VERSION_NUMBER_EXT &&
2345-
dest->version != INDEX_VERSION_NUMBER)
2397+
if (dest->version < INDEX_VERSION_NUMBER_LB ||
2398+
dest->version > INDEX_VERSION_NUMBER_UB)
23462399
return index_error_invalid("incorrect header version");
23472400

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

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

2473+
index->version = header.version;
2474+
if (index->version >= INDEX_VERSION_NUMBER_COMP)
2475+
last = &empty;
2476+
24182477
seek_forward(INDEX_HEADER_SIZE);
24192478

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

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

2521-
static int write_disk_entry(git_filebuf *file, git_index_entry *entry)
2580+
static int write_disk_entry(git_filebuf *file, git_index_entry *entry, const char **last)
25222581
{
25232582
void *mem = NULL;
25242583
struct entry_short *ondisk;
25252584
size_t path_len, disk_size;
25262585
char *path;
2586+
const char *path_start = entry->path;
2587+
size_t same_len = 0;
25272588

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

2591+
if (last) {
2592+
const char *last_c = *last;
2593+
2594+
while (*path_start == *last_c) {
2595+
if (!*path_start || !*last_c)
2596+
break;
2597+
++path_start;
2598+
++last_c;
2599+
++same_len;
2600+
}
2601+
path_len -= same_len;
2602+
*last = entry->path;
2603+
}
2604+
25302605
if (entry->flags & GIT_IDXENTRY_EXTENDED)
25312606
disk_size = long_entry_size(path_len);
25322607
else
@@ -2574,7 +2649,12 @@ static int write_disk_entry(git_filebuf *file, git_index_entry *entry)
25742649
else
25752650
path = ondisk->path;
25762651

2577-
memcpy(path, entry->path, path_len);
2652+
if (last) {
2653+
path += git_encode_varint((unsigned char *) path,
2654+
disk_size,
2655+
path_len - same_len);
2656+
}
2657+
memcpy(path, path_start, path_len);
25782658

25792659
return 0;
25802660
}
@@ -2585,6 +2665,8 @@ static int write_entries(git_index *index, git_filebuf *file)
25852665
size_t i;
25862666
git_vector case_sorted, *entries;
25872667
git_index_entry *entry;
2668+
const char **last = NULL;
2669+
const char *empty = "";
25882670

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

2681+
if (index->version >= INDEX_VERSION_NUMBER_COMP)
2682+
last = &empty;
2683+
25992684
git_vector_foreach(entries, i, entry)
2600-
if ((error = write_disk_entry(file, entry)) < 0)
2685+
if ((error = write_disk_entry(file, entry, last)) < 0)
26012686
break;
26022687

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

27632848
assert(index && file);
27642849

2765-
is_extended = is_index_extended(index);
2766-
index_version_number = is_extended ? INDEX_VERSION_NUMBER_EXT : INDEX_VERSION_NUMBER;
2850+
if (index->version <= INDEX_VERSION_NUMBER_EXT) {
2851+
is_extended = is_index_extended(index);
2852+
index_version_number = is_extended ? INDEX_VERSION_NUMBER_EXT : INDEX_VERSION_NUMBER_LB;
2853+
} else {
2854+
index_version_number = index->version;
2855+
}
27672856

27682857
header.signature = htonl(INDEX_HEADER_SIG);
27692858
header.version = htonl(index_version_number);

src/index.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ struct git_index {
4646
git_vector_cmp entries_search;
4747
git_vector_cmp entries_search_path;
4848
git_vector_cmp reuc_search;
49+
50+
unsigned int version;
4951
};
5052

5153
struct git_index_conflict_iterator {

tests/index/version.c

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#include "clar_libgit2.h"
2+
#include "index.h"
3+
4+
static git_repository *g_repo = NULL;
5+
6+
void test_index_version__can_write_v4(void)
7+
{
8+
git_index *index;
9+
const git_index_entry *entry;
10+
11+
g_repo = cl_git_sandbox_init("filemodes");
12+
cl_git_pass(git_repository_index(&index, g_repo));
13+
14+
cl_assert(index->on_disk);
15+
cl_assert(git_index_version(index) == 2);
16+
17+
cl_assert(git_index_entrycount(index) == 6);
18+
19+
cl_git_pass(git_index_set_version(index, 4));
20+
21+
cl_git_pass(git_index_write(index));
22+
git_index_free(index);
23+
24+
cl_git_pass(git_repository_index(&index, g_repo));
25+
cl_assert(git_index_version(index) == 4);
26+
27+
entry = git_index_get_bypath(index, "exec_off", 0);
28+
cl_assert(entry);
29+
entry = git_index_get_bypath(index, "exec_off2on_staged", 0);
30+
cl_assert(entry);
31+
entry = git_index_get_bypath(index, "exec_on", 0);
32+
cl_assert(entry);
33+
34+
git_index_free(index);
35+
}
36+
37+
void test_index_version__cleanup(void)
38+
{
39+
cl_git_sandbox_cleanup();
40+
g_repo = NULL;
41+
}

0 commit comments

Comments
 (0)