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