]>
Commit | Line | Data |
---|---|---|
68535125 | 1 | /* |
bb742ede | 2 | * Copyright (C) 2009-2011 the libgit2 contributors |
68535125 | 3 | * |
bb742ede VM |
4 | * This file is part of libgit2, distributed under the GNU GPL v2 with |
5 | * a Linking Exception. For full terms see the included COPYING file. | |
68535125 VM |
6 | */ |
7 | ||
8 | #include <stddef.h> | |
9 | ||
10 | #include "common.h" | |
c3a20d5c | 11 | #include "repository.h" |
68535125 | 12 | #include "index.h" |
b4171320 | 13 | #include "tree-cache.h" |
68535125 | 14 | #include "hash.h" |
44908fe7 VM |
15 | #include "git2/odb.h" |
16 | #include "git2/blob.h" | |
68535125 | 17 | |
68535125 VM |
18 | #define entry_size(type,len) ((offsetof(type, path) + (len) + 8) & ~7) |
19 | #define short_entry_size(len) entry_size(struct entry_short, len) | |
20 | #define long_entry_size(len) entry_size(struct entry_long, len) | |
21 | ||
22 | #define minimal_entry_size (offsetof(struct entry_short, path)) | |
23 | ||
68535125 VM |
24 | static const size_t INDEX_FOOTER_SIZE = GIT_OID_RAWSZ; |
25 | static const size_t INDEX_HEADER_SIZE = 12; | |
26 | ||
27 | static const unsigned int INDEX_VERSION_NUMBER = 2; | |
348c7335 VM |
28 | static const unsigned int INDEX_VERSION_NUMBER_EXT = 3; |
29 | ||
30 | static const unsigned int INDEX_HEADER_SIG = 0x44495243; | |
31 | static const char INDEX_EXT_TREECACHE_SIG[] = {'T', 'R', 'E', 'E'}; | |
4c0b6a6d | 32 | static const char INDEX_EXT_UNMERGED_SIG[] = {'R', 'E', 'U', 'C'}; |
68535125 VM |
33 | |
34 | struct index_header { | |
35 | uint32_t signature; | |
36 | uint32_t version; | |
37 | uint32_t entry_count; | |
38 | }; | |
39 | ||
40 | struct index_extension { | |
41 | char signature[4]; | |
42 | uint32_t extension_size; | |
43 | }; | |
44 | ||
a44fc1d4 VM |
45 | struct entry_time { |
46 | uint32_t seconds; | |
47 | uint32_t nanoseconds; | |
48 | }; | |
49 | ||
68535125 | 50 | struct entry_short { |
a44fc1d4 VM |
51 | struct entry_time ctime; |
52 | struct entry_time mtime; | |
68535125 VM |
53 | uint32_t dev; |
54 | uint32_t ino; | |
55 | uint32_t mode; | |
56 | uint32_t uid; | |
57 | uint32_t gid; | |
58 | uint32_t file_size; | |
59 | git_oid oid; | |
60 | uint16_t flags; | |
683581a3 | 61 | char path[1]; /* arbitrary length */ |
68535125 VM |
62 | }; |
63 | ||
64 | struct entry_long { | |
a44fc1d4 VM |
65 | struct entry_time ctime; |
66 | struct entry_time mtime; | |
68535125 VM |
67 | uint32_t dev; |
68 | uint32_t ino; | |
69 | uint32_t mode; | |
70 | uint32_t uid; | |
71 | uint32_t gid; | |
72 | uint32_t file_size; | |
73 | git_oid oid; | |
74 | uint16_t flags; | |
75 | uint16_t flags_extended; | |
683581a3 | 76 | char path[1]; /* arbitrary length */ |
68535125 VM |
77 | }; |
78 | ||
79 | /* local declarations */ | |
80 | static size_t read_extension(git_index *index, const char *buffer, size_t buffer_size); | |
81 | static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffer_size); | |
82 | static int read_header(struct index_header *dest, const void *buffer); | |
83 | ||
348c7335 | 84 | static int parse_index(git_index *index, const char *buffer, size_t buffer_size); |
1648fbd3 | 85 | static int is_index_extended(git_index *index); |
817c2820 | 86 | static int write_index(git_index *index, git_filebuf *file); |
68535125 | 87 | |
e1fca014 | 88 | static int index_srch(const void *key, const void *array_member) |
c4034e63 | 89 | { |
ae9f771c | 90 | const git_index_entry *entry = array_member; |
c4034e63 | 91 | |
ae9f771c | 92 | return strcmp(key, entry->path); |
c4034e63 VM |
93 | } |
94 | ||
e1fca014 | 95 | static int index_cmp(const void *a, const void *b) |
c4034e63 | 96 | { |
ae9f771c KS |
97 | const git_index_entry *entry_a = a; |
98 | const git_index_entry *entry_b = b; | |
c4034e63 VM |
99 | |
100 | return strcmp(entry_a->path, entry_b->path); | |
101 | } | |
102 | ||
e1fca014 | 103 | static int unmerged_srch(const void *key, const void *array_member) |
4c0b6a6d | 104 | { |
ae9f771c | 105 | const git_index_entry_unmerged *entry = array_member; |
4c0b6a6d | 106 | |
ae9f771c | 107 | return strcmp(key, entry->path); |
4c0b6a6d JP |
108 | } |
109 | ||
e1fca014 | 110 | static int unmerged_cmp(const void *a, const void *b) |
4c0b6a6d | 111 | { |
ae9f771c KS |
112 | const git_index_entry_unmerged *info_a = a; |
113 | const git_index_entry_unmerged *info_b = b; | |
4c0b6a6d JP |
114 | |
115 | return strcmp(info_a->path, info_b->path); | |
116 | } | |
c4034e63 | 117 | |
e1fca014 | 118 | static unsigned int index_create_mode(unsigned int mode) |
c1a2a14e JP |
119 | { |
120 | if (S_ISLNK(mode)) | |
121 | return S_IFLNK; | |
c1802641 SS |
122 | if (S_ISDIR(mode) || (mode & S_IFMT) == (S_IFLNK | S_IFDIR)) |
123 | return (S_IFLNK | S_IFDIR); | |
c1a2a14e JP |
124 | return S_IFREG | ((mode & 0100) ? 0755 : 0644); |
125 | } | |
126 | ||
c3a20d5c | 127 | static int index_initialize(git_index **index_out, git_repository *owner, const char *index_path) |
68535125 VM |
128 | { |
129 | git_index *index; | |
130 | ||
1795f879 | 131 | assert(index_out && index_path); |
68535125 VM |
132 | |
133 | index = git__malloc(sizeof(git_index)); | |
134 | if (index == NULL) | |
1795f879 | 135 | return GIT_ENOMEM; |
68535125 VM |
136 | |
137 | memset(index, 0x0, sizeof(git_index)); | |
138 | ||
139 | index->index_file_path = git__strdup(index_path); | |
140 | if (index->index_file_path == NULL) { | |
141 | free(index); | |
1795f879 | 142 | return GIT_ENOMEM; |
68535125 VM |
143 | } |
144 | ||
c3a20d5c | 145 | index->repository = owner; |
6fd195d7 | 146 | |
86d7e1ca | 147 | git_vector_init(&index->entries, 32, index_cmp); |
348c7335 | 148 | |
68535125 | 149 | /* Check if index file is stored on disk already */ |
f79026b4 | 150 | if (git_futils_exists(index->index_file_path) == 0) |
68535125 VM |
151 | index->on_disk = 1; |
152 | ||
1795f879 | 153 | *index_out = index; |
3bdc0d4c | 154 | return git_index_read(index); |
68535125 VM |
155 | } |
156 | ||
f7e59c4d | 157 | int git_index_open(git_index **index_out, const char *index_path) |
c3a20d5c VM |
158 | { |
159 | return index_initialize(index_out, NULL, index_path); | |
160 | } | |
161 | ||
f7e59c4d VM |
162 | /* |
163 | * Moved from `repository.c` | |
164 | */ | |
165 | int git_repository_index(git_index **index_out, git_repository *repo) | |
c3a20d5c | 166 | { |
c3dd69a9 | 167 | if (repo->is_bare) |
c90bfec7 | 168 | return git__throw(GIT_EBAREINDEX, "Failed to open index. Repository is bare"); |
c3dd69a9 | 169 | |
c3a20d5c VM |
170 | return index_initialize(index_out, repo, repo->path_index); |
171 | } | |
172 | ||
348c7335 VM |
173 | void git_index_free(git_index *index) |
174 | { | |
f7e59c4d | 175 | if (index == NULL) |
348c7335 VM |
176 | return; |
177 | ||
178 | git_index_clear(index); | |
26b1b157 KS |
179 | git_vector_free(&index->entries); |
180 | git_vector_free(&index->unmerged); | |
348c7335 VM |
181 | |
182 | free(index->index_file_path); | |
183 | free(index); | |
184 | } | |
185 | ||
68535125 VM |
186 | void git_index_clear(git_index *index) |
187 | { | |
188 | unsigned int i; | |
6fd195d7 VM |
189 | |
190 | assert(index); | |
191 | ||
c4034e63 VM |
192 | for (i = 0; i < index->entries.length; ++i) { |
193 | git_index_entry *e; | |
194 | e = git_vector_get(&index->entries, i); | |
7d9cc9f8 | 195 | free(e->path); |
c4034e63 VM |
196 | free(e); |
197 | } | |
68535125 | 198 | |
71da57ae CMN |
199 | for (i = 0; i < index->unmerged.length; ++i) { |
200 | git_index_entry_unmerged *e; | |
201 | e = git_vector_get(&index->unmerged, i); | |
7d9cc9f8 | 202 | free(e->path); |
71da57ae CMN |
203 | free(e); |
204 | } | |
205 | ||
c4034e63 | 206 | git_vector_clear(&index->entries); |
71da57ae | 207 | git_vector_clear(&index->unmerged); |
68535125 | 208 | index->last_modified = 0; |
68535125 | 209 | |
b4171320 | 210 | git_tree_cache_free(index->tree); |
68535125 VM |
211 | index->tree = NULL; |
212 | } | |
213 | ||
68535125 VM |
214 | int git_index_read(git_index *index) |
215 | { | |
7db40d45 CMN |
216 | int error = GIT_SUCCESS, updated; |
217 | git_fbuffer buffer = GIT_FBUFFER_INIT; | |
218 | time_t mtime; | |
68535125 | 219 | |
1795f879 | 220 | assert(index->index_file_path); |
68535125 | 221 | |
f79026b4 | 222 | if (!index->on_disk || git_futils_exists(index->index_file_path) < 0) { |
68535125 VM |
223 | git_index_clear(index); |
224 | index->on_disk = 0; | |
6f02c3ba | 225 | return GIT_SUCCESS; |
68535125 VM |
226 | } |
227 | ||
7db40d45 CMN |
228 | /* We don't want to update the mtime if we fail to parse the index */ |
229 | mtime = index->last_modified; | |
230 | error = git_futils_readbuffer_updated(&buffer, index->index_file_path, &mtime, &updated); | |
231 | if (error < GIT_SUCCESS) | |
232 | return git__rethrow(error, "Failed to read index"); | |
68535125 | 233 | |
7db40d45 | 234 | if (updated) { |
68535125 | 235 | git_index_clear(index); |
348c7335 | 236 | error = parse_index(index, buffer.data, buffer.len); |
68535125 | 237 | |
6f02c3ba | 238 | if (error == GIT_SUCCESS) |
7db40d45 | 239 | index->last_modified = mtime; |
68535125 | 240 | |
f79026b4 | 241 | git_futils_freebuffer(&buffer); |
68535125 VM |
242 | } |
243 | ||
c90bfec7 | 244 | if (error < GIT_SUCCESS) |
7db40d45 | 245 | return git__rethrow(error, "Failed to parse index"); |
68535125 VM |
246 | return error; |
247 | } | |
248 | ||
249 | int git_index_write(git_index *index) | |
250 | { | |
817c2820 | 251 | git_filebuf file; |
68535125 | 252 | struct stat indexst; |
348c7335 | 253 | int error; |
68535125 | 254 | |
fad6607a | 255 | git_vector_sort(&index->entries); |
68535125 | 256 | |
817c2820 | 257 | if ((error = git_filebuf_open(&file, index->index_file_path, GIT_FILEBUF_HASH_CONTENTS)) < GIT_SUCCESS) |
c90bfec7 | 258 | return git__rethrow(error, "Failed to write index"); |
68535125 | 259 | |
348c7335 | 260 | if ((error = write_index(index, &file)) < GIT_SUCCESS) { |
817c2820 | 261 | git_filebuf_cleanup(&file); |
c90bfec7 | 262 | return git__rethrow(error, "Failed to write index"); |
68535125 VM |
263 | } |
264 | ||
817c2820 | 265 | if ((error = git_filebuf_commit(&file)) < GIT_SUCCESS) |
9d27fd3b | 266 | return git__rethrow(error, "Failed to write index"); |
68535125 | 267 | |
f79026b4 | 268 | if (p_stat(index->index_file_path, &indexst) == 0) { |
68535125 VM |
269 | index->last_modified = indexst.st_mtime; |
270 | index->on_disk = 1; | |
271 | } | |
272 | ||
6f02c3ba | 273 | return GIT_SUCCESS; |
68535125 VM |
274 | } |
275 | ||
0be42199 SC |
276 | unsigned int git_index_entrycount(git_index *index) |
277 | { | |
278 | assert(index); | |
c4034e63 | 279 | return index->entries.length; |
0be42199 SC |
280 | } |
281 | ||
f4e2aca2 | 282 | unsigned int git_index_entrycount_unmerged(git_index *index) |
4c0b6a6d JP |
283 | { |
284 | assert(index); | |
4c0b6a6d JP |
285 | return index->unmerged.length; |
286 | } | |
287 | ||
f11e0797 | 288 | git_index_entry *git_index_get(git_index *index, unsigned int n) |
68535125 | 289 | { |
86d7e1ca | 290 | git_vector_sort(&index->entries); |
fad6607a | 291 | return git_vector_get(&index->entries, n); |
68535125 VM |
292 | } |
293 | ||
76159921 KS |
294 | static int index_entry_init(git_index_entry **entry_out, git_index *index, const char *rel_path, int stage) |
295 | { | |
296 | git_index_entry *entry; | |
297 | char full_path[GIT_PATH_MAX]; | |
298 | struct stat st; | |
299 | git_oid oid; | |
300 | int error; | |
301 | ||
302 | if (index->repository == NULL) | |
303 | return git__throw(GIT_EBAREINDEX, "Failed to initialize entry. Repository is bare"); | |
304 | ||
305 | git_path_join(full_path, index->repository->path_workdir, rel_path); | |
306 | ||
307 | if (p_lstat(full_path, &st) < 0) | |
308 | return git__throw(GIT_ENOTFOUND, "Failed to initialize entry. '%s' cannot be opened", full_path); | |
309 | ||
310 | if (stage < 0 || stage > 3) | |
311 | return git__throw(GIT_ERROR, "Failed to initialize entry. Invalid stage %i", stage); | |
312 | ||
313 | /* write the blob to disk and get the oid */ | |
314 | if ((error = git_blob_create_fromfile(&oid, index->repository, rel_path)) < GIT_SUCCESS) | |
315 | return git__rethrow(error, "Failed to initialize index entry"); | |
316 | ||
317 | entry = git__malloc(sizeof(git_index_entry)); | |
318 | if (!entry) | |
319 | return GIT_ENOMEM; | |
320 | memset(entry, 0x0, sizeof(git_index_entry)); | |
321 | ||
322 | entry->ctime.seconds = (git_time_t)st.st_ctime; | |
323 | entry->mtime.seconds = (git_time_t)st.st_mtime; | |
324 | /* entry.mtime.nanoseconds = st.st_mtimensec; */ | |
325 | /* entry.ctime.nanoseconds = st.st_ctimensec; */ | |
326 | entry->dev= st.st_rdev; | |
327 | entry->ino = st.st_ino; | |
328 | entry->mode = index_create_mode(st.st_mode); | |
329 | entry->uid = st.st_uid; | |
330 | entry->gid = st.st_gid; | |
331 | entry->file_size = st.st_size; | |
332 | entry->oid = oid; | |
333 | ||
334 | entry->flags |= (stage << GIT_IDXENTRY_STAGESHIFT); | |
335 | entry->path = git__strdup(rel_path); | |
336 | if (entry->path == NULL) { | |
337 | free(entry); | |
338 | return GIT_ENOMEM; | |
339 | } | |
340 | ||
341 | *entry_out = entry; | |
342 | return GIT_SUCCESS; | |
343 | } | |
344 | ||
51917d9c KS |
345 | static git_index_entry *index_entry_dup(const git_index_entry *source_entry) |
346 | { | |
347 | git_index_entry *entry; | |
348 | ||
349 | entry = git__malloc(sizeof(git_index_entry)); | |
350 | if (!entry) | |
351 | return NULL; | |
352 | ||
353 | memcpy(entry, source_entry, sizeof(git_index_entry)); | |
354 | ||
355 | /* duplicate the path string so we own it */ | |
356 | entry->path = git__strdup(entry->path); | |
357 | if (!entry->path) | |
358 | return NULL; | |
359 | ||
360 | return entry; | |
361 | } | |
362 | ||
b2dd6815 KS |
363 | static void index_entry_free(git_index_entry *entry) |
364 | { | |
365 | if (!entry) | |
366 | return; | |
367 | free(entry->path); | |
368 | free(entry); | |
369 | } | |
370 | ||
f939d39b | 371 | static int index_insert(git_index *index, git_index_entry *entry, int replace) |
68535125 | 372 | { |
c3a20d5c VM |
373 | size_t path_length; |
374 | int position; | |
8cc16e29 | 375 | git_index_entry **entry_array; |
68535125 | 376 | |
f939d39b | 377 | assert(index && entry); |
68535125 | 378 | |
f939d39b | 379 | if (entry->path == NULL) |
c90bfec7 | 380 | return git__throw(GIT_EMISSINGOBJDATA, "Failed to insert into index. Entry has no path"); |
68535125 | 381 | |
c3a20d5c | 382 | /* make sure that the path length flag is correct */ |
c4034e63 | 383 | path_length = strlen(entry->path); |
c3a20d5c | 384 | |
c4034e63 | 385 | entry->flags &= ~GIT_IDXENTRY_NAMEMASK; |
c3a20d5c VM |
386 | |
387 | if (path_length < GIT_IDXENTRY_NAMEMASK) | |
c4034e63 | 388 | entry->flags |= path_length & GIT_IDXENTRY_NAMEMASK; |
c3a20d5c | 389 | else |
c4034e63 | 390 | entry->flags |= GIT_IDXENTRY_NAMEMASK;; |
c3a20d5c | 391 | |
8cc16e29 KS |
392 | /* |
393 | * replacing is not requested: just insert entry at the end; | |
394 | * the index is no longer sorted | |
395 | */ | |
da5b1e1c VM |
396 | if (!replace) { |
397 | if (git_vector_insert(&index->entries, entry) < GIT_SUCCESS) | |
f939d39b | 398 | return GIT_ENOMEM; |
da5b1e1c VM |
399 | |
400 | return GIT_SUCCESS; | |
401 | } | |
68535125 | 402 | |
c4034e63 | 403 | /* look if an entry with this path already exists */ |
f939d39b | 404 | position = git_index_find(index, entry->path); |
68535125 | 405 | |
8cc16e29 KS |
406 | /* |
407 | * if no entry exists add the entry at the end; | |
408 | * the index is no longer sorted | |
409 | */ | |
da5b1e1c VM |
410 | if (position == GIT_ENOTFOUND) { |
411 | if (git_vector_insert(&index->entries, entry) < GIT_SUCCESS) | |
f939d39b | 412 | return GIT_ENOMEM; |
da5b1e1c VM |
413 | |
414 | return GIT_SUCCESS; | |
415 | } | |
8cc16e29 KS |
416 | |
417 | /* exists, replace it */ | |
418 | entry_array = (git_index_entry **) index->entries.contents; | |
76159921 KS |
419 | free(entry_array[position]->path); |
420 | free(entry_array[position]); | |
8cc16e29 | 421 | entry_array[position] = entry; |
68535125 | 422 | |
3f43678e | 423 | return GIT_SUCCESS; |
68535125 VM |
424 | } |
425 | ||
76159921 | 426 | static int index_add(git_index *index, const char *path, int stage, int replace) |
f7a5058a | 427 | { |
76159921 KS |
428 | git_index_entry *entry = NULL; |
429 | int ret; | |
f7a5058a | 430 | |
76159921 KS |
431 | ret = index_entry_init(&entry, index, path, stage); |
432 | if (ret) | |
433 | goto err; | |
f7a5058a | 434 | |
76159921 KS |
435 | ret = index_insert(index, entry, replace); |
436 | if (ret) | |
437 | goto err; | |
f7a5058a | 438 | |
e23ede0d CMN |
439 | git_tree_cache_invalidate_path(index->tree, entry->path); |
440 | ||
76159921 KS |
441 | return ret; |
442 | err: | |
443 | index_entry_free(entry); | |
444 | return git__rethrow(ret, "Failed to append to index"); | |
f7a5058a VM |
445 | } |
446 | ||
447 | int git_index_add(git_index *index, const char *path, int stage) | |
448 | { | |
76159921 | 449 | return index_add(index, path, stage, 1); |
f7a5058a VM |
450 | } |
451 | ||
452 | int git_index_append(git_index *index, const char *path, int stage) | |
453 | { | |
76159921 | 454 | return index_add(index, path, stage, 0); |
f939d39b KS |
455 | } |
456 | ||
457 | static int index_add2(git_index *index, const git_index_entry *source_entry, | |
458 | int replace) | |
459 | { | |
460 | git_index_entry *entry = NULL; | |
461 | int ret; | |
462 | ||
463 | entry = index_entry_dup(source_entry); | |
464 | if (entry == NULL) { | |
465 | ret = GIT_ENOMEM; | |
466 | goto err; | |
467 | } | |
468 | ||
469 | ret = index_insert(index, entry, replace); | |
470 | if (ret) | |
471 | goto err; | |
472 | ||
e23ede0d CMN |
473 | git_tree_cache_invalidate_path(index->tree, entry->path); |
474 | ||
f939d39b KS |
475 | return ret; |
476 | err: | |
477 | index_entry_free(entry); | |
478 | return git__rethrow(ret, "Failed to append to index"); | |
f7a5058a VM |
479 | } |
480 | ||
481 | int git_index_add2(git_index *index, const git_index_entry *source_entry) | |
482 | { | |
f939d39b | 483 | return index_add2(index, source_entry, 1); |
f7a5058a VM |
484 | } |
485 | ||
773bc20d | 486 | int git_index_append2(git_index *index, const git_index_entry *source_entry) |
f7a5058a | 487 | { |
f939d39b | 488 | return index_add2(index, source_entry, 1); |
f7a5058a VM |
489 | } |
490 | ||
c4034e63 | 491 | int git_index_remove(git_index *index, int position) |
68535125 | 492 | { |
b419fe2d CMN |
493 | git_index_entry *entry; |
494 | ||
fad6607a | 495 | git_vector_sort(&index->entries); |
b419fe2d CMN |
496 | entry = git_vector_get(&index->entries, position); |
497 | if (entry != NULL) | |
498 | git_tree_cache_invalidate_path(index->tree, entry->path); | |
499 | ||
c4034e63 VM |
500 | return git_vector_remove(&index->entries, (unsigned int)position); |
501 | } | |
68535125 | 502 | |
c4034e63 VM |
503 | int git_index_find(git_index *index, const char *path) |
504 | { | |
86d7e1ca | 505 | return git_vector_bsearch2(&index->entries, index_srch, path); |
68535125 VM |
506 | } |
507 | ||
245adf4f KS |
508 | void git_index_uniq(git_index *index) |
509 | { | |
510 | git_vector_uniq(&index->entries); | |
511 | } | |
512 | ||
f11e0797 | 513 | const git_index_entry_unmerged *git_index_get_unmerged_bypath(git_index *index, const char *path) |
4c0b6a6d JP |
514 | { |
515 | int pos; | |
f4e2aca2 | 516 | assert(index && path); |
4c0b6a6d | 517 | |
f4e2aca2 VM |
518 | if (!index->unmerged.length) |
519 | return NULL; | |
4c0b6a6d | 520 | |
f4e2aca2 VM |
521 | if ((pos = git_vector_bsearch2(&index->unmerged, unmerged_srch, path)) < GIT_SUCCESS) |
522 | return NULL; | |
4c0b6a6d | 523 | |
f4e2aca2 | 524 | return git_vector_get(&index->unmerged, pos); |
4c0b6a6d JP |
525 | } |
526 | ||
f11e0797 RG |
527 | const git_index_entry_unmerged *git_index_get_unmerged_byindex(git_index *index, unsigned int n) |
528 | { | |
529 | assert(index); | |
530 | return git_vector_get(&index->unmerged, n); | |
531 | } | |
532 | ||
f4e2aca2 | 533 | static int read_unmerged(git_index *index, const char *buffer, size_t size) |
4c0b6a6d | 534 | { |
f4e2aca2 VM |
535 | const char *endptr; |
536 | size_t len; | |
4c0b6a6d JP |
537 | int i; |
538 | ||
f4e2aca2 | 539 | git_vector_init(&index->unmerged, 16, unmerged_cmp); |
4c0b6a6d JP |
540 | |
541 | while (size) { | |
542 | git_index_entry_unmerged *lost; | |
543 | ||
4c0b6a6d JP |
544 | len = strlen(buffer) + 1; |
545 | if (size <= len) | |
d320c52d | 546 | return git__throw(GIT_ERROR, "Failed to read unmerged entries"); |
4c0b6a6d JP |
547 | |
548 | if ((lost = git__malloc(sizeof(git_index_entry_unmerged))) == NULL) | |
f4e2aca2 | 549 | return GIT_ENOMEM; |
4c0b6a6d JP |
550 | |
551 | if (git_vector_insert(&index->unmerged, lost) < GIT_SUCCESS) | |
d320c52d | 552 | return git__throw(GIT_ERROR, "Failed to read unmerged entries"); |
4c0b6a6d | 553 | |
f4e2aca2 VM |
554 | lost->path = git__strdup(buffer); |
555 | if (!lost->path) | |
556 | return GIT_ENOMEM; | |
557 | ||
4c0b6a6d JP |
558 | size -= len; |
559 | buffer += len; | |
560 | ||
561 | for (i = 0; i < 3; i++) { | |
ad196c6a | 562 | int tmp; |
b16692fa KS |
563 | |
564 | if (git__strtol32(&tmp, buffer, &endptr, 8) < GIT_SUCCESS || | |
c035ede2 | 565 | !endptr || endptr == buffer || *endptr || (unsigned)tmp > UINT_MAX) |
4c0b6a6d | 566 | return GIT_ERROR; |
f4e2aca2 | 567 | |
b16692fa KS |
568 | lost->mode[i] = tmp; |
569 | ||
ae9f771c | 570 | len = (endptr + 1) - buffer; |
4c0b6a6d | 571 | if (size <= len) |
d320c52d | 572 | return git__throw(GIT_ERROR, "Failed to read unmerged entries"); |
f4e2aca2 | 573 | |
4c0b6a6d JP |
574 | size -= len; |
575 | buffer += len; | |
576 | } | |
577 | ||
578 | for (i = 0; i < 3; i++) { | |
579 | if (!lost->mode[i]) | |
580 | continue; | |
581 | if (size < 20) | |
d320c52d | 582 | return git__throw(GIT_ERROR, "Failed to read unmerged entries"); |
7d9cc9f8 | 583 | git_oid_fromraw(&lost->oid[i], (const unsigned char *) buffer); |
4c0b6a6d JP |
584 | size -= 20; |
585 | buffer += 20; | |
586 | } | |
587 | } | |
588 | ||
4c0b6a6d JP |
589 | return GIT_SUCCESS; |
590 | } | |
591 | ||
68535125 VM |
592 | static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffer_size) |
593 | { | |
594 | size_t path_length, entry_size; | |
595 | uint16_t flags_raw; | |
596 | const char *path_ptr; | |
ae9f771c | 597 | const struct entry_short *source = buffer; |
68535125 VM |
598 | |
599 | if (INDEX_FOOTER_SIZE + minimal_entry_size > buffer_size) | |
600 | return 0; | |
601 | ||
91e88941 VM |
602 | memset(dest, 0x0, sizeof(git_index_entry)); |
603 | ||
56d8ca26 | 604 | dest->ctime.seconds = (git_time_t)ntohl(source->ctime.seconds); |
605 | dest->ctime.nanoseconds = ntohl(source->ctime.nanoseconds); | |
606 | dest->mtime.seconds = (git_time_t)ntohl(source->mtime.seconds); | |
607 | dest->mtime.nanoseconds = ntohl(source->mtime.nanoseconds); | |
68535125 VM |
608 | dest->dev = ntohl(source->dev); |
609 | dest->ino = ntohl(source->ino); | |
610 | dest->mode = ntohl(source->mode); | |
611 | dest->uid = ntohl(source->uid); | |
612 | dest->gid = ntohl(source->gid); | |
613 | dest->file_size = ntohl(source->file_size); | |
614 | git_oid_cpy(&dest->oid, &source->oid); | |
615 | dest->flags = ntohs(source->flags); | |
616 | ||
617 | if (dest->flags & GIT_IDXENTRY_EXTENDED) { | |
7d9cc9f8 | 618 | const struct entry_long *source_l = (const struct entry_long *)source; |
68535125 VM |
619 | path_ptr = source_l->path; |
620 | ||
621 | flags_raw = ntohs(source_l->flags_extended); | |
622 | memcpy(&dest->flags_extended, &flags_raw, 2); | |
623 | } else | |
624 | path_ptr = source->path; | |
625 | ||
626 | path_length = dest->flags & GIT_IDXENTRY_NAMEMASK; | |
627 | ||
628 | /* if this is a very long string, we must find its | |
629 | * real length without overflowing */ | |
630 | if (path_length == 0xFFF) { | |
631 | const char *path_end; | |
632 | ||
633 | path_end = memchr(path_ptr, '\0', buffer_size); | |
634 | if (path_end == NULL) | |
d320c52d | 635 | return 0; |
68535125 VM |
636 | |
637 | path_length = path_end - path_ptr; | |
638 | } | |
639 | ||
640 | if (dest->flags & GIT_IDXENTRY_EXTENDED) | |
641 | entry_size = long_entry_size(path_length); | |
642 | else | |
643 | entry_size = short_entry_size(path_length); | |
644 | ||
645 | if (INDEX_FOOTER_SIZE + entry_size > buffer_size) | |
646 | return 0; | |
647 | ||
648 | dest->path = git__strdup(path_ptr); | |
c3a20d5c | 649 | assert(dest->path); |
68535125 VM |
650 | |
651 | return entry_size; | |
652 | } | |
653 | ||
654 | static int read_header(struct index_header *dest, const void *buffer) | |
655 | { | |
ae9f771c | 656 | const struct index_header *source = buffer; |
68535125 | 657 | |
348c7335 VM |
658 | dest->signature = ntohl(source->signature); |
659 | if (dest->signature != INDEX_HEADER_SIG) | |
8146fe7c | 660 | return GIT_EOBJCORRUPTED; |
68535125 VM |
661 | |
662 | dest->version = ntohl(source->version); | |
348c7335 VM |
663 | if (dest->version != INDEX_VERSION_NUMBER_EXT && |
664 | dest->version != INDEX_VERSION_NUMBER) | |
8146fe7c | 665 | return GIT_EOBJCORRUPTED; |
68535125 VM |
666 | |
667 | dest->entry_count = ntohl(source->entry_count); | |
6f02c3ba | 668 | return GIT_SUCCESS; |
68535125 VM |
669 | } |
670 | ||
671 | static size_t read_extension(git_index *index, const char *buffer, size_t buffer_size) | |
672 | { | |
673 | const struct index_extension *source; | |
674 | struct index_extension dest; | |
675 | size_t total_size; | |
676 | ||
677 | source = (const struct index_extension *)(buffer); | |
678 | ||
679 | memcpy(dest.signature, source->signature, 4); | |
680 | dest.extension_size = ntohl(source->extension_size); | |
681 | ||
682 | total_size = dest.extension_size + sizeof(struct index_extension); | |
683 | ||
684 | if (buffer_size - total_size < INDEX_FOOTER_SIZE) | |
685 | return 0; | |
686 | ||
687 | /* optional extension */ | |
688 | if (dest.signature[0] >= 'A' && dest.signature[0] <= 'Z') { | |
689 | /* tree cache */ | |
690 | if (memcmp(dest.signature, INDEX_EXT_TREECACHE_SIG, 4) == 0) { | |
b4171320 | 691 | if (git_tree_cache_read(&index->tree, buffer + 8, dest.extension_size) < GIT_SUCCESS) |
68535125 | 692 | return 0; |
4c0b6a6d | 693 | } else if (memcmp(dest.signature, INDEX_EXT_UNMERGED_SIG, 4) == 0) { |
4c0b6a6d JP |
694 | if (read_unmerged(index, buffer + 8, dest.extension_size) < GIT_SUCCESS) |
695 | return 0; | |
68535125 | 696 | } |
f4e2aca2 VM |
697 | /* else, unsupported extension. We cannot parse this, but we can skip |
698 | * it by returning `total_size */ | |
68535125 VM |
699 | } else { |
700 | /* we cannot handle non-ignorable extensions; | |
701 | * in fact they aren't even defined in the standard */ | |
702 | return 0; | |
703 | } | |
704 | ||
705 | return total_size; | |
706 | } | |
707 | ||
348c7335 | 708 | static int parse_index(git_index *index, const char *buffer, size_t buffer_size) |
68535125 VM |
709 | { |
710 | unsigned int i; | |
711 | struct index_header header; | |
712 | git_oid checksum_calculated, checksum_expected; | |
713 | ||
714 | #define seek_forward(_increase) { \ | |
715 | if (_increase >= buffer_size) \ | |
d320c52d | 716 | return git__throw(GIT_EOBJCORRUPTED, "Failed to seek forward. Buffer size exceeded"); \ |
68535125 VM |
717 | buffer += _increase; \ |
718 | buffer_size -= _increase;\ | |
719 | } | |
720 | ||
721 | if (buffer_size < INDEX_HEADER_SIZE + INDEX_FOOTER_SIZE) | |
d320c52d | 722 | return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Buffer too small"); |
68535125 VM |
723 | |
724 | /* Precalculate the SHA1 of the files's contents -- we'll match it to | |
725 | * the provided SHA1 in the footer */ | |
ae9f771c | 726 | git_hash_buf(&checksum_calculated, buffer, buffer_size - INDEX_FOOTER_SIZE); |
68535125 VM |
727 | |
728 | /* Parse header */ | |
6f02c3ba | 729 | if (read_header(&header, buffer) < GIT_SUCCESS) |
d320c52d | 730 | return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Header is corrupted"); |
68535125 VM |
731 | |
732 | seek_forward(INDEX_HEADER_SIZE); | |
733 | ||
c4034e63 | 734 | git_vector_clear(&index->entries); |
68535125 VM |
735 | |
736 | /* Parse all the entries */ | |
c4034e63 | 737 | for (i = 0; i < header.entry_count && buffer_size > INDEX_FOOTER_SIZE; ++i) { |
68535125 | 738 | size_t entry_size; |
c4034e63 VM |
739 | git_index_entry *entry; |
740 | ||
741 | entry = git__malloc(sizeof(git_index_entry)); | |
742 | if (entry == NULL) | |
743 | return GIT_ENOMEM; | |
744 | ||
745 | entry_size = read_entry(entry, buffer, buffer_size); | |
68535125 VM |
746 | |
747 | /* 0 bytes read means an object corruption */ | |
748 | if (entry_size == 0) | |
d320c52d | 749 | return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Entry size is zero"); |
68535125 | 750 | |
6f02c3ba | 751 | if (git_vector_insert(&index->entries, entry) < GIT_SUCCESS) |
c4034e63 VM |
752 | return GIT_ENOMEM; |
753 | ||
68535125 VM |
754 | seek_forward(entry_size); |
755 | } | |
756 | ||
c4034e63 | 757 | if (i != header.entry_count) |
d320c52d | 758 | return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Header entries changed while parsing"); |
c3a20d5c | 759 | |
68535125 VM |
760 | /* There's still space for some extensions! */ |
761 | while (buffer_size > INDEX_FOOTER_SIZE) { | |
762 | size_t extension_size; | |
763 | ||
764 | extension_size = read_extension(index, buffer, buffer_size); | |
765 | ||
766 | /* see if we have read any bytes from the extension */ | |
767 | if (extension_size == 0) | |
d320c52d | 768 | return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Extension size is zero"); |
68535125 VM |
769 | |
770 | seek_forward(extension_size); | |
771 | } | |
772 | ||
773 | if (buffer_size != INDEX_FOOTER_SIZE) | |
d320c52d | 774 | return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Buffer size does not match index footer size"); |
68535125 VM |
775 | |
776 | /* 160-bit SHA-1 over the content of the index file before this checksum. */ | |
fa48608e | 777 | git_oid_fromraw(&checksum_expected, (const unsigned char *)buffer); |
68535125 VM |
778 | |
779 | if (git_oid_cmp(&checksum_calculated, &checksum_expected) != 0) | |
d320c52d | 780 | return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Calculated checksum does not match expected checksum"); |
68535125 VM |
781 | |
782 | #undef seek_forward | |
783 | ||
86d7e1ca VM |
784 | /* force sorting in the vector: the entries are |
785 | * assured to be sorted on the index */ | |
786 | index->entries.sorted = 1; | |
6f02c3ba | 787 | return GIT_SUCCESS; |
68535125 VM |
788 | } |
789 | ||
1648fbd3 VM |
790 | static int is_index_extended(git_index *index) |
791 | { | |
792 | unsigned int i, extended; | |
793 | ||
794 | extended = 0; | |
795 | ||
796 | for (i = 0; i < index->entries.length; ++i) { | |
797 | git_index_entry *entry; | |
798 | entry = git_vector_get(&index->entries, i); | |
799 | entry->flags &= ~GIT_IDXENTRY_EXTENDED; | |
800 | if (entry->flags_extended & GIT_IDXENTRY_EXTENDED_FLAGS) { | |
801 | extended++; | |
802 | entry->flags |= GIT_IDXENTRY_EXTENDED; | |
803 | } | |
804 | } | |
805 | return extended; | |
806 | } | |
807 | ||
817c2820 | 808 | static int write_disk_entry(git_filebuf *file, git_index_entry *entry) |
68535125 | 809 | { |
356f11fe | 810 | void *mem = NULL; |
348c7335 | 811 | struct entry_short *ondisk; |
817c2820 | 812 | size_t path_len, disk_size; |
348c7335 | 813 | char *path; |
68535125 | 814 | |
348c7335 | 815 | path_len = strlen(entry->path); |
68535125 | 816 | |
348c7335 | 817 | if (entry->flags & GIT_IDXENTRY_EXTENDED) |
817c2820 | 818 | disk_size = long_entry_size(path_len); |
348c7335 | 819 | else |
817c2820 | 820 | disk_size = short_entry_size(path_len); |
68535125 | 821 | |
356f11fe | 822 | if (git_filebuf_reserve(file, &mem, disk_size) < GIT_SUCCESS) |
817c2820 VM |
823 | return GIT_ENOMEM; |
824 | ||
356f11fe VM |
825 | ondisk = (struct entry_short *)mem; |
826 | ||
817c2820 | 827 | memset(ondisk, 0x0, disk_size); |
68535125 | 828 | |
3dd26d1e VM |
829 | /** |
830 | * Yes, we have to truncate. | |
831 | * | |
832 | * The on-disk format for Index entries clearly defines | |
833 | * the time and size fields to be 4 bytes each -- so even if | |
834 | * we store these values with 8 bytes on-memory, they must | |
835 | * be truncated to 4 bytes before writing to disk. | |
836 | * | |
837 | * In 2038 I will be either too dead or too rich to care about this | |
838 | */ | |
839 | ondisk->ctime.seconds = htonl((uint32_t)entry->ctime.seconds); | |
840 | ondisk->mtime.seconds = htonl((uint32_t)entry->mtime.seconds); | |
348c7335 VM |
841 | ondisk->ctime.nanoseconds = htonl(entry->ctime.nanoseconds); |
842 | ondisk->mtime.nanoseconds = htonl(entry->mtime.nanoseconds); | |
87d9869f VM |
843 | ondisk->dev = htonl(entry->dev); |
844 | ondisk->ino = htonl(entry->ino); | |
348c7335 | 845 | ondisk->mode = htonl(entry->mode); |
87d9869f VM |
846 | ondisk->uid = htonl(entry->uid); |
847 | ondisk->gid = htonl(entry->gid); | |
3dd26d1e | 848 | ondisk->file_size = htonl((uint32_t)entry->file_size); |
348c7335 VM |
849 | |
850 | git_oid_cpy(&ondisk->oid, &entry->oid); | |
851 | ||
852 | ondisk->flags = htons(entry->flags); | |
853 | ||
854 | if (entry->flags & GIT_IDXENTRY_EXTENDED) { | |
855 | struct entry_long *ondisk_ext; | |
856 | ondisk_ext = (struct entry_long *)ondisk; | |
857 | ondisk_ext->flags_extended = htons(entry->flags_extended); | |
858 | path = ondisk_ext->path; | |
859 | } | |
860 | else | |
861 | path = ondisk->path; | |
862 | ||
863 | memcpy(path, entry->path, path_len); | |
68535125 | 864 | |
e822508a | 865 | return GIT_SUCCESS; |
68535125 VM |
866 | } |
867 | ||
817c2820 | 868 | static int write_entries(git_index *index, git_filebuf *file) |
348c7335 VM |
869 | { |
870 | unsigned int i; | |
68535125 | 871 | |
c4034e63 | 872 | for (i = 0; i < index->entries.length; ++i) { |
68535125 | 873 | git_index_entry *entry; |
c4034e63 | 874 | entry = git_vector_get(&index->entries, i); |
817c2820 | 875 | if (write_disk_entry(file, entry) < GIT_SUCCESS) |
348c7335 | 876 | return GIT_ENOMEM; |
68535125 VM |
877 | } |
878 | ||
348c7335 VM |
879 | return GIT_SUCCESS; |
880 | } | |
881 | ||
817c2820 | 882 | static int write_index(git_index *index, git_filebuf *file) |
348c7335 VM |
883 | { |
884 | int error = GIT_SUCCESS; | |
348c7335 VM |
885 | git_oid hash_final; |
886 | ||
887 | struct index_header header; | |
888 | ||
1648fbd3 | 889 | int is_extended; |
348c7335 | 890 | |
817c2820 | 891 | assert(index && file); |
348c7335 | 892 | |
1648fbd3 VM |
893 | is_extended = is_index_extended(index); |
894 | ||
348c7335 | 895 | header.signature = htonl(INDEX_HEADER_SIG); |
1648fbd3 | 896 | header.version = htonl(is_extended ? INDEX_VERSION_NUMBER_EXT : INDEX_VERSION_NUMBER); |
348c7335 VM |
897 | header.entry_count = htonl(index->entries.length); |
898 | ||
817c2820 | 899 | git_filebuf_write(file, &header, sizeof(struct index_header)); |
348c7335 | 900 | |
817c2820 | 901 | error = write_entries(index, file); |
348c7335 | 902 | if (error < GIT_SUCCESS) |
d320c52d | 903 | return git__rethrow(error, "Failed to write index"); |
68535125 VM |
904 | |
905 | /* TODO: write extensions (tree cache) */ | |
906 | ||
817c2820 VM |
907 | /* get out the hash for all the contents we've appended to the file */ |
908 | git_filebuf_hash(&hash_final, file); | |
909 | ||
910 | /* write it at the end of the file */ | |
911 | git_filebuf_write(file, hash_final.id, GIT_OID_RAWSZ); | |
68535125 | 912 | |
d320c52d | 913 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write index"); |
68535125 | 914 | } |
3a42e0a3 VM |
915 | |
916 | int git_index_entry_stage(const git_index_entry *entry) | |
917 | { | |
918 | return (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT; | |
919 | } |