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