#include <git2/tag.h>
#include <git2/object.h>
#include <git2/oid.h>
+#include <git2/branch.h>
GIT__USE_STRMAP;
if (git_buf_joinpath(&path, repo_path, ref_name) < 0)
return -1;
- result = git_futils_readbuffer_updated(file_content, path.ptr, mtime, updated);
+ result = git_futils_readbuffer_updated(
+ file_content, path.ptr, mtime, NULL, updated);
git_buf_free(&path);
return result;
static int loose_parse_oid(git_oid *oid, git_buf *file_content)
{
- /* File format: 40 chars (OID) */
- if (git_buf_len(file_content) == GIT_OID_HEXSZ &&
- git_oid_fromstr(oid, git_buf_cstr(file_content)) == 0)
+ size_t len;
+ const char *str;
+
+ len = git_buf_len(file_content);
+ if (len < GIT_OID_HEXSZ)
+ goto corrupted;
+
+ /* str is guranteed to be zero-terminated */
+ str = git_buf_cstr(file_content);
+
+ /* we need to get 40 OID characters from the file */
+ if (git_oid_fromstr(oid, git_buf_cstr(file_content)) < 0)
+ goto corrupted;
+
+ /* If the file is longer than 40 chars, the 41st must be a space */
+ str += GIT_OID_HEXSZ;
+ if (*str == '\0' || git__isspace(*str))
return 0;
+corrupted:
giterr_set(GITERR_REFERENCE, "Corrupted loose reference file");
return -1;
}
if (!updated)
return 0;
- git_buf_rtrim(&ref_file);
-
if (ref->flags & GIT_REF_SYMBOLIC) {
git__free(ref->target.symbolic);
ref->target.symbolic = NULL;
if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) {
ref->flags |= GIT_REF_SYMBOLIC;
+ git_buf_rtrim(&ref_file);
result = loose_parse_symbolic(ref, &ref_file);
} else {
ref->flags |= GIT_REF_OID;
git_buf ref_path = GIT_BUF_INIT;
struct stat st;
- if (git_buf_joinpath(&ref_path, ref->owner->path_repository, ref->name) < 0)
- return -1;
-
- /* Remove a possibly existing empty directory hierarchy
+ /* Remove a possibly existing empty directory hierarchy
* which name would collide with the reference name
*/
- if (git_path_isdir(git_buf_cstr(&ref_path)) &&
- (git_futils_rmdir_r(git_buf_cstr(&ref_path), GIT_DIRREMOVAL_ONLY_EMPTY_DIRS) < 0)) {
- git_buf_free(&ref_path);
- return -1;
- }
+ if (git_futils_rmdir_r(ref->name, ref->owner->path_repository,
+ GIT_RMDIR_SKIP_NONEMPTY) < 0)
+ return -1;
+
+ if (git_buf_joinpath(&ref_path, ref->owner->path_repository, ref->name) < 0)
+ return -1;
if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) {
git_buf_free(&ref_path);
refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin);
if (refname_end == NULL)
- goto corrupt;
+ refname_end = buffer_end;
if (refname_end[-1] == '\r')
refname_end--;
return 0; /* we are filtering out this reference */
}
+ /* Locked references aren't returned */
+ if (!git__suffixcmp(file_path, GIT_FILELOCK_EXTENSION))
+ return 0;
+
if (data->callback(file_path, data->callback_payload))
data->callback_error = GIT_EUSER;
/*
* Find the object pointed at by this tag
*/
- git_oid_cpy(&ref->peel, git_tag_target_oid(tag));
+ git_oid_cpy(&ref->peel, git_tag_target_id(tag));
ref->flags |= GIT_PACKREF_HAS_PEEL;
/*
return git_reference_lookup_resolved(ref_out, repo, name, 0);
}
-int git_reference_name_to_oid(
+int git_reference_name_to_id(
git_oid *out, git_repository *repo, const char *name)
{
int error;
if ((error = git_reference_lookup_resolved(&ref, repo, name, -1)) < 0)
return error;
- git_oid_cpy(out, git_reference_oid(ref));
+ git_oid_cpy(out, git_reference_target(ref));
git_reference_free(ref);
return 0;
}
scan->name = git__calloc(GIT_REFNAME_MAX + 1, sizeof(char));
GITERR_CHECK_ALLOC(scan->name);
- if ((result = git_reference__normalize_name(
+ if ((result = git_reference__normalize_name_lax(
scan->name,
GIT_REFNAME_MAX,
name)) < 0) {
/**
* Getters
*/
-git_ref_t git_reference_type(git_reference *ref)
+git_ref_t git_reference_type(const git_reference *ref)
{
assert(ref);
return !!(ref->flags & GIT_REF_PACKED);
}
-const char *git_reference_name(git_reference *ref)
+const char *git_reference_name(const git_reference *ref)
{
assert(ref);
return ref->name;
}
-git_repository *git_reference_owner(git_reference *ref)
+git_repository *git_reference_owner(const git_reference *ref)
{
assert(ref);
return ref->owner;
}
-const git_oid *git_reference_oid(git_reference *ref)
+const git_oid *git_reference_target(const git_reference *ref)
{
assert(ref);
return &ref->target.oid;
}
-const char *git_reference_target(git_reference *ref)
+const char *git_reference_symbolic_target(const git_reference *ref)
{
assert(ref);
return ref->target.symbolic;
}
-int git_reference_create_symbolic(
+int git_reference_symbolic_create(
git_reference **ref_out,
git_repository *repo,
const char *name,
{
char normalized[GIT_REFNAME_MAX];
git_reference *ref = NULL;
+ int error;
- if (git_reference__normalize_name(
+ if ((error = git_reference__normalize_name_lax(
normalized,
sizeof(normalized),
- name) < 0)
- return -1;
+ name)) < 0)
+ return error;
- if (reference_can_write(repo, normalized, NULL, force) < 0)
- return -1;
+ if ((error = reference_can_write(repo, normalized, NULL, force)) < 0)
+ return error;
if (reference_alloc(&ref, repo, normalized) < 0)
return -1;
/* set the target; this will normalize the name automatically
* and write the reference on disk */
- if (git_reference_set_target(ref, target) < 0) {
+ if (git_reference_symbolic_set_target(ref, target) < 0) {
git_reference_free(ref);
return -1;
}
return 0;
}
-int git_reference_create_oid(
+int git_reference_create(
git_reference **ref_out,
git_repository *repo,
const char *name,
const git_oid *id,
int force)
{
+ int error;
git_reference *ref = NULL;
char normalized[GIT_REFNAME_MAX];
- if (git_reference__normalize_name_oid(
+ if ((error = git_reference__normalize_name_lax(
normalized,
sizeof(normalized),
- name) < 0)
- return -1;
+ name)) < 0)
+ return error;
- if (reference_can_write(repo, normalized, NULL, force) < 0)
- return -1;
+ if ((error = reference_can_write(repo, normalized, NULL, force)) < 0)
+ return error;
if (reference_alloc(&ref, repo, name) < 0)
return -1;
ref->flags |= GIT_REF_OID;
/* set the oid; this will write the reference on disk */
- if (git_reference_set_oid(ref, id) < 0) {
+ if (git_reference_set_target(ref, id) < 0) {
git_reference_free(ref);
return -1;
}
* We do not repack packed references because of performance
* reasons.
*/
-int git_reference_set_oid(git_reference *ref, const git_oid *id)
+int git_reference_set_target(git_reference *ref, const git_oid *id)
{
git_odb *odb = NULL;
* a pack. We just change the target in memory
* and overwrite the file on disk.
*/
-int git_reference_set_target(git_reference *ref, const char *target)
+int git_reference_symbolic_set_target(git_reference *ref, const char *target)
{
+ int error;
char normalized[GIT_REFNAME_MAX];
if ((ref->flags & GIT_REF_SYMBOLIC) == 0) {
return -1;
}
- if (git_reference__normalize_name(
+ if ((error = git_reference__normalize_name_lax(
normalized,
sizeof(normalized),
- target))
- return -1;
+ target)) < 0)
+ return error;
git__free(ref->target.symbolic);
ref->target.symbolic = git__strdup(normalized);
unsigned int normalization_flags;
git_buf aux_path = GIT_BUF_INIT;
char normalized[GIT_REFNAME_MAX];
-
- const char *head_target = NULL;
- git_reference *head = NULL;
+ bool should_head_be_updated = false;
normalization_flags = ref->flags & GIT_REF_SYMBOLIC ?
GIT_REF_FORMAT_ALLOW_ONELEVEL
: GIT_REF_FORMAT_NORMAL;
- if (git_reference_normalize_name(
+ if ((result = git_reference_normalize_name(
normalized,
sizeof(normalized),
new_name,
- normalization_flags) < 0)
- return -1;
+ normalization_flags)) < 0)
+ return result;
- if (reference_can_write(ref->owner, normalized, ref->name, force) < 0)
- return -1;
+ if ((result = reference_can_write(ref->owner, normalized, ref->name, force)) < 0)
+ return result;
/* Initialize path now so we won't get an allocation failure once
* we actually start removing things. */
if (git_buf_joinpath(&aux_path, ref->owner->path_repository, new_name) < 0)
return -1;
+ /*
+ * Check if we have to update HEAD.
+ */
+ if ((should_head_be_updated = git_branch_is_head(ref)) < 0)
+ goto cleanup;
+
/*
* Now delete the old ref and remove an possibly existing directory
* named `new_name`. Note that using the internal `reference_delete`
* Finally we can create the new reference.
*/
if (ref->flags & GIT_REF_SYMBOLIC) {
- result = git_reference_create_symbolic(
+ result = git_reference_symbolic_create(
NULL, ref->owner, new_name, ref->target.symbolic, force);
} else {
- result = git_reference_create_oid(
+ result = git_reference_create(
NULL, ref->owner, new_name, &ref->target.oid, force);
}
goto rollback;
/*
- * Check if we have to update HEAD.
+ * Update HEAD it was poiting to the reference being renamed.
*/
- if (git_reference_lookup(&head, ref->owner, GIT_HEAD_FILE) < 0) {
- giterr_set(GITERR_REFERENCE,
- "Failed to update HEAD after renaming reference");
- goto cleanup;
- }
-
- head_target = git_reference_target(head);
-
- if (head_target && !strcmp(head_target, ref->name)) {
- git_reference_free(head);
- head = NULL;
-
- if (git_reference_create_symbolic(&head, ref->owner, "HEAD", new_name, 1) < 0) {
+ if (should_head_be_updated &&
+ git_repository_set_head(ref->owner, new_name) < 0) {
giterr_set(GITERR_REFERENCE,
"Failed to update HEAD after renaming reference");
goto cleanup;
- }
}
/*
/* The reference is no longer packed */
ref->flags &= ~GIT_REF_PACKED;
- git_reference_free(head);
git_buf_free(&aux_path);
return 0;
cleanup:
- git_reference_free(head);
git_buf_free(&aux_path);
return -1;
* Try to create the old reference again, ignore failures
*/
if (ref->flags & GIT_REF_SYMBOLIC)
- git_reference_create_symbolic(
+ git_reference_symbolic_create(
NULL, ref->owner, ref->name, ref->target.symbolic, 0);
else
- git_reference_create_oid(
+ git_reference_create(
NULL, ref->owner, ref->name, &ref->target.oid, 0);
/* The reference is no longer packed */
return -1;
}
-int git_reference_resolve(git_reference **ref_out, git_reference *ref)
+int git_reference_resolve(git_reference **ref_out, const git_reference *ref)
{
if (ref->flags & GIT_REF_OID)
return git_reference_lookup(ref_out, ref->owner, ref->name);
int git_reference_foreach(
git_repository *repo,
unsigned int list_flags,
- int (*callback)(const char *, void *),
+ git_reference_foreach_cb callback,
void *payload)
{
int result;
}
}
-int git_reference_normalize_name(
- char *buffer_out,
- size_t buffer_size,
- const char *name,
- unsigned int flags)
+static int ensure_segment_validity(const char *name)
{
- const char *name_end, *buffer_out_start;
- const char *current;
- int contains_a_slash = 0;
+ const char *current = name;
+ char prev = '\0';
- assert(name && buffer_out);
+ if (*current == '.')
+ return -1; /* Refname starts with "." */
- if (flags & GIT_REF_FORMAT_REFSPEC_PATTERN) {
- giterr_set(GITERR_INVALID, "Unimplemented");
- return -1;
- }
+ for (current = name; ; current++) {
+ if (*current == '\0' || *current == '/')
+ break;
- buffer_out_start = buffer_out;
- current = name;
- name_end = name + strlen(name);
+ if (!is_valid_ref_char(*current))
+ return -1; /* Illegal character in refname */
- /* Terminating null byte */
- buffer_size--;
+ if (prev == '.' && *current == '.')
+ return -1; /* Refname contains ".." */
- /* A refname can not be empty */
- if (name_end == name)
- goto invalid_name;
+ if (prev == '@' && *current == '{')
+ return -1; /* Refname contains "@{" */
- /* A refname can not end with a dot or a slash */
- if (*(name_end - 1) == '.' || *(name_end - 1) == '/')
- goto invalid_name;
+ prev = *current;
+ }
- while (current < name_end && buffer_size > 0) {
- if (!is_valid_ref_char(*current))
- goto invalid_name;
+ return (int)(current - name);
+}
+
+static bool is_all_caps_and_underscore(const char *name, size_t len)
+{
+ size_t i;
+ char c;
- if (buffer_out > buffer_out_start) {
- char prev = *(buffer_out - 1);
+ assert(name && len > 0);
- /* A refname can not start with a dot nor contain a double dot */
- if (*current == '.' && ((prev == '.') || (prev == '/')))
- goto invalid_name;
+ for (i = 0; i < len; i++)
+ {
+ c = name[i];
+ if ((c < 'A' || c > 'Z') && c != '_')
+ return false;
+ }
- /* '@{' is forbidden within a refname */
- if (*current == '{' && prev == '@')
- goto invalid_name;
+ if (*name == '_' || name[len - 1] == '_')
+ return false;
- /* Prevent multiple slashes from being added to the output */
- if (*current == '/' && prev == '/') {
- current++;
- continue;
- }
+ return true;
+}
+
+int git_reference__normalize_name(
+ git_buf *buf,
+ const char *name,
+ unsigned int flags)
+{
+ // Inspired from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/refs.c#L36-100
+
+ char *current;
+ int segment_len, segments_count = 0, error = GIT_EINVALIDSPEC;
+ unsigned int process_flags;
+ bool normalize = (buf != NULL);
+ assert(name);
+
+ process_flags = flags;
+ current = (char *)name;
+
+ if (normalize)
+ git_buf_clear(buf);
+
+ while (true) {
+ segment_len = ensure_segment_validity(current);
+ if (segment_len < 0) {
+ if ((process_flags & GIT_REF_FORMAT_REFSPEC_PATTERN) &&
+ current[0] == '*' &&
+ (current[1] == '\0' || current[1] == '/')) {
+ /* Accept one wildcard as a full refname component. */
+ process_flags &= ~GIT_REF_FORMAT_REFSPEC_PATTERN;
+ segment_len = 1;
+ } else
+ goto cleanup;
}
- if (*current == '/') {
- if (buffer_out > buffer_out_start)
- contains_a_slash = 1;
- else {
- current++;
- continue;
+ if (segment_len > 0) {
+ if (normalize) {
+ size_t cur_len = git_buf_len(buf);
+
+ git_buf_joinpath(buf, git_buf_cstr(buf), current);
+ git_buf_truncate(buf,
+ cur_len + segment_len + (segments_count ? 1 : 0));
+
+ if (git_buf_oom(buf)) {
+ error = -1;
+ goto cleanup;
+ }
}
+
+ segments_count++;
}
- *buffer_out++ = *current++;
- buffer_size--;
- }
+ if (current[segment_len] == '\0')
+ break;
- if (current < name_end) {
- giterr_set(
- GITERR_REFERENCE,
- "The provided buffer is too short to hold the normalization of '%s'", name);
- return GIT_EBUFS;
+ current += segment_len + 1;
}
- /* Object id refname have to contain at least one slash, except
- * for HEAD in a detached state or MERGE_HEAD if we're in the
- * middle of a merge */
- if (!(flags & GIT_REF_FORMAT_ALLOW_ONELEVEL) &&
- !contains_a_slash &&
- strcmp(name, GIT_HEAD_FILE) != 0 &&
- strcmp(name, GIT_MERGE_HEAD_FILE) != 0 &&
- strcmp(name, GIT_FETCH_HEAD_FILE) != 0)
- goto invalid_name;
+ /* A refname can not be empty */
+ if (segment_len == 0 && segments_count == 0)
+ goto cleanup;
+
+ /* A refname can not end with "." */
+ if (current[segment_len - 1] == '.')
+ goto cleanup;
+
+ /* A refname can not end with "/" */
+ if (current[segment_len - 1] == '/')
+ goto cleanup;
/* A refname can not end with ".lock" */
if (!git__suffixcmp(name, GIT_FILELOCK_EXTENSION))
- goto invalid_name;
+ goto cleanup;
- *buffer_out = '\0';
+ if ((segments_count == 1 ) && !(flags & GIT_REF_FORMAT_ALLOW_ONELEVEL))
+ goto cleanup;
- return 0;
+ if ((segments_count == 1 ) &&
+ !(is_all_caps_and_underscore(name, (size_t)segment_len) ||
+ ((flags & GIT_REF_FORMAT_REFSPEC_PATTERN) && !strcmp("*", name))))
+ goto cleanup;
-invalid_name:
- giterr_set(
- GITERR_REFERENCE,
- "The given reference name '%s' is not valid", name);
- return -1;
+ if ((segments_count > 1)
+ && (is_all_caps_and_underscore(name, strchr(name, '/') - name)))
+ goto cleanup;
+
+ error = 0;
+
+cleanup:
+ if (error == GIT_EINVALIDSPEC)
+ giterr_set(
+ GITERR_REFERENCE,
+ "The given reference name '%s' is not valid", name);
+
+ if (error && normalize)
+ git_buf_free(buf);
+
+ return error;
}
-int git_reference__normalize_name(
+int git_reference_normalize_name(
char *buffer_out,
- size_t out_size,
- const char *name)
+ size_t buffer_size,
+ const char *name,
+ unsigned int flags)
{
- return git_reference_normalize_name(
- buffer_out,
- out_size,
- name,
- GIT_REF_FORMAT_ALLOW_ONELEVEL);
+ git_buf buf = GIT_BUF_INIT;
+ int error;
+
+ if ((error = git_reference__normalize_name(&buf, name, flags)) < 0)
+ goto cleanup;
+
+ if (git_buf_len(&buf) > buffer_size - 1) {
+ giterr_set(
+ GITERR_REFERENCE,
+ "The provided buffer is too short to hold the normalization of '%s'", name);
+ error = GIT_EBUFS;
+ goto cleanup;
+ }
+
+ git_buf_copy_cstr(buffer_out, buffer_size, &buf);
+
+ error = 0;
+
+cleanup:
+ git_buf_free(&buf);
+ return error;
}
-int git_reference__normalize_name_oid(
+int git_reference__normalize_name_lax(
char *buffer_out,
size_t out_size,
const char *name)
buffer_out,
out_size,
name,
- GIT_REF_FORMAT_NORMAL);
+ GIT_REF_FORMAT_ALLOW_ONELEVEL);
}
-
#define GIT_REF_TYPEMASK (GIT_REF_OID | GIT_REF_SYMBOLIC)
int git_reference_cmp(git_reference *ref1, git_reference *ref2)
* a new reference and that's it */
if (res == GIT_ENOTFOUND) {
giterr_clear();
- return git_reference_create_oid(NULL, repo, ref_name, oid, 1);
+ return git_reference_create(NULL, repo, ref_name, oid, 1);
}
if (res < 0)
const char *sym_target;
/* The target pointed at by this reference */
- sym_target = git_reference_target(ref);
+ sym_target = git_reference_symbolic_target(ref);
/* resolve the reference to the target it points to */
res = git_reference_resolve(&aux, ref);
*/
if (res == GIT_ENOTFOUND) {
giterr_clear();
- res = git_reference_create_oid(NULL, repo, sym_target, oid, 1);
+ res = git_reference_create(NULL, repo, sym_target, oid, 1);
git_reference_free(ref);
return res;
}
/* ref is made to point to `oid`: ref is either the original reference,
* or the target of the symbolic reference we've looked up */
- res = git_reference_set_oid(ref, oid);
+ res = git_reference_set_target(ref, oid);
git_reference_free(ref);
return res;
}
assert(ref);
return git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR) == 0;
}
+
+static int peel_error(int error, git_reference *ref, const char* msg)
+{
+ giterr_set(
+ GITERR_INVALID,
+ "The reference '%s' cannot be peeled - %s", git_reference_name(ref), msg);
+ return error;
+}
+
+static int reference_target(git_object **object, git_reference *ref)
+{
+ const git_oid *oid;
+
+ oid = git_reference_target(ref);
+
+ return git_object_lookup(object, git_reference_owner(ref), oid, GIT_OBJ_ANY);
+}
+
+int git_reference_peel(
+ git_object **peeled,
+ git_reference *ref,
+ git_otype target_type)
+{
+ git_reference *resolved = NULL;
+ git_object *target = NULL;
+ int error;
+
+ assert(ref);
+
+ if ((error = git_reference_resolve(&resolved, ref)) < 0)
+ return peel_error(error, ref, "Cannot resolve reference");
+
+ if ((error = reference_target(&target, resolved)) < 0) {
+ peel_error(error, ref, "Cannot retrieve reference target");
+ goto cleanup;
+ }
+
+ if (target_type == GIT_OBJ_ANY && git_object_type(target) != GIT_OBJ_TAG)
+ error = git_object__dup(peeled, target);
+ else
+ error = git_object_peel(peeled, target, target_type);
+
+cleanup:
+ git_object_free(target);
+ git_reference_free(resolved);
+ return error;
+}
+
+int git_reference__is_valid_name(
+ const char *refname,
+ unsigned int flags)
+{
+ int error;
+
+ error = git_reference__normalize_name(NULL, refname, flags) == 0;
+ giterr_clear();
+
+ return error;
+}
+
+int git_reference_is_valid_name(
+ const char *refname)
+{
+ return git_reference__is_valid_name(
+ refname,
+ GIT_REF_FORMAT_ALLOW_ONELEVEL);
+}