]> git.proxmox.com Git - libgit2.git/blob - src/notes.c
portability: Improve x86/amd64 compatibility
[libgit2.git] / src / notes.c
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"
12 #include "config.h"
13 #include "iterator.h"
14
15 static 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)
21 {
22 unsigned int i;
23 const git_tree_entry *entry;
24
25 *out = NULL;
26
27 if (parent == NULL)
28 return GIT_ENOTFOUND;
29
30 for (i = 0; i < git_tree_entrycount(parent); i++) {
31 entry = git_tree_entry_byindex(parent, i);
32
33 if (!git__ishex(git_tree_entry_name(entry)))
34 continue;
35
36 if (S_ISDIR(git_tree_entry_attributes(entry))
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));
40
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 }
45
46 return GIT_ENOTFOUND;
47 }
48
49 static 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;
54
55 *out = NULL;
56
57 error = find_subtree_in_current_level(&subtree, repo, root, target, *fanout);
58 if (error == GIT_EEXISTS) {
59 return git_tree_lookup(out, repo, git_tree_id(root));
60 }
61
62 if (error < 0)
63 return error;
64
65 *fanout += 2;
66 error = find_subtree_r(out, subtree, repo, target, fanout);
67 git_tree_free(subtree);
68
69 return error;
70 }
71
72 static 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));
84 return 0;
85 }
86 }
87 return GIT_ENOTFOUND;
88 }
89
90 static 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)
97 {
98 int error;
99 git_treebuilder *tb = NULL;
100 const git_tree_entry *entry;
101 git_oid tree_oid;
102
103 if ((error = git_treebuilder_create(&tb, source_tree)) < 0)
104 goto cleanup;
105
106 if (object_oid) {
107 if ((error = git_treebuilder_insert(
108 &entry, tb, treeentry_name, object_oid, attributes)) < 0)
109 goto cleanup;
110 } else {
111 if ((error = git_treebuilder_remove(tb, treeentry_name)) < 0)
112 goto cleanup;
113 }
114
115 if ((error = git_treebuilder_write(&tree_oid, repo, tb)) < 0)
116 goto cleanup;
117
118 error = git_tree_lookup(out, repo, &tree_oid);
119
120 cleanup:
121 git_treebuilder_free(tb);
122 return error;
123 }
124
125 static 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))
148 {
149 int error = -1;
150 git_tree *subtree = NULL, *new = NULL;
151 char subtree_name[3];
152
153 error = find_subtree_in_current_level(
154 &subtree, repo, parent, annotated_object_sha, fanout);
155
156 if (error == GIT_EEXISTS) {
157 error = note_exists_cb(
158 out, repo, parent, note_oid, annotated_object_sha, fanout, error);
159 goto cleanup;
160 }
161
162 if (error == GIT_ENOTFOUND) {
163 error = note_notfound_cb(
164 out, repo, parent, note_oid, annotated_object_sha, fanout, error);
165 goto cleanup;
166 }
167
168 if (error < 0)
169 goto cleanup;
170
171 /* An existing fanout has been found, let's dig deeper */
172 error = manipulate_note_in_tree_r(
173 &new, repo, subtree, note_oid, annotated_object_sha,
174 fanout + 2, note_exists_cb, note_notfound_cb);
175
176 if (error < 0)
177 goto cleanup;
178
179 strncpy(subtree_name, annotated_object_sha + fanout, 2);
180 subtree_name[2] = '\0';
181
182 error = tree_write(out, repo, parent, git_tree_id(new),
183 subtree_name, 0040000);
184
185
186 cleanup:
187 git_tree_free(new);
188 git_tree_free(subtree);
189 return error;
190 }
191
192 static 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 {
201 GIT_UNUSED(note_oid);
202 GIT_UNUSED(current_error);
203
204 return tree_write(out, repo, parent, NULL, annotated_object_sha + fanout, 0);
205 }
206
207 static 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 {
216 GIT_UNUSED(out);
217 GIT_UNUSED(repo);
218 GIT_UNUSED(parent);
219 GIT_UNUSED(note_oid);
220 GIT_UNUSED(fanout);
221
222 giterr_set(GITERR_REPOSITORY, "Object '%s' has no note", annotated_object_sha);
223 return current_error;
224 }
225
226 static 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 {
234 GIT_UNUSED(out);
235 GIT_UNUSED(repo);
236 GIT_UNUSED(parent);
237 GIT_UNUSED(note_oid);
238 GIT_UNUSED(fanout);
239
240 giterr_set(GITERR_REPOSITORY, "Note for '%s' exists already", annotated_object_sha);
241 return current_error;
242 }
243
244 static 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 {
252 GIT_UNUSED(current_error);
253
254 /* No existing fanout at this level, insert in place */
255 return tree_write(out, repo, parent, note_oid, annotated_object_sha + fanout, 0100644);
256 }
257
258 static 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)
267 {
268 int error;
269 git_oid oid;
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;
276
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)
280 goto cleanup;
281
282 if (out)
283 git_oid_cpy(out, &oid);
284
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);
288
289 cleanup:
290 git_tree_free(tree);
291 return error;
292 }
293
294 static int note_new(git_note **out, git_oid *note_oid, git_blob *blob)
295 {
296 git_note *note = NULL;
297
298 note = (git_note *)git__malloc(sizeof(git_note));
299 GITERR_CHECK_ALLOC(note);
300
301 git_oid_cpy(&note->oid, note_oid);
302 note->message = git__strdup((char *)git_blob_rawcontent(blob));
303 GITERR_CHECK_ALLOC(note->message);
304
305 *out = note;
306
307 return 0;
308 }
309
310 static int note_lookup(git_note **out, git_repository *repo,
311 git_tree *tree, const char *target)
312 {
313 int error, fanout = 0;
314 git_oid oid;
315 git_blob *blob = NULL;
316 git_note *note = NULL;
317 git_tree *subtree = NULL;
318
319 if ((error = find_subtree_r(&subtree, tree, repo, target, &fanout)) < 0)
320 goto cleanup;
321
322 if ((error = find_blob(&oid, subtree, target + fanout)) < 0)
323 goto cleanup;
324
325 if ((error = git_blob_lookup(&blob, repo, &oid)) < 0)
326 goto cleanup;
327
328 if ((error = note_new(&note, &oid, blob)) < 0)
329 goto cleanup;
330
331 *out = note;
332
333 cleanup:
334 git_tree_free(subtree);
335 git_blob_free(blob);
336 return error;
337 }
338
339 static 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
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)
351 goto cleanup;
352
353 error = git_commit_create(&oid, repo, notes_ref, author, committer,
354 NULL, GIT_NOTES_DEFAULT_MSG_RM,
355 tree_after_removal,
356 *parents == NULL ? 0 : 1,
357 (const git_commit **) parents);
358
359 cleanup:
360 git_tree_free(tree_after_removal);
361 return error;
362 }
363
364 static int note_get_default_ref(const char **out, git_repository *repo)
365 {
366 int ret;
367 git_config *cfg;
368
369 *out = NULL;
370
371 if (git_repository_config__weakptr(&cfg, repo) < 0)
372 return -1;
373
374 ret = git_config_get_string(out, cfg, "core.notesRef");
375 if (ret == GIT_ENOTFOUND) {
376 *out = GIT_NOTES_DEFAULT_REF;
377 return 0;
378 }
379
380 return ret;
381 }
382
383 static 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
391 static 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)
396 {
397 int error;
398 git_oid oid;
399
400 if ((error = normalize_namespace(notes_ref, repo)) < 0)
401 return error;
402
403 if ((error = git_reference_name_to_oid(&oid, repo, *notes_ref)) < 0)
404 return error;
405
406 if (git_commit_lookup(commit_out, repo, &oid) < 0)
407 return error;
408
409 if ((error = git_commit_tree(tree_out, *commit_out)) < 0)
410 return error;
411
412 return 0;
413 }
414
415 int git_note_read(git_note **out, git_repository *repo,
416 const char *notes_ref, const git_oid *oid)
417 {
418 int error;
419 char *target = NULL;
420 git_tree *tree = NULL;
421 git_commit *commit = NULL;
422
423 target = git_oid_allocfmt(oid);
424 GITERR_CHECK_ALLOC(target);
425
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);
430
431 cleanup:
432 git__free(target);
433 git_tree_free(tree);
434 git_commit_free(commit);
435 return error;
436 }
437
438 int 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)
443 {
444 int error;
445 char *target = NULL;
446 git_commit *commit = NULL;
447 git_tree *tree = NULL;
448
449 target = git_oid_allocfmt(oid);
450 GITERR_CHECK_ALLOC(target);
451
452 error = retrieve_note_tree_and_commit(&tree, &commit, repo, &notes_ref);
453
454 if (error < 0 && error != GIT_ENOTFOUND)
455 goto cleanup;
456
457 error = note_write(out, repo, author, committer, notes_ref,
458 note, tree, target, &commit);
459
460 cleanup:
461 git__free(target);
462 git_commit_free(commit);
463 git_tree_free(tree);
464 return error;
465 }
466
467 int 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;
472 char *target = NULL;
473 git_commit *commit = NULL;
474 git_tree *tree = NULL;
475
476 target = git_oid_allocfmt(oid);
477 GITERR_CHECK_ALLOC(target);
478
479 if ((error = retrieve_note_tree_and_commit(&tree, &commit, repo, &notes_ref)) < 0)
480 goto cleanup;
481
482 error = note_remove(repo, author, committer, notes_ref,
483 tree, target, &commit);
484
485 cleanup:
486 git__free(target);
487 git_commit_free(commit);
488 git_tree_free(tree);
489 return error;
490 }
491
492 int git_note_default_ref(const char **out, git_repository *repo)
493 {
494 assert(repo);
495 return note_get_default_ref(out, repo);
496 }
497
498 const char * git_note_message(git_note *note)
499 {
500 assert(note);
501 return note->message;
502 }
503
504 const git_oid * git_note_oid(git_note *note)
505 {
506 assert(note);
507 return &note->oid;
508 }
509
510 void git_note_free(git_note *note)
511 {
512 if (note == NULL)
513 return;
514
515 git__free(note->message);
516 git__free(note);
517 }
518
519 static int process_entry_path(
520 const char* entry_path,
521 const git_oid *note_oid,
522 int (*note_cb)(git_note_data *note_data, void *payload),
523 void *payload)
524 {
525 int error = -1;
526 size_t i = 0, j = 0, len;
527 git_buf buf = GIT_BUF_INIT;
528 git_note_data note_data;
529
530 if (git_buf_puts(&buf, entry_path) < 0)
531 goto cleanup;
532
533 len = git_buf_len(&buf);
534
535 while (i < len) {
536 if (buf.ptr[i] == '/') {
537 i++;
538 continue;
539 }
540
541 if (git__fromhex(buf.ptr[i]) < 0) {
542 /* This is not a note entry */
543 error = 0;
544 goto cleanup;
545 }
546
547 if (i != j)
548 buf.ptr[j] = buf.ptr[i];
549
550 i++;
551 j++;
552 }
553
554 buf.ptr[j] = '\0';
555 buf.size = j;
556
557 if (j != GIT_OID_HEXSZ) {
558 /* This is not a note entry */
559 error = 0;
560 goto cleanup;
561 }
562
563 if (git_oid_fromstr(&note_data.annotated_object_oid, buf.ptr) < 0)
564 return -1;
565
566 git_oid_cpy(&note_data.blob_oid, note_oid);
567
568 error = note_cb(&note_data, payload);
569
570 cleanup:
571 git_buf_free(&buf);
572 return error;
573 }
574
575 int git_note_foreach(
576 git_repository *repo,
577 const char *notes_ref,
578 int (*note_cb)(git_note_data *note_data, void *payload),
579 void *payload)
580 {
581 int error = -1;
582 git_iterator *iter = NULL;
583 git_tree *tree = NULL;
584 git_commit *commit = NULL;
585 const git_index_entry *item;
586
587 if ((error = retrieve_note_tree_and_commit(&tree, &commit, repo, &notes_ref)) < 0)
588 goto cleanup;
589
590 if (git_iterator_for_tree(&iter, repo, tree) < 0)
591 goto cleanup;
592
593 if (git_iterator_current(iter, &item) < 0)
594 goto cleanup;
595
596 while (item) {
597 if (process_entry_path(item->path, &item->oid, note_cb, payload) < 0)
598 goto cleanup;
599
600 if (git_iterator_advance(iter, &item) < 0)
601 goto cleanup;
602 }
603
604 error = 0;
605
606 cleanup:
607 git_iterator_free(iter);
608 git_tree_free(tree);
609 git_commit_free(commit);
610 return error;
611 }