libgit2/src/notes.c
Russell Belfer cee695ae6b Make iterators use GIT_ITEROVER & smart advance
1. internal iterators now return GIT_ITEROVER when you go past the
   last item in the iteration.
2. git_iterator_advance will "advance" to the first item in the
   iteration if it is called immediately after creating the
   iterator, which allows a simpler idiom for basic iteration.
3. if git_iterator_advance encounters an error reading data (e.g.
   a missing tree or an unreadable file), it returns the error
   but also attempts to advance past the invalid data to prevent
   an infinite loop.

Updated all tests and internal usage of iterators to account for
these new behaviors.
2013-05-31 12:18:43 -07:00

659 lines
14 KiB
C

/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* 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"
#include "signature.h"
static int note_error_notfound(void)
{
giterr_set(GITERR_INVALID, "Note could not be found");
return GIT_ENOTFOUND;
}
static int find_subtree_in_current_level(
git_tree **out,
git_repository *repo,
git_tree *parent,
const char *annotated_object_sha,
int fanout)
{
size_t i;
const git_tree_entry *entry;
*out = NULL;
if (parent == NULL)
return note_error_notfound();
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_filemode(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 note_error_notfound();
}
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)
{
size_t i;
const git_tree_entry *entry;
for (i=0; i<git_tree_entrycount(tree); i++) {
entry = git_tree_entry_byindex(tree, i);
if (!strcmp(git_tree_entry_name(entry), target)) {
/* found matching note object - return */
git_oid_cpy(blob, git_tree_entry_id(entry));
return 0;
}
}
return note_error_notfound();
}
static int tree_write(
git_tree **out,
git_repository *repo,
git_tree *source_tree,
const git_oid *object_oid,
const char *treeentry_name,
unsigned int attributes)
{
int error;
git_treebuilder *tb = NULL;
const git_tree_entry *entry;
git_oid tree_oid;
if ((error = git_treebuilder_create(&tb, source_tree)) < 0)
goto cleanup;
if (object_oid) {
if ((error = git_treebuilder_insert(
&entry, tb, treeentry_name, object_oid, attributes)) < 0)
goto cleanup;
} else {
if ((error = git_treebuilder_remove(tb, treeentry_name)) < 0)
goto cleanup;
}
if ((error = git_treebuilder_write(&tree_oid, repo, tb)) < 0)
goto cleanup;
error = git_tree_lookup(out, repo, &tree_oid);
cleanup:
git_treebuilder_free(tb);
return error;
}
static int manipulate_note_in_tree_r(
git_tree **out,
git_repository *repo,
git_tree *parent,
git_oid *note_oid,
const char *annotated_object_sha,
int fanout,
int (*note_exists_cb)(
git_tree **out,
git_repository *repo,
git_tree *parent,
git_oid *note_oid,
const char *annotated_object_sha,
int fanout,
int current_error),
int (*note_notfound_cb)(
git_tree **out,
git_repository *repo,
git_tree *parent,
git_oid *note_oid,
const char *annotated_object_sha,
int fanout,
int current_error))
{
int error;
git_tree *subtree = NULL, *new = NULL;
char subtree_name[3];
error = find_subtree_in_current_level(
&subtree, repo, parent, annotated_object_sha, fanout);
if (error == GIT_EEXISTS) {
error = note_exists_cb(
out, repo, parent, note_oid, annotated_object_sha, fanout, error);
goto cleanup;
}
if (error == GIT_ENOTFOUND) {
error = note_notfound_cb(
out, repo, parent, note_oid, annotated_object_sha, fanout, error);
goto cleanup;
}
if (error < 0)
goto cleanup;
/* An existing fanout has been found, let's dig deeper */
error = manipulate_note_in_tree_r(
&new, repo, subtree, note_oid, annotated_object_sha,
fanout + 2, note_exists_cb, note_notfound_cb);
if (error < 0)
goto cleanup;
strncpy(subtree_name, annotated_object_sha + fanout, 2);
subtree_name[2] = '\0';
error = tree_write(out, repo, parent, git_tree_id(new),
subtree_name, GIT_FILEMODE_TREE);
cleanup:
git_tree_free(new);
git_tree_free(subtree);
return error;
}
static int remove_note_in_tree_eexists_cb(
git_tree **out,
git_repository *repo,
git_tree *parent,
git_oid *note_oid,
const char *annotated_object_sha,
int fanout,
int current_error)
{
GIT_UNUSED(note_oid);
GIT_UNUSED(current_error);
return tree_write(out, repo, parent, NULL, annotated_object_sha + fanout, 0);
}
static int remove_note_in_tree_enotfound_cb(
git_tree **out,
git_repository *repo,
git_tree *parent,
git_oid *note_oid,
const char *annotated_object_sha,
int fanout,
int current_error)
{
GIT_UNUSED(out);
GIT_UNUSED(repo);
GIT_UNUSED(parent);
GIT_UNUSED(note_oid);
GIT_UNUSED(fanout);
giterr_set(GITERR_REPOSITORY, "Object '%s' has no note", annotated_object_sha);
return current_error;
}
static int insert_note_in_tree_eexists_cb(git_tree **out,
git_repository *repo,
git_tree *parent,
git_oid *note_oid,
const char *annotated_object_sha,
int fanout,
int current_error)
{
GIT_UNUSED(out);
GIT_UNUSED(repo);
GIT_UNUSED(parent);
GIT_UNUSED(note_oid);
GIT_UNUSED(fanout);
giterr_set(GITERR_REPOSITORY, "Note for '%s' exists already", annotated_object_sha);
return current_error;
}
static int insert_note_in_tree_enotfound_cb(git_tree **out,
git_repository *repo,
git_tree *parent,
git_oid *note_oid,
const char *annotated_object_sha,
int fanout,
int current_error)
{
GIT_UNUSED(current_error);
/* No existing fanout at this level, insert in place */
return tree_write(
out,
repo,
parent,
note_oid,
annotated_object_sha + fanout,
GIT_FILEMODE_BLOB);
}
static int note_write(git_oid *out,
git_repository *repo,
const git_signature *author,
const git_signature *committer,
const char *notes_ref,
const char *note,
git_tree *commit_tree,
const char *target,
git_commit **parents,
int allow_note_overwrite)
{
int error;
git_oid oid;
git_tree *tree = NULL;
// TODO: should we apply filters?
/* create note object */
if ((error = git_blob_create_frombuffer(&oid, repo, note, strlen(note))) < 0)
goto cleanup;
if ((error = manipulate_note_in_tree_r(
&tree, repo, commit_tree, &oid, target, 0,
allow_note_overwrite ? insert_note_in_tree_enotfound_cb : insert_note_in_tree_eexists_cb,
insert_note_in_tree_enotfound_cb)) < 0)
goto cleanup;
if (out)
git_oid_cpy(out, &oid);
error = git_commit_create(&oid, repo, notes_ref, author, committer,
NULL, GIT_NOTES_DEFAULT_MSG_ADD,
tree, *parents == NULL ? 0 : 1, (const git_commit **) parents);
cleanup:
git_tree_free(tree);
return error;
}
static int note_new(git_note **out, git_oid *note_oid, git_blob *blob)
{
git_note *note = NULL;
note = (git_note *)git__malloc(sizeof(git_note));
GITERR_CHECK_ALLOC(note);
git_oid_cpy(&note->oid, 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(&note, &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,
const git_signature *author, const 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) {
giterr_clear();
*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_id(&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, &notes_ref)))
error = note_lookup(out, repo, tree, target);
git__free(target);
git_tree_free(tree);
git_commit_free(commit);
return error;
}
int git_note_create(
git_oid *out,
git_repository *repo,
const git_signature *author,
const git_signature *committer,
const char *notes_ref,
const git_oid *oid,
const char *note,
int allow_note_overwrite)
{
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, &notes_ref);
if (error < 0 && error != GIT_ENOTFOUND)
goto cleanup;
error = note_write(out, repo, author, committer, notes_ref,
note, tree, target, &commit, allow_note_overwrite);
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,
const git_signature *author, const 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, &notes_ref)))
error = note_remove(
repo, author, committer, notes_ref, tree, target, &commit);
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(const git_note *note)
{
assert(note);
return note->message;
}
const git_oid * git_note_oid(const git_note *note)
{
assert(note);
return &note->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,
git_oid *annotated_object_id)
{
int error = 0;
size_t i = 0, j = 0, len;
git_buf buf = GIT_BUF_INIT;
if ((error = 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 */
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 */
goto cleanup;
}
error = git_oid_fromstr(annotated_object_id, buf.ptr);
cleanup:
git_buf_free(&buf);
return error;
}
int git_note_foreach(
git_repository *repo,
const char *notes_ref,
git_note_foreach_cb note_cb,
void *payload)
{
int error;
git_note_iterator *iter = NULL;
git_oid note_id, annotated_id;
if ((error = git_note_iterator_new(&iter, repo, notes_ref)) < 0)
return error;
while (!(error = git_note_next(&note_id, &annotated_id, iter))) {
if (note_cb(&note_id, &annotated_id, payload)) {
error = GIT_EUSER;
break;
}
}
if (error == GIT_ITEROVER)
error = 0;
git_note_iterator_free(iter);
return error;
}
void git_note_iterator_free(git_note_iterator *it)
{
if (it == NULL)
return;
git_iterator_free(it);
}
int git_note_iterator_new(
git_note_iterator **it,
git_repository *repo,
const char *notes_ref)
{
int error;
git_commit *commit = NULL;
git_tree *tree = NULL;
error = retrieve_note_tree_and_commit(&tree, &commit, repo, &notes_ref);
if (error < 0)
goto cleanup;
if ((error = git_iterator_for_tree(it, tree, 0, NULL, NULL)) < 0)
git_iterator_free(*it);
cleanup:
git_tree_free(tree);
git_commit_free(commit);
return error;
}
int git_note_next(
git_oid* note_id,
git_oid* annotated_id,
git_note_iterator *it)
{
int error;
const git_index_entry *item;
if ((error = git_iterator_current(&item, it)) < 0)
return error;
git_oid_cpy(note_id, &item->oid);
if (!(error = process_entry_path(item->path, annotated_id)))
git_iterator_advance(NULL, it);
return error;
}