]> git.proxmox.com Git - libgit2.git/blame - src/tree.c
Simplify object table parse functions
[libgit2.git] / src / tree.c
CommitLineData
225fe215 1/*
359fc2d2 2 * Copyright (C) the libgit2 contributors. All rights reserved.
225fe215 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.
225fe215
VM
6 */
7
8#include "common.h"
9#include "commit.h"
225fe215 10#include "tree.h"
44908fe7
VM
11#include "git2/repository.h"
12#include "git2/object.h"
225fe215 13
c8f5ff8f 14#define DEFAULT_TREE_SIZE 16
9de27ad0 15#define MAX_FILEMODE_BYTES 6
c8f5ff8f 16
a7dbac0b 17static bool valid_filemode(const int filemode)
8e9bfa4c 18{
a7dbac0b 19 return (filemode == GIT_FILEMODE_TREE
20 || filemode == GIT_FILEMODE_BLOB
a7dbac0b 21 || filemode == GIT_FILEMODE_BLOB_EXECUTABLE
22 || filemode == GIT_FILEMODE_LINK
23 || filemode == GIT_FILEMODE_COMMIT);
0ad6efa1
VM
24}
25
f1c75b94
CMN
26GIT_INLINE(git_filemode_t) normalize_filemode(git_filemode_t filemode)
27{
28 /* Tree bits set, but it's not a commit */
29 if (filemode & GIT_FILEMODE_TREE && !(filemode & 0100000))
30 return GIT_FILEMODE_TREE;
31
32 /* If any of the x bits is set */
33 if (filemode & 0111)
34 return GIT_FILEMODE_BLOB_EXECUTABLE;
35
36 /* 16XXXX means commit */
37 if ((filemode & GIT_FILEMODE_COMMIT) == GIT_FILEMODE_COMMIT)
38 return GIT_FILEMODE_COMMIT;
39
40 /* 12XXXX means commit */
41 if ((filemode & GIT_FILEMODE_LINK) == GIT_FILEMODE_LINK)
42 return GIT_FILEMODE_LINK;
43
44 /* Otherwise, return a blob */
45 return GIT_FILEMODE_BLOB;
46}
47
28c1451a
VM
48static int valid_entry_name(const char *filename)
49{
cfeef7ce
RB
50 return *filename != '\0' &&
51 strchr(filename, '/') == NULL &&
52 (*filename != '.' ||
53 (strcmp(filename, ".") != 0 &&
54 strcmp(filename, "..") != 0 &&
55 strcmp(filename, DOT_GIT) != 0));
28c1451a
VM
56}
57
0c468633 58static int entry_sort_cmp(const void *a, const void *b)
28c1451a 59{
0c468633
RB
60 const git_tree_entry *e1 = (const git_tree_entry *)a;
61 const git_tree_entry *e2 = (const git_tree_entry *)b;
62
1744fafe 63 return git_path_cmp(
98527b5b 64 e1->filename, e1->filename_len, git_tree_entry__is_tree(e1),
0c468633
RB
65 e2->filename, e2->filename_len, git_tree_entry__is_tree(e2),
66 git__strncmp);
98527b5b
RB
67}
68
0c468633 69int git_tree_entry_cmp(const git_tree_entry *e1, const git_tree_entry *e2)
98527b5b 70{
0c468633 71 return entry_sort_cmp(e1, e2);
98527b5b
RB
72}
73
0c468633 74int git_tree_entry_icmp(const git_tree_entry *e1, const git_tree_entry *e2)
98527b5b 75{
0c468633
RB
76 return git_path_cmp(
77 e1->filename, e1->filename_len, git_tree_entry__is_tree(e1),
78 e2->filename, e2->filename_len, git_tree_entry__is_tree(e2),
79 git__strncasecmp);
28c1451a
VM
80}
81
0e2fcca8
VM
82static git_tree_entry *alloc_entry(const char *filename)
83{
84 git_tree_entry *entry = NULL;
85 size_t filename_len = strlen(filename);
86
87 entry = git__malloc(sizeof(git_tree_entry) + filename_len + 1);
88 if (!entry)
89 return NULL;
90
91 memset(entry, 0x0, sizeof(git_tree_entry));
92 memcpy(entry->filename, filename, filename_len);
93 entry->filename[filename_len] = 0;
94 entry->filename_len = filename_len;
95
96 return entry;
97}
28c1451a 98
761aa2aa
VM
99struct tree_key_search {
100 const char *filename;
101 size_t filename_len;
102};
103
28c1451a 104static int homing_search_cmp(const void *key, const void *array_member)
2a884588 105{
0cbbdc26
KS
106 const struct tree_key_search *ksearch = key;
107 const git_tree_entry *entry = array_member;
2a884588 108
28c1451a
VM
109 const size_t len1 = ksearch->filename_len;
110 const size_t len2 = entry->filename_len;
e6629d83 111
28c1451a
VM
112 return memcmp(
113 ksearch->filename,
114 entry->filename,
115 len1 < len2 ? len1 : len2
116 );
9de27ad0
SJ
117}
118
28c1451a
VM
119/*
120 * Search for an entry in a given tree.
121 *
122 * Note that this search is performed in two steps because
123 * of the way tree entries are sorted internally in git:
124 *
125 * Entries in a tree are not sorted alphabetically; two entries
126 * with the same root prefix will have different positions
127 * depending on whether they are folders (subtrees) or normal files.
128 *
129 * Consequently, it is not possible to find an entry on the tree
130 * with a binary search if you don't know whether the filename
131 * you're looking for is a folder or a normal file.
132 *
133 * To work around this, we first perform a homing binary search
134 * on the tree, using the minimal length root prefix of our filename.
135 * Once the comparisons for this homing search start becoming
136 * ambiguous because of folder vs file sorting, we look linearly
137 * around the area for our target file.
138 */
16248ee2 139static int tree_key_search(
11d9f6b3 140 size_t *at_pos, git_vector *entries, const char *filename, size_t filename_len)
2a884588 141{
28c1451a
VM
142 struct tree_key_search ksearch;
143 const git_tree_entry *entry;
11d9f6b3 144 size_t homing, i;
2a884588 145
28c1451a 146 ksearch.filename = filename;
0e2fcca8 147 ksearch.filename_len = filename_len;
e6629d83 148
28c1451a
VM
149 /* Initial homing search; find an entry on the tree with
150 * the same prefix as the filename we're looking for */
11d9f6b3
PK
151 if (git_vector_bsearch2(&homing, entries, &homing_search_cmp, &ksearch) < 0)
152 return GIT_ENOTFOUND;
28c1451a
VM
153
154 /* We found a common prefix. Look forward as long as
155 * there are entries that share the common prefix */
11d9f6b3 156 for (i = homing; i < entries->length; ++i) {
28c1451a
VM
157 entry = entries->contents[i];
158
181bbf14 159 if (homing_search_cmp(&ksearch, entry) < 0)
28c1451a
VM
160 break;
161
0e2fcca8 162 if (entry->filename_len == filename_len &&
11d9f6b3
PK
163 memcmp(filename, entry->filename, filename_len) == 0) {
164 if (at_pos)
165 *at_pos = i;
166
167 return 0;
168 }
8cf2de07 169 }
e6629d83 170
28c1451a
VM
171 /* If we haven't found our filename yet, look backwards
172 * too as long as we have entries with the same prefix */
11d9f6b3
PK
173 if (homing > 0) {
174 i = homing - 1;
e6629d83 175
11d9f6b3
PK
176 do {
177 entry = entries->contents[i];
e6629d83 178
11d9f6b3
PK
179 if (homing_search_cmp(&ksearch, entry) > 0)
180 break;
181
182 if (entry->filename_len == filename_len &&
183 memcmp(filename, entry->filename, filename_len) == 0) {
184 if (at_pos)
185 *at_pos = i;
186
187 return 0;
188 }
189 } while (i-- > 0);
28c1451a
VM
190 }
191
192 /* The filename doesn't exist at all */
904b67e6 193 return GIT_ENOTFOUND;
e6629d83
VM
194}
195
0e2fcca8
VM
196void git_tree_entry_free(git_tree_entry *entry)
197{
1c3edb30 198 if (entry == NULL)
199 return;
200
0e2fcca8
VM
201 git__free(entry);
202}
203
46ea40d9 204git_tree_entry *git_tree_entry_dup(const git_tree_entry *entry)
0e2fcca8
VM
205{
206 size_t total_size;
207 git_tree_entry *copy;
208
209 assert(entry);
210
211 total_size = sizeof(git_tree_entry) + entry->filename_len + 1;
212
213 copy = git__malloc(total_size);
214 if (!copy)
215 return NULL;
216
217 memcpy(copy, entry, total_size);
e120123e 218
0e2fcca8
VM
219 return copy;
220}
221
78606263 222void git_tree__free(void *tree)
225fe215 223{
78606263 224 git_vector *entries = &((git_tree *)tree)->entries;
e2237179
RB
225 size_t i;
226 git_tree_entry *e;
003c2690 227
78606263 228 git_vector_foreach(entries, i, e)
0e2fcca8 229 git_tree_entry_free(e);
003c2690 230
78606263 231 git_vector_free(entries);
3286c408 232 git__free(tree);
225fe215
VM
233}
234
9950d27a 235const git_oid *git_tree_id(const git_tree *t)
d45b4a9a 236{
9950d27a
RB
237 return git_object_id((const git_object *)t);
238}
239
240git_repository *git_tree_owner(const git_tree *t)
241{
242 return git_object_owner((const git_object *)t);
225fe215
VM
243}
244
9d7ac675 245git_filemode_t git_tree_entry_filemode(const git_tree_entry *entry)
003c2690 246{
9d7ac675 247 return (git_filemode_t)entry->attr;
003c2690
VM
248}
249
0ad6efa1 250const char *git_tree_entry_name(const git_tree_entry *entry)
003c2690 251{
c4b5bedc 252 assert(entry);
2a884588
VM
253 return entry->filename;
254}
003c2690 255
0ad6efa1 256const git_oid *git_tree_entry_id(const git_tree_entry *entry)
2a884588 257{
c4b5bedc 258 assert(entry);
2a884588 259 return &entry->oid;
003c2690
VM
260}
261
ff9a4c13
RG
262git_otype git_tree_entry_type(const git_tree_entry *entry)
263{
264 assert(entry);
265
266 if (S_ISGITLINK(entry->attr))
267 return GIT_OBJ_COMMIT;
268 else if (S_ISDIR(entry->attr))
269 return GIT_OBJ_TREE;
270 else
271 return GIT_OBJ_BLOB;
272}
273
9d0011fd
VM
274int git_tree_entry_to_object(
275 git_object **object_out,
276 git_repository *repo,
277 const git_tree_entry *entry)
003c2690 278{
1795f879 279 assert(entry && object_out);
72a3fe42 280 return git_object_lookup(object_out, repo, &entry->oid, GIT_OBJ_ANY);
122c3405
VM
281}
282
16248ee2
RB
283static const git_tree_entry *entry_fromname(
284 git_tree *tree, const char *name, size_t name_len)
2a884588 285{
11d9f6b3
PK
286 size_t idx;
287
288 if (tree_key_search(&idx, &tree->entries, name, name_len) < 0)
c4034e63
VM
289 return NULL;
290
291 return git_vector_get(&tree->entries, idx);
2a884588
VM
292}
293
16248ee2
RB
294const git_tree_entry *git_tree_entry_byname(
295 git_tree *tree, const char *filename)
0e2fcca8
VM
296{
297 assert(tree && filename);
298 return entry_fromname(tree, filename, strlen(filename));
299}
300
16248ee2
RB
301const git_tree_entry *git_tree_entry_byindex(
302 git_tree *tree, size_t idx)
003c2690 303{
c4b5bedc 304 assert(tree);
e5c80097 305 return git_vector_get(&tree->entries, idx);
003c2690
VM
306}
307
16248ee2
RB
308const git_tree_entry *git_tree_entry_byoid(
309 const git_tree *tree, const git_oid *oid)
2031760c 310{
16248ee2
RB
311 size_t i;
312 const git_tree_entry *e;
2031760c
RB
313
314 assert(tree);
315
316 git_vector_foreach(&tree->entries, i, e) {
317 if (memcmp(&e->oid.id, &oid->id, sizeof(oid->id)) == 0)
318 return e;
319 }
320
321 return NULL;
322}
323
9d0011fd 324int git_tree__prefix_position(git_tree *tree, const char *path)
41a82592
RB
325{
326 git_vector *entries = &tree->entries;
327 struct tree_key_search ksearch;
16248ee2 328 size_t at_pos;
41a82592 329
91e7d263
RB
330 if (!path)
331 return 0;
332
41a82592
RB
333 ksearch.filename = path;
334 ksearch.filename_len = strlen(path);
335
336 /* Find tree entry with appropriate prefix */
11d9f6b3 337 git_vector_bsearch2(&at_pos, entries, &homing_search_cmp, &ksearch);
41a82592
RB
338
339 for (; at_pos < entries->length; ++at_pos) {
340 const git_tree_entry *entry = entries->contents[at_pos];
341 if (homing_search_cmp(&ksearch, entry) < 0)
342 break;
343 }
344
345 for (; at_pos > 0; --at_pos) {
346 const git_tree_entry *entry = entries->contents[at_pos - 1];
347 if (homing_search_cmp(&ksearch, entry) > 0)
348 break;
349 }
350
16248ee2 351 return (int)at_pos;
41a82592
RB
352}
353
e120123e 354size_t git_tree_entrycount(const git_tree *tree)
003c2690 355{
c4b5bedc 356 assert(tree);
e120123e 357 return tree->entries.length;
003c2690
VM
358}
359
5fb98206
JW
360unsigned int git_treebuilder_entrycount(git_treebuilder *bld)
361{
362 assert(bld);
93ab370b 363 return (unsigned int)bld->entrycount;
5fb98206
JW
364}
365
cfeef7ce 366static int tree_error(const char *str, const char *path)
d8603ed9 367{
cfeef7ce
RB
368 if (path)
369 giterr_set(GITERR_TREE, "%s - %s", str, path);
370 else
371 giterr_set(GITERR_TREE, "%s", str);
3aa351ea
CMN
372 return -1;
373}
2a884588 374
3f27127d 375int git_tree__parse(void *tree, git_odb_object *odb_obj)
3aa351ea 376{
3f27127d
RB
377 const char *buffer = git_odb_object_data(odb_obj);
378 const char *buffer_end = buffer + git_odb_object_size(odb_obj);
78606263
RB
379 git_vector *tree_entries = &((git_tree *)tree)->entries;
380
381 if (git_vector_init(tree_entries, DEFAULT_TREE_SIZE, entry_sort_cmp) < 0)
3aa351ea 382 return -1;
e4def81a 383
003c2690
VM
384 while (buffer < buffer_end) {
385 git_tree_entry *entry;
0e2fcca8 386 int attr;
d8603ed9 387
f1c75b94 388 if (git__strtol32(&attr, buffer, &buffer, 8) < 0 || !buffer)
cfeef7ce 389 return tree_error("Failed to parse tree. Can't parse filemode", NULL);
8e9bfa4c 390
f1c75b94
CMN
391 attr = normalize_filemode(attr); /* make sure to normalize the filemode */
392
3aa351ea 393 if (*buffer++ != ' ')
cfeef7ce 394 return tree_error("Failed to parse tree. Object is corrupted", NULL);
d8603ed9 395
3aa351ea 396 if (memchr(buffer, 0, buffer_end - buffer) == NULL)
cfeef7ce 397 return tree_error("Failed to parse tree. Object is corrupted", NULL);
58519018 398
0e2fcca8
VM
399 /** Allocate the entry and store it in the entries vector */
400 {
401 entry = alloc_entry(buffer);
402 GITERR_CHECK_ALLOC(entry);
403
78606263 404 if (git_vector_insert(tree_entries, entry) < 0) {
e2237179 405 git__free(entry);
0e2fcca8 406 return -1;
e2237179 407 }
0e2fcca8
VM
408
409 entry->attr = attr;
410 }
d8603ed9 411
2a884588
VM
412 while (buffer < buffer_end && *buffer != 0)
413 buffer++;
003c2690 414
2a884588 415 buffer++;
d8603ed9 416
fa48608e 417 git_oid_fromraw(&entry->oid, (const unsigned char *)buffer);
d8603ed9 418 buffer += GIT_OID_RAWSZ;
d8603ed9
VM
419 }
420
3aa351ea 421 return 0;
d8603ed9 422}
58519018 423
a8122b5d 424static size_t find_next_dir(const char *dirname, git_index *index, size_t start)
8255c69b 425{
a8122b5d 426 size_t dirlen, i, entries = git_index_entrycount(index);
8255c69b
CMN
427
428 dirlen = strlen(dirname);
429 for (i = start; i < entries; ++i) {
f45d51ff 430 const git_index_entry *entry = git_index_get_byindex(index, i);
8255c69b
CMN
431 if (strlen(entry->path) < dirlen ||
432 memcmp(entry->path, dirname, dirlen) ||
433 (dirlen > 0 && entry->path[dirlen] != '/')) {
434 break;
435 }
436 }
437
438 return i;
439}
440
0e2fcca8
VM
441static int append_entry(
442 git_treebuilder *bld,
443 const char *filename,
444 const git_oid *id,
a7dbac0b 445 git_filemode_t filemode)
9ef9e8c3
VM
446{
447 git_tree_entry *entry;
448
0d778b1a 449 if (!valid_entry_name(filename))
cfeef7ce 450 return tree_error("Failed to insert entry. Invalid name for a tree entry", filename);
0d778b1a 451
0e2fcca8 452 entry = alloc_entry(filename);
3aa351ea 453 GITERR_CHECK_ALLOC(entry);
9ef9e8c3 454
9ef9e8c3 455 git_oid_cpy(&entry->oid, id);
a7dbac0b 456 entry->attr = (uint16_t)filemode;
9ef9e8c3 457
e2237179
RB
458 if (git_vector_insert(&bld->entries, entry) < 0) {
459 git__free(entry);
3aa351ea 460 return -1;
e2237179 461 }
9ef9e8c3 462
93ab370b 463 bld->entrycount++;
3aa351ea 464 return 0;
9ef9e8c3
VM
465}
466
9462c471
VM
467static int write_tree(
468 git_oid *oid,
469 git_repository *repo,
470 git_index *index,
471 const char *dirname,
a8122b5d 472 size_t start)
47d8ec56 473{
4a619797 474 git_treebuilder *bld = NULL;
a8122b5d 475 size_t i, entries = git_index_entrycount(index);
4a619797
CMN
476 int error;
477 size_t dirname_len = strlen(dirname);
8255c69b
CMN
478 const git_tree_cache *cache;
479
480 cache = git_tree_cache_get(index->tree, dirname);
481 if (cache != NULL && cache->entries >= 0){
482 git_oid_cpy(oid, &cache->oid);
a8122b5d 483 return (int)find_next_dir(dirname, index, start);
8255c69b 484 }
29e1789b 485
a8122b5d 486 if ((error = git_treebuilder_create(&bld, NULL)) < 0 || bld == NULL)
3aa351ea 487 return -1;
932d1baf 488
4a619797
CMN
489 /*
490 * This loop is unfortunate, but necessary. The index doesn't have
491 * any directores, so we need to handle that manually, and we
492 * need to keep track of the current position.
493 */
494 for (i = start; i < entries; ++i) {
f45d51ff 495 const git_index_entry *entry = git_index_get_byindex(index, i);
16248ee2 496 const char *filename, *next_slash;
4a619797
CMN
497
498 /*
499 * If we've left our (sub)tree, exit the loop and return. The
500 * first check is an early out (and security for the
501 * third). The second check is a simple prefix comparison. The
502 * third check catches situations where there is a directory
503 * win32/sys and a file win32mmap.c. Without it, the following
504 * code believes there is a file win32/mmap.c
505 */
506 if (strlen(entry->path) < dirname_len ||
507 memcmp(entry->path, dirname, dirname_len) ||
508 (dirname_len > 0 && entry->path[dirname_len] != '/')) {
47d8ec56 509 break;
4a619797 510 }
932d1baf 511
4a619797
CMN
512 filename = entry->path + dirname_len;
513 if (*filename == '/')
514 filename++;
515 next_slash = strchr(filename, '/');
516 if (next_slash) {
517 git_oid sub_oid;
518 int written;
519 char *subdir, *last_comp;
520
521 subdir = git__strndup(entry->path, next_slash - entry->path);
3aa351ea 522 GITERR_CHECK_ALLOC(subdir);
29e1789b 523
4a619797 524 /* Write out the subtree */
9462c471 525 written = write_tree(&sub_oid, repo, index, subdir, i);
4a619797 526 if (written < 0) {
cfeef7ce 527 git__free(subdir);
3aa351ea 528 goto on_error;
4a619797
CMN
529 } else {
530 i = written - 1; /* -1 because of the loop increment */
29e1789b 531 }
932d1baf 532
4a619797
CMN
533 /*
534 * We need to figure out what we want toinsert
535 * into this tree. If we're traversing
536 * deps/zlib/, then we only want to write
537 * 'zlib' into the tree.
538 */
539 last_comp = strrchr(subdir, '/');
540 if (last_comp) {
541 last_comp++; /* Get rid of the '/' */
542 } else {
543 last_comp = subdir;
544 }
cfeef7ce 545
9ef9e8c3 546 error = append_entry(bld, last_comp, &sub_oid, S_IFDIR);
3286c408 547 git__free(subdir);
cfeef7ce 548 if (error < 0)
3aa351ea 549 goto on_error;
4a619797 550 } else {
9ef9e8c3 551 error = append_entry(bld, filename, &entry->oid, entry->mode);
cfeef7ce 552 if (error < 0)
3aa351ea 553 goto on_error;
47d8ec56 554 }
29e1789b 555 }
932d1baf 556
3aa351ea
CMN
557 if (git_treebuilder_write(oid, repo, bld) < 0)
558 goto on_error;
29e1789b 559
4a619797 560 git_treebuilder_free(bld);
a8122b5d 561 return (int)i;
4a619797 562
3aa351ea
CMN
563on_error:
564 git_treebuilder_free(bld);
565 return -1;
29e1789b
VM
566}
567
16248ee2
RB
568int git_tree__write_index(
569 git_oid *oid, git_index *index, git_repository *repo)
29e1789b 570{
3aa351ea 571 int ret;
3f0d0c85 572 bool old_ignore_case = false;
29e1789b 573
276ea401 574 assert(oid && index && repo);
47d8ec56 575
f92bcaea 576 if (git_index_has_conflicts(index)) {
577 giterr_set(GITERR_INDEX,
578 "Cannot create a tree from a not fully merged index.");
579 return GIT_EUNMERGED;
580 }
581
8255c69b
CMN
582 if (index->tree != NULL && index->tree->entries >= 0) {
583 git_oid_cpy(oid, &index->tree->oid);
3aa351ea 584 return 0;
8255c69b
CMN
585 }
586
3f0d0c85 587 /* The tree cache didn't help us; we'll have to write
cb53669e 588 * out a tree. If the index is ignore_case, we must
3f0d0c85
PK
589 * make it case-sensitive for the duration of the tree-write
590 * operation. */
591
592 if (index->ignore_case) {
593 old_ignore_case = true;
cb53669e 594 git_index__set_ignore_case(index, false);
3f0d0c85
PK
595 }
596
3aa351ea 597 ret = write_tree(oid, repo, index, "", 0);
3f0d0c85
PK
598
599 if (old_ignore_case)
cb53669e 600 git_index__set_ignore_case(index, true);
3f0d0c85 601
3aa351ea 602 return ret < 0 ? ret : 0;
47d8ec56 603}
0ad6efa1 604
0ad6efa1
VM
605int git_treebuilder_create(git_treebuilder **builder_p, const git_tree *source)
606{
607 git_treebuilder *bld;
b8457baa 608 size_t i, source_entries = DEFAULT_TREE_SIZE;
0ad6efa1
VM
609
610 assert(builder_p);
611
612 bld = git__calloc(1, sizeof(git_treebuilder));
3aa351ea 613 GITERR_CHECK_ALLOC(bld);
0ad6efa1
VM
614
615 if (source != NULL)
616 source_entries = source->entries.length;
617
3aa351ea
CMN
618 if (git_vector_init(&bld->entries, source_entries, entry_sort_cmp) < 0)
619 goto on_error;
0ad6efa1
VM
620
621 if (source != NULL) {
e2237179 622 git_tree_entry *entry_src;
0ad6efa1 623
e2237179 624 git_vector_foreach(&source->entries, i, entry_src) {
66439b0b 625 if (append_entry(
626 bld, entry_src->filename,
627 &entry_src->oid,
f1c75b94 628 entry_src->attr) < 0)
3aa351ea 629 goto on_error;
0ad6efa1
VM
630 }
631 }
632
633 *builder_p = bld;
3aa351ea
CMN
634 return 0;
635
636on_error:
637 git_treebuilder_free(bld);
638 return -1;
0ad6efa1
VM
639}
640
0e2fcca8
VM
641int git_treebuilder_insert(
642 const git_tree_entry **entry_out,
643 git_treebuilder *bld,
644 const char *filename,
645 const git_oid *id,
a7dbac0b 646 git_filemode_t filemode)
0ad6efa1
VM
647{
648 git_tree_entry *entry;
11d9f6b3 649 size_t pos;
0ad6efa1
VM
650
651 assert(bld && id && filename);
652
a7dbac0b 653 if (!valid_filemode(filemode))
cfeef7ce 654 return tree_error("Failed to insert entry. Invalid filemode for file", filename);
0ad6efa1 655
28c1451a 656 if (!valid_entry_name(filename))
cfeef7ce 657 return tree_error("Failed to insert entry. Invalid name for a tree entry", filename);
f4ad64c1 658
11d9f6b3 659 if (!tree_key_search(&pos, &bld->entries, filename, strlen(filename))) {
0ad6efa1 660 entry = git_vector_get(&bld->entries, pos);
93ab370b 661 if (entry->removed) {
0ad6efa1 662 entry->removed = 0;
93ab370b
RB
663 bld->entrycount++;
664 }
0ad6efa1 665 } else {
0e2fcca8 666 entry = alloc_entry(filename);
3aa351ea 667 GITERR_CHECK_ALLOC(entry);
0ad6efa1 668
e2237179
RB
669 if (git_vector_insert(&bld->entries, entry) < 0) {
670 git__free(entry);
3aa351ea 671 return -1;
e2237179 672 }
93ab370b
RB
673
674 bld->entrycount++;
0ad6efa1
VM
675 }
676
11d9f6b3
PK
677 git_oid_cpy(&entry->oid, id);
678 entry->attr = filemode;
679
680 if (entry_out)
0ad6efa1
VM
681 *entry_out = entry;
682
3aa351ea 683 return 0;
0ad6efa1
VM
684}
685
0cbbdc26 686static git_tree_entry *treebuilder_get(git_treebuilder *bld, const char *filename)
0ad6efa1 687{
11d9f6b3 688 size_t idx;
0ad6efa1
VM
689 git_tree_entry *entry;
690
691 assert(bld && filename);
692
11d9f6b3 693 if (tree_key_search(&idx, &bld->entries, filename, strlen(filename)) < 0)
0ad6efa1
VM
694 return NULL;
695
696 entry = git_vector_get(&bld->entries, idx);
697 if (entry->removed)
698 return NULL;
699
700 return entry;
701}
702
0cbbdc26
KS
703const git_tree_entry *git_treebuilder_get(git_treebuilder *bld, const char *filename)
704{
705 return treebuilder_get(bld, filename);
706}
707
0ad6efa1
VM
708int git_treebuilder_remove(git_treebuilder *bld, const char *filename)
709{
0cbbdc26 710 git_tree_entry *remove_ptr = treebuilder_get(bld, filename);
0ad6efa1
VM
711
712 if (remove_ptr == NULL || remove_ptr->removed)
cfeef7ce 713 return tree_error("Failed to remove entry. File isn't in the tree", filename);
0ad6efa1
VM
714
715 remove_ptr->removed = 1;
93ab370b 716 bld->entrycount--;
3aa351ea 717 return 0;
0ad6efa1
VM
718}
719
720int git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *bld)
721{
e2237179
RB
722 int error = 0;
723 size_t i;
afeecf4f 724 git_buf tree = GIT_BUF_INIT;
9462c471 725 git_odb *odb;
0ad6efa1
VM
726
727 assert(bld);
728
e2237179 729 git_vector_sort(&bld->entries);
0ad6efa1 730
afeecf4f 731 /* Grow the buffer beforehand to an estimated size */
e2237179 732 error = git_buf_grow(&tree, bld->entries.length * 72);
afeecf4f 733
e2237179
RB
734 for (i = 0; i < bld->entries.length && !error; ++i) {
735 git_tree_entry *entry = git_vector_get(&bld->entries, i);
0ad6efa1
VM
736
737 if (entry->removed)
738 continue;
739
afeecf4f
VM
740 git_buf_printf(&tree, "%o ", entry->attr);
741 git_buf_put(&tree, entry->filename, entry->filename_len + 1);
742 git_buf_put(&tree, (char *)entry->oid.id, GIT_OID_RAWSZ);
0ad6efa1 743
e2237179
RB
744 if (git_buf_oom(&tree))
745 error = -1;
746 }
9462c471 747
e2237179
RB
748 if (!error &&
749 !(error = git_repository_odb__weakptr(&odb, repo)))
750 error = git_odb_write(oid, odb, tree.ptr, tree.size, GIT_OBJ_TREE);
0ad6efa1 751
3aa351ea 752 git_buf_free(&tree);
e2237179 753 return error;
0ad6efa1
VM
754}
755
e120123e
RB
756void git_treebuilder_filter(
757 git_treebuilder *bld,
758 git_treebuilder_filter_cb filter,
759 void *payload)
0ad6efa1 760{
e2237179
RB
761 size_t i;
762 git_tree_entry *entry;
0ad6efa1
VM
763
764 assert(bld && filter);
765
e2237179 766 git_vector_foreach(&bld->entries, i, entry) {
93ab370b 767 if (!entry->removed && filter(entry, payload)) {
0ad6efa1 768 entry->removed = 1;
93ab370b
RB
769 bld->entrycount--;
770 }
0ad6efa1
VM
771 }
772}
773
774void git_treebuilder_clear(git_treebuilder *bld)
775{
e2237179
RB
776 size_t i;
777 git_tree_entry *e;
778
0ad6efa1
VM
779 assert(bld);
780
e2237179 781 git_vector_foreach(&bld->entries, i, e)
0e2fcca8 782 git_tree_entry_free(e);
0ad6efa1
VM
783
784 git_vector_clear(&bld->entries);
93ab370b 785 bld->entrycount = 0;
0ad6efa1
VM
786}
787
788void git_treebuilder_free(git_treebuilder *bld)
789{
b0b3b4e3 790 if (bld == NULL)
791 return;
792
0ad6efa1
VM
793 git_treebuilder_clear(bld);
794 git_vector_free(&bld->entries);
3286c408 795 git__free(bld);
0ad6efa1
VM
796}
797
0e2fcca8
VM
798static size_t subpath_len(const char *path)
799{
800 const char *slash_pos = strchr(path, '/');
801 if (slash_pos == NULL)
802 return strlen(path);
803
804 return slash_pos - path;
805}
806
807int git_tree_entry_bypath(
808 git_tree_entry **entry_out,
9432af36 809 git_tree *root,
0e2fcca8 810 const char *path)
3fa735ca 811{
e172cf08 812 int error = 0;
3fa735ca 813 git_tree *subtree;
0e2fcca8
VM
814 const git_tree_entry *entry;
815 size_t filename_len;
3fa735ca 816
0e2fcca8
VM
817 /* Find how long is the current path component (i.e.
818 * the filename between two slashes */
819 filename_len = subpath_len(path);
3fa735ca 820
0e2fcca8
VM
821 if (filename_len == 0) {
822 giterr_set(GITERR_TREE, "Invalid tree path given");
823 return GIT_ENOTFOUND;
3aa351ea 824 }
3fa735ca 825
0e2fcca8 826 entry = entry_fromname(root, path, filename_len);
3fa735ca 827
3aa351ea
CMN
828 if (entry == NULL) {
829 giterr_set(GITERR_TREE,
0e2fcca8 830 "The path '%s' does not exist in the given tree", path);
904b67e6 831 return GIT_ENOTFOUND;
3aa351ea 832 }
0ad6efa1 833
0e2fcca8 834 switch (path[filename_len]) {
2031760c 835 case '/':
0e2fcca8
VM
836 /* If there are more components in the path...
837 * then this entry *must* be a tree */
838 if (!git_tree_entry__is_tree(entry)) {
839 giterr_set(GITERR_TREE,
840 "The path '%s' does not exist in the given tree", path);
dc1f4b32 841 return GIT_ENOTFOUND;
0e2fcca8
VM
842 }
843
844 /* If there's only a slash left in the path, we
845 * return the current entry; otherwise, we keep
846 * walking down the path */
847 if (path[filename_len + 1] != '\0')
848 break;
849
850 case '\0':
851 /* If there are no more components in the path, return
852 * this entry */
46ea40d9 853 *entry_out = git_tree_entry_dup(entry);
0e2fcca8
VM
854 return 0;
855 }
9432af36 856
3aa351ea 857 if (git_tree_lookup(&subtree, root->object.repo, &entry->oid) < 0)
0e2fcca8 858 return -1;
3fa735ca 859
0e2fcca8
VM
860 error = git_tree_entry_bypath(
861 entry_out,
9432af36 862 subtree,
0e2fcca8 863 path + filename_len + 1
9432af36 864 );
3fa735ca 865
45e79e37 866 git_tree_free(subtree);
3fa735ca 867 return error;
868}
869
c6f42953 870static int tree_walk(
f45d51ff 871 const git_tree *tree,
9432af36 872 git_treewalk_cb callback,
97769280 873 git_buf *path,
c6f42953
MS
874 void *payload,
875 bool preorder)
da37654d 876{
e172cf08 877 int error = 0;
16248ee2 878 size_t i;
e2237179 879 const git_tree_entry *entry;
da37654d 880
e2237179 881 git_vector_foreach(&tree->entries, i, entry) {
a6bf1687
CMN
882 if (preorder) {
883 error = callback(path->ptr, entry, payload);
884 if (error > 0)
885 continue;
e2237179
RB
886 if (error < 0) {
887 giterr_clear();
a6bf1687 888 return GIT_EUSER;
e2237179 889 }
a6bf1687 890 }
da37654d 891
9d0011fd 892 if (git_tree_entry__is_tree(entry)) {
da37654d 893 git_tree *subtree;
fa6420f7 894 size_t path_len = git_buf_len(path);
da37654d 895
9432af36
VM
896 if ((error = git_tree_lookup(
897 &subtree, tree->object.repo, &entry->oid)) < 0)
97769280 898 break;
da37654d 899
97769280
RB
900 /* append the next entry to the path */
901 git_buf_puts(path, entry->filename);
902 git_buf_putc(path, '/');
da37654d 903
cb8a7961 904 if (git_buf_oom(path))
3aa351ea 905 return -1;
da37654d 906
2031760c
RB
907 error = tree_walk(subtree, callback, path, payload, preorder);
908 if (error != 0)
909 break;
da37654d 910
97769280 911 git_buf_truncate(path, path_len);
45e79e37 912 git_tree_free(subtree);
da37654d 913 }
c6f42953 914
a6bf1687 915 if (!preorder && callback(path->ptr, entry, payload) < 0) {
e2237179 916 giterr_clear();
b0d37669 917 error = GIT_EUSER;
2031760c 918 break;
b0d37669 919 }
da37654d
VM
920 }
921
2031760c 922 return error;
da37654d
VM
923}
924
e120123e 925int git_tree_walk(
f45d51ff 926 const git_tree *tree,
e120123e
RB
927 git_treewalk_mode mode,
928 git_treewalk_cb callback,
929 void *payload)
da37654d 930{
e172cf08 931 int error = 0;
97769280 932 git_buf root_path = GIT_BUF_INIT;
da37654d 933
e2237179 934 if (mode != GIT_TREEWALK_POST && mode != GIT_TREEWALK_PRE) {
e120123e
RB
935 giterr_set(GITERR_INVALID, "Invalid walking mode for tree walk");
936 return -1;
da37654d 937 }
97769280 938
e2237179
RB
939 error = tree_walk(
940 tree, callback, &root_path, payload, (mode == GIT_TREEWALK_PRE));
941
97769280
RB
942 git_buf_free(&root_path);
943
944 return error;
da37654d 945}
a1fdea28 946