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