diff options
Diffstat (limited to 'src/refs.c')
-rw-r--r-- | src/refs.c | 1864 |
1 files changed, 561 insertions, 1303 deletions
diff --git a/src/refs.c b/src/refs.c index 1ef3e13a4..b1f679632 100644 --- a/src/refs.c +++ b/src/refs.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 the libgit2 contributors + * 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. @@ -11,9 +11,15 @@ #include "fileops.h" #include "pack.h" #include "reflog.h" +#include "refdb.h" #include <git2/tag.h> #include <git2/object.h> +#include <git2/oid.h> +#include <git2/branch.h> +#include <git2/refs.h> +#include <git2/refdb.h> +#include <git2/refdb_backend.h> GIT__USE_STRMAP; @@ -25,789 +31,55 @@ enum { GIT_PACKREF_WAS_LOOSE = 2 }; -struct packref { - git_oid oid; - git_oid peel; - char flags; - char name[GIT_FLEX_ARRAY]; -}; - -static int reference_read( - git_buf *file_content, - time_t *mtime, - const char *repo_path, - const char *ref_name, - int *updated); - -/* loose refs */ -static int loose_parse_symbolic(git_reference *ref, git_buf *file_content); -static int loose_parse_oid(git_oid *ref, git_buf *file_content); -static int loose_lookup(git_reference *ref); -static int loose_lookup_to_packfile(struct packref **ref_out, - git_repository *repo, const char *name); -static int loose_write(git_reference *ref); - -/* packed refs */ -static int packed_parse_peel(struct packref *tag_ref, - const char **buffer_out, const char *buffer_end); -static int packed_parse_oid(struct packref **ref_out, - const char **buffer_out, const char *buffer_end); -static int packed_load(git_repository *repo); -static int packed_loadloose(git_repository *repository); -static int packed_write_ref(struct packref *ref, git_filebuf *file); -static int packed_find_peel(git_repository *repo, struct packref *ref); -static int packed_remove_loose(git_repository *repo, git_vector *packing_list); -static int packed_sort(const void *a, const void *b); -static int packed_lookup(git_reference *ref); -static int packed_write(git_repository *repo); - -/* internal helpers */ -static int reference_path_available(git_repository *repo, - const char *ref, const char *old_ref); -static int reference_delete(git_reference *ref); -static int reference_lookup(git_reference *ref); - -/* name normalization */ -static int normalize_name(char *buffer_out, size_t out_size, - const char *name, int is_oid_ref); - - -void git_reference_free(git_reference *reference) -{ - if (reference == NULL) - return; - - git__free(reference->name); - reference->name = NULL; - - if (reference->flags & GIT_REF_SYMBOLIC) { - git__free(reference->target.symbolic); - reference->target.symbolic = NULL; - } - - git__free(reference); -} - -static int reference_alloc( - git_reference **ref_out, - git_repository *repo, - const char *name) -{ - git_reference *reference = NULL; - - assert(ref_out && repo && name); - - reference = git__malloc(sizeof(git_reference)); - GITERR_CHECK_ALLOC(reference); - - memset(reference, 0x0, sizeof(git_reference)); - reference->owner = repo; - - reference->name = git__strdup(name); - GITERR_CHECK_ALLOC(reference->name); - - *ref_out = reference; - return 0; -} - -static int reference_read( - git_buf *file_content, - time_t *mtime, - const char *repo_path, - const char *ref_name, - int *updated) -{ - git_buf path = GIT_BUF_INIT; - int result; - - assert(file_content && repo_path && ref_name); - - /* Determine the full path of the file */ - if (git_buf_joinpath(&path, repo_path, ref_name) < 0) - return -1; - - result = git_futils_readbuffer_updated(file_content, path.ptr, mtime, updated); - git_buf_free(&path); - return result; -} - -static int loose_parse_symbolic(git_reference *ref, git_buf *file_content) -{ - const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF); - const char *refname_start; - char *eol; - - refname_start = (const char *)file_content->ptr; - - if (git_buf_len(file_content) < header_len + 1) - goto corrupt; - - /* - * Assume we have already checked for the header - * before calling this function - */ - refname_start += header_len; - - ref->target.symbolic = git__strdup(refname_start); - GITERR_CHECK_ALLOC(ref->target.symbolic); - - /* remove newline at the end of file */ - eol = strchr(ref->target.symbolic, '\n'); - if (eol == NULL) - goto corrupt; - - *eol = '\0'; - if (eol[-1] == '\r') - eol[-1] = '\0'; - - return 0; - -corrupt: - giterr_set(GITERR_REFERENCE, "Corrupted loose reference file"); - return -1; -} - -static int loose_parse_oid(git_oid *oid, git_buf *file_content) -{ - char *buffer; - - buffer = (char *)file_content->ptr; - - /* File format: 40 chars (OID) + newline */ - if (git_buf_len(file_content) < GIT_OID_HEXSZ + 1) - goto corrupt; - - if (git_oid_fromstr(oid, buffer) < 0) - goto corrupt; - - buffer = buffer + GIT_OID_HEXSZ; - if (*buffer == '\r') - buffer++; - - if (*buffer != '\n') - goto corrupt; - - return 0; - -corrupt: - giterr_set(GITERR_REFERENCE, "Corrupted loose reference file"); - return -1; -} - -static git_ref_t loose_guess_rtype(const git_buf *full_path) -{ - git_buf ref_file = GIT_BUF_INIT; - git_ref_t type; - - type = GIT_REF_INVALID; - - if (git_futils_readbuffer(&ref_file, full_path->ptr) == 0) { - if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) - type = GIT_REF_SYMBOLIC; - else - type = GIT_REF_OID; - } - - git_buf_free(&ref_file); - return type; -} - -static int loose_lookup(git_reference *ref) -{ - int result, updated; - git_buf ref_file = GIT_BUF_INIT; - - result = reference_read(&ref_file, &ref->mtime, - ref->owner->path_repository, ref->name, &updated); - - if (result < 0) - return result; - - if (!updated) - return 0; - - if (ref->flags & GIT_REF_SYMBOLIC) { - git__free(ref->target.symbolic); - ref->target.symbolic = NULL; - } - - ref->flags = 0; - - if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) { - ref->flags |= GIT_REF_SYMBOLIC; - result = loose_parse_symbolic(ref, &ref_file); - } else { - ref->flags |= GIT_REF_OID; - result = loose_parse_oid(&ref->target.oid, &ref_file); - } - - git_buf_free(&ref_file); - return result; -} - -static int loose_lookup_to_packfile( - struct packref **ref_out, - git_repository *repo, - const char *name) -{ - git_buf ref_file = GIT_BUF_INIT; - struct packref *ref = NULL; - size_t name_len; - - *ref_out = NULL; - - if (reference_read(&ref_file, NULL, repo->path_repository, name, NULL) < 0) - return -1; - - name_len = strlen(name); - ref = git__malloc(sizeof(struct packref) + name_len + 1); - GITERR_CHECK_ALLOC(ref); - - memcpy(ref->name, name, name_len); - ref->name[name_len] = 0; - - if (loose_parse_oid(&ref->oid, &ref_file) < 0) { - git_buf_free(&ref_file); - git__free(ref); - return -1; - } - - ref->flags = GIT_PACKREF_WAS_LOOSE; - - *ref_out = ref; - git_buf_free(&ref_file); - return 0; -} - -static int loose_write(git_reference *ref) -{ - git_filebuf file = GIT_FILEBUF_INIT; - 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 - * 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_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) { - git_buf_free(&ref_path); - return -1; - } - - git_buf_free(&ref_path); - - if (ref->flags & GIT_REF_OID) { - char oid[GIT_OID_HEXSZ + 1]; - - git_oid_fmt(oid, &ref->target.oid); - oid[GIT_OID_HEXSZ] = '\0'; - - git_filebuf_printf(&file, "%s\n", oid); - - } else if (ref->flags & GIT_REF_SYMBOLIC) { - git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref->target.symbolic); - } else { - assert(0); /* don't let this happen */ - } - - if (p_stat(ref_path.ptr, &st) == 0) - ref->mtime = st.st_mtime; - - return git_filebuf_commit(&file, GIT_REFS_FILE_MODE); -} - -static int packed_parse_peel( - struct packref *tag_ref, - const char **buffer_out, - const char *buffer_end) -{ - const char *buffer = *buffer_out + 1; - - assert(buffer[-1] == '^'); - - /* Ensure it's not the first entry of the file */ - if (tag_ref == NULL) - goto corrupt; - - /* Ensure reference is a tag */ - if (git__prefixcmp(tag_ref->name, GIT_REFS_TAGS_DIR) != 0) - goto corrupt; - - if (buffer + GIT_OID_HEXSZ >= buffer_end) - goto corrupt; - - /* Is this a valid object id? */ - if (git_oid_fromstr(&tag_ref->peel, buffer) < 0) - goto corrupt; - - buffer = buffer + GIT_OID_HEXSZ; - if (*buffer == '\r') - buffer++; - - if (*buffer != '\n') - goto corrupt; - - *buffer_out = buffer + 1; - return 0; - -corrupt: - giterr_set(GITERR_REFERENCE, "The packed references file is corrupted"); - return -1; -} - -static int packed_parse_oid( - struct packref **ref_out, - const char **buffer_out, - const char *buffer_end) -{ - struct packref *ref = NULL; - - const char *buffer = *buffer_out; - const char *refname_begin, *refname_end; - - size_t refname_len; - git_oid id; - - refname_begin = (buffer + GIT_OID_HEXSZ + 1); - if (refname_begin >= buffer_end || refname_begin[-1] != ' ') - goto corrupt; - - /* Is this a valid object id? */ - if (git_oid_fromstr(&id, buffer) < 0) - goto corrupt; - - refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin); - if (refname_end == NULL) - goto corrupt; - - if (refname_end[-1] == '\r') - refname_end--; - - refname_len = refname_end - refname_begin; - - ref = git__malloc(sizeof(struct packref) + refname_len + 1); - GITERR_CHECK_ALLOC(ref); - - memcpy(ref->name, refname_begin, refname_len); - ref->name[refname_len] = 0; - - git_oid_cpy(&ref->oid, &id); - - ref->flags = 0; - - *ref_out = ref; - *buffer_out = refname_end + 1; - - return 0; - -corrupt: - git__free(ref); - giterr_set(GITERR_REFERENCE, "The packed references file is corrupted"); - return -1; -} - -static int packed_load(git_repository *repo) -{ - int result, updated; - git_buf packfile = GIT_BUF_INIT; - const char *buffer_start, *buffer_end; - git_refcache *ref_cache = &repo->references; - - /* First we make sure we have allocated the hash table */ - if (ref_cache->packfile == NULL) { - ref_cache->packfile = git_strmap_alloc(); - GITERR_CHECK_ALLOC(ref_cache->packfile); - } - - result = reference_read(&packfile, &ref_cache->packfile_time, - repo->path_repository, GIT_PACKEDREFS_FILE, &updated); - - /* - * If we couldn't find the file, we need to clear the table and - * return. On any other error, we return that error. If everything - * went fine and the file wasn't updated, then there's nothing new - * for us here, so just return. Anything else means we need to - * refresh the packed refs. - */ - if (result == GIT_ENOTFOUND) { - git_strmap_clear(ref_cache->packfile); - return 0; - } - - if (result < 0) - return -1; - - if (!updated) - return 0; - - /* - * At this point, we want to refresh the packed refs. We already - * have the contents in our buffer. - */ - git_strmap_clear(ref_cache->packfile); - - buffer_start = (const char *)packfile.ptr; - buffer_end = (const char *)(buffer_start) + packfile.size; - while (buffer_start < buffer_end && buffer_start[0] == '#') { - buffer_start = strchr(buffer_start, '\n'); - if (buffer_start == NULL) - goto parse_failed; - - buffer_start++; - } - - while (buffer_start < buffer_end) { - int err; - struct packref *ref = NULL; - - if (packed_parse_oid(&ref, &buffer_start, buffer_end) < 0) - goto parse_failed; - - if (buffer_start[0] == '^') { - if (packed_parse_peel(ref, &buffer_start, buffer_end) < 0) - goto parse_failed; - } - - git_strmap_insert(ref_cache->packfile, ref->name, ref, err); - if (err < 0) - goto parse_failed; - } - - git_buf_free(&packfile); - return 0; - -parse_failed: - git_strmap_free(ref_cache->packfile); - ref_cache->packfile = NULL; - git_buf_free(&packfile); - return -1; -} - - -struct dirent_list_data { - git_repository *repo; - size_t repo_path_len; - unsigned int list_flags; - - int (*callback)(const char *, void *); - void *callback_payload; -}; - -static int _dirent_loose_listall(void *_data, git_buf *full_path) -{ - struct dirent_list_data *data = (struct dirent_list_data *)_data; - const char *file_path = full_path->ptr + data->repo_path_len; - - if (git_path_isdir(full_path->ptr) == true) - return git_path_direach(full_path, _dirent_loose_listall, _data); - - /* do not add twice a reference that exists already in the packfile */ - if ((data->list_flags & GIT_REF_PACKED) != 0 && - git_strmap_exists(data->repo->references.packfile, file_path)) - return 0; - - if (data->list_flags != GIT_REF_LISTALL) { - if ((data->list_flags & loose_guess_rtype(full_path)) == 0) - return 0; /* we are filtering out this reference */ - } - - return data->callback(file_path, data->callback_payload); -} - -static int _dirent_loose_load(void *data, git_buf *full_path) -{ - git_repository *repository = (git_repository *)data; - void *old_ref = NULL; - struct packref *ref; - const char *file_path; - int err; - - if (git_path_isdir(full_path->ptr) == true) - return git_path_direach(full_path, _dirent_loose_load, repository); - - file_path = full_path->ptr + strlen(repository->path_repository); - - if (loose_lookup_to_packfile(&ref, repository, file_path) < 0) - return -1; - - git_strmap_insert2( - repository->references.packfile, ref->name, ref, old_ref, err); - if (err < 0) { - git__free(ref); - return -1; - } - - git__free(old_ref); - return 0; -} - -/* - * Load all the loose references from the repository - * into the in-memory Packfile, and build a vector with - * all the references so it can be written back to - * disk. - */ -static int packed_loadloose(git_repository *repository) +git_reference *git_reference__alloc( + git_refdb *refdb, + const char *name, + const git_oid *oid, + const char *symbolic) { - git_buf refs_path = GIT_BUF_INIT; - int result; - - /* the packfile must have been previously loaded! */ - assert(repository->references.packfile); - - if (git_buf_joinpath(&refs_path, repository->path_repository, GIT_REFS_DIR) < 0) - return -1; - - /* - * Load all the loose files from disk into the Packfile table. - * This will overwrite any old packed entries with their - * updated loose versions - */ - result = git_path_direach(&refs_path, _dirent_loose_load, repository); - git_buf_free(&refs_path); - - return result; -} + git_reference *ref; + size_t namelen; -/* - * Write a single reference into a packfile - */ -static int packed_write_ref(struct packref *ref, git_filebuf *file) -{ - char oid[GIT_OID_HEXSZ + 1]; + assert(refdb && name && ((oid && !symbolic) || (!oid && symbolic))); - git_oid_fmt(oid, &ref->oid); - oid[GIT_OID_HEXSZ] = 0; + namelen = strlen(name); - /* - * For references that peel to an object in the repo, we must - * write the resulting peel on a separate line, e.g. - * - * 6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4 - * ^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100 - * - * This obviously only applies to tags. - * The required peels have already been loaded into `ref->peel_target`. - */ - if (ref->flags & GIT_PACKREF_HAS_PEEL) { - char peel[GIT_OID_HEXSZ + 1]; - git_oid_fmt(peel, &ref->peel); - peel[GIT_OID_HEXSZ] = 0; + if ((ref = git__calloc(1, sizeof(git_reference) + namelen + 1)) == NULL) + return NULL; - if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0) - return -1; + if (oid) { + ref->type = GIT_REF_OID; + git_oid_cpy(&ref->target.oid, oid); } else { - if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0) - return -1; - } - - return 0; -} - -/* - * Find out what object this reference resolves to. - * - * For references that point to a 'big' tag (e.g. an - * actual tag object on the repository), we need to - * cache on the packfile the OID of the object to - * which that 'big tag' is pointing to. - */ -static int packed_find_peel(git_repository *repo, struct packref *ref) -{ - git_object *object; - - if (ref->flags & GIT_PACKREF_HAS_PEEL) - return 0; - - /* - * Only applies to tags, i.e. references - * in the /refs/tags folder - */ - if (git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) != 0) - return 0; - - /* - * Find the tagged object in the repository - */ - if (git_object_lookup(&object, repo, &ref->oid, GIT_OBJ_ANY) < 0) - return -1; - - /* - * If the tagged object is a Tag object, we need to resolve it; - * if the ref is actually a 'weak' ref, we don't need to resolve - * anything. - */ - if (git_object_type(object) == GIT_OBJ_TAG) { - git_tag *tag = (git_tag *)object; - - /* - * Find the object pointed at by this tag - */ - git_oid_cpy(&ref->peel, git_tag_target_oid(tag)); - ref->flags |= GIT_PACKREF_HAS_PEEL; - - /* - * The reference has now cached the resolved OID, and is - * marked at such. When written to the packfile, it'll be - * accompanied by this resolved oid - */ - } - - git_object_free(object); - return 0; -} - -/* - * Remove all loose references - * - * Once we have successfully written a packfile, - * all the loose references that were packed must be - * removed from disk. - * - * This is a dangerous method; make sure the packfile - * is well-written, because we are destructing references - * here otherwise. - */ -static int packed_remove_loose(git_repository *repo, git_vector *packing_list) -{ - unsigned int i; - git_buf full_path = GIT_BUF_INIT; - int failed = 0; - - for (i = 0; i < packing_list->length; ++i) { - struct packref *ref = git_vector_get(packing_list, i); - - if ((ref->flags & GIT_PACKREF_WAS_LOOSE) == 0) - continue; - - if (git_buf_joinpath(&full_path, repo->path_repository, ref->name) < 0) - return -1; /* critical; do not try to recover on oom */ - - if (git_path_exists(full_path.ptr) == true && p_unlink(full_path.ptr) < 0) { - if (failed) - continue; + ref->type = GIT_REF_SYMBOLIC; - giterr_set(GITERR_REFERENCE, - "Failed to remove loose reference '%s' after packing: %s", - full_path.ptr, strerror(errno)); - - failed = 1; + if ((ref->target.symbolic = git__strdup(symbolic)) == NULL) { + git__free(ref); + return NULL; } - - /* - * if we fail to remove a single file, this is *not* good, - * but we should keep going and remove as many as possible. - * After we've removed as many files as possible, we return - * the error code anyway. - */ } - git_buf_free(&full_path); - return failed ? -1 : 0; -} - -static int packed_sort(const void *a, const void *b) -{ - const struct packref *ref_a = (const struct packref *)a; - const struct packref *ref_b = (const struct packref *)b; + ref->db = refdb; + memcpy(ref->name, name, namelen + 1); - return strcmp(ref_a->name, ref_b->name); + return ref; } -/* - * Write all the contents in the in-memory packfile to disk. - */ -static int packed_write(git_repository *repo) +void git_reference_free(git_reference *reference) { - git_filebuf pack_file = GIT_FILEBUF_INIT; - unsigned int i; - git_buf pack_file_path = GIT_BUF_INIT; - git_vector packing_list; - unsigned int total_refs; - - assert(repo && repo->references.packfile); - - total_refs = - (unsigned int)git_strmap_num_entries(repo->references.packfile); - - if (git_vector_init(&packing_list, total_refs, packed_sort) < 0) - return -1; - - /* Load all the packfile into a vector */ - { - struct packref *reference; - - /* cannot fail: vector already has the right size */ - git_strmap_foreach_value(repo->references.packfile, reference, { - git_vector_insert(&packing_list, reference); - }); - } - - /* sort the vector so the entries appear sorted on the packfile */ - git_vector_sort(&packing_list); - - /* Now we can open the file! */ - if (git_buf_joinpath(&pack_file_path, repo->path_repository, GIT_PACKEDREFS_FILE) < 0) - goto cleanup_memory; - - if (git_filebuf_open(&pack_file, pack_file_path.ptr, 0) < 0) - goto cleanup_packfile; - - /* Packfiles have a header... apparently - * This is in fact not required, but we might as well print it - * just for kicks */ - if (git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER) < 0) - goto cleanup_packfile; - - for (i = 0; i < packing_list.length; ++i) { - struct packref *ref = (struct packref *)git_vector_get(&packing_list, i); - - if (packed_find_peel(repo, ref) < 0) - goto cleanup_packfile; + if (reference == NULL) + return; - if (packed_write_ref(ref, &pack_file) < 0) - goto cleanup_packfile; + if (reference->type == GIT_REF_SYMBOLIC) { + git__free(reference->target.symbolic); + reference->target.symbolic = NULL; } - /* if we've written all the references properly, we can commit - * the packfile to make the changes effective */ - if (git_filebuf_commit(&pack_file, GIT_PACKEDREFS_FILE_MODE) < 0) - goto cleanup_memory; - - /* when and only when the packfile has been properly written, - * we can go ahead and remove the loose refs */ - if (packed_remove_loose(repo, &packing_list) < 0) - goto cleanup_memory; - - { - struct stat st; - if (p_stat(pack_file_path.ptr, &st) == 0) - repo->references.packfile_time = st.st_mtime; - } - - git_vector_free(&packing_list); - git_buf_free(&pack_file_path); - - /* we're good now */ - return 0; - -cleanup_packfile: - git_filebuf_cleanup(&pack_file); + reference->db = NULL; + reference->type = GIT_REF_INVALID; -cleanup_memory: - git_vector_free(&packing_list); - git_buf_free(&pack_file_path); - - return -1; + git__free(reference); } struct reference_available_t { @@ -843,15 +115,17 @@ static int reference_path_available( const char *ref, const char* old_ref) { + int error; struct reference_available_t data; data.new_ref = ref; data.old_ref = old_ref; data.available = 1; - if (git_reference_foreach(repo, GIT_REF_LISTALL, - _reference_available_cb, (void *)&data) < 0) - return -1; + error = git_reference_foreach( + repo, GIT_REF_LISTALL, _reference_available_cb, (void *)&data); + if (error < 0) + return error; if (!data.available) { giterr_set(GITERR_REFERENCE, @@ -862,28 +136,6 @@ static int reference_path_available( return 0; } -static int reference_exists(int *exists, git_repository *repo, const char *ref_name) -{ - git_buf ref_path = GIT_BUF_INIT; - - if (packed_load(repo) < 0) - return -1; - - if (git_buf_joinpath(&ref_path, repo->path_repository, ref_name) < 0) - return -1; - - if (git_path_isfile(ref_path.ptr) == true || - git_strmap_exists(repo->references.packfile, ref_path.ptr)) - { - *exists = 1; - } else { - *exists = 0; - } - - git_buf_free(&ref_path); - return 0; -} - /* * Check if a reference could be written to disk, based on: * @@ -899,6 +151,11 @@ static int reference_can_write( const char *previous_name, int force) { + git_refdb *refdb; + + if (git_repository_refdb__weakptr(&refdb, repo) < 0) + return -1; + /* see if the reference shares a path with an existing reference; * if a path is shared, we cannot create the reference, even when forcing */ if (reference_path_available(repo, refname, previous_name) < 0) @@ -909,7 +166,7 @@ static int reference_can_write( if (!force) { int exists; - if (reference_exists(&exists, repo, refname) < 0) + if (git_refdb_exists(&exists, refdb, refname) < 0) return -1; /* We cannot proceed if the reference already exists and we're not forcing @@ -936,139 +193,9 @@ static int reference_can_write( return 0; } - -static int packed_lookup(git_reference *ref) -{ - struct packref *pack_ref = NULL; - git_strmap *packfile_refs; - khiter_t pos; - - if (packed_load(ref->owner) < 0) - return -1; - - /* maybe the packfile hasn't changed at all, so we don't - * have to re-lookup the reference */ - if ((ref->flags & GIT_REF_PACKED) && - ref->mtime == ref->owner->references.packfile_time) - return 0; - - if (ref->flags & GIT_REF_SYMBOLIC) { - git__free(ref->target.symbolic); - ref->target.symbolic = NULL; - } - - /* Look up on the packfile */ - packfile_refs = ref->owner->references.packfile; - pos = git_strmap_lookup_index(packfile_refs, ref->name); - if (!git_strmap_valid_index(packfile_refs, pos)) { - giterr_set(GITERR_REFERENCE, "Reference '%s' not found", ref->name); - return GIT_ENOTFOUND; - } - - pack_ref = git_strmap_value_at(packfile_refs, pos); - - ref->flags = GIT_REF_OID | GIT_REF_PACKED; - ref->mtime = ref->owner->references.packfile_time; - git_oid_cpy(&ref->target.oid, &pack_ref->oid); - - return 0; -} - -static int reference_lookup(git_reference *ref) -{ - int result; - - result = loose_lookup(ref); - if (result == 0) - return 0; - - /* only try to lookup this reference on the packfile if it - * wasn't found on the loose refs; not if there was a critical error */ - if (result == GIT_ENOTFOUND) { - giterr_clear(); - result = packed_lookup(ref); - if (result == 0) - return 0; - } - - /* unexpected error; free the reference */ - git_reference_free(ref); - return result; -} - -/* - * Delete a reference. - * This is an internal method; the reference is removed - * from disk or the packfile, but the pointer is not freed - */ -static int reference_delete(git_reference *ref) -{ - int result; - - assert(ref); - - /* If the reference is packed, this is an expensive operation. - * We need to reload the packfile, remove the reference from the - * packing list, and repack */ - if (ref->flags & GIT_REF_PACKED) { - git_strmap *packfile_refs; - struct packref *packref; - khiter_t pos; - - /* load the existing packfile */ - if (packed_load(ref->owner) < 0) - return -1; - - packfile_refs = ref->owner->references.packfile; - pos = git_strmap_lookup_index(packfile_refs, ref->name); - if (!git_strmap_valid_index(packfile_refs, pos)) { - giterr_set(GITERR_REFERENCE, - "Reference %s stopped existing in the packfile", ref->name); - return -1; - } - - packref = git_strmap_value_at(packfile_refs, pos); - git_strmap_delete_at(packfile_refs, pos); - - git__free(packref); - if (packed_write(ref->owner) < 0) - return -1; - - /* If the reference is loose, we can just remove the reference - * from the filesystem */ - } else { - git_reference *ref_in_pack; - git_buf full_path = GIT_BUF_INIT; - - if (git_buf_joinpath(&full_path, ref->owner->path_repository, ref->name) < 0) - return -1; - - result = p_unlink(full_path.ptr); - git_buf_free(&full_path); /* done with path at this point */ - - if (result < 0) { - giterr_set(GITERR_OS, "Failed to unlink '%s'", full_path.ptr); - return -1; - } - - /* When deleting a loose reference, we have to ensure that an older - * packed version of it doesn't exist */ - if (git_reference_lookup(&ref_in_pack, ref->owner, ref->name) == 0) { - assert((ref_in_pack->flags & GIT_REF_PACKED) != 0); - return git_reference_delete(ref_in_pack); - } - - giterr_clear(); - } - - return 0; -} - int git_reference_delete(git_reference *ref) { - int result = reference_delete(ref); - git_reference_free(ref); - return result; + return git_refdb_delete(ref->db, ref); } int git_reference_lookup(git_reference **ref_out, @@ -1077,7 +204,7 @@ int git_reference_lookup(git_reference **ref_out, 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; @@ -1086,7 +213,7 @@ int git_reference_name_to_oid( 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; } @@ -1097,8 +224,11 @@ int git_reference_lookup_resolved( const char *name, int max_nesting) { - git_reference *scan; - int result, nesting; + char scan_name[GIT_REFNAME_MAX]; + git_ref_t scan_type; + int error = 0, nesting; + git_reference *ref = NULL; + git_refdb *refdb; assert(ref_out && repo && name); @@ -1108,409 +238,294 @@ int git_reference_lookup_resolved( max_nesting = MAX_NESTING_LEVEL; else if (max_nesting < 0) max_nesting = DEFAULT_NESTING_LEVEL; + + strncpy(scan_name, name, GIT_REFNAME_MAX); + scan_type = GIT_REF_SYMBOLIC; + + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) + return -1; - scan = git__calloc(1, sizeof(git_reference)); - GITERR_CHECK_ALLOC(scan); - - scan->name = git__calloc(GIT_REFNAME_MAX + 1, sizeof(char)); - GITERR_CHECK_ALLOC(scan->name); - - if ((result = normalize_name(scan->name, GIT_REFNAME_MAX, name, 0)) < 0) { - git_reference_free(scan); - return result; - } - - scan->target.symbolic = git__strdup(scan->name); - GITERR_CHECK_ALLOC(scan->target.symbolic); - - scan->owner = repo; - scan->flags = GIT_REF_SYMBOLIC; + if ((error = git_reference__normalize_name_lax(scan_name, GIT_REFNAME_MAX, name)) < 0) + return error; for (nesting = max_nesting; - nesting >= 0 && (scan->flags & GIT_REF_SYMBOLIC) != 0; + nesting >= 0 && scan_type == GIT_REF_SYMBOLIC; nesting--) { - if (nesting != max_nesting) - strncpy(scan->name, scan->target.symbolic, GIT_REFNAME_MAX); - - scan->mtime = 0; + if (nesting != max_nesting) { + strncpy(scan_name, ref->target.symbolic, GIT_REFNAME_MAX); + git_reference_free(ref); + } - if ((result = reference_lookup(scan)) < 0) - return result; /* lookup git_reference_free on scan already */ + if ((error = git_refdb_lookup(&ref, refdb, scan_name)) < 0) + return error; + + scan_type = ref->type; } - if ((scan->flags & GIT_REF_OID) == 0 && max_nesting != 0) { + if (scan_type != GIT_REF_OID && max_nesting != 0) { giterr_set(GITERR_REFERENCE, "Cannot resolve reference (>%u levels deep)", max_nesting); - git_reference_free(scan); + git_reference_free(ref); return -1; } - *ref_out = scan; + *ref_out = ref; return 0; } /** * Getters */ -git_ref_t git_reference_type(git_reference *ref) -{ - assert(ref); - - if (ref->flags & GIT_REF_OID) - return GIT_REF_OID; - - if (ref->flags & GIT_REF_SYMBOLIC) - return GIT_REF_SYMBOLIC; - - return GIT_REF_INVALID; -} - -int git_reference_is_packed(git_reference *ref) +git_ref_t git_reference_type(const git_reference *ref) { assert(ref); - return !!(ref->flags & GIT_REF_PACKED); + return ref->type; } -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; + return ref->db->repo; } -const git_oid *git_reference_oid(git_reference *ref) +const git_oid *git_reference_target(const git_reference *ref) { assert(ref); - if ((ref->flags & GIT_REF_OID) == 0) + if (ref->type != GIT_REF_OID) return NULL; return &ref->target.oid; } -const char *git_reference_target(git_reference *ref) +const char *git_reference_symbolic_target(const git_reference *ref) { assert(ref); - if ((ref->flags & GIT_REF_SYMBOLIC) == 0) + if (ref->type != GIT_REF_SYMBOLIC) return NULL; return ref->target.symbolic; } -int git_reference_create_symbolic( +static int reference__create( git_reference **ref_out, git_repository *repo, const char *name, - const char *target, + const git_oid *oid, + const char *symbolic, int force) { char normalized[GIT_REFNAME_MAX]; + git_refdb *refdb; git_reference *ref = NULL; - - if (normalize_name(normalized, sizeof(normalized), name, 0) < 0) - return -1; - - if (reference_can_write(repo, normalized, NULL, force) < 0) - return -1; - - if (reference_alloc(&ref, repo, normalized) < 0) + int error = 0; + + if (ref_out) + *ref_out = NULL; + + if ((error = git_reference__normalize_name_lax(normalized, sizeof(normalized), name)) < 0 || + (error = reference_can_write(repo, normalized, NULL, force)) < 0 || + (error = git_repository_refdb__weakptr(&refdb, repo)) < 0) + return error; + + if ((ref = git_reference__alloc(refdb, name, oid, symbolic)) == NULL) return -1; - ref->flags |= GIT_REF_SYMBOLIC; - - /* set the target; this will normalize the name automatically - * and write the reference on disk */ - if (git_reference_set_target(ref, target) < 0) { + if ((error = git_refdb_write(refdb, ref)) < 0) { git_reference_free(ref); - return -1; + return error; } - if (ref_out == NULL) { + + if (ref_out == NULL) git_reference_free(ref); - } else { + else *ref_out = ref; - } 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, + const git_oid *oid, int force) { - git_reference *ref = NULL; - char normalized[GIT_REFNAME_MAX]; + git_odb *odb; + int error = 0; - if (normalize_name(normalized, sizeof(normalized), name, 1) < 0) - return -1; - - if (reference_can_write(repo, normalized, NULL, force) < 0) - return -1; - - 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) { - git_reference_free(ref); + assert(repo && name && oid); + + /* Sanity check the reference being created - target must exist. */ + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) + return error; + + if (!git_odb_exists(odb, oid)) { + giterr_set(GITERR_REFERENCE, + "Target OID for the reference doesn't exist on the repository"); return -1; } - - if (ref_out == NULL) { - git_reference_free(ref); - } else { - *ref_out = ref; - } - - return 0; + + return reference__create(ref_out, repo, name, oid, NULL, force); } -/* - * Change the OID target of a reference. - * - * For both loose and packed references, just change - * the oid in memory and (over)write the file in disk. - * - * We do not repack packed references because of performance - * reasons. - */ -int git_reference_set_oid(git_reference *ref, const git_oid *id) -{ - git_odb *odb = NULL; - if ((ref->flags & GIT_REF_OID) == 0) { - giterr_set(GITERR_REFERENCE, "Cannot set OID on symbolic reference"); - return -1; - } +int git_reference_symbolic_create( + git_reference **ref_out, + git_repository *repo, + const char *name, + const char *target, + int force) +{ + char normalized[GIT_REFNAME_MAX]; + int error = 0; - assert(ref->owner); + assert(repo && name && target); + + if ((error = git_reference__normalize_name_lax( + normalized, sizeof(normalized), target)) < 0) + return error; - if (git_repository_odb__weakptr(&odb, ref->owner) < 0) - return -1; + return reference__create(ref_out, repo, name, NULL, normalized, force); +} - /* Don't let the user create references to OIDs that - * don't exist in the ODB */ - if (!git_odb_exists(odb, id)) { - giterr_set(GITERR_REFERENCE, - "Target OID for the reference doesn't exist on the repository"); +int git_reference_set_target( + git_reference **out, + git_reference *ref, + const git_oid *id) +{ + assert(out && ref && id); + + if (ref->type != GIT_REF_OID) { + giterr_set(GITERR_REFERENCE, "Cannot set OID on symbolic reference"); return -1; } - /* Update the OID value on `ref` */ - git_oid_cpy(&ref->target.oid, id); - - /* Write back to disk */ - return loose_write(ref); + return git_reference_create(out, ref->db->repo, ref->name, id, 1); } -/* - * Change the target of a symbolic reference. - * - * This is easy because symrefs cannot be inside - * 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 **out, + git_reference *ref, + const char *target) { - char normalized[GIT_REFNAME_MAX]; - - if ((ref->flags & GIT_REF_SYMBOLIC) == 0) { + assert(out && ref && target); + + if (ref->type != GIT_REF_SYMBOLIC) { giterr_set(GITERR_REFERENCE, "Cannot set symbolic target on a direct reference"); return -1; } - - if (normalize_name(normalized, sizeof(normalized), target, 0)) - return -1; - - git__free(ref->target.symbolic); - ref->target.symbolic = git__strdup(normalized); - GITERR_CHECK_ALLOC(ref->target.symbolic); - - return loose_write(ref); + + return git_reference_symbolic_create(out, ref->db->repo, ref->name, target, 1); } -int git_reference_rename(git_reference *ref, const char *new_name, int force) +int git_reference_rename( + git_reference **out, + git_reference *ref, + const char *new_name, + int force) { - int result; - git_buf aux_path = GIT_BUF_INIT; + unsigned int normalization_flags; char normalized[GIT_REFNAME_MAX]; - - const char *head_target = NULL; - git_reference *head = NULL; - - if (normalize_name(normalized, sizeof(normalized), - new_name, ref->flags & GIT_REF_OID) < 0) - return -1; - - if (reference_can_write(ref->owner, normalized, ref->name, force) < 0) - return -1; - - /* 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; - - /* - * Now delete the old ref and remove an possibly existing directory - * named `new_name`. Note that using the internal `reference_delete` - * 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 - */ - if (reference_delete(ref) < 0) - goto cleanup; + bool should_head_be_updated = false; + git_reference *result = NULL; + git_oid *oid; + const char *symbolic; + int error = 0; + int reference_has_log; + + *out = NULL; + + normalization_flags = ref->type == GIT_REF_SYMBOLIC ? + GIT_REF_FORMAT_ALLOW_ONELEVEL : GIT_REF_FORMAT_NORMAL; + + if ((error = git_reference_normalize_name(normalized, sizeof(normalized), new_name, normalization_flags)) < 0 || + (error = reference_can_write(ref->db->repo, normalized, ref->name, force)) < 0) + return error; /* - * Finally we can create the new reference. + * Create the new reference. */ - if (ref->flags & GIT_REF_SYMBOLIC) { - result = git_reference_create_symbolic( - NULL, ref->owner, new_name, ref->target.symbolic, force); + if (ref->type == GIT_REF_OID) { + oid = &ref->target.oid; + symbolic = NULL; } else { - result = git_reference_create_oid( - NULL, ref->owner, new_name, &ref->target.oid, force); + oid = NULL; + symbolic = ref->target.symbolic; } + + if ((result = git_reference__alloc(ref->db, new_name, oid, symbolic)) == NULL) + return -1; - if (result < 0) - goto rollback; - - /* - * Check if we have to update HEAD. - */ - if (git_reference_lookup(&head, ref->owner, GIT_HEAD_FILE) < 0) { - giterr_set(GITERR_REFERENCE, - "Failed to update HEAD after renaming reference"); - goto cleanup; - } + /* Check if we have to update HEAD. */ + if ((error = git_branch_is_head(ref)) < 0) + goto on_error; - head_target = git_reference_target(head); + should_head_be_updated = (error > 0); - if (head_target && !strcmp(head_target, ref->name)) { - if (git_reference_create_symbolic(&head, ref->owner, "HEAD", new_name, 1) < 0) { - giterr_set(GITERR_REFERENCE, - "Failed to update HEAD after renaming reference"); - goto cleanup; - } + /* Now delete the old ref and save the new one. */ + if ((error = git_refdb_delete(ref->db, ref)) < 0) + goto on_error; + + /* Save the new reference. */ + if ((error = git_refdb_write(ref->db, result)) < 0) + goto rollback; + + /* Update HEAD it was poiting to the reference being renamed. */ + if (should_head_be_updated && (error = git_repository_set_head(ref->db->repo, new_name)) < 0) { + giterr_set(GITERR_REFERENCE, "Failed to update HEAD after renaming reference"); + goto on_error; } - /* - * Rename the reflog file. - */ - if (git_buf_join_n(&aux_path, '/', 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name) < 0) - goto cleanup; - - if (git_path_exists(aux_path.ptr) == true) { - if (git_reflog_rename(ref, new_name) < 0) - goto cleanup; - } else { - giterr_clear(); + /* Rename the reflog file, if it exists. */ + reference_has_log = git_reference_has_log(ref); + if (reference_has_log < 0) { + error = reference_has_log; + goto on_error; } + if (reference_has_log && (error = git_reflog_rename(ref, new_name)) < 0) + goto on_error; - /* - * Change the name of the reference given by the user. - */ - git__free(ref->name); - ref->name = git__strdup(new_name); + *out = result; - /* 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; + return error; rollback: - /* - * Try to create the old reference again, ignore failures - */ - if (ref->flags & GIT_REF_SYMBOLIC) - git_reference_create_symbolic( - NULL, ref->owner, ref->name, ref->target.symbolic, 0); - else - git_reference_create_oid( - NULL, ref->owner, ref->name, &ref->target.oid, 0); + git_refdb_write(ref->db, ref); - /* The reference is no longer packed */ - ref->flags &= ~GIT_REF_PACKED; +on_error: + git_reference_free(result); - git_buf_free(&aux_path); - return -1; + return error; } -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); + if (ref->type == GIT_REF_OID) + return git_reference_lookup(ref_out, ref->db->repo, ref->name); else - return git_reference_lookup_resolved(ref_out, ref->owner, ref->target.symbolic, -1); -} - -int git_reference_packall(git_repository *repo) -{ - if (packed_load(repo) < 0 || /* load the existing packfile */ - packed_loadloose(repo) < 0 || /* add all the loose refs */ - packed_write(repo) < 0) /* write back to disk */ - return -1; - - return 0; + return git_reference_lookup_resolved(ref_out, ref->db->repo, + ref->target.symbolic, -1); } 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; - struct dirent_list_data data; - git_buf refs_path = GIT_BUF_INIT; - - /* list all the packed references first */ - if (list_flags & GIT_REF_PACKED) { - const char *ref_name; - void *ref; - GIT_UNUSED(ref); - - if (packed_load(repo) < 0) - return -1; - - git_strmap_foreach(repo->references.packfile, ref_name, ref, { - if (callback(ref_name, payload) < 0) - return 0; - }); - } - - /* now list the loose references, trying not to - * duplicate the ref names already in the packed-refs file */ - - data.repo_path_len = strlen(repo->path_repository); - data.list_flags = list_flags; - data.repo = repo; - data.callback = callback; - data.callback_payload = payload; - - if (git_buf_joinpath(&refs_path, repo->path_repository, GIT_REFS_DIR) < 0) - return -1; - - result = git_path_direach(&refs_path, _dirent_loose_listall, &data); - git_buf_free(&refs_path); + git_refdb *refdb; + git_repository_refdb__weakptr(&refdb, repo); - return result; + return git_refdb_foreach(refdb, list_flags, callback, payload); } static int cb__reflist_add(const char *ref, void *data) @@ -1544,26 +559,6 @@ int git_reference_list( return 0; } -int git_reference_reload(git_reference *ref) -{ - return reference_lookup(ref); -} - -void git_repository__refcache_free(git_refcache *refs) -{ - assert(refs); - - if (refs->packfile) { - struct packref *reference; - - git_strmap_foreach_value(refs->packfile, reference, { - git__free(reference); - }); - - git_strmap_free(refs->packfile); - } -} - static int is_valid_ref_char(char ch) { if ((unsigned) ch <= ' ') @@ -1583,112 +578,202 @@ static int is_valid_ref_char(char ch) } } -static int normalize_name( - char *buffer_out, - size_t out_size, - const char *name, - int is_oid_ref) +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'; + const int lock_len = (int)strlen(GIT_FILELOCK_EXTENSION); + int segment_len; - assert(name && buffer_out); + if (*current == '.') + return -1; /* Refname starts with "." */ - buffer_out_start = buffer_out; - current = name; - name_end = name + strlen(name); + for (current = name; ; current++) { + if (*current == '\0' || *current == '/') + break; - /* Terminating null byte */ - out_size--; + if (!is_valid_ref_char(*current)) + return -1; /* Illegal character in refname */ - /* 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; + if (prev == '@' && *current == '{') + return -1; /* Refname contains "@{" */ - while (current < name_end && out_size) { - if (!is_valid_ref_char(*current)) - goto invalid_name; + prev = *current; + } + + segment_len = (int)(current - name); + + /* A refname component can not end with ".lock" */ + if (segment_len >= lock_len && + !memcmp(current - lock_len, GIT_FILELOCK_EXTENSION, lock_len)) + return -1; + + return segment_len; +} + +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); + + for (i = 0; i < len; i++) + { + c = name[i]; + if ((c < 'A' || c > 'Z') && c != '_') + return false; + } - /* A refname can not start with a dot nor contain a double dot */ - if (*current == '.' && ((prev == '.') || (prev == '/'))) - goto invalid_name; + if (*name == '_' || name[len - 1] == '_') + return false; - /* '@{' is forbidden within a refname */ - if (*current == '{' && prev == '@') - goto invalid_name; + 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 - /* Prevent multiple slashes from being added to the output */ - if (*current == '/' && prev == '/') { - current++; - continue; + 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 (*current == '/') + goto cleanup; + + 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 (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++; } - if (*current == '/') - contains_a_slash = 1; + /* No empty segment is allowed when not normalizing */ + if (segment_len == 0 && !normalize) + goto cleanup; + + if (current[segment_len] == '\0') + break; - *buffer_out++ = *current++; - out_size--; + current += segment_len + 1; } - if (!out_size) - goto invalid_name; + /* A refname can not be empty */ + if (segment_len == 0 && segments_count == 0) + goto cleanup; - /* 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 (is_oid_ref && - !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 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; + /* A refname can not end with "/" */ + if (current[segment_len - 1] == '/') + goto cleanup; - *buffer_out = '\0'; + if ((segments_count == 1 ) && !(flags & GIT_REF_FORMAT_ALLOW_ONELEVEL)) + goto cleanup; - /* - * For object id references, name has to start with refs/. Again, - * we need to allow HEAD to be in a detached state. - */ - if (is_oid_ref && !(git__prefixcmp(buffer_out_start, GIT_REFS_DIR) || - strcmp(buffer_out_start, GIT_HEAD_FILE))) - goto invalid_name; + if ((segments_count == 1 ) && + !(is_all_caps_and_underscore(name, (size_t)segment_len) || + ((flags & GIT_REF_FORMAT_REFSPEC_PATTERN) && !strcmp("*", name)))) + goto cleanup; - return 0; + if ((segments_count > 1) + && (is_all_caps_and_underscore(name, strchr(name, '/') - name))) + goto cleanup; + + error = 0; -invalid_name: - giterr_set(GITERR_REFERENCE, "The given reference name is not valid"); - return -1; +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 normalize_name(buffer_out, out_size, name, 0); + 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) { - return normalize_name(buffer_out, out_size, name, 1); + return git_reference_normalize_name( + buffer_out, + out_size, + name, + 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) @@ -1696,12 +781,185 @@ int git_reference_cmp(git_reference *ref1, git_reference *ref2) assert(ref1 && ref2); /* let's put symbolic refs before OIDs */ - if ((ref1->flags & GIT_REF_TYPEMASK) != (ref2->flags & GIT_REF_TYPEMASK)) - return (ref1->flags & GIT_REF_SYMBOLIC) ? -1 : 1; + if (ref1->type != ref2->type) + return (ref1->type == GIT_REF_SYMBOLIC) ? -1 : 1; - if (ref1->flags & GIT_REF_SYMBOLIC) + if (ref1->type == GIT_REF_SYMBOLIC) return strcmp(ref1->target.symbolic, ref2->target.symbolic); return git_oid_cmp(&ref1->target.oid, &ref2->target.oid); } +static int reference__update_terminal( + git_repository *repo, + const char *ref_name, + const git_oid *oid, + int nesting) +{ + git_reference *ref; + int error = 0; + + if (nesting > MAX_NESTING_LEVEL) + return GIT_ENOTFOUND; + + error = git_reference_lookup(&ref, repo, ref_name); + + /* If we haven't found the reference at all, create a new reference. */ + if (error == GIT_ENOTFOUND) { + giterr_clear(); + return git_reference_create(NULL, repo, ref_name, oid, 0); + } + + if (error < 0) + return error; + + /* If the ref is a symbolic reference, follow its target. */ + if (git_reference_type(ref) == GIT_REF_SYMBOLIC) { + error = reference__update_terminal(repo, git_reference_symbolic_target(ref), oid, + nesting+1); + git_reference_free(ref); + } else { + git_reference_free(ref); + error = git_reference_create(NULL, repo, ref_name, oid, 1); + } + + return error; +} + +/* + * Starting with the reference given by `ref_name`, follows symbolic + * references until a direct reference is found and updated the OID + * on that direct reference to `oid`. + */ +int git_reference__update_terminal( + git_repository *repo, + const char *ref_name, + const git_oid *oid) +{ + return reference__update_terminal(repo, ref_name, oid, 0); +} + +int git_reference_foreach_glob( + git_repository *repo, + const char *glob, + unsigned int list_flags, + int (*callback)( + const char *reference_name, + void *payload), + void *payload) +{ + git_refdb *refdb; + + assert(repo && glob && callback); + + git_repository_refdb__weakptr(&refdb, repo); + + return git_refdb_foreach_glob(refdb, glob, list_flags, callback, payload); +} + +int git_reference_has_log( + git_reference *ref) +{ + git_buf path = GIT_BUF_INIT; + int result; + + assert(ref); + + if (git_buf_join_n(&path, '/', 3, ref->db->repo->path_repository, + GIT_REFLOG_DIR, ref->name) < 0) + return -1; + + result = git_path_isfile(git_buf_cstr(&path)); + git_buf_free(&path); + + return result; +} + +int git_reference__is_branch(const char *ref_name) +{ + return git__prefixcmp(ref_name, GIT_REFS_HEADS_DIR) == 0; +} + +int git_reference_is_branch(git_reference *ref) +{ + assert(ref); + return git_reference__is_branch(ref->name); +} + +int git_reference__is_remote(const char *ref_name) +{ + return git__prefixcmp(ref_name, GIT_REFS_REMOTES_DIR) == 0; +} + +int git_reference_is_remote(git_reference *ref) +{ + assert(ref); + return git_reference__is_remote(ref->name); +} + +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); +} |