File Coverage

deps/libgit2/src/libgit2/tree.c
Criterion Covered Total %
statement 375 607 61.7
branch 193 436 44.2
condition n/a
subroutine n/a
pod n/a
total 568 1043 54.4


line stmt bran cond sub pod time code
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 "tree.h"
9              
10             #include "commit.h"
11             #include "git2/repository.h"
12             #include "git2/object.h"
13             #include "futils.h"
14             #include "tree-cache.h"
15             #include "index.h"
16             #include "path.h"
17              
18             #define DEFAULT_TREE_SIZE 16
19             #define MAX_FILEMODE_BYTES 6
20              
21             #define TREE_ENTRY_CHECK_NAMELEN(n) \
22             if (n > UINT16_MAX) { git_error_set(GIT_ERROR_INVALID, "tree entry path too long"); }
23              
24 198           static bool valid_filemode(const int filemode)
25             {
26 198           return (filemode == GIT_FILEMODE_TREE
27 154 100         || filemode == GIT_FILEMODE_BLOB
28 4 100         || filemode == GIT_FILEMODE_BLOB_EXECUTABLE
29 2 100         || filemode == GIT_FILEMODE_LINK
30 352 100         || filemode == GIT_FILEMODE_COMMIT);
    50          
31             }
32              
33 509           GIT_INLINE(git_filemode_t) normalize_filemode(git_filemode_t filemode)
34             {
35             /* Tree bits set, but it's not a commit */
36 509 100         if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_TREE)
37 161           return GIT_FILEMODE_TREE;
38              
39             /* If any of the x bits are set */
40 348 100         if (GIT_PERMS_IS_EXEC(filemode))
41 3           return GIT_FILEMODE_BLOB_EXECUTABLE;
42              
43             /* 16XXXX means commit */
44 345 50         if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_COMMIT)
45 0           return GIT_FILEMODE_COMMIT;
46              
47             /* 12XXXX means symlink */
48 345 100         if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_LINK)
49 3           return GIT_FILEMODE_LINK;
50              
51             /* Otherwise, return a blob */
52 342           return GIT_FILEMODE_BLOB;
53             }
54              
55 197           static int valid_entry_name(git_repository *repo, const char *filename)
56             {
57 394           return *filename != '\0' &&
58 197           git_path_is_valid(repo, filename, 0,
59             GIT_FS_PATH_REJECT_TRAVERSAL | GIT_PATH_REJECT_DOT_GIT | GIT_FS_PATH_REJECT_SLASH);
60             }
61              
62 212           static int entry_sort_cmp(const void *a, const void *b)
63             {
64 212           const git_tree_entry *e1 = (const git_tree_entry *)a;
65 212           const git_tree_entry *e2 = (const git_tree_entry *)b;
66              
67 212           return git_fs_path_cmp(
68 212           e1->filename, e1->filename_len, git_tree_entry__is_tree(e1),
69 212           e2->filename, e2->filename_len, git_tree_entry__is_tree(e2),
70             git__strncmp);
71             }
72              
73 0           int git_tree_entry_cmp(const git_tree_entry *e1, const git_tree_entry *e2)
74             {
75 0           return entry_sort_cmp(e1, e2);
76             }
77              
78             /**
79             * Allocate a new self-contained entry, with enough space after it to
80             * store the filename and the id.
81             */
82 246           static git_tree_entry *alloc_entry(const char *filename, size_t filename_len, const git_oid *id)
83             {
84 246           git_tree_entry *entry = NULL;
85             char *filename_ptr;
86             size_t tree_len;
87              
88 246 50         TREE_ENTRY_CHECK_NAMELEN(filename_len);
89              
90 738 50         if (GIT_ADD_SIZET_OVERFLOW(&tree_len, sizeof(git_tree_entry), filename_len) ||
91 246           GIT_ADD_SIZET_OVERFLOW(&tree_len, tree_len, 1) ||
92 246           GIT_ADD_SIZET_OVERFLOW(&tree_len, tree_len, GIT_OID_RAWSZ))
93 0           return NULL;
94              
95 246           entry = git__calloc(1, tree_len);
96 246 50         if (!entry)
97 0           return NULL;
98              
99 246           filename_ptr = ((char *) entry) + sizeof(git_tree_entry);
100 246           memcpy(filename_ptr, filename, filename_len);
101 246           entry->filename = filename_ptr;
102 246           entry->filename_len = (uint16_t)filename_len;
103              
104 246           git_oid_cpy(&entry->oid, id);
105              
106 246           return entry;
107             }
108              
109             struct tree_key_search {
110             const char *filename;
111             uint16_t filename_len;
112             };
113              
114 86           static int homing_search_cmp(const void *key, const void *array_member)
115             {
116 86           const struct tree_key_search *ksearch = key;
117 86           const git_tree_entry *entry = array_member;
118              
119 86           const uint16_t len1 = ksearch->filename_len;
120 86           const uint16_t len2 = entry->filename_len;
121              
122 86           return memcmp(
123 86           ksearch->filename,
124 86           entry->filename,
125 86           len1 < len2 ? len1 : len2
126             );
127             }
128              
129             /*
130             * Search for an entry in a given tree.
131             *
132             * Note that this search is performed in two steps because
133             * of the way tree entries are sorted internally in git:
134             *
135             * Entries in a tree are not sorted alphabetically; two entries
136             * with the same root prefix will have different positions
137             * depending on whether they are folders (subtrees) or normal files.
138             *
139             * Consequently, it is not possible to find an entry on the tree
140             * with a binary search if you don't know whether the filename
141             * you're looking for is a folder or a normal file.
142             *
143             * To work around this, we first perform a homing binary search
144             * on the tree, using the minimal length root prefix of our filename.
145             * Once the comparisons for this homing search start becoming
146             * ambiguous because of folder vs file sorting, we look linearly
147             * around the area for our target file.
148             */
149 38           static int tree_key_search(
150             size_t *at_pos,
151             const git_tree *tree,
152             const char *filename,
153             size_t filename_len)
154             {
155             struct tree_key_search ksearch;
156             const git_tree_entry *entry;
157             size_t homing, i;
158              
159 38 50         TREE_ENTRY_CHECK_NAMELEN(filename_len);
160              
161 38           ksearch.filename = filename;
162 38           ksearch.filename_len = (uint16_t)filename_len;
163              
164             /* Initial homing search; find an entry on the tree with
165             * the same prefix as the filename we're looking for */
166              
167 38 100         if (git_array_search(&homing,
168             tree->entries, &homing_search_cmp, &ksearch) < 0)
169 4           return GIT_ENOTFOUND; /* just a signal error; not passed back to user */
170              
171             /* We found a common prefix. Look forward as long as
172             * there are entries that share the common prefix */
173 34 50         for (i = homing; i < tree->entries.size; ++i) {
174 34 50         entry = git_array_get(tree->entries, i);
175              
176 34 50         if (homing_search_cmp(&ksearch, entry) < 0)
177 0           break;
178              
179 34 50         if (entry->filename_len == filename_len &&
    50          
180 34           memcmp(filename, entry->filename, filename_len) == 0) {
181 34 50         if (at_pos)
182 34           *at_pos = i;
183              
184 34           return 0;
185             }
186             }
187              
188             /* If we haven't found our filename yet, look backwards
189             * too as long as we have entries with the same prefix */
190 0 0         if (homing > 0) {
191 0           i = homing - 1;
192              
193             do {
194 0 0         entry = git_array_get(tree->entries, i);
195              
196 0 0         if (homing_search_cmp(&ksearch, entry) > 0)
197 0           break;
198              
199 0 0         if (entry->filename_len == filename_len &&
    0          
200 0           memcmp(filename, entry->filename, filename_len) == 0) {
201 0 0         if (at_pos)
202 0           *at_pos = i;
203              
204 0           return 0;
205             }
206 0 0         } while (i-- > 0);
207             }
208              
209             /* The filename doesn't exist at all */
210 38           return GIT_ENOTFOUND;
211             }
212              
213 2673           void git_tree_entry_free(git_tree_entry *entry)
214             {
215 2673 100         if (entry == NULL)
216 2427           return;
217              
218 246           git__free(entry);
219             }
220              
221 45           int git_tree_entry_dup(git_tree_entry **dest, const git_tree_entry *source)
222             {
223             git_tree_entry *cpy;
224              
225 45 50         GIT_ASSERT_ARG(source);
226              
227 45           cpy = alloc_entry(source->filename, source->filename_len, &source->oid);
228 45 50         if (cpy == NULL)
229 0           return -1;
230              
231 45           cpy->attr = source->attr;
232              
233 45           *dest = cpy;
234 45           return 0;
235             }
236              
237 99           void git_tree__free(void *_tree)
238             {
239 99           git_tree *tree = _tree;
240              
241 99           git_odb_object_free(tree->odb_obj);
242 99           git_array_clear(tree->entries);
243 99           git__free(tree);
244 99           }
245              
246 509           git_filemode_t git_tree_entry_filemode(const git_tree_entry *entry)
247             {
248 509           return normalize_filemode(entry->attr);
249             }
250              
251 0           git_filemode_t git_tree_entry_filemode_raw(const git_tree_entry *entry)
252             {
253 0           return entry->attr;
254             }
255              
256 166           const char *git_tree_entry_name(const git_tree_entry *entry)
257             {
258 166 50         GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL);
259 166           return entry->filename;
260             }
261              
262 135           const git_oid *git_tree_entry_id(const git_tree_entry *entry)
263             {
264 135 50         GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL);
265 135           return &entry->oid;
266             }
267              
268 43           git_object_t git_tree_entry_type(const git_tree_entry *entry)
269             {
270 43 50         GIT_ASSERT_ARG_WITH_RETVAL(entry, GIT_OBJECT_INVALID);
271              
272 43 50         if (S_ISGITLINK(entry->attr))
273 0           return GIT_OBJECT_COMMIT;
274 43 100         else if (S_ISDIR(entry->attr))
275 9           return GIT_OBJECT_TREE;
276             else
277 34           return GIT_OBJECT_BLOB;
278             }
279              
280 39           int git_tree_entry_to_object(
281             git_object **object_out,
282             git_repository *repo,
283             const git_tree_entry *entry)
284             {
285 39 50         GIT_ASSERT_ARG(entry);
286 39 50         GIT_ASSERT_ARG(object_out);
287              
288 39           return git_object_lookup(object_out, repo, &entry->oid, GIT_OBJECT_ANY);
289             }
290              
291 38           static const git_tree_entry *entry_fromname(
292             const git_tree *tree, const char *name, size_t name_len)
293             {
294             size_t idx;
295              
296 38 100         if (tree_key_search(&idx, tree, name, name_len) < 0)
297 4           return NULL;
298              
299 38 50         return git_array_get(tree->entries, idx);
300             }
301              
302 2           const git_tree_entry *git_tree_entry_byname(
303             const git_tree *tree, const char *filename)
304             {
305 2 50         GIT_ASSERT_ARG_WITH_RETVAL(tree, NULL);
306 2 50         GIT_ASSERT_ARG_WITH_RETVAL(filename, NULL);
307              
308 2           return entry_fromname(tree, filename, strlen(filename));
309             }
310              
311 561           const git_tree_entry *git_tree_entry_byindex(
312             const git_tree *tree, size_t idx)
313             {
314 561 50         GIT_ASSERT_ARG_WITH_RETVAL(tree, NULL);
315 561 50         return git_array_get(tree->entries, idx);
316             }
317              
318 0           const git_tree_entry *git_tree_entry_byid(
319             const git_tree *tree, const git_oid *id)
320             {
321             size_t i;
322             const git_tree_entry *e;
323              
324 0 0         GIT_ASSERT_ARG_WITH_RETVAL(tree, NULL);
325              
326 0 0         git_array_foreach(tree->entries, i, e) {
    0          
327 0 0         if (git_oid_equal(&e->oid, id))
328 0           return e;
329             }
330              
331 0           return NULL;
332             }
333              
334 214           size_t git_tree_entrycount(const git_tree *tree)
335             {
336 214 50         GIT_ASSERT_ARG_WITH_RETVAL(tree, 0);
337 214           return tree->entries.size;
338             }
339              
340 6           size_t git_treebuilder_entrycount(git_treebuilder *bld)
341             {
342 6 50         GIT_ASSERT_ARG_WITH_RETVAL(bld, 0);
343              
344 6           return git_strmap_size(bld->map);
345             }
346              
347 3           GIT_INLINE(void) set_error(const char *str, const char *path)
348             {
349 3 50         if (path)
350 3           git_error_set(GIT_ERROR_TREE, "%s - %s", str, path);
351             else
352 0           git_error_set(GIT_ERROR_TREE, "%s", str);
353 3           }
354              
355 3           static int tree_error(const char *str, const char *path)
356             {
357 3           set_error(str, path);
358 3           return -1;
359             }
360              
361 0           static int tree_parse_error(const char *str, const char *path)
362             {
363 0           set_error(str, path);
364 0           return GIT_EINVALID;
365             }
366              
367 210           static int parse_mode(uint16_t *mode_out, const char *buffer, size_t buffer_len, const char **buffer_out)
368             {
369             int32_t mode;
370             int error;
371              
372 210 50         if (!buffer_len || git__isspace(*buffer))
    50          
373 0           return -1;
374              
375 210 50         if ((error = git__strntol32(&mode, buffer, buffer_len, buffer_out, 8)) < 0)
376 0           return error;
377              
378 210 50         if (mode < 0 || mode > UINT16_MAX)
    50          
379 0           return -1;
380              
381 210           *mode_out = mode;
382              
383 210           return 0;
384             }
385              
386 101           int git_tree__parse_raw(void *_tree, const char *data, size_t size)
387             {
388 101           git_tree *tree = _tree;
389             const char *buffer;
390             const char *buffer_end;
391              
392 101           buffer = data;
393 101           buffer_end = buffer + size;
394              
395 101           tree->odb_obj = NULL;
396 101           git_array_init_to_size(tree->entries, DEFAULT_TREE_SIZE);
397 101 50         GIT_ERROR_CHECK_ARRAY(tree->entries);
398              
399 311 100         while (buffer < buffer_end) {
400             git_tree_entry *entry;
401             size_t filename_len;
402             const char *nul;
403             uint16_t attr;
404              
405 210 50         if (parse_mode(&attr, buffer, buffer_end - buffer, &buffer) < 0 || !buffer)
    50          
406 0           return tree_parse_error("failed to parse tree: can't parse filemode", NULL);
407              
408 210 50         if (buffer >= buffer_end || (*buffer++) != ' ')
    50          
409 0           return tree_parse_error("failed to parse tree: missing space after filemode", NULL);
410              
411 210 50         if ((nul = memchr(buffer, 0, buffer_end - buffer)) == NULL)
412 0           return tree_parse_error("failed to parse tree: object is corrupted", NULL);
413              
414 210 50         if ((filename_len = nul - buffer) == 0 || filename_len > UINT16_MAX)
    50          
415 0           return tree_parse_error("failed to parse tree: can't parse filename", NULL);
416              
417 210 50         if ((buffer_end - (nul + 1)) < GIT_OID_RAWSZ)
418 0           return tree_parse_error("failed to parse tree: can't parse OID", NULL);
419              
420             /* Allocate the entry */
421             {
422 210 50         entry = git_array_alloc(tree->entries);
    0          
423 210 50         GIT_ERROR_CHECK_ALLOC(entry);
424              
425 210           entry->attr = attr;
426 210           entry->filename_len = (uint16_t)filename_len;
427 210           entry->filename = buffer;
428 210           git_oid_fromraw(&entry->oid, ((unsigned char *) buffer + filename_len + 1));
429             }
430              
431 210           buffer += filename_len + 1;
432 210           buffer += GIT_OID_RAWSZ;
433             }
434              
435 101           return 0;
436             }
437              
438 101           int git_tree__parse(void *_tree, git_odb_object *odb_obj)
439             {
440 101           git_tree *tree = _tree;
441 101           const char *data = git_odb_object_data(odb_obj);
442 101           size_t size = git_odb_object_size(odb_obj);
443             int error;
444              
445 101 50         if ((error = git_tree__parse_raw(tree, data, size)) < 0 ||
    50          
446 101           (error = git_odb_object_dup(&tree->odb_obj, odb_obj)) < 0)
447 0           return error;
448              
449 101           return error;
450             }
451              
452 11           static size_t find_next_dir(const char *dirname, git_index *index, size_t start)
453             {
454 11           size_t dirlen, i, entries = git_index_entrycount(index);
455              
456 11           dirlen = strlen(dirname);
457 22 100         for (i = start; i < entries; ++i) {
458 13           const git_index_entry *entry = git_index_get_byindex(index, i);
459 13 50         if (strlen(entry->path) < dirlen ||
    100          
460 11 50         memcmp(entry->path, dirname, dirlen) ||
461 11 50         (dirlen > 0 && entry->path[dirlen] != '/')) {
462             break;
463             }
464             }
465              
466 11           return i;
467             }
468              
469 196           static git_object_t otype_from_mode(git_filemode_t filemode)
470             {
471 196           switch (filemode) {
472             case GIT_FILEMODE_TREE:
473 44           return GIT_OBJECT_TREE;
474             case GIT_FILEMODE_COMMIT:
475 0           return GIT_OBJECT_COMMIT;
476             default:
477 152           return GIT_OBJECT_BLOB;
478             }
479             }
480              
481 198           static int check_entry(git_repository *repo, const char *filename, const git_oid *id, git_filemode_t filemode)
482             {
483 198 100         if (!valid_filemode(filemode))
484 1           return tree_error("failed to insert entry: invalid filemode for file", filename);
485              
486 197 100         if (!valid_entry_name(repo, filename))
487 1           return tree_error("failed to insert entry: invalid name for a tree entry", filename);
488              
489 196 50         if (git_oid_is_zero(id))
490 0           return tree_error("failed to insert entry: invalid null OID", filename);
491              
492 392           if (filemode != GIT_FILEMODE_COMMIT &&
493 196           !git_object__is_valid(repo, id, otype_from_mode(filemode)))
494 0           return tree_error("failed to insert entry: invalid object specified", filename);
495              
496 196           return 0;
497             }
498              
499 95           static int git_treebuilder__write_with_buffer(
500             git_oid *oid,
501             git_treebuilder *bld,
502             git_str *buf)
503             {
504 95           int error = 0;
505             size_t i, entrycount;
506             git_odb *odb;
507             git_tree_entry *entry;
508 95           git_vector entries = GIT_VECTOR_INIT;
509              
510 95           git_str_clear(buf);
511              
512 95           entrycount = git_strmap_size(bld->map);
513 95 50         if ((error = git_vector_init(&entries, entrycount, entry_sort_cmp)) < 0)
514 0           goto out;
515              
516 95 100         if (buf->asize == 0 &&
    50          
517 62           (error = git_str_grow(buf, entrycount * 72)) < 0)
518 0           goto out;
519              
520 295 50         git_strmap_foreach_value(bld->map, entry, {
    100          
521             if ((error = git_vector_insert(&entries, entry)) < 0)
522             goto out;
523             });
524              
525 95           git_vector_sort(&entries);
526              
527 295 100         for (i = 0; i < entries.length && !error; ++i) {
    50          
528 200           entry = git_vector_get(&entries, i);
529              
530 200           git_str_printf(buf, "%o ", entry->attr);
531 200           git_str_put(buf, entry->filename, entry->filename_len + 1);
532 200           git_str_put(buf, (char *)entry->oid.id, GIT_OID_RAWSZ);
533              
534 200 50         if (git_str_oom(buf)) {
535 0           error = -1;
536 0           goto out;
537             }
538             }
539              
540 95 50         if ((error = git_repository_odb__weakptr(&odb, bld->repo)) == 0)
541 95           error = git_odb_write(oid, odb, buf->ptr, buf->size, GIT_OBJECT_TREE);
542              
543             out:
544 95           git_vector_free(&entries);
545              
546 95           return error;
547             }
548              
549 195           static int append_entry(
550             git_treebuilder *bld,
551             const char *filename,
552             const git_oid *id,
553             git_filemode_t filemode,
554             bool validate)
555             {
556             git_tree_entry *entry;
557 195           int error = 0;
558              
559 195 100         if (validate && ((error = check_entry(bld->repo, filename, id, filemode)) < 0))
    50          
560 0           return error;
561              
562 195           entry = alloc_entry(filename, strlen(filename), id);
563 195 50         GIT_ERROR_CHECK_ALLOC(entry);
564              
565 195           entry->attr = (uint16_t)filemode;
566              
567 195 50         if ((error = git_strmap_set(bld->map, entry->filename, entry)) < 0) {
568 0           git_tree_entry_free(entry);
569 0           git_error_set(GIT_ERROR_TREE, "failed to append entry %s to the tree builder", filename);
570 0           return -1;
571             }
572              
573 195           return 0;
574             }
575              
576 98           static int write_tree(
577             git_oid *oid,
578             git_repository *repo,
579             git_index *index,
580             const char *dirname,
581             size_t start,
582             git_str *shared_buf)
583             {
584 98           git_treebuilder *bld = NULL;
585 98           size_t i, entries = git_index_entrycount(index);
586             int error;
587 98           size_t dirname_len = strlen(dirname);
588             const git_tree_cache *cache;
589              
590 98           cache = git_tree_cache_get(index->tree, dirname);
591 98 100         if (cache != NULL && cache->entry_count >= 0){
    50          
592 11           git_oid_cpy(oid, &cache->oid);
593 11           return (int)find_next_dir(dirname, index, start);
594             }
595              
596 87 50         if ((error = git_treebuilder_new(&bld, repo, NULL)) < 0 || bld == NULL)
    50          
597 0           return -1;
598              
599             /*
600             * This loop is unfortunate, but necessary. The index doesn't have
601             * any directories, so we need to handle that manually, and we
602             * need to keep track of the current position.
603             */
604 276 100         for (i = start; i < entries; ++i) {
605 200           const git_index_entry *entry = git_index_get_byindex(index, i);
606             const char *filename, *next_slash;
607              
608             /*
609             * If we've left our (sub)tree, exit the loop and return. The
610             * first check is an early out (and security for the
611             * third). The second check is a simple prefix comparison. The
612             * third check catches situations where there is a directory
613             * win32/sys and a file win32mmap.c. Without it, the following
614             * code believes there is a file win32/mmap.c
615             */
616 200 100         if (strlen(entry->path) < dirname_len ||
    100          
617 189 100         memcmp(entry->path, dirname, dirname_len) ||
618 35 50         (dirname_len > 0 && entry->path[dirname_len] != '/')) {
619             break;
620             }
621              
622 189           filename = entry->path + dirname_len;
623 189 100         if (*filename == '/')
624 35           filename++;
625 189           next_slash = strchr(filename, '/');
626 189 100         if (next_slash) {
627             git_oid sub_oid;
628             int written;
629             char *subdir, *last_comp;
630              
631 43           subdir = git__strndup(entry->path, next_slash - entry->path);
632 43 50         GIT_ERROR_CHECK_ALLOC(subdir);
633              
634             /* Write out the subtree */
635 43           written = write_tree(&sub_oid, repo, index, subdir, i, shared_buf);
636 43 50         if (written < 0) {
637 0           git__free(subdir);
638 0           goto on_error;
639             } else {
640 43           i = written - 1; /* -1 because of the loop increment */
641             }
642              
643             /*
644             * We need to figure out what we want toinsert
645             * into this tree. If we're traversing
646             * deps/zlib/, then we only want to write
647             * 'zlib' into the tree.
648             */
649 43           last_comp = strrchr(subdir, '/');
650 43 100         if (last_comp) {
651 22           last_comp++; /* Get rid of the '/' */
652             } else {
653 21           last_comp = subdir;
654             }
655              
656 43           error = append_entry(bld, last_comp, &sub_oid, S_IFDIR, true);
657 43           git__free(subdir);
658 43 50         if (error < 0)
659 43           goto on_error;
660             } else {
661 146           error = append_entry(bld, filename, &entry->id, entry->mode, true);
662 146 50         if (error < 0)
663 0           goto on_error;
664             }
665             }
666              
667 87 50         if (git_treebuilder__write_with_buffer(oid, bld, shared_buf) < 0)
668 0           goto on_error;
669              
670 87           git_treebuilder_free(bld);
671 87           return (int)i;
672              
673             on_error:
674 0           git_treebuilder_free(bld);
675 98           return -1;
676             }
677              
678 56           int git_tree__write_index(
679             git_oid *oid, git_index *index, git_repository *repo)
680             {
681             int ret;
682             git_tree *tree;
683 56           git_str shared_buf = GIT_STR_INIT;
684 56           bool old_ignore_case = false;
685              
686 56 50         GIT_ASSERT_ARG(oid);
687 56 50         GIT_ASSERT_ARG(index);
688 56 50         GIT_ASSERT_ARG(repo);
689              
690 56 50         if (git_index_has_conflicts(index)) {
691 0           git_error_set(GIT_ERROR_INDEX,
692             "cannot create a tree from a not fully merged index.");
693 0           return GIT_EUNMERGED;
694             }
695              
696 56 100         if (index->tree != NULL && index->tree->entry_count >= 0) {
    100          
697 1           git_oid_cpy(oid, &index->tree->oid);
698 1           return 0;
699             }
700              
701             /* The tree cache didn't help us; we'll have to write
702             * out a tree. If the index is ignore_case, we must
703             * make it case-sensitive for the duration of the tree-write
704             * operation. */
705              
706 55 50         if (index->ignore_case) {
707 0           old_ignore_case = true;
708 0           git_index__set_ignore_case(index, false);
709             }
710              
711 55           ret = write_tree(oid, repo, index, "", 0, &shared_buf);
712 55           git_str_dispose(&shared_buf);
713              
714 55 50         if (old_ignore_case)
715 0           git_index__set_ignore_case(index, true);
716              
717 55           index->tree = NULL;
718              
719 55 50         if (ret < 0)
720 0           return ret;
721              
722 55           git_pool_clear(&index->tree_pool);
723              
724 55 50         if ((ret = git_tree_lookup(&tree, repo, oid)) < 0)
725 0           return ret;
726              
727             /* Read the tree cache into the index */
728 55           ret = git_tree_cache_read_tree(&index->tree, tree, &index->tree_pool);
729 55           git_tree_free(tree);
730              
731 56           return ret;
732             }
733              
734 94           int git_treebuilder_new(
735             git_treebuilder **builder_p,
736             git_repository *repo,
737             const git_tree *source)
738             {
739             git_treebuilder *bld;
740             size_t i;
741              
742 94 50         GIT_ASSERT_ARG(builder_p);
743 94 50         GIT_ASSERT_ARG(repo);
744              
745 94           bld = git__calloc(1, sizeof(git_treebuilder));
746 94 50         GIT_ERROR_CHECK_ALLOC(bld);
747              
748 94           bld->repo = repo;
749              
750 94 50         if (git_strmap_new(&bld->map) < 0) {
751 0           git__free(bld);
752 0           return -1;
753             }
754              
755 94 100         if (source != NULL) {
756             git_tree_entry *entry_src;
757              
758 10 100         git_array_foreach(source->entries, i, entry_src) {
    50          
759 6 50         if (append_entry(
760             bld, entry_src->filename,
761 6           &entry_src->oid,
762 6           entry_src->attr,
763             false) < 0)
764 0           goto on_error;
765             }
766             }
767              
768 94           *builder_p = bld;
769 94           return 0;
770              
771             on_error:
772 0           git_treebuilder_free(bld);
773 0           return -1;
774             }
775              
776 9           int git_treebuilder_insert(
777             const git_tree_entry **entry_out,
778             git_treebuilder *bld,
779             const char *filename,
780             const git_oid *id,
781             git_filemode_t filemode)
782             {
783             git_tree_entry *entry;
784             int error;
785              
786 9 50         GIT_ASSERT_ARG(bld);
787 9 50         GIT_ASSERT_ARG(id);
788 9 50         GIT_ASSERT_ARG(filename);
789              
790 9 100         if ((error = check_entry(bld->repo, filename, id, filemode)) < 0)
791 2           return error;
792              
793 7 100         if ((entry = git_strmap_get(bld->map, filename)) != NULL) {
794 1           git_oid_cpy(&entry->oid, id);
795             } else {
796 6           entry = alloc_entry(filename, strlen(filename), id);
797 6 50         GIT_ERROR_CHECK_ALLOC(entry);
798              
799 6 50         if ((error = git_strmap_set(bld->map, entry->filename, entry)) < 0) {
800 0           git_tree_entry_free(entry);
801 0           git_error_set(GIT_ERROR_TREE, "failed to insert %s", filename);
802 0           return -1;
803             }
804             }
805              
806 7           entry->attr = filemode;
807              
808 7 100         if (entry_out)
809 4           *entry_out = entry;
810              
811 7           return 0;
812             }
813              
814 8           static git_tree_entry *treebuilder_get(git_treebuilder *bld, const char *filename)
815             {
816 8 50         GIT_ASSERT_ARG_WITH_RETVAL(bld, NULL);
817 8 50         GIT_ASSERT_ARG_WITH_RETVAL(filename, NULL);
818              
819 8           return git_strmap_get(bld->map, filename);
820             }
821              
822 4           const git_tree_entry *git_treebuilder_get(git_treebuilder *bld, const char *filename)
823             {
824 4           return treebuilder_get(bld, filename);
825             }
826              
827 4           int git_treebuilder_remove(git_treebuilder *bld, const char *filename)
828             {
829 4           git_tree_entry *entry = treebuilder_get(bld, filename);
830              
831 4 100         if (entry == NULL)
832 1           return tree_error("failed to remove entry: file isn't in the tree", filename);
833              
834 3           git_strmap_delete(bld->map, filename);
835 3           git_tree_entry_free(entry);
836              
837 3           return 0;
838             }
839              
840 8           int git_treebuilder_write(git_oid *oid, git_treebuilder *bld)
841             {
842 8 50         GIT_ASSERT_ARG(oid);
843 8 50         GIT_ASSERT_ARG(bld);
844              
845 8           return git_treebuilder__write_with_buffer(oid, bld, &bld->write_cache);
846             }
847              
848 0           int git_treebuilder_filter(
849             git_treebuilder *bld,
850             git_treebuilder_filter_cb filter,
851             void *payload)
852             {
853             const char *filename;
854             git_tree_entry *entry;
855              
856 0 0         GIT_ASSERT_ARG(bld);
857 0 0         GIT_ASSERT_ARG(filter);
858              
859 0 0         git_strmap_foreach(bld->map, filename, entry, {
    0          
860             if (filter(entry, payload)) {
861             git_strmap_delete(bld->map, filename);
862             git_tree_entry_free(entry);
863             }
864             });
865              
866 0           return 0;
867             }
868              
869 95           int git_treebuilder_clear(git_treebuilder *bld)
870             {
871             git_tree_entry *e;
872              
873 95 50         GIT_ASSERT_ARG(bld);
874              
875 293 100         git_strmap_foreach_value(bld->map, e, git_tree_entry_free(e));
876 95           git_strmap_clear(bld->map);
877              
878 95           return 0;
879             }
880              
881 94           void git_treebuilder_free(git_treebuilder *bld)
882             {
883 94 50         if (bld == NULL)
884 0           return;
885              
886 94           git_str_dispose(&bld->write_cache);
887 94           git_treebuilder_clear(bld);
888 94           git_strmap_free(bld->map);
889 94           git__free(bld);
890             }
891              
892 36           static size_t subpath_len(const char *path)
893             {
894 36           const char *slash_pos = strchr(path, '/');
895 36 100         if (slash_pos == NULL)
896 16           return strlen(path);
897              
898 20           return slash_pos - path;
899             }
900              
901 36           int git_tree_entry_bypath(
902             git_tree_entry **entry_out,
903             const git_tree *root,
904             const char *path)
905             {
906 36           int error = 0;
907             git_tree *subtree;
908             const git_tree_entry *entry;
909             size_t filename_len;
910              
911             /* Find how long is the current path component (i.e.
912             * the filename between two slashes */
913 36           filename_len = subpath_len(path);
914              
915 36 50         if (filename_len == 0) {
916 0           git_error_set(GIT_ERROR_TREE, "invalid tree path given");
917 0           return GIT_ENOTFOUND;
918             }
919              
920 36           entry = entry_fromname(root, path, filename_len);
921              
922 36 100         if (entry == NULL) {
923 3           git_error_set(GIT_ERROR_TREE,
924             "the path '%.*s' does not exist in the given tree", (int) filename_len, path);
925 3           return GIT_ENOTFOUND;
926             }
927              
928 33           switch (path[filename_len]) {
929             case '/':
930             /* If there are more components in the path...
931             * then this entry *must* be a tree */
932 20 50         if (!git_tree_entry__is_tree(entry)) {
933 0           git_error_set(GIT_ERROR_TREE,
934             "the path '%.*s' exists but is not a tree", (int) filename_len, path);
935 0           return GIT_ENOTFOUND;
936             }
937              
938             /* If there's only a slash left in the path, we
939             * return the current entry; otherwise, we keep
940             * walking down the path */
941 20 50         if (path[filename_len + 1] != '\0')
942 20           break;
943             /* fall through */
944             case '\0':
945             /* If there are no more components in the path, return
946             * this entry */
947 13           return git_tree_entry_dup(entry_out, entry);
948             }
949              
950 20 50         if (git_tree_lookup(&subtree, root->object.repo, &entry->oid) < 0)
951 0           return -1;
952              
953 20           error = git_tree_entry_bypath(
954             entry_out,
955             subtree,
956 20           path + filename_len + 1
957             );
958              
959 20           git_tree_free(subtree);
960 36           return error;
961             }
962              
963 13           static int tree_walk(
964             const git_tree *tree,
965             git_treewalk_cb callback,
966             git_str *path,
967             void *payload,
968             bool preorder)
969             {
970 13           int error = 0;
971             size_t i;
972             const git_tree_entry *entry;
973              
974 37 100         git_array_foreach(tree->entries, i, entry) {
    50          
975 24 100         if (preorder) {
976 11           error = callback(path->ptr, entry, payload);
977 11 50         if (error < 0) { /* negative value stops iteration */
978 0           git_error_set_after_callback_function(error, "git_tree_walk");
979 0           break;
980             }
981 11 50         if (error > 0) { /* positive value skips this entry */
982 0           error = 0;
983 0           continue;
984             }
985             }
986              
987 24 100         if (git_tree_entry__is_tree(entry)) {
988             git_tree *subtree;
989 4           size_t path_len = git_str_len(path);
990              
991 4           error = git_tree_lookup(&subtree, tree->object.repo, &entry->oid);
992 4 50         if (error < 0)
993 0           break;
994              
995             /* append the next entry to the path */
996 4           git_str_puts(path, entry->filename);
997 4           git_str_putc(path, '/');
998              
999 4 50         if (git_str_oom(path))
1000 0           error = -1;
1001             else
1002 4           error = tree_walk(subtree, callback, path, payload, preorder);
1003              
1004 4           git_tree_free(subtree);
1005 4 50         if (error != 0)
1006 0           break;
1007              
1008 4           git_str_truncate(path, path_len);
1009             }
1010              
1011 24 100         if (!preorder) {
1012 13           error = callback(path->ptr, entry, payload);
1013 13 50         if (error < 0) { /* negative value stops iteration */
1014 0           git_error_set_after_callback_function(error, "git_tree_walk");
1015 0           break;
1016             }
1017 13           error = 0;
1018             }
1019             }
1020              
1021 13           return error;
1022             }
1023              
1024 9           int git_tree_walk(
1025             const git_tree *tree,
1026             git_treewalk_mode mode,
1027             git_treewalk_cb callback,
1028             void *payload)
1029             {
1030 9           int error = 0;
1031 9           git_str root_path = GIT_STR_INIT;
1032              
1033 9 100         if (mode != GIT_TREEWALK_POST && mode != GIT_TREEWALK_PRE) {
    50          
1034 0           git_error_set(GIT_ERROR_INVALID, "invalid walking mode for tree walk");
1035 0           return -1;
1036             }
1037              
1038 9           error = tree_walk(
1039             tree, callback, &root_path, payload, (mode == GIT_TREEWALK_PRE));
1040              
1041 9           git_str_dispose(&root_path);
1042              
1043 9           return error;
1044             }
1045              
1046 0           static int compare_entries(const void *_a, const void *_b)
1047             {
1048 0           const git_tree_update *a = (git_tree_update *) _a;
1049 0           const git_tree_update *b = (git_tree_update *) _b;
1050              
1051 0           return strcmp(a->path, b->path);
1052             }
1053              
1054 0           static int on_dup_entry(void **old, void *new)
1055             {
1056 0           GIT_UNUSED(old); GIT_UNUSED(new);
1057              
1058 0           git_error_set(GIT_ERROR_TREE, "duplicate entries given for update");
1059 0           return -1;
1060             }
1061              
1062             /*
1063             * We keep the previous tree and the new one at each level of the
1064             * stack. When we leave a level we're done with that tree and we can
1065             * write it out to the odb.
1066             */
1067             typedef struct {
1068             git_treebuilder *bld;
1069             git_tree *tree;
1070             char *name;
1071             } tree_stack_entry;
1072              
1073             /** Count how many slashes (i.e. path components) there are in this string */
1074 0           GIT_INLINE(size_t) count_slashes(const char *path)
1075             {
1076 0           size_t count = 0;
1077             const char *slash;
1078              
1079 0 0         while ((slash = strchr(path, '/')) != NULL) {
1080 0           count++;
1081 0           path = slash + 1;
1082             }
1083              
1084 0           return count;
1085             }
1086              
1087 0           static bool next_component(git_str *out, const char *in)
1088             {
1089 0           const char *slash = strchr(in, '/');
1090              
1091 0           git_str_clear(out);
1092              
1093 0 0         if (slash)
1094 0           git_str_put(out, in, slash - in);
1095              
1096 0           return !!slash;
1097             }
1098              
1099 0           static int create_popped_tree(tree_stack_entry *current, tree_stack_entry *popped, git_str *component)
1100             {
1101             int error;
1102             git_oid new_tree;
1103              
1104 0           git_tree_free(popped->tree);
1105              
1106             /* If the tree would be empty, remove it from the one higher up */
1107 0 0         if (git_treebuilder_entrycount(popped->bld) == 0) {
1108 0           git_treebuilder_free(popped->bld);
1109 0           error = git_treebuilder_remove(current->bld, popped->name);
1110 0           git__free(popped->name);
1111 0           return error;
1112             }
1113              
1114 0           error = git_treebuilder_write(&new_tree, popped->bld);
1115 0           git_treebuilder_free(popped->bld);
1116              
1117 0 0         if (error < 0) {
1118 0           git__free(popped->name);
1119 0           return error;
1120             }
1121              
1122             /* We've written out the tree, now we have to put the new value into its parent */
1123 0           git_str_clear(component);
1124 0           git_str_puts(component, popped->name);
1125 0           git__free(popped->name);
1126              
1127 0 0         GIT_ERROR_CHECK_ALLOC(component->ptr);
1128              
1129             /* Error out if this would create a D/F conflict in this update */
1130 0 0         if (current->tree) {
1131             const git_tree_entry *to_replace;
1132 0           to_replace = git_tree_entry_byname(current->tree, component->ptr);
1133 0 0         if (to_replace && git_tree_entry_type(to_replace) != GIT_OBJECT_TREE) {
    0          
1134 0           git_error_set(GIT_ERROR_TREE, "D/F conflict when updating tree");
1135 0           return -1;
1136             }
1137             }
1138              
1139 0           return git_treebuilder_insert(NULL, current->bld, component->ptr, &new_tree, GIT_FILEMODE_TREE);
1140             }
1141              
1142 0           int git_tree_create_updated(git_oid *out, git_repository *repo, git_tree *baseline, size_t nupdates, const git_tree_update *updates)
1143             {
1144 0           git_array_t(tree_stack_entry) stack = GIT_ARRAY_INIT;
1145             tree_stack_entry *root_elem;
1146             git_vector entries;
1147             int error;
1148             size_t i;
1149 0           git_str component = GIT_STR_INIT;
1150              
1151 0 0         if ((error = git_vector_init(&entries, nupdates, compare_entries)) < 0)
1152 0           return error;
1153              
1154             /* Sort the entries for treversal */
1155 0 0         for (i = 0 ; i < nupdates; i++) {
1156 0 0         if ((error = git_vector_insert_sorted(&entries, (void *) &updates[i], on_dup_entry)) < 0)
1157 0           goto cleanup;
1158             }
1159              
1160 0 0         root_elem = git_array_alloc(stack);
    0          
1161 0 0         GIT_ERROR_CHECK_ALLOC(root_elem);
1162 0           memset(root_elem, 0, sizeof(*root_elem));
1163              
1164 0 0         if (baseline && (error = git_tree_dup(&root_elem->tree, baseline)) < 0)
    0          
1165 0           goto cleanup;
1166              
1167 0 0         if ((error = git_treebuilder_new(&root_elem->bld, repo, root_elem->tree)) < 0)
1168 0           goto cleanup;
1169              
1170 0 0         for (i = 0; i < nupdates; i++) {
1171 0 0         const git_tree_update *last_update = i == 0 ? NULL : git_vector_get(&entries, i-1);
1172 0           const git_tree_update *update = git_vector_get(&entries, i);
1173 0           size_t common_prefix = 0, steps_up, j;
1174             const char *path;
1175              
1176             /* Figure out how much we need to change from the previous tree */
1177 0 0         if (last_update)
1178 0           common_prefix = git_fs_path_common_dirlen(last_update->path, update->path);
1179              
1180             /*
1181             * The entries are sorted, so when we find we're no
1182             * longer in the same directory, we need to abandon
1183             * the old tree (steps up) and dive down to the next
1184             * one.
1185             */
1186 0 0         steps_up = last_update == NULL ? 0 : count_slashes(&last_update->path[common_prefix]);
1187              
1188 0 0         for (j = 0; j < steps_up; j++) {
1189 0 0         tree_stack_entry *current, *popped = git_array_pop(stack);
1190 0 0         GIT_ASSERT(popped);
1191              
1192 0 0         current = git_array_last(stack);
1193 0 0         GIT_ASSERT(current);
1194              
1195 0 0         if ((error = create_popped_tree(current, popped, &component)) < 0)
1196 0           goto cleanup;
1197             }
1198              
1199             /* Now that we've created the trees we popped from the stack, let's go back down */
1200 0           path = &update->path[common_prefix];
1201 0 0         while (next_component(&component, path)) {
1202             tree_stack_entry *last, *new_entry;
1203             const git_tree_entry *entry;
1204              
1205 0 0         last = git_array_last(stack);
1206 0 0         entry = last->tree ? git_tree_entry_byname(last->tree, component.ptr) : NULL;
1207 0 0         if (!entry)
1208 0           entry = treebuilder_get(last->bld, component.ptr);
1209              
1210 0 0         if (entry && git_tree_entry_type(entry) != GIT_OBJECT_TREE) {
    0          
1211 0           git_error_set(GIT_ERROR_TREE, "D/F conflict when updating tree");
1212 0           error = -1;
1213 0           goto cleanup;
1214             }
1215              
1216 0 0         new_entry = git_array_alloc(stack);
    0          
1217 0 0         GIT_ERROR_CHECK_ALLOC(new_entry);
1218 0           memset(new_entry, 0, sizeof(*new_entry));
1219              
1220 0           new_entry->tree = NULL;
1221 0 0         if (entry && (error = git_tree_lookup(&new_entry->tree, repo, git_tree_entry_id(entry))) < 0)
    0          
1222 0           goto cleanup;
1223              
1224 0 0         if ((error = git_treebuilder_new(&new_entry->bld, repo, new_entry->tree)) < 0)
1225 0           goto cleanup;
1226              
1227 0           new_entry->name = git__strdup(component.ptr);
1228 0 0         GIT_ERROR_CHECK_ALLOC(new_entry->name);
1229              
1230             /* Get to the start of the next component */
1231 0           path += component.size + 1;
1232             }
1233              
1234             /* After all that, we're finally at the place where we want to perform the update */
1235 0           switch (update->action) {
1236             case GIT_TREE_UPDATE_UPSERT:
1237             {
1238             /* Make sure we're replacing something of the same type */
1239 0 0         tree_stack_entry *last = git_array_last(stack);
1240 0           char *basename = git_fs_path_basename(update->path);
1241 0           const git_tree_entry *e = git_treebuilder_get(last->bld, basename);
1242 0 0         if (e && git_tree_entry_type(e) != git_object__type_from_filemode(update->filemode)) {
    0          
1243 0           git__free(basename);
1244 0           git_error_set(GIT_ERROR_TREE, "cannot replace '%s' with '%s' at '%s'",
1245             git_object_type2string(git_tree_entry_type(e)),
1246             git_object_type2string(git_object__type_from_filemode(update->filemode)),
1247             update->path);
1248 0           error = -1;
1249 0           goto cleanup;
1250             }
1251              
1252 0           error = git_treebuilder_insert(NULL, last->bld, basename, &update->id, update->filemode);
1253 0           git__free(basename);
1254 0           break;
1255             }
1256             case GIT_TREE_UPDATE_REMOVE:
1257             {
1258 0 0         tree_stack_entry *last = git_array_last(stack);
1259 0           char *basename = git_fs_path_basename(update->path);
1260 0           error = git_treebuilder_remove(last->bld, basename);
1261 0           git__free(basename);
1262 0           break;
1263             }
1264             default:
1265 0           git_error_set(GIT_ERROR_TREE, "unknown action for update");
1266 0           error = -1;
1267 0           goto cleanup;
1268             }
1269              
1270 0 0         if (error < 0)
1271 0           goto cleanup;
1272             }
1273              
1274             /* We're done, go up the stack again and write out the tree */
1275             {
1276 0           tree_stack_entry *current = NULL, *popped = NULL;
1277 0 0         while ((popped = git_array_pop(stack)) != NULL) {
    0          
1278 0 0         current = git_array_last(stack);
1279             /* We've reached the top, current is the root tree */
1280 0 0         if (!current)
1281 0           break;
1282              
1283 0 0         if ((error = create_popped_tree(current, popped, &component)) < 0)
1284 0           goto cleanup;
1285             }
1286              
1287             /* Write out the root tree */
1288 0           git__free(popped->name);
1289 0           git_tree_free(popped->tree);
1290              
1291 0           error = git_treebuilder_write(out, popped->bld);
1292 0           git_treebuilder_free(popped->bld);
1293 0 0         if (error < 0)
1294 0           goto cleanup;
1295             }
1296              
1297             cleanup:
1298             {
1299             tree_stack_entry *e;
1300 0 0         while ((e = git_array_pop(stack)) != NULL) {
    0          
1301 0           git_treebuilder_free(e->bld);
1302 0           git_tree_free(e->tree);
1303 0           git__free(e->name);
1304             }
1305             }
1306              
1307 0           git_str_dispose(&component);
1308 0           git_array_clear(stack);
1309 0           git_vector_free(&entries);
1310 0           return error;
1311             }
1312              
1313             /* Deprecated Functions */
1314              
1315             #ifndef GIT_DEPRECATE_HARD
1316              
1317 0           int git_treebuilder_write_with_buffer(git_oid *oid, git_treebuilder *bld, git_buf *buf)
1318             {
1319 0           GIT_UNUSED(buf);
1320              
1321 0           return git_treebuilder_write(oid, bld);
1322             }
1323              
1324             #endif