]>
Commit | Line | Data |
---|---|---|
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 |
18 | static int valid_attributes(const int attributes) |
19 | { | |
932d1baf | 20 | return attributes >= 0 && attributes <= MAX_FILEMODE; |
0ad6efa1 VM |
21 | } |
22 | ||
28c1451a VM |
23 | static int valid_entry_name(const char *filename) |
24 | { | |
3f035860 | 25 | return *filename != '\0' && strchr(filename, '/') == NULL; |
28c1451a VM |
26 | } |
27 | ||
28 | static 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 |
39 | struct tree_key_search { |
40 | const char *filename; | |
41 | size_t filename_len; | |
42 | }; | |
43 | ||
28c1451a | 44 | static 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 | */ | |
79 | static 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 | 123 | void 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 |
139 | const git_oid *git_tree_id(git_tree *c) |
140 | { | |
141 | return git_object_id((git_object *)c); | |
225fe215 VM |
142 | } |
143 | ||
0ad6efa1 | 144 | unsigned int git_tree_entry_attributes(const git_tree_entry *entry) |
003c2690 | 145 | { |
2a884588 | 146 | return entry->attr; |
003c2690 VM |
147 | } |
148 | ||
0ad6efa1 | 149 | const 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 | 155 | const 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 |
161 | git_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 |
173 | int 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 | 182 | const 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 | 195 | const 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 | 201 | int 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 | 228 | unsigned int git_tree_entrycount(git_tree *tree) |
003c2690 | 229 | { |
c4b5bedc | 230 | assert(tree); |
c4034e63 | 231 | return tree->entries.length; |
003c2690 VM |
232 | } |
233 | ||
3aa351ea | 234 | static 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 |
240 | static 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 | 282 | int 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 |
288 | static 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 |
306 | static 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 |
325 | static 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 |
427 | on_error: |
428 | git_treebuilder_free(bld); | |
429 | return -1; | |
29e1789b VM |
430 | } |
431 | ||
432 | int 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 | |
453 | static void sort_entries(git_treebuilder *bld) | |
454 | { | |
455 | git_vector_sort(&bld->entries); | |
456 | } | |
457 | ||
458 | int 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 | ||
486 | on_error: | |
487 | git_treebuilder_free(bld); | |
488 | return -1; | |
0ad6efa1 VM |
489 | } |
490 | ||
491 | int 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 | 532 | static 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 |
550 | const git_tree_entry *git_treebuilder_get(git_treebuilder *bld, const char *filename) |
551 | { | |
552 | return treebuilder_get(bld, filename); | |
553 | } | |
554 | ||
0ad6efa1 VM |
555 | int 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 | ||
566 | int 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 |
603 | on_error: |
604 | git_buf_free(&tree); | |
605 | return -1; | |
0ad6efa1 VM |
606 | } |
607 | ||
608 | void 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 | ||
621 | void 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 | ||
635 | void 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 |
645 | static 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 |
706 | int 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 |
724 | static 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 | 765 | int 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 |