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