]> git.proxmox.com Git - libgit2.git/blame - src/notes.c
filemode: introduce enum to ease use of attributes
[libgit2.git] / src / notes.c
CommitLineData
bf477ed4
MS
1/*
2 * Copyright (C) 2009-2012 the libgit2 contributors
3 *
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.
6 */
7
8#include "notes.h"
9
10#include "git2.h"
11#include "refs.h"
caea5e54 12#include "config.h"
86ecd844 13#include "iterator.h"
bf477ed4 14
b93688d0
VM
15static int find_subtree_in_current_level(
16 git_tree **out,
17 git_repository *repo,
18 git_tree *parent,
19 const char *annotated_object_sha,
20 int fanout)
bf477ed4 21{
bf477ed4 22 unsigned int i;
bf477ed4
MS
23 const git_tree_entry *entry;
24
a02e7249 25 *out = NULL;
26
27 if (parent == NULL)
28 return GIT_ENOTFOUND;
bf477ed4 29
a02e7249 30 for (i = 0; i < git_tree_entrycount(parent); i++) {
31 entry = git_tree_entry_byindex(parent, i);
bf477ed4
MS
32
33 if (!git__ishex(git_tree_entry_name(entry)))
34 continue;
35
bf477ed4 36 if (S_ISDIR(git_tree_entry_attributes(entry))
a02e7249 37 && strlen(git_tree_entry_name(entry)) == 2
38 && !strncmp(git_tree_entry_name(entry), annotated_object_sha + fanout, 2))
39 return git_tree_lookup(out, repo, git_tree_entry_id(entry));
bf477ed4 40
a02e7249 41 /* Not a DIR, so do we have an already existing blob? */
42 if (!strcmp(git_tree_entry_name(entry), annotated_object_sha + fanout))
43 return GIT_EEXISTS;
44 }
bf477ed4 45
a02e7249 46 return GIT_ENOTFOUND;
47}
bf477ed4 48
a02e7249 49static int find_subtree_r(git_tree **out, git_tree *root,
50 git_repository *repo, const char *target, int *fanout)
51{
52 int error;
53 git_tree *subtree = NULL;
bf477ed4 54
a02e7249 55 *out = NULL;
bf477ed4 56
a02e7249 57 error = find_subtree_in_current_level(&subtree, repo, root, target, *fanout);
b93688d0 58 if (error == GIT_EEXISTS) {
dca6b228 59 return git_tree_lookup(out, repo, git_tree_id(root));
bf477ed4
MS
60 }
61
a02e7249 62 if (error < 0)
b93688d0 63 return error;
a02e7249 64
65 *fanout += 2;
66 error = find_subtree_r(out, subtree, repo, target, fanout);
dca6b228 67 git_tree_free(subtree);
a02e7249 68
a02e7249 69 return error;
bf477ed4
MS
70}
71
72static int find_blob(git_oid *blob, git_tree *tree, const char *target)
73{
74 unsigned int i;
75 const git_tree_entry *entry;
76
77 for (i=0; i<git_tree_entrycount(tree); i++) {
78 entry = git_tree_entry_byindex(tree, i);
79
80 if (!strcmp(git_tree_entry_name(entry), target)) {
81 /* found matching note object - return */
82
83 git_oid_cpy(blob, git_tree_entry_id(entry));
4aa7de15 84 return 0;
bf477ed4
MS
85 }
86 }
904b67e6 87 return GIT_ENOTFOUND;
bf477ed4
MS
88}
89
b93688d0
VM
90static int tree_write(
91 git_tree **out,
92 git_repository *repo,
93 git_tree *source_tree,
94 const git_oid *object_oid,
95 const char *treeentry_name,
96 unsigned int attributes)
bf477ed4 97{
a02e7249 98 int error;
99 git_treebuilder *tb = NULL;
0e2fcca8 100 const git_tree_entry *entry;
a02e7249 101 git_oid tree_oid;
bf477ed4 102
a02e7249 103 if ((error = git_treebuilder_create(&tb, source_tree)) < 0)
104 goto cleanup;
bf477ed4 105
a02e7249 106 if (object_oid) {
b93688d0
VM
107 if ((error = git_treebuilder_insert(
108 &entry, tb, treeentry_name, object_oid, attributes)) < 0)
a02e7249 109 goto cleanup;
110 } else {
111 if ((error = git_treebuilder_remove(tb, treeentry_name)) < 0)
112 goto cleanup;
113 }
bf477ed4 114
a02e7249 115 if ((error = git_treebuilder_write(&tree_oid, repo, tb)) < 0)
116 goto cleanup;
bf477ed4 117
a02e7249 118 error = git_tree_lookup(out, repo, &tree_oid);
bf477ed4 119
a02e7249 120cleanup:
121 git_treebuilder_free(tb);
122 return error;
123}
bf477ed4 124
b93688d0
VM
125static int manipulate_note_in_tree_r(
126 git_tree **out,
127 git_repository *repo,
128 git_tree *parent,
129 git_oid *note_oid,
130 const char *annotated_object_sha,
131 int fanout,
132 int (*note_exists_cb)(
133 git_tree **out,
134 git_repository *repo,
135 git_tree *parent,
136 git_oid *note_oid,
137 const char *annotated_object_sha,
138 int fanout,
139 int current_error),
140 int (*note_notfound_cb)(
141 git_tree **out,
142 git_repository *repo,
143 git_tree *parent,
144 git_oid *note_oid,
145 const char *annotated_object_sha,
146 int fanout,
147 int current_error))
a02e7249 148{
149 int error = -1;
dca6b228 150 git_tree *subtree = NULL, *new = NULL;
a02e7249 151 char subtree_name[3];
bf477ed4 152
b93688d0
VM
153 error = find_subtree_in_current_level(
154 &subtree, repo, parent, annotated_object_sha, fanout);
bf477ed4 155
a02e7249 156 if (error == GIT_EEXISTS) {
b93688d0
VM
157 error = note_exists_cb(
158 out, repo, parent, note_oid, annotated_object_sha, fanout, error);
a02e7249 159 goto cleanup;
160 }
bf477ed4 161
a02e7249 162 if (error == GIT_ENOTFOUND) {
b93688d0
VM
163 error = note_notfound_cb(
164 out, repo, parent, note_oid, annotated_object_sha, fanout, error);
a02e7249 165 goto cleanup;
166 }
bf477ed4 167
4aa7de15 168 if (error < 0)
a02e7249 169 goto cleanup;
bf477ed4 170
a02e7249 171 /* An existing fanout has been found, let's dig deeper */
b93688d0 172 error = manipulate_note_in_tree_r(
dca6b228 173 &new, repo, subtree, note_oid, annotated_object_sha,
b93688d0 174 fanout + 2, note_exists_cb, note_notfound_cb);
bf477ed4 175
a02e7249 176 if (error < 0)
177 goto cleanup;
bf477ed4 178
a02e7249 179 strncpy(subtree_name, annotated_object_sha + fanout, 2);
180 subtree_name[2] = '\0';
bf477ed4 181
dca6b228
MS
182 error = tree_write(out, repo, parent, git_tree_id(new),
183 subtree_name, 0040000);
184
4aa7de15 185
a02e7249 186cleanup:
dca6b228 187 git_tree_free(new);
a02e7249 188 git_tree_free(subtree);
189 return error;
190}
bf477ed4 191
b93688d0
VM
192static int remove_note_in_tree_eexists_cb(
193 git_tree **out,
194 git_repository *repo,
195 git_tree *parent,
196 git_oid *note_oid,
197 const char *annotated_object_sha,
198 int fanout,
199 int current_error)
200{
a02e7249 201 GIT_UNUSED(note_oid);
202 GIT_UNUSED(current_error);
bf477ed4 203
a02e7249 204 return tree_write(out, repo, parent, NULL, annotated_object_sha + fanout, 0);
205}
bf477ed4 206
b93688d0
VM
207static int remove_note_in_tree_enotfound_cb(
208 git_tree **out,
209 git_repository *repo,
210 git_tree *parent,
211 git_oid *note_oid,
212 const char *annotated_object_sha,
213 int fanout,
214 int current_error)
215{
a02e7249 216 GIT_UNUSED(out);
217 GIT_UNUSED(repo);
218 GIT_UNUSED(parent);
219 GIT_UNUSED(note_oid);
220 GIT_UNUSED(fanout);
bf477ed4 221
a02e7249 222 giterr_set(GITERR_REPOSITORY, "Object '%s' has no note", annotated_object_sha);
223 return current_error;
224}
bf477ed4 225
b93688d0
VM
226static int insert_note_in_tree_eexists_cb(git_tree **out,
227 git_repository *repo,
228 git_tree *parent,
229 git_oid *note_oid,
230 const char *annotated_object_sha,
231 int fanout,
232 int current_error)
233{
a02e7249 234 GIT_UNUSED(out);
235 GIT_UNUSED(repo);
236 GIT_UNUSED(parent);
237 GIT_UNUSED(note_oid);
238 GIT_UNUSED(fanout);
bf477ed4 239
a02e7249 240 giterr_set(GITERR_REPOSITORY, "Note for '%s' exists already", annotated_object_sha);
241 return current_error;
242}
bf477ed4 243
b93688d0
VM
244static int insert_note_in_tree_enotfound_cb(git_tree **out,
245 git_repository *repo,
246 git_tree *parent,
247 git_oid *note_oid,
248 const char *annotated_object_sha,
249 int fanout,
250 int current_error)
251{
a02e7249 252 GIT_UNUSED(current_error);
bf477ed4 253
a02e7249 254 /* No existing fanout at this level, insert in place */
255 return tree_write(out, repo, parent, note_oid, annotated_object_sha + fanout, 0100644);
bf477ed4
MS
256}
257
b93688d0
VM
258static int note_write(git_oid *out,
259 git_repository *repo,
260 git_signature *author,
261 git_signature *committer,
262 const char *notes_ref,
263 const char *note,
264 git_tree *commit_tree,
265 const char *target,
266 git_commit **parents)
bf477ed4 267{
a02e7249 268 int error;
bf477ed4 269 git_oid oid;
a02e7249 270 git_tree *tree = NULL;
271
272 // TODO: should we apply filters?
273 /* create note object */
274 if ((error = git_blob_create_frombuffer(&oid, repo, note, strlen(note))) < 0)
275 goto cleanup;
bf477ed4 276
b93688d0
VM
277 if ((error = manipulate_note_in_tree_r(
278 &tree, repo, commit_tree, &oid, target, 0,
279 insert_note_in_tree_eexists_cb, insert_note_in_tree_enotfound_cb)) < 0)
a02e7249 280 goto cleanup;
bf477ed4 281
a02e7249 282 if (out)
283 git_oid_cpy(out, &oid);
4aa7de15 284
a02e7249 285 error = git_commit_create(&oid, repo, notes_ref, author, committer,
286 NULL, GIT_NOTES_DEFAULT_MSG_ADD,
287 tree, *parents == NULL ? 0 : 1, (const git_commit **) parents);
4aa7de15 288
a02e7249 289cleanup:
bf477ed4 290 git_tree_free(tree);
a02e7249 291 return error;
292}
bf477ed4 293
a02e7249 294static int note_new(git_note **out, git_oid *note_oid, git_blob *blob)
295{
296 git_note *note = NULL;
bf477ed4 297
a02e7249 298 note = (git_note *)git__malloc(sizeof(git_note));
4aa7de15 299 GITERR_CHECK_ALLOC(note);
bf477ed4 300
a02e7249 301 git_oid_cpy(&note->oid, note_oid);
302 note->message = git__strdup((char *)git_blob_rawcontent(blob));
4aa7de15 303 GITERR_CHECK_ALLOC(note->message);
bf477ed4
MS
304
305 *out = note;
306
a02e7249 307 return 0;
bf477ed4
MS
308}
309
a02e7249 310static int note_lookup(git_note **out, git_repository *repo,
311 git_tree *tree, const char *target)
bf477ed4
MS
312{
313 int error, fanout = 0;
314 git_oid oid;
a02e7249 315 git_blob *blob = NULL;
316 git_note *note = NULL;
317 git_tree *subtree = NULL;
bf477ed4 318
a02e7249 319 if ((error = find_subtree_r(&subtree, tree, repo, target, &fanout)) < 0)
320 goto cleanup;
bf477ed4 321
a02e7249 322 if ((error = find_blob(&oid, subtree, target + fanout)) < 0)
323 goto cleanup;
bf477ed4 324
a02e7249 325 if ((error = git_blob_lookup(&blob, repo, &oid)) < 0)
326 goto cleanup;
bf477ed4 327
a02e7249 328 if ((error = note_new(&note, &oid, blob)) < 0)
329 goto cleanup;
bf477ed4 330
a02e7249 331 *out = note;
bf477ed4 332
a02e7249 333cleanup:
334 git_tree_free(subtree);
335 git_blob_free(blob);
336 return error;
337}
bf477ed4 338
a02e7249 339static int note_remove(git_repository *repo,
340 git_signature *author, git_signature *committer,
341 const char *notes_ref, git_tree *tree,
342 const char *target, git_commit **parents)
343{
344 int error;
345 git_tree *tree_after_removal = NULL;
346 git_oid oid;
347
b93688d0
VM
348 if ((error = manipulate_note_in_tree_r(
349 &tree_after_removal, repo, tree, NULL, target, 0,
350 remove_note_in_tree_eexists_cb, remove_note_in_tree_enotfound_cb)) < 0)
a02e7249 351 goto cleanup;
bf477ed4
MS
352
353 error = git_commit_create(&oid, repo, notes_ref, author, committer,
b93688d0
VM
354 NULL, GIT_NOTES_DEFAULT_MSG_RM,
355 tree_after_removal,
356 *parents == NULL ? 0 : 1,
357 (const git_commit **) parents);
bf477ed4 358
a02e7249 359cleanup:
360 git_tree_free(tree_after_removal);
bf477ed4
MS
361 return error;
362}
363
caea5e54
MS
364static int note_get_default_ref(const char **out, git_repository *repo)
365{
f95e8cc0 366 int ret;
caea5e54
MS
367 git_config *cfg;
368
369 *out = NULL;
370
371 if (git_repository_config__weakptr(&cfg, repo) < 0)
372 return -1;
373
29e948de 374 ret = git_config_get_string(out, cfg, "core.notesRef");
904b67e6 375 if (ret == GIT_ENOTFOUND) {
caea5e54
MS
376 *out = GIT_NOTES_DEFAULT_REF;
377 return 0;
378 }
379
f95e8cc0 380 return ret;
caea5e54
MS
381}
382
86ecd844 383static int normalize_namespace(const char **notes_ref, git_repository *repo)
384{
385 if (*notes_ref)
386 return 0;
387
388 return note_get_default_ref(notes_ref, repo);
389}
390
b93688d0
VM
391static int retrieve_note_tree_and_commit(
392 git_tree **tree_out,
393 git_commit **commit_out,
394 git_repository *repo,
395 const char **notes_ref)
86ecd844 396{
a02e7249 397 int error;
86ecd844 398 git_oid oid;
399
a02e7249 400 if ((error = normalize_namespace(notes_ref, repo)) < 0)
401 return error;
86ecd844 402
a02e7249 403 if ((error = git_reference_name_to_oid(&oid, repo, *notes_ref)) < 0)
404 return error;
86ecd844 405
a02e7249 406 if (git_commit_lookup(commit_out, repo, &oid) < 0)
407 return error;
86ecd844 408
a02e7249 409 if ((error = git_commit_tree(tree_out, *commit_out)) < 0)
410 return error;
86ecd844 411
a02e7249 412 return 0;
86ecd844 413}
414
bf477ed4
MS
415int git_note_read(git_note **out, git_repository *repo,
416 const char *notes_ref, const git_oid *oid)
417{
418 int error;
a02e7249 419 char *target = NULL;
420 git_tree *tree = NULL;
421 git_commit *commit = NULL;
bf477ed4 422
bf477ed4 423 target = git_oid_allocfmt(oid);
4aa7de15 424 GITERR_CHECK_ALLOC(target);
bf477ed4 425
a02e7249 426 if ((error = retrieve_note_tree_and_commit(&tree, &commit, repo, &notes_ref)) < 0)
427 goto cleanup;
428
429 error = note_lookup(out, repo, tree, target);
bf477ed4 430
a02e7249 431cleanup:
bf477ed4 432 git__free(target);
a02e7249 433 git_tree_free(tree);
434 git_commit_free(commit);
4aa7de15 435 return error;
bf477ed4
MS
436}
437
4aa7de15
RB
438int git_note_create(
439 git_oid *out, git_repository *repo,
440 git_signature *author, git_signature *committer,
441 const char *notes_ref, const git_oid *oid,
442 const char *note)
bf477ed4 443{
a02e7249 444 int error;
445 char *target = NULL;
bf477ed4 446 git_commit *commit = NULL;
a02e7249 447 git_tree *tree = NULL;
bf477ed4
MS
448
449 target = git_oid_allocfmt(oid);
4aa7de15 450 GITERR_CHECK_ALLOC(target);
bf477ed4 451
a02e7249 452 error = retrieve_note_tree_and_commit(&tree, &commit, repo, &notes_ref);
453
454 if (error < 0 && error != GIT_ENOTFOUND)
455 goto cleanup;
456
bf477ed4 457 error = note_write(out, repo, author, committer, notes_ref,
a02e7249 458 note, tree, target, &commit);
bf477ed4 459
a02e7249 460cleanup:
bf477ed4
MS
461 git__free(target);
462 git_commit_free(commit);
a02e7249 463 git_tree_free(tree);
4aa7de15 464 return error;
bf477ed4
MS
465}
466
467int git_note_remove(git_repository *repo, const char *notes_ref,
468 git_signature *author, git_signature *committer,
469 const git_oid *oid)
470{
471 int error;
a02e7249 472 char *target = NULL;
473 git_commit *commit = NULL;
474 git_tree *tree = NULL;
bf477ed4
MS
475
476 target = git_oid_allocfmt(oid);
4aa7de15 477 GITERR_CHECK_ALLOC(target);
bf477ed4 478
a02e7249 479 if ((error = retrieve_note_tree_and_commit(&tree, &commit, repo, &notes_ref)) < 0)
480 goto cleanup;
481
bf477ed4 482 error = note_remove(repo, author, committer, notes_ref,
a02e7249 483 tree, target, &commit);
bf477ed4 484
a02e7249 485cleanup:
bf477ed4
MS
486 git__free(target);
487 git_commit_free(commit);
a02e7249 488 git_tree_free(tree);
4aa7de15 489 return error;
bf477ed4
MS
490}
491
630c5a4a
MS
492int git_note_default_ref(const char **out, git_repository *repo)
493{
494 assert(repo);
495 return note_get_default_ref(out, repo);
496}
497
bf477ed4
MS
498const char * git_note_message(git_note *note)
499{
500 assert(note);
501 return note->message;
502}
503
504const git_oid * git_note_oid(git_note *note)
505{
506 assert(note);
507 return &note->oid;
508}
509
510void git_note_free(git_note *note)
511{
512 if (note == NULL)
513 return;
514
515 git__free(note->message);
516 git__free(note);
517}
86ecd844 518
519static int process_entry_path(
520 const char* entry_path,
ee7680d5 521 const git_oid *note_oid,
522 int (*note_cb)(git_note_data *note_data, void *payload),
86ecd844 523 void *payload)
524{
b8457baa 525 int error = -1;
526 size_t i = 0, j = 0, len;
86ecd844 527 git_buf buf = GIT_BUF_INIT;
ee7680d5 528 git_note_data note_data;
86ecd844 529
5dca2010 530 if ((error = git_buf_puts(&buf, entry_path)) < 0)
86ecd844 531 goto cleanup;
5dca2010 532
86ecd844 533 len = git_buf_len(&buf);
534
535 while (i < len) {
536 if (buf.ptr[i] == '/') {
537 i++;
538 continue;
539 }
5dca2010 540
86ecd844 541 if (git__fromhex(buf.ptr[i]) < 0) {
542 /* This is not a note entry */
86ecd844 543 goto cleanup;
544 }
545
546 if (i != j)
547 buf.ptr[j] = buf.ptr[i];
548
549 i++;
550 j++;
551 }
552
553 buf.ptr[j] = '\0';
554 buf.size = j;
555
556 if (j != GIT_OID_HEXSZ) {
557 /* This is not a note entry */
86ecd844 558 goto cleanup;
559 }
560
5dca2010
RB
561 if ((error = git_oid_fromstr(
562 &note_data.annotated_object_oid, buf.ptr)) < 0)
563 goto cleanup;
86ecd844 564
ee7680d5 565 git_oid_cpy(&note_data.blob_oid, note_oid);
566
5dca2010
RB
567 if (note_cb(&note_data, payload))
568 error = GIT_EUSER;
86ecd844 569
570cleanup:
571 git_buf_free(&buf);
572 return error;
573}
574
575int git_note_foreach(
576 git_repository *repo,
577 const char *notes_ref,
ee7680d5 578 int (*note_cb)(git_note_data *note_data, void *payload),
86ecd844 579 void *payload)
580{
5dca2010 581 int error;
86ecd844 582 git_iterator *iter = NULL;
583 git_tree *tree = NULL;
a02e7249 584 git_commit *commit = NULL;
d5ed6348 585 const git_index_entry *item;
86ecd844 586
5dca2010
RB
587 if (!(error = retrieve_note_tree_and_commit(
588 &tree, &commit, repo, &notes_ref)) &&
589 !(error = git_iterator_for_tree(&iter, repo, tree)))
590 error = git_iterator_current(iter, &item);
86ecd844 591
5dca2010
RB
592 while (!error && item) {
593 error = process_entry_path(item->path, &item->oid, note_cb, payload);
86ecd844 594
5dca2010
RB
595 if (!error)
596 error = git_iterator_advance(iter, &item);
86ecd844 597 }
598
86ecd844 599 git_iterator_free(iter);
600 git_tree_free(tree);
a02e7249 601 git_commit_free(commit);
5dca2010 602
86ecd844 603 return error;
604}