mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-11 02:10:03 +00:00

This changes the behavior of callbacks so that the callback error code is not converted into GIT_EUSER and instead we propagate the return value through to the caller. Instead of using the giterr_capture and giterr_restore functions, we now rely on all functions to pass back the return value from a callback. To avoid having a return value with no error message, the user can call the public giterr_set_str or some such function to set an error message. There is a new helper 'giterr_set_callback' that functions can invoke after making a callback which ensures that some error message was set in case the callback did not set one. In places where the sign of the callback return value is meaningful (e.g. positive to skip, negative to abort), only the negative values are returned back to the caller, obviously, since the other values allow for continuing the loop. The hardest parts of this were in the checkout code where positive return values were overloaded as meaningful values for checkout. I fixed this by adding an output parameter to many of the internal checkout functions and removing the overload. This added some code, but it is probably a better implementation. There is some funkiness in the network code where user provided callbacks could be returning a positive or a negative value and we want to rely on that to cancel the loop. There are still a couple places where an user error might get turned into GIT_EUSER there, I think, though none exercised by the tests.
507 lines
11 KiB
C
507 lines
11 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 "common.h"
|
|
#include "commit.h"
|
|
#include "tag.h"
|
|
#include "signature.h"
|
|
#include "message.h"
|
|
#include "git2/object.h"
|
|
#include "git2/repository.h"
|
|
#include "git2/signature.h"
|
|
#include "git2/odb_backend.h"
|
|
|
|
void git_tag__free(void *_tag)
|
|
{
|
|
git_tag *tag = _tag;
|
|
git_signature_free(tag->tagger);
|
|
git__free(tag->message);
|
|
git__free(tag->tag_name);
|
|
git__free(tag);
|
|
}
|
|
|
|
int git_tag_target(git_object **target, const git_tag *t)
|
|
{
|
|
assert(t);
|
|
return git_object_lookup(target, t->object.repo, &t->target, t->type);
|
|
}
|
|
|
|
const git_oid *git_tag_target_id(const git_tag *t)
|
|
{
|
|
assert(t);
|
|
return &t->target;
|
|
}
|
|
|
|
git_otype git_tag_target_type(const git_tag *t)
|
|
{
|
|
assert(t);
|
|
return t->type;
|
|
}
|
|
|
|
const char *git_tag_name(const git_tag *t)
|
|
{
|
|
assert(t);
|
|
return t->tag_name;
|
|
}
|
|
|
|
const git_signature *git_tag_tagger(const git_tag *t)
|
|
{
|
|
return t->tagger;
|
|
}
|
|
|
|
const char *git_tag_message(const git_tag *t)
|
|
{
|
|
assert(t);
|
|
return t->message;
|
|
}
|
|
|
|
static int tag_error(const char *str)
|
|
{
|
|
giterr_set(GITERR_TAG, "Failed to parse tag. %s", str);
|
|
return -1;
|
|
}
|
|
|
|
static int tag_parse(git_tag *tag, const char *buffer, const char *buffer_end)
|
|
{
|
|
static const char *tag_types[] = {
|
|
NULL, "commit\n", "tree\n", "blob\n", "tag\n"
|
|
};
|
|
|
|
unsigned int i;
|
|
size_t text_len;
|
|
char *search;
|
|
|
|
if (git_oid__parse(&tag->target, &buffer, buffer_end, "object ") < 0)
|
|
return tag_error("Object field invalid");
|
|
|
|
if (buffer + 5 >= buffer_end)
|
|
return tag_error("Object too short");
|
|
|
|
if (memcmp(buffer, "type ", 5) != 0)
|
|
return tag_error("Type field not found");
|
|
buffer += 5;
|
|
|
|
tag->type = GIT_OBJ_BAD;
|
|
|
|
for (i = 1; i < ARRAY_SIZE(tag_types); ++i) {
|
|
size_t type_length = strlen(tag_types[i]);
|
|
|
|
if (buffer + type_length >= buffer_end)
|
|
return tag_error("Object too short");
|
|
|
|
if (memcmp(buffer, tag_types[i], type_length) == 0) {
|
|
tag->type = i;
|
|
buffer += type_length;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (tag->type == GIT_OBJ_BAD)
|
|
return tag_error("Invalid object type");
|
|
|
|
if (buffer + 4 >= buffer_end)
|
|
return tag_error("Object too short");
|
|
|
|
if (memcmp(buffer, "tag ", 4) != 0)
|
|
return tag_error("Tag field not found");
|
|
|
|
buffer += 4;
|
|
|
|
search = memchr(buffer, '\n', buffer_end - buffer);
|
|
if (search == NULL)
|
|
return tag_error("Object too short");
|
|
|
|
text_len = search - buffer;
|
|
|
|
tag->tag_name = git__malloc(text_len + 1);
|
|
GITERR_CHECK_ALLOC(tag->tag_name);
|
|
|
|
memcpy(tag->tag_name, buffer, text_len);
|
|
tag->tag_name[text_len] = '\0';
|
|
|
|
buffer = search + 1;
|
|
|
|
tag->tagger = NULL;
|
|
if (buffer < buffer_end && *buffer != '\n') {
|
|
tag->tagger = git__malloc(sizeof(git_signature));
|
|
GITERR_CHECK_ALLOC(tag->tagger);
|
|
|
|
if (git_signature__parse(tag->tagger, &buffer, buffer_end, "tagger ", '\n') < 0)
|
|
return -1;
|
|
}
|
|
|
|
tag->message = NULL;
|
|
if (buffer < buffer_end) {
|
|
if( *buffer != '\n' )
|
|
return tag_error("No new line before message");
|
|
|
|
text_len = buffer_end - ++buffer;
|
|
|
|
tag->message = git__malloc(text_len + 1);
|
|
GITERR_CHECK_ALLOC(tag->message);
|
|
|
|
memcpy(tag->message, buffer, text_len);
|
|
tag->message[text_len] = '\0';
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int git_tag__parse(void *_tag, git_odb_object *odb_obj)
|
|
{
|
|
git_tag *tag = _tag;
|
|
const char *buffer = git_odb_object_data(odb_obj);
|
|
const char *buffer_end = buffer + git_odb_object_size(odb_obj);
|
|
|
|
return tag_parse(tag, buffer, buffer_end);
|
|
}
|
|
|
|
static int retrieve_tag_reference(
|
|
git_reference **tag_reference_out,
|
|
git_buf *ref_name_out,
|
|
git_repository *repo,
|
|
const char *tag_name)
|
|
{
|
|
git_reference *tag_ref;
|
|
int error;
|
|
|
|
*tag_reference_out = NULL;
|
|
|
|
if (git_buf_joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name) < 0)
|
|
return -1;
|
|
|
|
error = git_reference_lookup(&tag_ref, repo, ref_name_out->ptr);
|
|
if (error < 0)
|
|
return error; /* Be it not foundo or corrupted */
|
|
|
|
*tag_reference_out = tag_ref;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int retrieve_tag_reference_oid(
|
|
git_oid *oid,
|
|
git_buf *ref_name_out,
|
|
git_repository *repo,
|
|
const char *tag_name)
|
|
{
|
|
if (git_buf_joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name) < 0)
|
|
return -1;
|
|
|
|
return git_reference_name_to_id(oid, repo, ref_name_out->ptr);
|
|
}
|
|
|
|
static int write_tag_annotation(
|
|
git_oid *oid,
|
|
git_repository *repo,
|
|
const char *tag_name,
|
|
const git_object *target,
|
|
const git_signature *tagger,
|
|
const char *message)
|
|
{
|
|
git_buf tag = GIT_BUF_INIT;
|
|
git_odb *odb;
|
|
|
|
git_oid__writebuf(&tag, "object ", git_object_id(target));
|
|
git_buf_printf(&tag, "type %s\n", git_object_type2string(git_object_type(target)));
|
|
git_buf_printf(&tag, "tag %s\n", tag_name);
|
|
git_signature__writebuf(&tag, "tagger ", tagger);
|
|
git_buf_putc(&tag, '\n');
|
|
|
|
if (git_buf_puts(&tag, message) < 0)
|
|
goto on_error;
|
|
|
|
if (git_repository_odb__weakptr(&odb, repo) < 0)
|
|
goto on_error;
|
|
|
|
if (git_odb_write(oid, odb, tag.ptr, tag.size, GIT_OBJ_TAG) < 0)
|
|
goto on_error;
|
|
|
|
git_buf_free(&tag);
|
|
return 0;
|
|
|
|
on_error:
|
|
git_buf_free(&tag);
|
|
giterr_set(GITERR_OBJECT, "Failed to create tag annotation.");
|
|
return -1;
|
|
}
|
|
|
|
static int git_tag_create__internal(
|
|
git_oid *oid,
|
|
git_repository *repo,
|
|
const char *tag_name,
|
|
const git_object *target,
|
|
const git_signature *tagger,
|
|
const char *message,
|
|
int allow_ref_overwrite,
|
|
int create_tag_annotation)
|
|
{
|
|
git_reference *new_ref = NULL;
|
|
git_buf ref_name = GIT_BUF_INIT;
|
|
|
|
int error;
|
|
|
|
assert(repo && tag_name && target);
|
|
assert(!create_tag_annotation || (tagger && message));
|
|
|
|
if (git_object_owner(target) != repo) {
|
|
giterr_set(GITERR_INVALID, "The given target does not belong to this repository");
|
|
return -1;
|
|
}
|
|
|
|
error = retrieve_tag_reference_oid(oid, &ref_name, repo, tag_name);
|
|
if (error < 0 && error != GIT_ENOTFOUND)
|
|
goto cleanup;
|
|
|
|
/** Ensure the tag name doesn't conflict with an already existing
|
|
* reference unless overwriting has explictly been requested **/
|
|
if (error == 0 && !allow_ref_overwrite) {
|
|
git_buf_free(&ref_name);
|
|
giterr_set(GITERR_TAG, "Tag already exists");
|
|
return GIT_EEXISTS;
|
|
}
|
|
|
|
if (create_tag_annotation) {
|
|
if (write_tag_annotation(oid, repo, tag_name, target, tagger, message) < 0)
|
|
return -1;
|
|
} else
|
|
git_oid_cpy(oid, git_object_id(target));
|
|
|
|
error = git_reference_create(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite);
|
|
|
|
cleanup:
|
|
git_reference_free(new_ref);
|
|
git_buf_free(&ref_name);
|
|
return error;
|
|
}
|
|
|
|
int git_tag_create(
|
|
git_oid *oid,
|
|
git_repository *repo,
|
|
const char *tag_name,
|
|
const git_object *target,
|
|
const git_signature *tagger,
|
|
const char *message,
|
|
int allow_ref_overwrite)
|
|
{
|
|
return git_tag_create__internal(oid, repo, tag_name, target, tagger, message, allow_ref_overwrite, 1);
|
|
}
|
|
|
|
int git_tag_annotation_create(
|
|
git_oid *oid,
|
|
git_repository *repo,
|
|
const char *tag_name,
|
|
const git_object *target,
|
|
const git_signature *tagger,
|
|
const char *message)
|
|
{
|
|
assert(oid && repo && tag_name && target && tagger && message);
|
|
|
|
return write_tag_annotation(oid, repo, tag_name, target, tagger, message);
|
|
}
|
|
|
|
int git_tag_create_lightweight(
|
|
git_oid *oid,
|
|
git_repository *repo,
|
|
const char *tag_name,
|
|
const git_object *target,
|
|
int allow_ref_overwrite)
|
|
{
|
|
return git_tag_create__internal(oid, repo, tag_name, target, NULL, NULL, allow_ref_overwrite, 0);
|
|
}
|
|
|
|
int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *buffer, int allow_ref_overwrite)
|
|
{
|
|
git_tag tag;
|
|
int error;
|
|
git_odb *odb;
|
|
git_odb_stream *stream;
|
|
git_odb_object *target_obj;
|
|
|
|
git_reference *new_ref = NULL;
|
|
git_buf ref_name = GIT_BUF_INIT;
|
|
|
|
assert(oid && buffer);
|
|
|
|
memset(&tag, 0, sizeof(tag));
|
|
|
|
if (git_repository_odb__weakptr(&odb, repo) < 0)
|
|
return -1;
|
|
|
|
/* validate the buffer */
|
|
if (tag_parse(&tag, buffer, buffer + strlen(buffer)) < 0)
|
|
return -1;
|
|
|
|
/* validate the target */
|
|
if (git_odb_read(&target_obj, odb, &tag.target) < 0)
|
|
goto on_error;
|
|
|
|
if (tag.type != target_obj->cached.type) {
|
|
giterr_set(GITERR_TAG, "The type for the given target is invalid");
|
|
goto on_error;
|
|
}
|
|
|
|
error = retrieve_tag_reference_oid(oid, &ref_name, repo, tag.tag_name);
|
|
if (error < 0 && error != GIT_ENOTFOUND)
|
|
goto on_error;
|
|
|
|
/* We don't need these objects after this */
|
|
git_signature_free(tag.tagger);
|
|
git__free(tag.tag_name);
|
|
git__free(tag.message);
|
|
git_odb_object_free(target_obj);
|
|
|
|
/** Ensure the tag name doesn't conflict with an already existing
|
|
* reference unless overwriting has explictly been requested **/
|
|
if (error == 0 && !allow_ref_overwrite) {
|
|
giterr_set(GITERR_TAG, "Tag already exists");
|
|
return GIT_EEXISTS;
|
|
}
|
|
|
|
/* write the buffer */
|
|
if (git_odb_open_wstream(&stream, odb, strlen(buffer), GIT_OBJ_TAG) < 0)
|
|
return -1;
|
|
|
|
git_odb_stream_write(stream, buffer, strlen(buffer));
|
|
|
|
error = git_odb_stream_finalize_write(oid, stream);
|
|
git_odb_stream_free(stream);
|
|
|
|
if (error < 0) {
|
|
git_buf_free(&ref_name);
|
|
return -1;
|
|
}
|
|
|
|
error = git_reference_create(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite);
|
|
|
|
git_reference_free(new_ref);
|
|
git_buf_free(&ref_name);
|
|
|
|
return error;
|
|
|
|
on_error:
|
|
git_signature_free(tag.tagger);
|
|
git__free(tag.tag_name);
|
|
git__free(tag.message);
|
|
git_odb_object_free(target_obj);
|
|
return -1;
|
|
}
|
|
|
|
int git_tag_delete(git_repository *repo, const char *tag_name)
|
|
{
|
|
git_reference *tag_ref;
|
|
git_buf ref_name = GIT_BUF_INIT;
|
|
int error;
|
|
|
|
error = retrieve_tag_reference(&tag_ref, &ref_name, repo, tag_name);
|
|
|
|
git_buf_free(&ref_name);
|
|
|
|
if (error < 0)
|
|
return error;
|
|
|
|
if ((error = git_reference_delete(tag_ref)) == 0)
|
|
git_reference_free(tag_ref);
|
|
|
|
return error;
|
|
}
|
|
|
|
typedef struct {
|
|
git_repository *repo;
|
|
git_tag_foreach_cb cb;
|
|
void *cb_data;
|
|
} tag_cb_data;
|
|
|
|
static int tags_cb(const char *ref, void *data)
|
|
{
|
|
int error;
|
|
git_oid oid;
|
|
tag_cb_data *d = (tag_cb_data *)data;
|
|
|
|
if (git__prefixcmp(ref, GIT_REFS_TAGS_DIR) != 0)
|
|
return 0; /* no tag */
|
|
|
|
if (!(error = git_reference_name_to_id(&oid, d->repo, ref))) {
|
|
error = d->cb(ref, &oid, d->cb_data);
|
|
giterr_set_callback(error, "git_tag_foreach");
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
int git_tag_foreach(git_repository *repo, git_tag_foreach_cb cb, void *cb_data)
|
|
{
|
|
tag_cb_data data;
|
|
|
|
assert(repo && cb);
|
|
|
|
data.cb = cb;
|
|
data.cb_data = cb_data;
|
|
data.repo = repo;
|
|
|
|
return git_reference_foreach_name(repo, &tags_cb, &data);
|
|
}
|
|
|
|
typedef struct {
|
|
git_vector *taglist;
|
|
const char *pattern;
|
|
} tag_filter_data;
|
|
|
|
#define GIT_REFS_TAGS_DIR_LEN strlen(GIT_REFS_TAGS_DIR)
|
|
|
|
static int tag_list_cb(const char *tag_name, git_oid *oid, void *data)
|
|
{
|
|
tag_filter_data *filter = (tag_filter_data *)data;
|
|
GIT_UNUSED(oid);
|
|
|
|
if (!*filter->pattern ||
|
|
p_fnmatch(filter->pattern, tag_name + GIT_REFS_TAGS_DIR_LEN, 0) == 0)
|
|
{
|
|
char *matched = git__strdup(tag_name + GIT_REFS_TAGS_DIR_LEN);
|
|
GITERR_CHECK_ALLOC(matched);
|
|
|
|
return git_vector_insert(filter->taglist, matched);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int git_tag_list_match(git_strarray *tag_names, const char *pattern, git_repository *repo)
|
|
{
|
|
int error;
|
|
tag_filter_data filter;
|
|
git_vector taglist;
|
|
|
|
assert(tag_names && repo && pattern);
|
|
|
|
if ((error = git_vector_init(&taglist, 8, NULL)) < 0)
|
|
return error;
|
|
|
|
filter.taglist = &taglist;
|
|
filter.pattern = pattern;
|
|
|
|
error = git_tag_foreach(repo, &tag_list_cb, (void *)&filter);
|
|
|
|
if (error < 0)
|
|
git_vector_free(&taglist);
|
|
|
|
tag_names->strings =
|
|
(char **)git_vector_detach(&tag_names->count, NULL, &taglist);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int git_tag_list(git_strarray *tag_names, git_repository *repo)
|
|
{
|
|
return git_tag_list_match(tag_names, "", repo);
|
|
}
|
|
|
|
int git_tag_peel(git_object **tag_target, const git_tag *tag)
|
|
{
|
|
return git_object_peel(tag_target, (const git_object *)tag, GIT_OBJ_ANY);
|
|
}
|