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