/* * Copyright (C) 2009-2012 the libgit2 contributors * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ #include "notes.h" #include "git2.h" #include "refs.h" #include "config.h" #include "iterator.h" static int find_subtree_in_current_level( git_tree **out, git_repository *repo, git_tree *parent, const char *annotated_object_sha, int fanout) { unsigned int i; const git_tree_entry *entry; *out = NULL; if (parent == NULL) return GIT_ENOTFOUND; for (i = 0; i < git_tree_entrycount(parent); i++) { entry = git_tree_entry_byindex(parent, i); if (!git__ishex(git_tree_entry_name(entry))) continue; if (S_ISDIR(git_tree_entry_attributes(entry)) && strlen(git_tree_entry_name(entry)) == 2 && !strncmp(git_tree_entry_name(entry), annotated_object_sha + fanout, 2)) return git_tree_lookup(out, repo, git_tree_entry_id(entry)); /* Not a DIR, so do we have an already existing blob? */ if (!strcmp(git_tree_entry_name(entry), annotated_object_sha + fanout)) return GIT_EEXISTS; } return GIT_ENOTFOUND; } static int find_subtree_r(git_tree **out, git_tree *root, git_repository *repo, const char *target, int *fanout) { int error; git_tree *subtree = NULL; *out = NULL; error = find_subtree_in_current_level(&subtree, repo, root, target, *fanout); if (error == GIT_EEXISTS) { return git_tree_lookup(out, repo, git_tree_id(root)); } if (error < 0) return error; *fanout += 2; error = find_subtree_r(out, subtree, repo, target, fanout); git_tree_free(subtree); return error; } static int find_blob(git_oid *blob, git_tree *tree, const char *target) { unsigned int i; const git_tree_entry *entry; for (i=0; ioid, note_oid); note->message = git__strdup((char *)git_blob_rawcontent(blob)); GITERR_CHECK_ALLOC(note->message); *out = note; return 0; } static int note_lookup(git_note **out, git_repository *repo, git_tree *tree, const char *target) { int error, fanout = 0; git_oid oid; git_blob *blob = NULL; git_note *note = NULL; git_tree *subtree = NULL; if ((error = find_subtree_r(&subtree, tree, repo, target, &fanout)) < 0) goto cleanup; if ((error = find_blob(&oid, subtree, target + fanout)) < 0) goto cleanup; if ((error = git_blob_lookup(&blob, repo, &oid)) < 0) goto cleanup; if ((error = note_new(¬e, &oid, blob)) < 0) goto cleanup; *out = note; cleanup: git_tree_free(subtree); git_blob_free(blob); return error; } static int note_remove(git_repository *repo, git_signature *author, git_signature *committer, const char *notes_ref, git_tree *tree, const char *target, git_commit **parents) { int error; git_tree *tree_after_removal = NULL; git_oid oid; if ((error = manipulate_note_in_tree_r( &tree_after_removal, repo, tree, NULL, target, 0, remove_note_in_tree_eexists_cb, remove_note_in_tree_enotfound_cb)) < 0) goto cleanup; error = git_commit_create(&oid, repo, notes_ref, author, committer, NULL, GIT_NOTES_DEFAULT_MSG_RM, tree_after_removal, *parents == NULL ? 0 : 1, (const git_commit **) parents); cleanup: git_tree_free(tree_after_removal); return error; } static int note_get_default_ref(const char **out, git_repository *repo) { int ret; git_config *cfg; *out = NULL; if (git_repository_config__weakptr(&cfg, repo) < 0) return -1; ret = git_config_get_string(out, cfg, "core.notesRef"); if (ret == GIT_ENOTFOUND) { *out = GIT_NOTES_DEFAULT_REF; return 0; } return ret; } static int normalize_namespace(const char **notes_ref, git_repository *repo) { if (*notes_ref) return 0; return note_get_default_ref(notes_ref, repo); } static int retrieve_note_tree_and_commit( git_tree **tree_out, git_commit **commit_out, git_repository *repo, const char **notes_ref) { int error; git_oid oid; if ((error = normalize_namespace(notes_ref, repo)) < 0) return error; if ((error = git_reference_name_to_oid(&oid, repo, *notes_ref)) < 0) return error; if (git_commit_lookup(commit_out, repo, &oid) < 0) return error; if ((error = git_commit_tree(tree_out, *commit_out)) < 0) return error; return 0; } int git_note_read(git_note **out, git_repository *repo, const char *notes_ref, const git_oid *oid) { int error; char *target = NULL; git_tree *tree = NULL; git_commit *commit = NULL; target = git_oid_allocfmt(oid); GITERR_CHECK_ALLOC(target); if ((error = retrieve_note_tree_and_commit(&tree, &commit, repo, ¬es_ref)) < 0) goto cleanup; error = note_lookup(out, repo, tree, target); cleanup: git__free(target); git_tree_free(tree); git_commit_free(commit); return error; } int git_note_create( git_oid *out, git_repository *repo, git_signature *author, git_signature *committer, const char *notes_ref, const git_oid *oid, const char *note) { int error; char *target = NULL; git_commit *commit = NULL; git_tree *tree = NULL; target = git_oid_allocfmt(oid); GITERR_CHECK_ALLOC(target); error = retrieve_note_tree_and_commit(&tree, &commit, repo, ¬es_ref); if (error < 0 && error != GIT_ENOTFOUND) goto cleanup; error = note_write(out, repo, author, committer, notes_ref, note, tree, target, &commit); cleanup: git__free(target); git_commit_free(commit); git_tree_free(tree); return error; } int git_note_remove(git_repository *repo, const char *notes_ref, git_signature *author, git_signature *committer, const git_oid *oid) { int error; char *target = NULL; git_commit *commit = NULL; git_tree *tree = NULL; target = git_oid_allocfmt(oid); GITERR_CHECK_ALLOC(target); if ((error = retrieve_note_tree_and_commit(&tree, &commit, repo, ¬es_ref)) < 0) goto cleanup; error = note_remove(repo, author, committer, notes_ref, tree, target, &commit); cleanup: git__free(target); git_commit_free(commit); git_tree_free(tree); return error; } int git_note_default_ref(const char **out, git_repository *repo) { assert(repo); return note_get_default_ref(out, repo); } const char * git_note_message(git_note *note) { assert(note); return note->message; } const git_oid * git_note_oid(git_note *note) { assert(note); return ¬e->oid; } void git_note_free(git_note *note) { if (note == NULL) return; git__free(note->message); git__free(note); } static int process_entry_path( const char* entry_path, const git_oid *note_oid, int (*note_cb)(git_note_data *note_data, void *payload), void *payload) { int error = -1; size_t i = 0, j = 0, len; git_buf buf = GIT_BUF_INIT; git_note_data note_data; if (git_buf_puts(&buf, entry_path) < 0) goto cleanup; len = git_buf_len(&buf); while (i < len) { if (buf.ptr[i] == '/') { i++; continue; } if (git__fromhex(buf.ptr[i]) < 0) { /* This is not a note entry */ error = 0; goto cleanup; } if (i != j) buf.ptr[j] = buf.ptr[i]; i++; j++; } buf.ptr[j] = '\0'; buf.size = j; if (j != GIT_OID_HEXSZ) { /* This is not a note entry */ error = 0; goto cleanup; } if (git_oid_fromstr(¬e_data.annotated_object_oid, buf.ptr) < 0) return -1; git_oid_cpy(¬e_data.blob_oid, note_oid); error = note_cb(¬e_data, payload); cleanup: git_buf_free(&buf); return error; } int git_note_foreach( git_repository *repo, const char *notes_ref, int (*note_cb)(git_note_data *note_data, void *payload), void *payload) { int error = -1; git_iterator *iter = NULL; git_tree *tree = NULL; git_commit *commit = NULL; const git_index_entry *item; if ((error = retrieve_note_tree_and_commit(&tree, &commit, repo, ¬es_ref)) < 0) goto cleanup; if (git_iterator_for_tree(&iter, repo, tree) < 0) goto cleanup; if (git_iterator_current(iter, &item) < 0) goto cleanup; while (item) { if (process_entry_path(item->path, &item->oid, note_cb, payload) < 0) goto cleanup; if (git_iterator_advance(iter, &item) < 0) goto cleanup; } error = 0; cleanup: git_iterator_free(iter); git_tree_free(tree); git_commit_free(commit); return error; }