mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-29 13:52:17 +00:00
refs: Error handling rework. WIP
This commit is contained in:
parent
60bc2d20c4
commit
45d387ac78
@ -122,9 +122,12 @@ typedef struct {
|
|||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
GITERR_NOMEMORY,
|
GITERR_NOMEMORY,
|
||||||
|
GITERR_REFERENCE,
|
||||||
|
|
||||||
} git_error_class;
|
} git_error_class;
|
||||||
|
|
||||||
|
#define GITERR_CHECK_ALLOC(ptr, error) if (ptr == NULL) { giterr_set_oom(error); return -1 }
|
||||||
|
|
||||||
GIT_EXTERN(void) giterr_set(git_error **error_out, int error_class, const char *string, ...);
|
GIT_EXTERN(void) giterr_set(git_error **error_out, int error_class, const char *string, ...);
|
||||||
GIT_EXTERN(void) giterr_set_oom(git_error **error);
|
GIT_EXTERN(void) giterr_set_oom(git_error **error);
|
||||||
GIT_EXTERN(void) giterr_free(git_error *error);
|
GIT_EXTERN(void) giterr_free(git_error *error);
|
||||||
|
27
src/path.c
27
src/path.c
@ -486,20 +486,23 @@ GIT_INLINE(int) is_dot_or_dotdot(const char *name)
|
|||||||
|
|
||||||
int git_path_direach(
|
int git_path_direach(
|
||||||
git_buf *path,
|
git_buf *path,
|
||||||
int (*fn)(void *, git_buf *),
|
int (*fn)(void *, git_buf *, git_error **),
|
||||||
void *arg)
|
void *arg,
|
||||||
|
git_error **error)
|
||||||
{
|
{
|
||||||
ssize_t wd_len;
|
ssize_t wd_len;
|
||||||
DIR *dir;
|
DIR *dir;
|
||||||
struct dirent de_buf, *de;
|
struct dirent de_buf, *de;
|
||||||
|
|
||||||
if (git_path_to_dir(path) < GIT_SUCCESS)
|
if (git_path_to_dir(path, error) < 0)
|
||||||
return git_buf_lasterror(path);
|
return -1;
|
||||||
|
|
||||||
wd_len = path->size;
|
wd_len = path->size;
|
||||||
dir = opendir(path->ptr);
|
dir = opendir(path->ptr);
|
||||||
if (!dir)
|
if (!dir) {
|
||||||
return git__throw(GIT_EOSERR, "Failed to process `%s` tree structure. An error occured while opening the directory", path->ptr);
|
giterr_set(error, GITERR_OS, "Failed to `opendir` %s: %s", path->ptr, strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
while (p_readdir_r(dir, &de_buf, &de) == 0 && de != NULL) {
|
while (p_readdir_r(dir, &de_buf, &de) == 0 && de != NULL) {
|
||||||
int result;
|
int result;
|
||||||
@ -507,16 +510,18 @@ int git_path_direach(
|
|||||||
if (is_dot_or_dotdot(de->d_name))
|
if (is_dot_or_dotdot(de->d_name))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (git_buf_puts(path, de->d_name) < GIT_SUCCESS)
|
if (git_buf_puts(path, de->d_name) < 0) {
|
||||||
return git_buf_lasterror(path);
|
giterr_set_oom(error);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
result = fn(arg, path);
|
result = fn(arg, path, error);
|
||||||
|
|
||||||
git_buf_truncate(path, wd_len); /* restore path */
|
git_buf_truncate(path, wd_len); /* restore path */
|
||||||
|
|
||||||
if (result != GIT_SUCCESS) {
|
if (result < 0) {
|
||||||
closedir(dir);
|
closedir(dir);
|
||||||
return result; /* The callee is reponsible for setting the correct error message */
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
513
src/refs.c
513
src/refs.c
@ -62,7 +62,7 @@ static int packed_write(git_repository *repo);
|
|||||||
|
|
||||||
/* internal helpers */
|
/* internal helpers */
|
||||||
static int reference_available(git_repository *repo,
|
static int reference_available(git_repository *repo,
|
||||||
const char *ref, const char *old_ref);
|
const char *ref, const char *old_ref, git_error **error);
|
||||||
static int reference_delete(git_reference *ref);
|
static int reference_delete(git_reference *ref);
|
||||||
static int reference_lookup(git_reference *ref);
|
static int reference_lookup(git_reference *ref);
|
||||||
|
|
||||||
@ -521,7 +521,7 @@ struct dirent_list_data {
|
|||||||
void *callback_payload;
|
void *callback_payload;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int _dirent_loose_listall(void *_data, git_buf *full_path)
|
static int _dirent_loose_listall(void *_data, git_buf *full_path, git_error **error)
|
||||||
{
|
{
|
||||||
struct dirent_list_data *data = (struct dirent_list_data *)_data;
|
struct dirent_list_data *data = (struct dirent_list_data *)_data;
|
||||||
const char *file_path = full_path->ptr + data->repo_path_len;
|
const char *file_path = full_path->ptr + data->repo_path_len;
|
||||||
@ -844,49 +844,55 @@ cleanup:
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct reference_available_t {
|
||||||
|
const char *new_ref;
|
||||||
|
const char *old_ref;
|
||||||
|
int available;
|
||||||
|
};
|
||||||
|
|
||||||
static int _reference_available_cb(const char *ref, void *data)
|
static int _reference_available_cb(const char *ref, void *data)
|
||||||
{
|
{
|
||||||
const char *new, *old;
|
struct reference_available_t *d;
|
||||||
const char **refs;
|
|
||||||
|
|
||||||
assert(ref && data);
|
assert(ref && data);
|
||||||
|
|
||||||
|
d = (reference_available_t *)data;
|
||||||
refs = (const char **)data;
|
refs = (const char **)data;
|
||||||
|
|
||||||
new = (const char *)refs[0];
|
if (!d->old_ref || strcmp(d->old_ref, ref)) {
|
||||||
old = (const char *)refs[1];
|
|
||||||
|
|
||||||
if (!old || strcmp(old, ref)) {
|
|
||||||
int reflen = strlen(ref);
|
int reflen = strlen(ref);
|
||||||
int newlen = strlen(new);
|
int newlen = strlen(d->new_ref);
|
||||||
int cmplen = reflen < newlen ? reflen : newlen;
|
int cmplen = reflen < newlen ? reflen : newlen;
|
||||||
const char *lead = reflen < newlen ? new : ref;
|
const char *lead = reflen < newlen ? d->new_ref : ref;
|
||||||
|
|
||||||
if (!strncmp(new, ref, cmplen) &&
|
if (!strncmp(d->new_ref, ref, cmplen) && lead[cmplen] == '/') {
|
||||||
lead[cmplen] == '/')
|
d->available = 0;
|
||||||
return GIT_EEXISTS;
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return GIT_SUCCESS;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int reference_available(
|
static int reference_available(
|
||||||
|
int *available,
|
||||||
git_repository *repo,
|
git_repository *repo,
|
||||||
const char *ref,
|
const char *ref,
|
||||||
const char* old_ref)
|
const char* old_ref,
|
||||||
|
git_error **error)
|
||||||
{
|
{
|
||||||
const char *refs[2];
|
struct reference_available_t data;
|
||||||
|
|
||||||
refs[0] = ref;
|
data.new_ref = ref;
|
||||||
refs[1] = old_ref;
|
data.old_ref = old_ref;
|
||||||
|
data.available = 1;
|
||||||
|
|
||||||
if (git_reference_foreach(repo, GIT_REF_LISTALL,
|
if (git_reference_foreach(repo, GIT_REF_LISTALL,
|
||||||
_reference_available_cb, (void *)refs) < 0) {
|
_reference_available_cb, (void *)&data, error) < 0)
|
||||||
return git__throw(GIT_EEXISTS,
|
return -1;
|
||||||
"Reference name `%s` conflicts with existing reference", ref);
|
|
||||||
}
|
|
||||||
|
|
||||||
return GIT_SUCCESS;
|
*available = data.available;
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int reference_exists(int *exists, git_repository *repo, const char *ref_name)
|
static int reference_exists(int *exists, git_repository *repo, const char *ref_name)
|
||||||
@ -946,17 +952,17 @@ static int packed_lookup(git_reference *ref)
|
|||||||
return GIT_SUCCESS;
|
return GIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int reference_lookup(git_reference *ref)
|
static int reference_lookup(git_reference *ref, git_error **error)
|
||||||
{
|
{
|
||||||
int error_loose, error_packed;
|
int result;
|
||||||
|
|
||||||
error_loose = loose_lookup(ref);
|
result = loose_lookup(ref, error);
|
||||||
if (error_loose == GIT_SUCCESS)
|
if (result != GIT_ENOTFOUND)
|
||||||
return GIT_SUCCESS;
|
return result;
|
||||||
|
|
||||||
error_packed = packed_lookup(ref);
|
result = packed_lookup(ref, error);
|
||||||
if (error_packed == GIT_SUCCESS)
|
if (result != GIT_ENOTFOUND)
|
||||||
return GIT_SUCCESS;
|
return result;
|
||||||
|
|
||||||
git_reference_free(ref);
|
git_reference_free(ref);
|
||||||
|
|
||||||
@ -974,9 +980,9 @@ static int reference_lookup(git_reference *ref)
|
|||||||
* This is an internal method; the reference is removed
|
* This is an internal method; the reference is removed
|
||||||
* from disk or the packfile, but the pointer is not freed
|
* from disk or the packfile, but the pointer is not freed
|
||||||
*/
|
*/
|
||||||
static int reference_delete(git_reference *ref)
|
static int reference_delete(git_reference *ref, git_error **error)
|
||||||
{
|
{
|
||||||
int error;
|
int result;
|
||||||
|
|
||||||
assert(ref);
|
assert(ref);
|
||||||
|
|
||||||
@ -986,15 +992,19 @@ static int reference_delete(git_reference *ref)
|
|||||||
if (ref->flags & GIT_REF_PACKED) {
|
if (ref->flags & GIT_REF_PACKED) {
|
||||||
struct packref *packref;
|
struct packref *packref;
|
||||||
/* load the existing packfile */
|
/* load the existing packfile */
|
||||||
if ((error = packed_load(ref->owner)) < GIT_SUCCESS)
|
if (packed_load(ref->owner, error) < 0)
|
||||||
return git__rethrow(error, "Failed to delete reference");
|
return -1;
|
||||||
|
|
||||||
if (git_hashtable_remove2(ref->owner->references.packfile,
|
if (git_hashtable_remove2(ref->owner->references.packfile,
|
||||||
ref->name, (void **) &packref) < GIT_SUCCESS)
|
ref->name, (void **) &packref) < 0) {
|
||||||
return git__throw(GIT_ENOTFOUND, "Reference not found");
|
giterr_set(error, GITERR_REFERENCE,
|
||||||
|
"Reference %s stopped existing in the packfile", ref->name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
git__free (packref);
|
git__free(packref);
|
||||||
error = packed_write(ref->owner);
|
if (packed_write(ref->owner, error) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
/* If the reference is loose, we can just remove the reference
|
/* If the reference is loose, we can just remove the reference
|
||||||
* from the filesystem */
|
* from the filesystem */
|
||||||
@ -1002,66 +1012,55 @@ static int reference_delete(git_reference *ref)
|
|||||||
git_reference *ref_in_pack;
|
git_reference *ref_in_pack;
|
||||||
git_buf full_path = GIT_BUF_INIT;
|
git_buf full_path = GIT_BUF_INIT;
|
||||||
|
|
||||||
error = git_buf_joinpath(&full_path, ref->owner->path_repository, ref->name);
|
if (git_buf_joinpath(&full_path, ref->owner->path_repository, ref->name) < 0) {
|
||||||
if (error < GIT_SUCCESS)
|
giterr_set_oom(error);
|
||||||
goto cleanup;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
error = p_unlink(full_path.ptr);
|
result = p_unlink(full_path.ptr);
|
||||||
git_buf_free(&full_path); /* done with path at this point */
|
git_buf_free(&full_path); /* done with path at this point */
|
||||||
if (error < GIT_SUCCESS)
|
|
||||||
goto cleanup;
|
if (result < 0) {
|
||||||
|
giterr_set(error, GITERR_OS,
|
||||||
|
"Failed to unlink '%s': %s", full_path.ptr, strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
/* When deleting a loose reference, we have to ensure that an older
|
/* When deleting a loose reference, we have to ensure that an older
|
||||||
* packed version of it doesn't exist */
|
* packed version of it doesn't exist */
|
||||||
if (git_reference_lookup(&ref_in_pack, ref->owner,
|
if (git_reference_lookup(&ref_in_pack, ref->owner, ref->name, NULL) == GIT_SUCCESS) {
|
||||||
ref->name) == GIT_SUCCESS) {
|
|
||||||
assert((ref_in_pack->flags & GIT_REF_PACKED) != 0);
|
assert((ref_in_pack->flags & GIT_REF_PACKED) != 0);
|
||||||
error = git_reference_delete(ref_in_pack);
|
return git_reference_delete(ref_in_pack, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup:
|
return 0;
|
||||||
return error == GIT_SUCCESS ?
|
|
||||||
GIT_SUCCESS :
|
|
||||||
git__rethrow(error, "Failed to delete reference");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int git_reference_delete(git_reference *ref)
|
int git_reference_delete(git_reference *ref, git_error **error)
|
||||||
{
|
{
|
||||||
int error = reference_delete(ref);
|
int result = reference_delete(ref, error);
|
||||||
if (error < GIT_SUCCESS)
|
|
||||||
return error;
|
|
||||||
|
|
||||||
git_reference_free(ref);
|
git_reference_free(ref);
|
||||||
return GIT_SUCCESS;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int git_reference_lookup(git_reference **ref_out,
|
int git_reference_lookup(git_reference **ref_out,
|
||||||
git_repository *repo, const char *name)
|
git_repository *repo, const char *name, git_error **error)
|
||||||
{
|
{
|
||||||
int error;
|
|
||||||
char normalized_name[GIT_REFNAME_MAX];
|
char normalized_name[GIT_REFNAME_MAX];
|
||||||
git_reference *ref = NULL;
|
git_reference *ref = NULL;
|
||||||
|
|
||||||
assert(ref_out && repo && name);
|
assert(ref_out && repo && name);
|
||||||
|
|
||||||
*ref_out = NULL;
|
if (normalize_name(normalized_name, sizeof(normalized_name), name, 0, error) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
error = normalize_name(normalized_name, sizeof(normalized_name), name, 0);
|
if (reference_alloc(&ref, repo, normalized_name, error) < 0)
|
||||||
if (error < GIT_SUCCESS)
|
return -1;
|
||||||
return git__rethrow(error, "Failed to lookup reference");
|
|
||||||
|
|
||||||
error = reference_alloc(&ref, repo, normalized_name);
|
|
||||||
if (error < GIT_SUCCESS)
|
|
||||||
return git__rethrow(error, "Failed to lookup reference");
|
|
||||||
|
|
||||||
error = reference_lookup(ref);
|
|
||||||
if (error < GIT_SUCCESS)
|
|
||||||
return git__rethrow(error, "Failed to lookup reference");
|
|
||||||
|
|
||||||
*ref_out = ref;
|
*ref_out = ref;
|
||||||
return GIT_SUCCESS;
|
return reference_lookup(ref, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1123,46 +1122,43 @@ int git_reference_create_symbolic(
|
|||||||
git_repository *repo,
|
git_repository *repo,
|
||||||
const char *name,
|
const char *name,
|
||||||
const char *target,
|
const char *target,
|
||||||
int force)
|
int force,
|
||||||
|
git_error **error)
|
||||||
{
|
{
|
||||||
char normalized[GIT_REFNAME_MAX];
|
char normalized[GIT_REFNAME_MAX];
|
||||||
int ref_exists, error = GIT_SUCCESS;
|
int exists;
|
||||||
git_reference *ref = NULL;
|
git_reference *ref = NULL;
|
||||||
|
|
||||||
error = normalize_name(normalized, sizeof(normalized), name, 0);
|
if (normalize_name(normalized, sizeof(normalized), name, 0, error) < 0)
|
||||||
if (error < GIT_SUCCESS)
|
return -1;
|
||||||
goto cleanup;
|
|
||||||
|
|
||||||
if ((error = reference_exists(&ref_exists, repo, normalized) < GIT_SUCCESS))
|
if (reference_exists(&exists, repo, normalized, error) < 0)
|
||||||
return git__rethrow(error, "Failed to create symbolic reference");
|
return -1;
|
||||||
|
|
||||||
if (ref_exists && !force)
|
if (exists && !force) {
|
||||||
return git__throw(GIT_EEXISTS,
|
giterr_set(error, GITERR_REFERENCE,
|
||||||
"Failed to create symbolic reference. Reference already exists");
|
"A reference with that name (%s) already exists");
|
||||||
|
return GIT_EEXISTS;
|
||||||
|
}
|
||||||
|
|
||||||
error = reference_alloc(&ref, repo, normalized);
|
if (reference_alloc(&ref, repo, normalized, error) < 0)
|
||||||
if (error < GIT_SUCCESS)
|
return -1;
|
||||||
goto cleanup;
|
|
||||||
|
|
||||||
ref->flags |= GIT_REF_SYMBOLIC;
|
ref->flags |= GIT_REF_SYMBOLIC;
|
||||||
|
|
||||||
/* set the target; this will normalize the name automatically
|
/* set the target; this will normalize the name automatically
|
||||||
* and write the reference on disk */
|
* and write the reference on disk */
|
||||||
error = git_reference_set_target(ref, target);
|
if (git_reference_set_target(ref, target, error) < 0) {
|
||||||
if (error < GIT_SUCCESS)
|
git_reference_free(ref);
|
||||||
goto cleanup;
|
return -1;
|
||||||
|
}
|
||||||
if (ref_out == NULL) {
|
if (ref_out == NULL) {
|
||||||
git_reference_free(ref);
|
git_reference_free(ref);
|
||||||
} else {
|
} else {
|
||||||
*ref_out = ref;
|
*ref_out = ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
return GIT_SUCCESS;
|
return 0;
|
||||||
|
|
||||||
cleanup:
|
|
||||||
git_reference_free(ref);
|
|
||||||
return git__rethrow(error, "Failed to create symbolic reference");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int git_reference_create_oid(
|
int git_reference_create_oid(
|
||||||
@ -1170,36 +1166,35 @@ int git_reference_create_oid(
|
|||||||
git_repository *repo,
|
git_repository *repo,
|
||||||
const char *name,
|
const char *name,
|
||||||
const git_oid *id,
|
const git_oid *id,
|
||||||
int force)
|
int force,
|
||||||
|
git_error **error)
|
||||||
{
|
{
|
||||||
int error = GIT_SUCCESS, ref_exists;
|
int exists;
|
||||||
git_reference *ref = NULL;
|
git_reference *ref = NULL;
|
||||||
char normalized[GIT_REFNAME_MAX];
|
char normalized[GIT_REFNAME_MAX];
|
||||||
|
|
||||||
error = normalize_name(normalized, sizeof(normalized), name, 1);
|
if (normalize_name(normalized, sizeof(normalized), name, 1, error) < 0)
|
||||||
if (error < GIT_SUCCESS)
|
return -1;
|
||||||
goto cleanup;
|
|
||||||
|
|
||||||
if ((error = reference_exists(&ref_exists, repo, normalized) < GIT_SUCCESS))
|
if (reference_exists(&exists, repo, normalized, error) < 0)
|
||||||
return git__rethrow(error, "Failed to create OID reference");
|
return -1;
|
||||||
|
|
||||||
if (ref_exists && !force)
|
if (exists && !force) {
|
||||||
return git__throw(GIT_EEXISTS,
|
giterr_set(error, GITERR_REFERENCE,
|
||||||
"Failed to create OID reference. Reference already exists");
|
"A reference with that name (%s) already exists");
|
||||||
|
return GIT_EEXISTS;
|
||||||
|
}
|
||||||
|
|
||||||
if ((error = reference_available(repo, name, NULL)) < GIT_SUCCESS)
|
if (reference_alloc(&ref, repo, name, error) < 0)
|
||||||
return git__rethrow(error, "Failed to create reference");
|
return -1;
|
||||||
|
|
||||||
error = reference_alloc(&ref, repo, name);
|
|
||||||
if (error < GIT_SUCCESS)
|
|
||||||
goto cleanup;
|
|
||||||
|
|
||||||
ref->flags |= GIT_REF_OID;
|
ref->flags |= GIT_REF_OID;
|
||||||
|
|
||||||
/* set the oid; this will write the reference on disk */
|
/* set the oid; this will write the reference on disk */
|
||||||
error = git_reference_set_oid(ref, id);
|
if (git_reference_set_oid(ref, id, error) < 0) {
|
||||||
if (error < GIT_SUCCESS)
|
git_reference_free(ref);
|
||||||
goto cleanup;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (ref_out == NULL) {
|
if (ref_out == NULL) {
|
||||||
git_reference_free(ref);
|
git_reference_free(ref);
|
||||||
@ -1207,13 +1202,8 @@ int git_reference_create_oid(
|
|||||||
*ref_out = ref;
|
*ref_out = ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
return GIT_SUCCESS;
|
return 0;
|
||||||
|
|
||||||
cleanup:
|
|
||||||
git_reference_free(ref);
|
|
||||||
return git__rethrow(error, "Failed to create reference OID");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Change the OID target of a reference.
|
* Change the OID target of a reference.
|
||||||
*
|
*
|
||||||
@ -1223,40 +1213,34 @@ cleanup:
|
|||||||
* We do not repack packed references because of performance
|
* We do not repack packed references because of performance
|
||||||
* reasons.
|
* reasons.
|
||||||
*/
|
*/
|
||||||
int git_reference_set_oid(git_reference *ref, const git_oid *id)
|
int git_reference_set_oid(git_reference *ref, const git_oid *id, git_error **error)
|
||||||
{
|
{
|
||||||
int error = GIT_SUCCESS, exists;
|
|
||||||
git_odb *odb = NULL;
|
git_odb *odb = NULL;
|
||||||
|
|
||||||
if ((ref->flags & GIT_REF_OID) == 0)
|
if ((ref->flags & GIT_REF_OID) == 0) {
|
||||||
return git__throw(GIT_EINVALIDREFSTATE,
|
giterr_set(error, GITERR_REFERENCE,
|
||||||
"Failed to set OID target of reference. Not an OID reference");
|
"Cannot set OID on symbolic reference");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
assert(ref->owner);
|
assert(ref->owner);
|
||||||
|
|
||||||
error = git_repository_odb__weakptr(&odb, ref->owner);
|
if (git_repository_odb__weakptr(&odb, ref->owner, error) < 0)
|
||||||
if (error < GIT_SUCCESS)
|
return -1;
|
||||||
return error;
|
|
||||||
|
|
||||||
exists = git_odb_exists(odb, id);
|
|
||||||
|
|
||||||
git_odb_free(odb);
|
|
||||||
|
|
||||||
/* Don't let the user create references to OIDs that
|
/* Don't let the user create references to OIDs that
|
||||||
* don't exist in the ODB */
|
* don't exist in the ODB */
|
||||||
if (!exists)
|
if (!git_odb_exists(odb, id)) {
|
||||||
return git__throw(GIT_ENOTFOUND,
|
giterr_set(error, GITERR_REFERENCE,
|
||||||
"Failed to set OID target of reference. OID doesn't exist in ODB");
|
"Target OID for the reference doesn't exist on the repository");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
/* Update the OID value on `ref` */
|
/* Update the OID value on `ref` */
|
||||||
git_oid_cpy(&ref->target.oid, id);
|
git_oid_cpy(&ref->target.oid, id);
|
||||||
|
|
||||||
/* Write back to disk */
|
/* Write back to disk */
|
||||||
error = loose_write(ref);
|
return loose_write(ref, error);
|
||||||
if (error < GIT_SUCCESS)
|
|
||||||
return git__rethrow(error, "Failed to set OID target of reference");
|
|
||||||
|
|
||||||
return GIT_SUCCESS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1266,84 +1250,72 @@ int git_reference_set_oid(git_reference *ref, const git_oid *id)
|
|||||||
* a pack. We just change the target in memory
|
* a pack. We just change the target in memory
|
||||||
* and overwrite the file on disk.
|
* and overwrite the file on disk.
|
||||||
*/
|
*/
|
||||||
int git_reference_set_target(git_reference *ref, const char *target)
|
int git_reference_set_target(git_reference *ref, const char *target, git_error **error)
|
||||||
{
|
{
|
||||||
int error;
|
|
||||||
char normalized[GIT_REFNAME_MAX];
|
char normalized[GIT_REFNAME_MAX];
|
||||||
|
|
||||||
if ((ref->flags & GIT_REF_SYMBOLIC) == 0)
|
if ((ref->flags & GIT_REF_SYMBOLIC) == 0) {
|
||||||
return git__throw(GIT_EINVALIDREFSTATE,
|
giterr_set(error, GITERR_REFERENCE,
|
||||||
"Failed to set reference target. Not a symbolic reference");
|
"Cannot set symbolic target on a direct reference");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
error = normalize_name(normalized, sizeof(normalized), target, 0);
|
if (normalize_name(normalized, sizeof(normalized), target, 0, error))
|
||||||
if (error < GIT_SUCCESS)
|
return -1;
|
||||||
return git__rethrow(error,
|
|
||||||
"Failed to set reference target. Invalid target name");
|
|
||||||
|
|
||||||
git__free(ref->target.symbolic);
|
git__free(ref->target.symbolic);
|
||||||
ref->target.symbolic = git__strdup(normalized);
|
ref->target.symbolic = git__strdup(normalized);
|
||||||
if (ref->target.symbolic == NULL)
|
GITERR_CHECK_ALLOC(ref->target.symbolic, error);
|
||||||
return GIT_ENOMEM;
|
|
||||||
|
|
||||||
return loose_write(ref);
|
return loose_write(ref, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
int git_reference_rename(git_reference *ref, const char *new_name, int force)
|
int git_reference_rename(git_reference *ref, const char *new_name, int force, git_error **error)
|
||||||
{
|
{
|
||||||
int error;
|
int result, ref_available;
|
||||||
git_buf aux_path = GIT_BUF_INIT;
|
git_buf aux_path = GIT_BUF_INIT;
|
||||||
char normalized[GIT_REFNAME_MAX];
|
char normalized[GIT_REFNAME_MAX];
|
||||||
|
|
||||||
const char *head_target = NULL;
|
const char *head_target = NULL;
|
||||||
git_reference *existing_ref = NULL, *head = NULL;
|
git_reference *existing_ref = NULL, *head = NULL;
|
||||||
|
|
||||||
error = normalize_name(normalized, sizeof(normalized),
|
if (normalize_name(normalized, sizeof(normalized),
|
||||||
new_name, ref->flags & GIT_REF_OID);
|
new_name, ref->flags & GIT_REF_OID, error) < 0)
|
||||||
|
return -1;
|
||||||
if (error < GIT_SUCCESS)
|
|
||||||
return git__rethrow(error, "Failed to rename reference. Invalid name");
|
|
||||||
|
|
||||||
new_name = normalized;
|
new_name = normalized;
|
||||||
|
|
||||||
/* If we are forcing the rename, try to lookup a reference with the
|
/* see if the reference already exists */
|
||||||
* new one. If the lookup succeeds, we need to delete that ref
|
if (reference_available(&ref_available, ref->owner, new_name, ref->name, error) < 0)
|
||||||
* before the renaming can proceed */
|
return -1;
|
||||||
if (force) {
|
|
||||||
error = git_reference_lookup(&existing_ref, ref->owner, new_name);
|
|
||||||
|
|
||||||
if (error == GIT_SUCCESS) {
|
/* We cannot proceed if the reference already exists and we're not forcing
|
||||||
error = git_reference_delete(existing_ref);
|
* the rename; the existing one would be overwritten */
|
||||||
if (error < GIT_SUCCESS)
|
if (!force && !ref_available) {
|
||||||
return git__rethrow(error,
|
giterr_set(error, GITERR_REFERENCE,
|
||||||
"Failed to rename reference. "
|
"A reference with the same name (%s) already exists", normalized);
|
||||||
"The existing reference cannot be deleted");
|
return GIT_EEXISTS;
|
||||||
} else if (error != GIT_ENOTFOUND)
|
|
||||||
goto cleanup;
|
|
||||||
|
|
||||||
/* If we're not forcing the rename, check if the reference exists.
|
|
||||||
* If it does, renaming cannot continue */
|
|
||||||
} else {
|
|
||||||
int exists;
|
|
||||||
|
|
||||||
error = reference_exists(&exists, ref->owner, normalized);
|
|
||||||
if (error < GIT_SUCCESS)
|
|
||||||
goto cleanup;
|
|
||||||
|
|
||||||
if (exists)
|
|
||||||
return git__throw(GIT_EEXISTS,
|
|
||||||
"Failed to rename reference. Reference already exists");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((error = reference_available(ref->owner, new_name, ref->name)) < GIT_SUCCESS)
|
/* FIXME: if the reference exists and we are forcing, do we really need to
|
||||||
return git__rethrow(error,
|
* remove the reference first?
|
||||||
"Failed to rename reference. Reference already exists");
|
*
|
||||||
|
* Two cases:
|
||||||
|
*
|
||||||
|
* - the reference already exists and is loose: not a problem, the file
|
||||||
|
* gets overwritten on disk
|
||||||
|
*
|
||||||
|
* - the reference already exists and is packed: we write a new one as
|
||||||
|
* loose, which by all means renders the packed one useless
|
||||||
|
*/
|
||||||
|
|
||||||
/* Initialize path now so we won't get an allocation failure once
|
/* Initialize path now so we won't get an allocation failure once
|
||||||
* we actually start removing things.
|
* we actually start removing things.
|
||||||
*/
|
*/
|
||||||
error = git_buf_joinpath(&aux_path, ref->owner->path_repository, new_name);
|
if (git_buf_joinpath(&aux_path, ref->owner->path_repository, new_name) < 0) {
|
||||||
if (error < GIT_SUCCESS)
|
giterr_set_oom(error);
|
||||||
goto cleanup;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Now delete the old ref and remove an possibly existing directory
|
* Now delete the old ref and remove an possibly existing directory
|
||||||
@ -1351,12 +1323,12 @@ int git_reference_rename(git_reference *ref, const char *new_name, int force)
|
|||||||
* method deletes the ref from disk but doesn't free the pointer, so
|
* method deletes the ref from disk but doesn't free the pointer, so
|
||||||
* we can still access the ref's attributes for creating the new one
|
* we can still access the ref's attributes for creating the new one
|
||||||
*/
|
*/
|
||||||
if ((error = reference_delete(ref)) < GIT_SUCCESS)
|
if (reference_delete(ref, error) < 0)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
||||||
if (git_path_exists(aux_path.ptr) == GIT_SUCCESS) {
|
if (git_path_exists(aux_path.ptr) == GIT_SUCCESS) {
|
||||||
if (git_path_isdir(aux_path.ptr) == GIT_SUCCESS) {
|
if (git_path_isdir(aux_path.ptr) == GIT_SUCCESS) {
|
||||||
if ((error = git_futils_rmdir_r(aux_path.ptr, 0)) < GIT_SUCCESS)
|
if (git_futils_rmdir_r(aux_path.ptr, 0, error) < 0)
|
||||||
goto rollback;
|
goto rollback;
|
||||||
} else goto rollback;
|
} else goto rollback;
|
||||||
}
|
}
|
||||||
@ -1365,43 +1337,48 @@ int git_reference_rename(git_reference *ref, const char *new_name, int force)
|
|||||||
* Finally we can create the new reference.
|
* Finally we can create the new reference.
|
||||||
*/
|
*/
|
||||||
if (ref->flags & GIT_REF_SYMBOLIC) {
|
if (ref->flags & GIT_REF_SYMBOLIC) {
|
||||||
error = git_reference_create_symbolic(
|
result = git_reference_create_symbolic(
|
||||||
NULL, ref->owner, new_name, ref->target.symbolic, 0);
|
NULL, ref->owner, new_name, ref->target.symbolic, force, error);
|
||||||
} else {
|
} else {
|
||||||
error = git_reference_create_oid(
|
result = git_reference_create_oid(
|
||||||
NULL, ref->owner, new_name, &ref->target.oid, 0);
|
NULL, ref->owner, new_name, &ref->target.oid, force, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error < GIT_SUCCESS)
|
if (result < 0)
|
||||||
goto rollback;
|
goto rollback;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check if we have to update HEAD.
|
* Check if we have to update HEAD.
|
||||||
*/
|
*/
|
||||||
error = git_reference_lookup(&head, ref->owner, GIT_HEAD_FILE);
|
if (git_reference_lookup(&head, ref->owner, GIT_HEAD_FILE, NULL) < 0) {
|
||||||
if (error < GIT_SUCCESS)
|
giterr_set(error, GITERR_REFERENCE,
|
||||||
|
"Failed to update HEAD after renaming reference");
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
head_target = git_reference_target(head);
|
head_target = git_reference_target(head);
|
||||||
|
|
||||||
if (head_target && !strcmp(head_target, ref->name)) {
|
if (head_target && !strcmp(head_target, ref->name)) {
|
||||||
error = git_reference_create_symbolic(
|
if (git_reference_create_symbolic(&head, ref->owner, "HEAD", new_name, 1, NULL) < 0) {
|
||||||
&head, ref->owner, "HEAD", new_name, 1);
|
giterr_set(error, GITERR_REFERENCE,
|
||||||
|
"Failed to update HEAD after renaming reference");
|
||||||
if (error < GIT_SUCCESS)
|
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Rename the reflog file.
|
* Rename the reflog file.
|
||||||
*/
|
*/
|
||||||
error = git_buf_join_n(&aux_path, '/', 3, ref->owner->path_repository,
|
if (git_buf_join_n(&aux_path, '/', 3, ref->owner->path_repository,
|
||||||
GIT_REFLOG_DIR, ref->name);
|
GIT_REFLOG_DIR, ref->name) < 0) {
|
||||||
if (error < GIT_SUCCESS)
|
giterr_set_oom(error);
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
if (git_path_exists(aux_path.ptr) == GIT_SUCCESS)
|
if (git_path_exists(aux_path.ptr) == 0) {
|
||||||
error = git_reflog_rename(ref, new_name);
|
if (git_reflog_rename(ref, new_name, error) < 0)
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Change the name of the reference given by the user.
|
* Change the name of the reference given by the user.
|
||||||
@ -1412,38 +1389,37 @@ int git_reference_rename(git_reference *ref, const char *new_name, int force)
|
|||||||
/* The reference is no longer packed */
|
/* The reference is no longer packed */
|
||||||
ref->flags &= ~GIT_REF_PACKED;
|
ref->flags &= ~GIT_REF_PACKED;
|
||||||
|
|
||||||
cleanup:
|
|
||||||
/* We no longer need the newly created reference nor the head */
|
|
||||||
git_reference_free(head);
|
git_reference_free(head);
|
||||||
git_buf_free(&aux_path);
|
git_buf_free(&aux_path);
|
||||||
return error == GIT_SUCCESS ?
|
return 0;
|
||||||
GIT_SUCCESS :
|
|
||||||
git__rethrow(error, "Failed to rename reference");
|
cleanup:
|
||||||
|
git_reference_free(head);
|
||||||
|
git_buf_free(&aux_path);
|
||||||
|
return -1;
|
||||||
|
|
||||||
rollback:
|
rollback:
|
||||||
/*
|
/*
|
||||||
* Try to create the old reference again.
|
* Try to create the old reference again, ignore failures
|
||||||
*/
|
*/
|
||||||
if (ref->flags & GIT_REF_SYMBOLIC)
|
if (ref->flags & GIT_REF_SYMBOLIC)
|
||||||
error = git_reference_create_symbolic(
|
git_reference_create_symbolic(
|
||||||
NULL, ref->owner, ref->name, ref->target.symbolic, 0);
|
NULL, ref->owner, ref->name, ref->target.symbolic, 0, NULL);
|
||||||
else
|
else
|
||||||
error = git_reference_create_oid(
|
git_reference_create_oid(
|
||||||
NULL, ref->owner, ref->name, &ref->target.oid, 0);
|
NULL, ref->owner, ref->name, &ref->target.oid, 0. NULL);
|
||||||
|
|
||||||
/* The reference is no longer packed */
|
/* The reference is no longer packed */
|
||||||
ref->flags &= ~GIT_REF_PACKED;
|
ref->flags &= ~GIT_REF_PACKED;
|
||||||
|
|
||||||
git_buf_free(&aux_path);
|
git_buf_free(&aux_path);
|
||||||
|
|
||||||
return error == GIT_SUCCESS ?
|
return -1;
|
||||||
git__rethrow(GIT_ERROR, "Failed to rename reference. Did rollback") :
|
|
||||||
git__rethrow(error, "Failed to rename reference. Failed to rollback");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int git_reference_resolve(git_reference **ref_out, git_reference *ref)
|
int git_reference_resolve(git_reference **ref_out, git_reference *ref, git_error **error)
|
||||||
{
|
{
|
||||||
int error, i = 0;
|
int result, i = 0;
|
||||||
git_repository *repo;
|
git_repository *repo;
|
||||||
|
|
||||||
assert(ref);
|
assert(ref);
|
||||||
@ -1455,15 +1431,15 @@ int git_reference_resolve(git_reference **ref_out, git_reference *ref)
|
|||||||
* copy. Instead of duplicating `ref`, we look it up again to
|
* copy. Instead of duplicating `ref`, we look it up again to
|
||||||
* ensure the copy is out to date */
|
* ensure the copy is out to date */
|
||||||
if (ref->flags & GIT_REF_OID)
|
if (ref->flags & GIT_REF_OID)
|
||||||
return git_reference_lookup(ref_out, ref->owner, ref->name);
|
return git_reference_lookup(ref_out, ref->owner, ref->name, error);
|
||||||
|
|
||||||
/* Otherwise, keep iterating until the reference is resolved */
|
/* Otherwise, keep iterating until the reference is resolved */
|
||||||
for (i = 0; i < MAX_NESTING_LEVEL; ++i) {
|
for (i = 0; i < MAX_NESTING_LEVEL; ++i) {
|
||||||
git_reference *new_ref;
|
git_reference *new_ref;
|
||||||
|
|
||||||
error = git_reference_lookup(&new_ref, repo, ref->target.symbolic);
|
result = git_reference_lookup(&new_ref, repo, ref->target.symbolic, error);
|
||||||
if (error < GIT_SUCCESS)
|
if (result < 0)
|
||||||
return git__rethrow(error, "Failed to resolve reference");
|
return result;
|
||||||
|
|
||||||
/* Free intermediate references, except for the original one
|
/* Free intermediate references, except for the original one
|
||||||
* we've received */
|
* we've received */
|
||||||
@ -1480,33 +1456,30 @@ int git_reference_resolve(git_reference **ref_out, git_reference *ref)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return git__throw(GIT_ENOMEM,
|
giterr_set(error, GITERR_REFERENCE,
|
||||||
"Failed to resolve reference. Reference is too nested");
|
"Symbolic reference too nested (%d levels deep)", MAX_NESTING_LEVEL);
|
||||||
|
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int git_reference_packall(git_repository *repo)
|
int git_reference_packall(git_repository *repo, git_error **error)
|
||||||
{
|
{
|
||||||
int error;
|
if (packed_load(repo, error) < 0 || /* load the existing packfile */
|
||||||
|
packed_loadloose(repo, error) < 0 || /* add all the loose refs */
|
||||||
|
packed_write(repo, error) < 0) /* write back to disk */
|
||||||
|
return -1;
|
||||||
|
|
||||||
/* load the existing packfile */
|
return 0;
|
||||||
if ((error = packed_load(repo)) < GIT_SUCCESS)
|
|
||||||
return git__rethrow(error, "Failed to pack references");
|
|
||||||
|
|
||||||
/* update it in-memory with all the loose references */
|
|
||||||
if ((error = packed_loadloose(repo)) < GIT_SUCCESS)
|
|
||||||
return git__rethrow(error, "Failed to pack references");
|
|
||||||
|
|
||||||
/* write it back to disk */
|
|
||||||
return packed_write(repo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int git_reference_foreach(
|
int git_reference_foreach(
|
||||||
git_repository *repo,
|
git_repository *repo,
|
||||||
unsigned int list_flags,
|
unsigned int list_flags,
|
||||||
int (*callback)(const char *, void *),
|
int (*callback)(const char *, void *),
|
||||||
void *payload)
|
void *payload,
|
||||||
|
git_error **error)
|
||||||
{
|
{
|
||||||
int error;
|
int result;
|
||||||
struct dirent_list_data data;
|
struct dirent_list_data data;
|
||||||
git_buf refs_path = GIT_BUF_INIT;
|
git_buf refs_path = GIT_BUF_INIT;
|
||||||
|
|
||||||
@ -1514,13 +1487,12 @@ int git_reference_foreach(
|
|||||||
if (list_flags & GIT_REF_PACKED) {
|
if (list_flags & GIT_REF_PACKED) {
|
||||||
const char *ref_name;
|
const char *ref_name;
|
||||||
|
|
||||||
if ((error = packed_load(repo)) < GIT_SUCCESS)
|
if (packed_load(repo, error) < 0)
|
||||||
return git__rethrow(error, "Failed to list references");
|
return -1;
|
||||||
|
|
||||||
GIT_HASHTABLE_FOREACH_KEY(repo->references.packfile, ref_name,
|
GIT_HASHTABLE_FOREACH(repo->references.packfile, ref_name, _unused,
|
||||||
if ((error = callback(ref_name, payload)) < GIT_SUCCESS)
|
if (callback(ref_name, payload) < 0)
|
||||||
return git__throw(error,
|
return 0;
|
||||||
"Failed to list references. User callback failed");
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1533,15 +1505,15 @@ int git_reference_foreach(
|
|||||||
data.callback = callback;
|
data.callback = callback;
|
||||||
data.callback_payload = payload;
|
data.callback_payload = payload;
|
||||||
|
|
||||||
if ((error = git_buf_joinpath(&refs_path,
|
if (git_buf_joinpath(&refs_path, repo->path_repository, GIT_REFS_DIR) < 0) {
|
||||||
repo->path_repository, GIT_REFS_DIR)) < GIT_SUCCESS)
|
giterr_set_oom(error);
|
||||||
return git__rethrow(error, "Failed to alloc space for references");
|
return -1;
|
||||||
|
}
|
||||||
error = git_path_direach(&refs_path, _dirent_loose_listall, &data);
|
|
||||||
|
|
||||||
|
result = git_path_direach(&refs_path, _dirent_loose_listall, &data, error);
|
||||||
git_buf_free(&refs_path);
|
git_buf_free(&refs_path);
|
||||||
|
|
||||||
return error;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int cb__reflist_add(const char *ref, void *data)
|
static int cb__reflist_add(const char *ref, void *data)
|
||||||
@ -1552,9 +1524,10 @@ static int cb__reflist_add(const char *ref, void *data)
|
|||||||
int git_reference_listall(
|
int git_reference_listall(
|
||||||
git_strarray *array,
|
git_strarray *array,
|
||||||
git_repository *repo,
|
git_repository *repo,
|
||||||
unsigned int list_flags)
|
unsigned int list_flags,
|
||||||
|
git_error **error)
|
||||||
{
|
{
|
||||||
int error;
|
int result;
|
||||||
git_vector ref_list;
|
git_vector ref_list;
|
||||||
|
|
||||||
assert(array && repo);
|
assert(array && repo);
|
||||||
@ -1562,15 +1535,15 @@ int git_reference_listall(
|
|||||||
array->strings = NULL;
|
array->strings = NULL;
|
||||||
array->count = 0;
|
array->count = 0;
|
||||||
|
|
||||||
if (git_vector_init(&ref_list, 8, NULL) < GIT_SUCCESS)
|
if (git_vector_init(&ref_list, 8, NULL) < GIT_SUCCESS) {
|
||||||
return GIT_ENOMEM;
|
giterr_set_oom(error);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
error = git_reference_foreach(
|
if (git_reference_foreach(
|
||||||
repo, list_flags, &cb__reflist_add, (void *)&ref_list);
|
repo, list_flags, &cb__reflist_add, (void *)&ref_list, error) < 0) {
|
||||||
|
|
||||||
if (error < GIT_SUCCESS) {
|
|
||||||
git_vector_free(&ref_list);
|
git_vector_free(&ref_list);
|
||||||
return error;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
array->strings = (char **)ref_list.contents;
|
array->strings = (char **)ref_list.contents;
|
||||||
@ -1578,17 +1551,11 @@ int git_reference_listall(
|
|||||||
return GIT_SUCCESS;
|
return GIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
int git_reference_reload(git_reference *ref)
|
int git_reference_reload(git_reference *ref, git_error **error)
|
||||||
{
|
{
|
||||||
int error = reference_lookup(ref);
|
return reference_lookup(ref, error);
|
||||||
|
|
||||||
if (error < GIT_SUCCESS)
|
|
||||||
return git__rethrow(error, "Failed to reload reference");
|
|
||||||
|
|
||||||
return GIT_SUCCESS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void git_repository__refcache_free(git_refcache *refs)
|
void git_repository__refcache_free(git_refcache *refs)
|
||||||
{
|
{
|
||||||
assert(refs);
|
assert(refs);
|
||||||
|
Loading…
Reference in New Issue
Block a user