]> git.proxmox.com Git - libgit2.git/blame - src/tree.c
tree: Use an internal append functiont to add new entries
[libgit2.git] / src / tree.c
CommitLineData
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
0ad6efa1 18static int valid_attributes(const int attributes) {
932d1baf 19 return attributes >= 0 && attributes <= MAX_FILEMODE;
0ad6efa1
VM
20}
21
761aa2aa
VM
22struct tree_key_search {
23 const char *filename;
24 size_t filename_len;
25};
26
d568d585 27static int entry_search_cmp(const void *key, const void *array_member)
2a884588 28{
0cbbdc26
KS
29 const struct tree_key_search *ksearch = key;
30 const git_tree_entry *entry = array_member;
2a884588 31
e6629d83
VM
32 int result =
33 git_futils_cmp_path(
34 ksearch->filename, ksearch->filename_len, entry->attr & 040000,
35 entry->filename, entry->filename_len, entry->attr & 040000);
36
37 return result ? result : ((int)ksearch->filename_len - (int)entry->filename_len);
9de27ad0
SJ
38}
39
d568d585 40static int entry_sort_cmp(const void *a, const void *b)
2a884588 41{
de18f276
VM
42 const git_tree_entry *entry_a = (const git_tree_entry *)(a);
43 const git_tree_entry *entry_b = (const git_tree_entry *)(b);
2a884588 44
761aa2aa
VM
45 return git_futils_cmp_path(
46 entry_a->filename, entry_a->filename_len, entry_a->attr & 040000,
47 entry_b->filename, entry_b->filename_len, entry_b->attr & 040000);
2a884588
VM
48}
49
e6629d83
VM
50static int build_ksearch(struct tree_key_search *ksearch, const char *path)
51{
52 size_t len = strlen(path);
53
54 if (len && path[len - 1] == '/')
55 len--;
56
57 if (len == 0 || memchr(path, '/', len) != NULL)
58 return GIT_ERROR;
59
60 ksearch->filename = path;
61 ksearch->filename_len = len;
62
63 return GIT_SUCCESS;
64}
65
72a3fe42 66void git_tree__free(git_tree *tree)
225fe215 67{
c4034e63 68 unsigned int i;
003c2690 69
c4034e63
VM
70 for (i = 0; i < tree->entries.length; ++i) {
71 git_tree_entry *e;
72 e = git_vector_get(&tree->entries, i);
73
74 free(e->filename);
75 free(e);
58519018 76 }
003c2690 77
c8f5ff8f 78 git_vector_free(&tree->entries);
225fe215
VM
79 free(tree);
80}
81
d45b4a9a
VM
82const git_oid *git_tree_id(git_tree *c)
83{
84 return git_object_id((git_object *)c);
225fe215
VM
85}
86
0ad6efa1 87unsigned int git_tree_entry_attributes(const git_tree_entry *entry)
003c2690 88{
2a884588 89 return entry->attr;
003c2690
VM
90}
91
0ad6efa1 92const char *git_tree_entry_name(const git_tree_entry *entry)
003c2690 93{
c4b5bedc 94 assert(entry);
2a884588
VM
95 return entry->filename;
96}
003c2690 97
0ad6efa1 98const git_oid *git_tree_entry_id(const git_tree_entry *entry)
2a884588 99{
c4b5bedc 100 assert(entry);
2a884588 101 return &entry->oid;
003c2690
VM
102}
103
ff9a4c13
RG
104git_otype git_tree_entry_type(const git_tree_entry *entry)
105{
106 assert(entry);
107
108 if (S_ISGITLINK(entry->attr))
109 return GIT_OBJ_COMMIT;
110 else if (S_ISDIR(entry->attr))
111 return GIT_OBJ_TREE;
112 else
113 return GIT_OBJ_BLOB;
114}
115
0ad6efa1 116int git_tree_entry_2object(git_object **object_out, git_repository *repo, const git_tree_entry *entry)
003c2690 117{
1795f879 118 assert(entry && object_out);
72a3fe42 119 return git_object_lookup(object_out, repo, &entry->oid, GIT_OBJ_ANY);
122c3405
VM
120}
121
0ad6efa1 122const git_tree_entry *git_tree_entry_byname(git_tree *tree, const char *filename)
2a884588 123{
c4034e63 124 int idx;
761aa2aa 125 struct tree_key_search ksearch;
c4b5bedc
VM
126
127 assert(tree && filename);
128
e6629d83
VM
129 if (build_ksearch(&ksearch, filename) < GIT_SUCCESS)
130 return NULL;
761aa2aa
VM
131
132 idx = git_vector_bsearch2(&tree->entries, entry_search_cmp, &ksearch);
c4034e63
VM
133 if (idx == GIT_ENOTFOUND)
134 return NULL;
135
136 return git_vector_get(&tree->entries, idx);
2a884588
VM
137}
138
e5c80097 139const git_tree_entry *git_tree_entry_byindex(git_tree *tree, unsigned int idx)
003c2690 140{
c4b5bedc 141 assert(tree);
e5c80097 142 return git_vector_get(&tree->entries, idx);
003c2690
VM
143}
144
e5c80097 145unsigned int git_tree_entrycount(git_tree *tree)
003c2690 146{
c4b5bedc 147 assert(tree);
c4034e63 148 return tree->entries.length;
003c2690
VM
149}
150
720d5472 151static int tree_parse_buffer(git_tree *tree, const char *buffer, const char *buffer_end)
d8603ed9 152{
6f02c3ba 153 int error = GIT_SUCCESS;
2a884588 154
72a3fe42
VM
155 if (git_vector_init(&tree->entries, DEFAULT_TREE_SIZE, entry_sort_cmp) < GIT_SUCCESS)
156 return GIT_ENOMEM;
e4def81a 157
003c2690
VM
158 while (buffer < buffer_end) {
159 git_tree_entry *entry;
ad196c6a 160 int tmp;
d8603ed9 161
29e1789b 162 entry = git__calloc(1, sizeof(git_tree_entry));
2a884588
VM
163 if (entry == NULL) {
164 error = GIT_ENOMEM;
165 break;
003c2690 166 }
d8603ed9 167
6f02c3ba 168 if (git_vector_insert(&tree->entries, entry) < GIT_SUCCESS)
c4034e63 169 return GIT_ENOMEM;
003c2690 170
0b2c4061
KS
171 if (git__strtol32(&tmp, buffer, &buffer, 8) < GIT_SUCCESS ||
172 !buffer || tmp > UINT_MAX || tmp < 0)
d6de92b6 173 return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tree. Can't parse attributes");
0b2c4061 174 entry->attr = tmp;
d8603ed9
VM
175
176 if (*buffer++ != ' ') {
d6de92b6 177 error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse tree. Object it corrupted");
d8603ed9
VM
178 break;
179 }
180
58519018 181 if (memchr(buffer, 0, buffer_end - buffer) == NULL) {
d6de92b6 182 error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse tree. Object it corrupted");
58519018
VM
183 break;
184 }
185
186 entry->filename = git__strdup(buffer);
0ad6efa1 187 entry->filename_len = strlen(buffer);
d8603ed9 188
2a884588
VM
189 while (buffer < buffer_end && *buffer != 0)
190 buffer++;
003c2690 191
2a884588 192 buffer++;
d8603ed9 193
fa48608e 194 git_oid_fromraw(&entry->oid, (const unsigned char *)buffer);
d8603ed9 195 buffer += GIT_OID_RAWSZ;
d8603ed9
VM
196 }
197
bc06a4ee 198 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse buffer");
d8603ed9 199}
58519018 200
72a3fe42 201int git_tree__parse(git_tree *tree, git_odb_object *obj)
58519018 202{
72a3fe42
VM
203 assert(tree);
204 return tree_parse_buffer(tree, (char *)obj->raw.data, (char *)obj->raw.data + obj->raw.len);
58519018
VM
205}
206
8255c69b
CMN
207static unsigned int find_next_dir(const char *dirname, git_index *index, unsigned int start)
208{
209 unsigned int i, entries = git_index_entrycount(index);
210 size_t dirlen;
211
212 dirlen = strlen(dirname);
213 for (i = start; i < entries; ++i) {
214 git_index_entry *entry = git_index_get(index, i);
215 if (strlen(entry->path) < dirlen ||
216 memcmp(entry->path, dirname, dirlen) ||
217 (dirlen > 0 && entry->path[dirlen] != '/')) {
218 break;
219 }
220 }
221
222 return i;
223}
224
9ef9e8c3
VM
225static int append_entry(git_treebuilder *bld, const char *filename, const git_oid *id, unsigned int attributes)
226{
227 git_tree_entry *entry;
228
229 if ((entry = git__malloc(sizeof(git_tree_entry))) == NULL)
230 return GIT_ENOMEM;
231
232 memset(entry, 0x0, sizeof(git_tree_entry));
233 entry->filename = git__strdup(filename);
234 entry->filename_len = strlen(entry->filename);
235
236 bld->entry_count++;
237
238 git_oid_cpy(&entry->oid, id);
239 entry->attr = attributes;
240
241 if (git_vector_insert(&bld->entries, entry) < 0)
242 return GIT_ENOMEM;
243
244 return GIT_SUCCESS;
245}
246
4a619797 247static int write_tree(git_oid *oid, git_index *index, const char *dirname, unsigned int start)
47d8ec56 248{
4a619797
CMN
249 git_treebuilder *bld = NULL;
250 unsigned int i, entries = git_index_entrycount(index);
251 int error;
252 size_t dirname_len = strlen(dirname);
8255c69b
CMN
253 const git_tree_cache *cache;
254
255 cache = git_tree_cache_get(index->tree, dirname);
256 if (cache != NULL && cache->entries >= 0){
257 git_oid_cpy(oid, &cache->oid);
258 return find_next_dir(dirname, index, start);
259 }
29e1789b 260
4a619797
CMN
261 error = git_treebuilder_create(&bld, NULL);
262 if (bld == NULL) {
47d8ec56 263 return GIT_ENOMEM;
4a619797 264 }
932d1baf 265
4a619797
CMN
266 /*
267 * This loop is unfortunate, but necessary. The index doesn't have
268 * any directores, so we need to handle that manually, and we
269 * need to keep track of the current position.
270 */
271 for (i = start; i < entries; ++i) {
272 git_index_entry *entry = git_index_get(index, i);
273 char *filename, *next_slash;
274
275 /*
276 * If we've left our (sub)tree, exit the loop and return. The
277 * first check is an early out (and security for the
278 * third). The second check is a simple prefix comparison. The
279 * third check catches situations where there is a directory
280 * win32/sys and a file win32mmap.c. Without it, the following
281 * code believes there is a file win32/mmap.c
282 */
283 if (strlen(entry->path) < dirname_len ||
284 memcmp(entry->path, dirname, dirname_len) ||
285 (dirname_len > 0 && entry->path[dirname_len] != '/')) {
47d8ec56 286 break;
4a619797 287 }
932d1baf 288
4a619797
CMN
289 filename = entry->path + dirname_len;
290 if (*filename == '/')
291 filename++;
292 next_slash = strchr(filename, '/');
293 if (next_slash) {
294 git_oid sub_oid;
295 int written;
296 char *subdir, *last_comp;
297
298 subdir = git__strndup(entry->path, next_slash - entry->path);
299 if (subdir == NULL) {
300 error = GIT_ENOMEM;
301 goto cleanup;
29e1789b 302 }
29e1789b 303
4a619797
CMN
304 /* Write out the subtree */
305 written = write_tree(&sub_oid, index, subdir, i);
306 if (written < 0) {
307 error = git__rethrow(written, "Failed to write subtree %s", subdir);
308 } else {
309 i = written - 1; /* -1 because of the loop increment */
29e1789b 310 }
932d1baf 311
4a619797
CMN
312 /*
313 * We need to figure out what we want toinsert
314 * into this tree. If we're traversing
315 * deps/zlib/, then we only want to write
316 * 'zlib' into the tree.
317 */
318 last_comp = strrchr(subdir, '/');
319 if (last_comp) {
320 last_comp++; /* Get rid of the '/' */
321 } else {
322 last_comp = subdir;
323 }
9ef9e8c3 324 error = append_entry(bld, last_comp, &sub_oid, S_IFDIR);
4a619797
CMN
325 free(subdir);
326 if (error < GIT_SUCCESS) {
327 error = git__rethrow(error, "Failed to insert dir");
328 goto cleanup;
329 }
330 } else {
9ef9e8c3 331 error = append_entry(bld, filename, &entry->oid, entry->mode);
4a619797
CMN
332 if (error < GIT_SUCCESS) {
333 error = git__rethrow(error, "Failed to insert file");
334 }
47d8ec56 335 }
29e1789b 336 }
932d1baf 337
4a619797
CMN
338 error = git_treebuilder_write(oid, index->repository, bld);
339 if (error < GIT_SUCCESS)
340 error = git__rethrow(error, "Failed to write tree to db");
29e1789b 341
4a619797
CMN
342 cleanup:
343 git_treebuilder_free(bld);
344
345 if (error < GIT_SUCCESS)
346 return error;
347 else
348 return i;
29e1789b
VM
349}
350
351int git_tree_create_fromindex(git_oid *oid, git_index *index)
352{
353 int error;
354
355 if (index->repository == NULL)
d6de92b6 356 return git__throw(GIT_EBAREINDEX, "Failed to create tree. The index file is not backed up by an existing repository");
47d8ec56 357
8255c69b
CMN
358 if (index->tree != NULL && index->tree->entries >= 0) {
359 git_oid_cpy(oid, &index->tree->oid);
360 return GIT_SUCCESS;
361 }
362
4a619797
CMN
363 /* The tree cache didn't help us */
364 error = write_tree(oid, index, "", 0);
bc06a4ee 365 return (error < GIT_SUCCESS) ? git__rethrow(error, "Failed to create tree") : GIT_SUCCESS;
47d8ec56 366}
0ad6efa1
VM
367
368static void sort_entries(git_treebuilder *bld)
369{
370 git_vector_sort(&bld->entries);
371}
372
373int git_treebuilder_create(git_treebuilder **builder_p, const git_tree *source)
374{
375 git_treebuilder *bld;
c5d8745f 376 unsigned int i, source_entries = DEFAULT_TREE_SIZE;
0ad6efa1
VM
377
378 assert(builder_p);
379
380 bld = git__calloc(1, sizeof(git_treebuilder));
381 if (bld == NULL)
382 return GIT_ENOMEM;
383
384 if (source != NULL)
385 source_entries = source->entries.length;
386
387 if (git_vector_init(&bld->entries, source_entries, entry_sort_cmp) < GIT_SUCCESS) {
388 free(bld);
389 return GIT_ENOMEM;
390 }
391
392 if (source != NULL) {
0ad6efa1
VM
393 for (i = 0; i < source->entries.length; ++i) {
394 git_tree_entry *entry_src = source->entries.contents[i];
0ad6efa1 395
9ef9e8c3 396 if (append_entry(bld, entry_src->filename, &entry_src->oid, entry_src->attr) < 0) {
0ad6efa1
VM
397 git_treebuilder_free(bld);
398 return GIT_ENOMEM;
399 }
0ad6efa1
VM
400 }
401 }
402
403 *builder_p = bld;
404 return GIT_SUCCESS;
405}
406
407int git_treebuilder_insert(git_tree_entry **entry_out, git_treebuilder *bld, const char *filename, const git_oid *id, unsigned int attributes)
408{
409 git_tree_entry *entry;
410 int pos;
761aa2aa 411 struct tree_key_search ksearch;
0ad6efa1
VM
412
413 assert(bld && id && filename);
414
415 if (!valid_attributes(attributes))
f4ad64c1 416 return git__throw(GIT_ERROR, "Failed to insert entry. Invalid attributes");
0ad6efa1 417
f4ad64c1 418 if (build_ksearch(&ksearch, filename) < GIT_SUCCESS)
419 return git__throw(GIT_ERROR, "Failed to insert entry. Invalid filename '%s'", filename);
420
421 if ((pos = git_vector_bsearch2(&bld->entries, entry_search_cmp, &ksearch)) != GIT_ENOTFOUND) {
0ad6efa1
VM
422 entry = git_vector_get(&bld->entries, pos);
423 if (entry->removed) {
424 entry->removed = 0;
425 bld->entry_count++;
426 }
427 } else {
428 if ((entry = git__malloc(sizeof(git_tree_entry))) == NULL)
429 return GIT_ENOMEM;
430
431 memset(entry, 0x0, sizeof(git_tree_entry));
432 entry->filename = git__strdup(filename);
433 entry->filename_len = strlen(entry->filename);
434
435 bld->entry_count++;
436 }
437
438 git_oid_cpy(&entry->oid, id);
439 entry->attr = attributes;
440
98ac6780 441 if (pos == GIT_ENOTFOUND) {
0ad6efa1
VM
442 if (git_vector_insert(&bld->entries, entry) < 0)
443 return GIT_ENOMEM;
444 }
445
446 if (entry_out != NULL)
447 *entry_out = entry;
448
449 return GIT_SUCCESS;
450}
451
0cbbdc26 452static git_tree_entry *treebuilder_get(git_treebuilder *bld, const char *filename)
0ad6efa1
VM
453{
454 int idx;
455 git_tree_entry *entry;
761aa2aa 456 struct tree_key_search ksearch;
0ad6efa1
VM
457
458 assert(bld && filename);
459
e6629d83
VM
460 if (build_ksearch(&ksearch, filename) < GIT_SUCCESS)
461 return NULL;
761aa2aa 462
761aa2aa 463 idx = git_vector_bsearch2(&bld->entries, entry_search_cmp, &ksearch);
0ad6efa1
VM
464 if (idx == GIT_ENOTFOUND)
465 return NULL;
466
467 entry = git_vector_get(&bld->entries, idx);
468 if (entry->removed)
469 return NULL;
470
471 return entry;
472}
473
0cbbdc26
KS
474const git_tree_entry *git_treebuilder_get(git_treebuilder *bld, const char *filename)
475{
476 return treebuilder_get(bld, filename);
477}
478
0ad6efa1
VM
479int git_treebuilder_remove(git_treebuilder *bld, const char *filename)
480{
0cbbdc26 481 git_tree_entry *remove_ptr = treebuilder_get(bld, filename);
0ad6efa1
VM
482
483 if (remove_ptr == NULL || remove_ptr->removed)
d6de92b6 484 return git__throw(GIT_ENOTFOUND, "Failed to remove entry. File isn't in the tree");
0ad6efa1
VM
485
486 remove_ptr->removed = 1;
487 bld->entry_count--;
488 return GIT_SUCCESS;
489}
490
491int git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *bld)
492{
afeecf4f 493 unsigned int i;
0ad6efa1 494 int error;
afeecf4f 495 git_buf tree = GIT_BUF_INIT;
0ad6efa1
VM
496
497 assert(bld);
498
499 sort_entries(bld);
500
afeecf4f
VM
501 /* Grow the buffer beforehand to an estimated size */
502 git_buf_grow(&tree, bld->entries.length * 72);
503
0ad6efa1
VM
504 for (i = 0; i < bld->entries.length; ++i) {
505 git_tree_entry *entry = bld->entries.contents[i];
506
507 if (entry->removed)
508 continue;
509
afeecf4f
VM
510 git_buf_printf(&tree, "%o ", entry->attr);
511 git_buf_put(&tree, entry->filename, entry->filename_len + 1);
512 git_buf_put(&tree, (char *)entry->oid.id, GIT_OID_RAWSZ);
0ad6efa1
VM
513 }
514
afeecf4f
VM
515 if (git_buf_oom(&tree)) {
516 git_buf_free(&tree);
517 return git__throw(GIT_ENOMEM, "Not enough memory to build the tree data");
c5d8745f 518 }
0ad6efa1 519
afeecf4f
VM
520 error = git_odb_write(oid, git_repository_database(repo), tree.ptr, tree.size, GIT_OBJ_TREE);
521 git_buf_free(&tree);
0ad6efa1 522
bc06a4ee 523 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write tree");
0ad6efa1
VM
524}
525
526void git_treebuilder_filter(git_treebuilder *bld, int (*filter)(const git_tree_entry *, void *), void *payload)
527{
c5d8745f 528 unsigned int i;
0ad6efa1
VM
529
530 assert(bld && filter);
531
532 for (i = 0; i < bld->entries.length; ++i) {
533 git_tree_entry *entry = bld->entries.contents[i];
534 if (!entry->removed && filter(entry, payload))
535 entry->removed = 1;
536 }
537}
538
539void git_treebuilder_clear(git_treebuilder *bld)
540{
c5d8745f 541 unsigned int i;
0ad6efa1
VM
542 assert(bld);
543
544 for (i = 0; i < bld->entries.length; ++i) {
545 git_tree_entry *e = bld->entries.contents[i];
546 free(e->filename);
547 free(e);
548 }
549
550 git_vector_clear(&bld->entries);
551}
552
553void git_treebuilder_free(git_treebuilder *bld)
554{
555 git_treebuilder_clear(bld);
556 git_vector_free(&bld->entries);
557 free(bld);
558}
559
560