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