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