diff --git a/include/git2/refs.h b/include/git2/refs.h index 773ae445c..82c5d8881 100644 --- a/include/git2/refs.h +++ b/include/git2/refs.h @@ -116,8 +116,13 @@ GIT_EXTERN(const char *) git_reference_name(git_reference *ref); * Thie method iteratively peels a symbolic reference * until it resolves to a direct reference to an OID. * + * The peeled reference is returned in the `resolved_ref` + * argument, and must be freed manually once it's no longer + * needed. + * * If a direct reference is passed as an argument, - * that reference is returned immediately + * a copy of that reference is returned. This copy must + * be manually freed too. * * @param resolved_ref Pointer to the peeled reference * @param ref The reference @@ -170,11 +175,19 @@ GIT_EXTERN(int) git_reference_set_oid(git_reference *ref, const git_oid *id); * The new name will be checked for validity and may be * modified into a normalized form. * - * The given git_reference will be updated. + * The given git_reference will be updated in place. * * The reference will be immediately renamed in-memory * and on disk. * + * If the `force` flag is not enabled, and there's already + * a reference with the given name, the renaming will fail. + * + * @param ref The reference to rename + * @param new_name The new name for the reference + * @param force Overwrite an existing reference + * @return GIT_SUCCESS or an error code + * */ GIT_EXTERN(int) git_reference_rename(git_reference *ref, const char *new_name, int force); @@ -186,6 +199,8 @@ GIT_EXTERN(int) git_reference_rename(git_reference *ref, const char *new_name, i * The reference will be immediately removed on disk and from * memory. The given reference pointer will no longer be valid. * + * @param ref The reference to remove + * @return GIT_SUCCESS or an error code */ GIT_EXTERN(int) git_reference_delete(git_reference *ref); @@ -250,13 +265,33 @@ GIT_EXTERN(int) git_reference_listall(git_strarray *array, git_repository *repo, GIT_EXTERN(int) git_reference_foreach(git_repository *repo, unsigned int list_flags, int (*callback)(const char *, void *), void *payload); /** - * Check if a reference is packed + * Check if a reference has been loaded from a packfile * - * @param ref git_reference + * @param ref A git reference * @return 0 in case it's not packed; 1 otherwise */ GIT_EXTERN(int) git_reference_is_packed(git_reference *ref); +/** + * Reload a reference from disk + * + * Reference pointers may become outdated if the Git + * repository is accessed simultaneously by other clients + * whilt the library is open. + * + * This method forces a reload of the reference from disk, + * to ensure that the provided information is still + * reliable. + * + * If the reload fails (e.g. the reference no longer exists + * on disk, or has become corrupted), an error code will be + * returned and the reference pointer will be invalidated. + * + * @param ref The reference to reload + * @return GIT_SUCCESS on success, or an error code + */ +GIT_EXTERN(int) git_reference_reload(git_reference *ref); + /** * Free the given reference * diff --git a/src/refs.c b/src/refs.c index 43fea2230..563406660 100644 --- a/src/refs.c +++ b/src/refs.c @@ -16,23 +16,17 @@ #define MAX_NESTING_LEVEL 5 -typedef struct { - git_repository *owner; - char *name; - unsigned int type; - time_t mtime; -} reference; +enum { + GIT_PACKREF_HAS_PEEL = 1, + GIT_PACKREF_WAS_LOOSE = 2 +}; -typedef struct { - reference ref; +struct packref { git_oid oid; - git_oid peel_target; -} reference_oid; - -typedef struct { - reference ref; - char *target; -} reference_symbolic; + git_oid peel; + char flags; + char name[GIT_FLEX_ARRAY]; +}; static const int default_table_size = 32; @@ -47,106 +41,83 @@ static uint32_t reftable_hash(const void *key, int hash_id) return git__hash(key, strlen((const char *)key), hash_seeds[hash_id]); } -static void reference_free(reference *reference); -static int reference_create(reference **ref_out, git_repository *repo, const char *name, git_rtype type); -static int reference_read(git_fbuffer *file_content, time_t *mtime, const char *repo_path, const char *ref_name, int *updated); - -static int reference_set_target(reference *ref, const char *target); -static int reference_set_oid(reference *ref, const git_oid *oid); - -static int reference_delete(reference *ref); +static int reference_read( + git_fbuffer *file_content, + time_t *mtime, + const char *repo_path, + const char *ref_name, + int *updated); /* loose refs */ -static int loose_parse_symbolic(reference *ref, git_fbuffer *file_content); -static int loose_parse_oid(reference *ref, git_fbuffer *file_content); -static int loose_lookup(reference **ref_out, git_repository *repo, const char *name, int skip_symbolic); -static int loose_write(reference *ref); -static int loose_update(reference *ref); +static int loose_parse_symbolic(git_reference *ref, git_fbuffer *file_content); +static int loose_parse_oid(git_oid *ref, git_fbuffer *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(reference_oid *tag_ref, const char **buffer_out, const char *buffer_end); -static int packed_parse_oid(reference_oid **ref_out, git_repository *repo, const char **buffer_out, const char *buffer_end); +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(reference_oid *ref, git_filebuf *file); -static int packed_find_peel(reference_oid *ref); +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_available(git_repository *repo, const char *ref, const char *old_ref); -static int reference_lookup(reference **out, git_repository *repo, const char *name); +static int reference_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 check_valid_ref_char(char ch); -static int normalize_name(char *buffer_out, size_t out_size, const char *name, int is_oid_ref); - -/* reference transition */ -static git_reference * reference_create_external(reference *ref); -static reference * reference_get_internal(git_reference *ref); +static int normalize_name(char *buffer_out, size_t out_size, + const char *name, int is_oid_ref); - -static void reference_free(reference *reference) +void git_reference_free(git_reference *reference) { if (reference == NULL) return; - if (reference->name) - git__free(reference->name); + git__free(reference->name); - if (reference->type == GIT_REF_SYMBOLIC) - git__free(((reference_symbolic *)reference)->target); + if (reference->flags & GIT_REF_SYMBOLIC) + git__free(reference->target.symbolic); git__free(reference); } static int reference_create( - reference **ref_out, + git_reference **ref_out, git_repository *repo, - const char *name, - git_rtype type) + const char *name) { - char normalized[GIT_REFNAME_MAX]; - int error = GIT_SUCCESS, size; - reference *reference = NULL; + git_reference *reference = NULL; assert(ref_out && repo && name); - if (type == GIT_REF_SYMBOLIC) - size = sizeof(reference_symbolic); - else if (type == GIT_REF_OID) - size = sizeof(reference_oid); - else - return git__throw(GIT_EINVALIDARGS, - "Invalid reference type. Use either GIT_REF_OID or GIT_REF_SYMBOLIC as type specifier"); - - reference = git__malloc(size); + reference = git__malloc(sizeof(git_reference)); if (reference == NULL) return GIT_ENOMEM; - memset(reference, 0x0, size); + memset(reference, 0x0, sizeof(git_reference)); reference->owner = repo; - reference->type = type; - error = normalize_name(normalized, sizeof(normalized), name, (type & GIT_REF_OID)); - if (error < GIT_SUCCESS) - goto cleanup; - - reference->name = git__strdup(normalized); + reference->name = git__strdup(name); if (reference->name == NULL) { - error = GIT_ENOMEM; - goto cleanup; + free(reference); + return GIT_ENOMEM; } *ref_out = reference; - - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference"); - -cleanup: - reference_free(reference); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference"); + return GIT_SUCCESS; } static int reference_read(git_fbuffer *file_content, time_t *mtime, const char *repo_path, const char *ref_name, int *updated) @@ -161,87 +132,13 @@ static int reference_read(git_fbuffer *file_content, time_t *mtime, const char * return git_futils_readbuffer_updated(file_content, path, mtime, updated); } -static git_reference * reference_create_external(reference *ref) -{ - size_t size = sizeof(git_reference); - git_reference *out; - - out = git__malloc(size); - if (out == NULL) - return NULL; - - memset(out, 0x0, size); - - out->owner = ref->owner; - - out->name = git__strdup(ref->name); - if (out->name == NULL) { - git__free(out); - return NULL; - } - - return out; -} - -static reference * reference_get_internal(git_reference *ref) -{ - reference *out = NULL; - - reference_lookup(&out, ref->owner, ref->name); - - return out; -} - -static int loose_update(reference *ref) -{ - int error, updated; - git_fbuffer ref_file = GIT_FBUFFER_INIT; - - if (ref->type & GIT_REF_PACKED) - return packed_load(ref->owner); - - -/* error = reference_read(NULL, &ref_time, ref->owner->path_repository, ref->name); - if (error < GIT_SUCCESS) - goto cleanup; - - if (ref_time == ref->mtime) - return GIT_SUCCESS; -*/ - error = reference_read(&ref_file, &ref->mtime, ref->owner->path_repository, ref->name, &updated); - if (error < GIT_SUCCESS) - goto cleanup; - - if (!updated) - goto cleanup; - - if (ref->type == GIT_REF_SYMBOLIC) - error = loose_parse_symbolic(ref, &ref_file); - else if (ref->type == GIT_REF_OID) - error = loose_parse_oid(ref, &ref_file); - else - error = git__throw(GIT_EOBJCORRUPTED, - "Invalid reference type (%d) for loose reference", ref->type); - -cleanup: - git_futils_freebuffer(&ref_file); - if (error != GIT_SUCCESS) { - reference_free(ref); - git_hashtable_remove(ref->owner->references.loose_cache, ref->name); - } - - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to update loose reference"); -} - -static int loose_parse_symbolic(reference *ref, git_fbuffer *file_content) +static int loose_parse_symbolic(git_reference *ref, git_fbuffer *file_content) { const unsigned int header_len = strlen(GIT_SYMREF); const char *refname_start; char *eol; - reference_symbolic *ref_sym; refname_start = (const char *)file_content->data; - ref_sym = (reference_symbolic *)ref; if (file_content->len < (header_len + 1)) return git__throw(GIT_EOBJCORRUPTED, @@ -254,13 +151,12 @@ static int loose_parse_symbolic(reference *ref, git_fbuffer *file_content) refname_start += header_len; - git__free(ref_sym->target); - ref_sym->target = git__strdup(refname_start); - if (ref_sym->target == NULL) + ref->target.symbolic = git__strdup(refname_start); + if (ref->target.symbolic == NULL) return GIT_ENOMEM; /* remove newline at the end of file */ - eol = strchr(ref_sym->target, '\n'); + eol = strchr(ref->target.symbolic, '\n'); if (eol == NULL) return git__throw(GIT_EOBJCORRUPTED, "Failed to parse loose reference. Missing EOL"); @@ -272,21 +168,19 @@ static int loose_parse_symbolic(reference *ref, git_fbuffer *file_content) return GIT_SUCCESS; } -static int loose_parse_oid(reference *ref, git_fbuffer *file_content) +static int loose_parse_oid(git_oid *oid, git_fbuffer *file_content) { int error; - reference_oid *ref_oid; char *buffer; buffer = (char *)file_content->data; - ref_oid = (reference_oid *)ref; /* File format: 40 chars (OID) + newline */ if (file_content->len < GIT_OID_HEXSZ + 1) return git__throw(GIT_EOBJCORRUPTED, "Failed to parse loose reference. Reference too short"); - if ((error = git_oid_fromstr(&ref_oid->oid, buffer)) < GIT_SUCCESS) + if ((error = git_oid_fromstr(oid, buffer)) < GIT_SUCCESS) return git__rethrow(GIT_EOBJCORRUPTED, "Failed to parse loose reference."); buffer = buffer + GIT_OID_HEXSZ; @@ -300,7 +194,6 @@ static int loose_parse_oid(reference *ref, git_fbuffer *file_content) return GIT_SUCCESS; } - static git_rtype loose_guess_rtype(const char *full_path) { git_fbuffer ref_file = GIT_FBUFFER_INIT; @@ -319,55 +212,78 @@ static git_rtype loose_guess_rtype(const char *full_path) return type; } -static int loose_lookup( - reference **ref_out, +static int loose_lookup(git_reference *ref) +{ + int error = GIT_SUCCESS, updated; + git_fbuffer ref_file = GIT_FBUFFER_INIT; + + if (reference_read(&ref_file, &ref->mtime, + ref->owner->path_repository, ref->name, &updated) < GIT_SUCCESS) + return git__throw(GIT_ENOTFOUND, "Failed to lookup loose reference"); + + if (!updated) + return GIT_SUCCESS; + + if (ref->flags & GIT_REF_SYMBOLIC) + free(ref->target.symbolic); + + ref->flags = 0; + + if (git__prefixcmp((const char *)(ref_file.data), GIT_SYMREF) == 0) { + ref->flags |= GIT_REF_SYMBOLIC; + error = loose_parse_symbolic(ref, &ref_file); + } else { + ref->flags |= GIT_REF_OID; + error = loose_parse_oid(&ref->target.oid, &ref_file); + } + + git_futils_freebuffer(&ref_file); + + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to lookup loose reference"); + + return GIT_SUCCESS; +} + +static int loose_lookup_to_packfile( + struct packref **ref_out, git_repository *repo, - const char *name, - int skip_symbolic) + const char *name) { int error = GIT_SUCCESS; git_fbuffer ref_file = GIT_FBUFFER_INIT; - reference *ref = NULL; - time_t ref_time = 0; + struct packref *ref = NULL; + size_t name_len; *ref_out = NULL; - error = reference_read(&ref_file, &ref_time, repo->path_repository, name, NULL); + error = reference_read(&ref_file, NULL, repo->path_repository, name, NULL); if (error < GIT_SUCCESS) goto cleanup; - if (git__prefixcmp((const char *)(ref_file.data), GIT_SYMREF) == 0) { - if (skip_symbolic) - return GIT_SUCCESS; + name_len = strlen(name); + ref = git__malloc(sizeof(struct packref) + name_len + 1); - error = reference_create(&ref, repo, name, GIT_REF_SYMBOLIC); - if (error < GIT_SUCCESS) - goto cleanup; - - error = loose_parse_symbolic(ref, &ref_file); - } else { - error = reference_create(&ref, repo, name, GIT_REF_OID); - if (error < GIT_SUCCESS) - goto cleanup; - - error = loose_parse_oid(ref, &ref_file); - } + memcpy(ref->name, name, name_len); + ref->name[name_len] = 0; + error = loose_parse_oid(&ref->oid, &ref_file); if (error < GIT_SUCCESS) goto cleanup; - ref->mtime = ref_time; + ref->flags = GIT_PACKREF_WAS_LOOSE; + *ref_out = ref; git_futils_freebuffer(&ref_file); return GIT_SUCCESS; cleanup: git_futils_freebuffer(&ref_file); - reference_free(ref); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to lookup loose reference"); + free(ref); + return git__rethrow(error, "Failed to lookup loose reference"); } -static int loose_write(reference *ref) +static int loose_write(git_reference *ref) { git_filebuf file; char ref_path[GIT_PATH_MAX]; @@ -379,40 +295,36 @@ static int loose_write(reference *ref) if ((error = git_filebuf_open(&file, ref_path, GIT_FILEBUF_FORCE)) < GIT_SUCCESS) return git__rethrow(error, "Failed to write loose reference"); - if (ref->type & GIT_REF_OID) { - reference_oid *ref_oid = (reference_oid *)ref; + if (ref->flags & GIT_REF_OID) { char oid[GIT_OID_HEXSZ + 1]; - memset(oid, 0x0, sizeof(oid)); + git_oid_fmt(oid, &ref->target.oid); + oid[GIT_OID_HEXSZ] = '\0'; - git_oid_fmt(oid, &ref_oid->oid); error = git_filebuf_printf(&file, "%s\n", oid); if (error < GIT_SUCCESS) goto unlock; - } else if (ref->type & GIT_REF_SYMBOLIC) { /* GIT_REF_SYMBOLIC */ - reference_symbolic *ref_sym = (reference_symbolic *)ref; - - error = git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref_sym->target); + } else if (ref->flags & GIT_REF_SYMBOLIC) { /* GIT_REF_SYMBOLIC */ + error = git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref->target.symbolic); } else { - error = git__throw(GIT_EOBJCORRUPTED, "Failed to write reference. Invalid reference type"); + error = git__throw(GIT_EOBJCORRUPTED, + "Failed to write reference. Invalid reference type"); goto unlock; } - error = git_filebuf_commit(&file, GIT_REFS_FILE_MODE); - if (p_stat(ref_path, &st) == GIT_SUCCESS) ref->mtime = st.st_mtime; - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write loose reference"); + return git_filebuf_commit(&file, GIT_REFS_FILE_MODE); unlock: git_filebuf_cleanup(&file); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write loose reference"); + return git__rethrow(error, "Failed to write loose reference"); } static int packed_parse_peel( - reference_oid *tag_ref, + struct packref *tag_ref, const char **buffer_out, const char *buffer_end) { @@ -422,47 +334,48 @@ static int packed_parse_peel( /* Ensure it's not the first entry of the file */ if (tag_ref == NULL) - return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Reference is the first entry of the file"); + return git__throw(GIT_EPACKEDREFSCORRUPTED, + "Failed to parse packed reference. " + "Reference is the first entry of the file"); /* Ensure reference is a tag */ - if (git__prefixcmp(tag_ref->ref.name, GIT_REFS_TAGS_DIR) != 0) - return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Reference is not a tag"); + if (git__prefixcmp(tag_ref->name, GIT_REFS_TAGS_DIR) != 0) + return git__throw(GIT_EPACKEDREFSCORRUPTED, + "Failed to parse packed reference. Reference is not a tag"); if (buffer + GIT_OID_HEXSZ >= buffer_end) - return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Buffer too small"); + return git__throw(GIT_EPACKEDREFSCORRUPTED, + "Failed to parse packed reference. Buffer too small"); /* Is this a valid object id? */ - if (git_oid_fromstr(&tag_ref->peel_target, buffer) < GIT_SUCCESS) - return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Not a valid object ID"); + if (git_oid_fromstr(&tag_ref->peel, buffer) < GIT_SUCCESS) + return git__throw(GIT_EPACKEDREFSCORRUPTED, + "Failed to parse packed reference. Not a valid object ID"); buffer = buffer + GIT_OID_HEXSZ; if (*buffer == '\r') buffer++; if (*buffer != '\n') - return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Buffer not terminated correctly"); + return git__throw(GIT_EPACKEDREFSCORRUPTED, + "Failed to parse packed reference. Buffer not terminated correctly"); *buffer_out = buffer + 1; - tag_ref->ref.type |= GIT_REF_HAS_PEEL; - return GIT_SUCCESS; } static int packed_parse_oid( - reference_oid **ref_out, - git_repository *repo, + struct packref **ref_out, const char **buffer_out, const char *buffer_end) { - git_reference *_ref = NULL; - reference_oid *ref = NULL; + struct packref *ref = NULL; const char *buffer = *buffer_out; const char *refname_begin, *refname_end; int error = GIT_SUCCESS; - int refname_len; - char refname[GIT_REFNAME_MAX]; + size_t refname_len; git_oid id; refname_begin = (buffer + GIT_OID_HEXSZ + 1); @@ -482,22 +395,19 @@ static int packed_parse_oid( goto cleanup; } + if (refname_end[-1] == '\r') + refname_end--; + refname_len = refname_end - refname_begin; - memcpy(refname, refname_begin, refname_len); - refname[refname_len] = 0; + ref = git__malloc(sizeof(struct packref) + refname_len + 1); - if (refname[refname_len - 1] == '\r') - refname[refname_len - 1] = 0; - - error = reference_create((reference **)&_ref, repo, refname, GIT_REF_OID); - if (error < GIT_SUCCESS) - goto cleanup; - - ref = (reference_oid *)_ref; + memcpy(ref->name, refname_begin, refname_len); + ref->name[refname_len] = 0; git_oid_cpy(&ref->oid, &id); - ref->ref.type |= GIT_REF_PACKED; + + ref->flags = 0; *ref_out = ref; *buffer_out = refname_end + 1; @@ -505,8 +415,8 @@ static int packed_parse_oid( return GIT_SUCCESS; cleanup: - reference_free((reference *)ref); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse OID of packed reference"); + free(ref); + return git__rethrow(error, "Failed to parse OID of packed reference"); } static int packed_load(git_repository *repo) @@ -521,7 +431,7 @@ static int packed_load(git_repository *repo) ref_cache->packfile = git_hashtable_alloc( default_table_size, reftable_hash, - (git_hash_keyeq_ptr)(&git__strcmp_cb)); + (git_hash_keyeq_ptr)&git__strcmp_cb); if (ref_cache->packfile == NULL) { error = GIT_ENOMEM; @@ -530,7 +440,7 @@ static int packed_load(git_repository *repo) } error = reference_read(&packfile, &ref_cache->packfile_time, - repo->path_repository, GIT_PACKEDREFS_FILE, &updated); + repo->path_repository, GIT_PACKEDREFS_FILE, &updated); /* * If we couldn't find the file, we need to clear the table and @@ -568,9 +478,9 @@ static int packed_load(git_repository *repo) } while (buffer_start < buffer_end) { - reference_oid *ref = NULL; + struct packref *ref = NULL; - error = packed_parse_oid(&ref, repo, &buffer_start, buffer_end); + error = packed_parse_oid(&ref, &buffer_start, buffer_end); if (error < GIT_SUCCESS) goto cleanup; @@ -580,9 +490,9 @@ static int packed_load(git_repository *repo) goto cleanup; } - error = git_hashtable_insert(ref_cache->packfile, ref->ref.name, ref); + error = git_hashtable_insert(ref_cache->packfile, ref->name, ref); if (error < GIT_SUCCESS) { - reference_free((reference *)ref); + free(ref); goto cleanup; } } @@ -594,7 +504,7 @@ cleanup: git_hashtable_free(ref_cache->packfile); ref_cache->packfile = NULL; git_futils_freebuffer(&packfile); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to load packed references"); + return git__rethrow(error, "Failed to load packed references"); } @@ -613,7 +523,8 @@ static int _dirent_loose_listall(void *_data, char *full_path) char *file_path = full_path + data->repo_path_len; if (git_futils_isdir(full_path) == GIT_SUCCESS) - return git_futils_direach(full_path, GIT_PATH_MAX, _dirent_loose_listall, _data); + return git_futils_direach(full_path, GIT_PATH_MAX, + _dirent_loose_listall, _data); /* do not add twice a reference that exists already in the packfile */ if ((data->list_flags & GIT_REF_PACKED) != 0 && @@ -632,28 +543,34 @@ static int _dirent_loose_load(void *data, char *full_path) { git_repository *repository = (git_repository *)data; void *old_ref = NULL; - reference *ref; + struct packref *ref; char *file_path; int error; if (git_futils_isdir(full_path) == GIT_SUCCESS) - return git_futils_direach(full_path, GIT_PATH_MAX, _dirent_loose_load, repository); + return git_futils_direach( + full_path, GIT_PATH_MAX, + _dirent_loose_load, repository); file_path = full_path + strlen(repository->path_repository); - error = loose_lookup(&ref, repository, file_path, 1); - if (error == GIT_SUCCESS && ref != NULL) { - ref->type |= GIT_REF_PACKED; + error = loose_lookup_to_packfile(&ref, repository, file_path); - if (git_hashtable_insert2(repository->references.packfile, ref->name, ref, &old_ref) < GIT_SUCCESS) { - reference_free(ref); + if (error == GIT_SUCCESS) { + + if (git_hashtable_insert2( + repository->references.packfile, + ref->name, ref, &old_ref) < GIT_SUCCESS) { + free(ref); return GIT_ENOMEM; } if (old_ref != NULL) - reference_free((reference *)old_ref); + free(old_ref); } - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to load loose dirent"); + return error == GIT_SUCCESS ? + GIT_SUCCESS : + git__rethrow(error, "Failed to load loose references into packfile"); } /* @@ -671,30 +588,20 @@ static int packed_loadloose(git_repository *repository) git_path_join(refs_path, repository->path_repository, GIT_REFS_DIR); - /* Remove any loose references from the cache */ - { - const void *GIT_UNUSED(_unused); - reference *reference; - - GIT_HASHTABLE_FOREACH(repository->references.loose_cache, _unused, reference, - reference_free(reference); - ); - } - - git_hashtable_clear(repository->references.loose_cache); - /* * Load all the loose files from disk into the Packfile table. * This will overwrite any old packed entries with their * updated loose versions */ - return git_futils_direach(refs_path, GIT_PATH_MAX, _dirent_loose_load, repository); + return git_futils_direach( + refs_path, GIT_PATH_MAX, + _dirent_loose_load, repository); } /* * Write a single reference into a packfile */ -static int packed_write_ref(reference_oid *ref, git_filebuf *file) +static int packed_write_ref(struct packref *ref, git_filebuf *file) { int error; char oid[GIT_OID_HEXSZ + 1]; @@ -712,17 +619,19 @@ static int packed_write_ref(reference_oid *ref, git_filebuf *file) * This obviously only applies to tags. * The required peels have already been loaded into `ref->peel_target`. */ - if (ref->ref.type & GIT_REF_HAS_PEEL) { + if (ref->flags & GIT_PACKREF_HAS_PEEL) { char peel[GIT_OID_HEXSZ + 1]; - git_oid_fmt(peel, &ref->peel_target); + git_oid_fmt(peel, &ref->peel); peel[GIT_OID_HEXSZ] = 0; - error = git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->ref.name, peel); + error = git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel); } else { - error = git_filebuf_printf(file, "%s %s\n", oid, ref->ref.name); + error = git_filebuf_printf(file, "%s %s\n", oid, ref->name); } - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write packed reference"); + return error == GIT_SUCCESS ? + GIT_SUCCESS : + git__rethrow(error, "Failed to write packed reference"); } /* @@ -733,25 +642,25 @@ static int packed_write_ref(reference_oid *ref, git_filebuf *file) * cache on the packfile the OID of the object to * which that 'big tag' is pointing to. */ -static int packed_find_peel(reference_oid *ref) +static int packed_find_peel(git_repository *repo, struct packref *ref) { git_object *object; int error; - if (ref->ref.type & GIT_REF_HAS_PEEL) + if (ref->flags & GIT_PACKREF_HAS_PEEL) return GIT_SUCCESS; /* * Only applies to tags, i.e. references * in the /refs/tags folder */ - if (git__prefixcmp(ref->ref.name, GIT_REFS_TAGS_DIR) != 0) + if (git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) != 0) return GIT_SUCCESS; /* * Find the tagged object in the repository */ - error = git_object_lookup(&object, ref->ref.owner, &ref->oid, GIT_OBJ_ANY); + error = git_object_lookup(&object, repo, &ref->oid, GIT_OBJ_ANY); if (error < GIT_SUCCESS) return git__throw(GIT_EOBJCORRUPTED, "Failed to find packed reference"); @@ -766,8 +675,8 @@ static int packed_find_peel(reference_oid *ref) /* * Find the object pointed at by this tag */ - git_oid_cpy(&ref->peel_target, git_tag_target_oid(tag)); - ref->ref.type |= GIT_REF_HAS_PEEL; + 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 @@ -777,7 +686,6 @@ static int packed_find_peel(reference_oid *ref) } git_object_close(object); - return GIT_SUCCESS; } @@ -797,16 +705,11 @@ static int packed_remove_loose(git_repository *repo, git_vector *packing_list) unsigned int i; char full_path[GIT_PATH_MAX]; int error = GIT_SUCCESS; - reference *r; for (i = 0; i < packing_list->length; ++i) { - reference *ref = git_vector_get(packing_list, i); + struct packref *ref = git_vector_get(packing_list, i); - /* Ensure the packed reference doesn't exist - * in a (more up-to-date?) state as a loose reference - */ - r = git_hashtable_lookup(ref->owner->references.loose_cache, ref->name); - if (r != NULL) + if ((ref->flags & GIT_PACKREF_WAS_LOOSE) == 0) continue; git_path_join(full_path, repo->path_repository, ref->name); @@ -820,19 +723,18 @@ static int packed_remove_loose(git_repository *repo, git_vector *packing_list) * 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. - * - * TODO: mark this with a very special error code? - * GIT_EFAILTORMLOOSE */ } - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to remove loose packed reference"); + return error == GIT_SUCCESS ? + GIT_SUCCESS : + git__rethrow(error, "Failed to remove loose packed reference"); } static int packed_sort(const void *a, const void *b) { - const reference *ref_a = (const reference *)a; - const reference *ref_b = (const reference *)b; + const struct packref *ref_a = (const struct packref *)a; + const struct packref *ref_b = (const struct packref *)b; return strcmp(ref_a->name, ref_b->name); } @@ -853,16 +755,18 @@ static int packed_write(git_repository *repo) assert(repo && repo->references.packfile); total_refs = repo->references.packfile->key_count; - if ((error = git_vector_init(&packing_list, total_refs, packed_sort)) < GIT_SUCCESS) + if ((error = + git_vector_init(&packing_list, total_refs, packed_sort)) < GIT_SUCCESS) return git__rethrow(error, "Failed to write packed reference"); /* Load all the packfile into a vector */ { - reference *reference; + struct packref *reference; const void *GIT_UNUSED(_unused); GIT_HASHTABLE_FOREACH(repo->references.packfile, _unused, reference, - git_vector_insert(&packing_list, reference); /* cannot fail: vector already has the right size */ + /* cannot fail: vector already has the right size */ + git_vector_insert(&packing_list, reference); ); } @@ -877,22 +781,19 @@ static int packed_write(git_repository *repo) /* Packfiles have a header... apparently * This is in fact not required, but we might as well print it * just for kicks */ - if ((error = git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER)) < GIT_SUCCESS) + if ((error = + git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER)) < GIT_SUCCESS) return git__rethrow(error, "Failed to write packed reference"); for (i = 0; i < packing_list.length; ++i) { - reference_oid *ref = (reference_oid *)git_vector_get(&packing_list, i); + struct packref *ref = (struct packref *)git_vector_get(&packing_list, i); - /* only direct references go to the packfile; otherwise - * this is a disaster */ - assert(ref->ref.type & GIT_REF_OID); - - if ((error = packed_find_peel(ref)) < GIT_SUCCESS) { - error = git__throw(GIT_EOBJCORRUPTED, "A reference cannot be peeled"); + if ((error = packed_find_peel(repo, ref)) < GIT_SUCCESS) { + error = git__throw(GIT_EOBJCORRUPTED, + "A reference cannot be peeled"); goto cleanup; } - if ((error = packed_write_ref(ref, &pack_file)) < GIT_SUCCESS) goto cleanup; } @@ -918,20 +819,22 @@ cleanup: git_vector_free(&packing_list); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write packed reference"); + return error == GIT_SUCCESS ? + GIT_SUCCESS : + git__rethrow(error, "Failed to write packed reference"); } static int _reference_available_cb(const char *ref, void *data) { const char *new, *old; - git_vector *refs; + const char **refs; assert(ref && data); - refs = (git_vector *)data; + refs = (const char **)data; - new = (const char *)git_vector_get(refs, 0); - old = (const char *)git_vector_get(refs, 1); + new = (const char *)refs[0]; + old = (const char *)refs[1]; if (!old || strcmp(old, ref)) { int reflen = strlen(ref); @@ -947,28 +850,168 @@ static int _reference_available_cb(const char *ref, void *data) return GIT_SUCCESS; } -static int reference_available(git_repository *repo, const char *ref, const char* old_ref) +static int reference_available( + git_repository *repo, + const char *ref, + const char* old_ref) { - int error; - git_vector refs; + const char *refs[2]; - if (git_vector_init(&refs, 2, NULL) < GIT_SUCCESS) - return GIT_ENOMEM; + refs[0] = ref; + refs[1] = old_ref; - git_vector_insert(&refs, (void *)ref); - git_vector_insert(&refs, (void *)old_ref); + if (git_reference_foreach(repo, GIT_REF_LISTALL, + _reference_available_cb, (void *)refs) < 0) { + return git__throw(GIT_EEXISTS, + "Reference name `%s` conflicts with existing reference", ref); + } - error = git_reference_foreach(repo, GIT_REF_LISTALL, _reference_available_cb, (void *)&refs); - - git_vector_free(&refs); - - return error == GIT_SUCCESS ? GIT_SUCCESS : git__throw(GIT_EEXISTS, "Reference name `%s` conflicts with existing reference", ref); + return GIT_SUCCESS; } -int reference_lookup(reference **ref_out, git_repository *repo, const char *name) +static int reference_exists(int *exists, git_repository *repo, const char *ref_name) +{ + int error; + char ref_path[GIT_PATH_MAX]; + + error = packed_load(repo); + if (error < GIT_SUCCESS) + return git__rethrow(error, "Cannot resolve if a reference exists"); + + git_path_join(ref_path, repo->path_repository, ref_name); + + if (git_futils_isfile(ref_path) == GIT_SUCCESS || + git_hashtable_lookup(repo->references.packfile, ref_path) != NULL) { + *exists = 1; + } else { + *exists = 0; + } + + return GIT_SUCCESS; +} + +static int packed_lookup(git_reference *ref) +{ + int error; + struct packref *pack_ref = NULL; + + error = packed_load(ref->owner); + if (error < GIT_SUCCESS) + return git__rethrow(error, + "Failed to lookup reference from packfile"); + + if (ref->flags & GIT_REF_PACKED && + ref->mtime == ref->owner->references.packfile_time) + return GIT_SUCCESS; + + if (ref->flags & GIT_REF_SYMBOLIC) + free(ref->target.symbolic); + + /* Look up on the packfile */ + pack_ref = git_hashtable_lookup(ref->owner->references.packfile, ref->name); + if (pack_ref == NULL) + return git__throw(GIT_ENOTFOUND, + "Failed to lookup reference from packfile"); + + 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 GIT_SUCCESS; +} + +static int reference_lookup(git_reference *ref) +{ + int error_loose, error_packed; + + error_loose = loose_lookup(ref); + if (error_loose == GIT_SUCCESS) + return GIT_SUCCESS; + + error_packed = packed_lookup(ref); + if (error_packed == GIT_SUCCESS) + return GIT_SUCCESS; + + git_reference_free(ref); + + if (error_loose != GIT_ENOTFOUND) + return git__rethrow(error_loose, "Failed to lookup reference"); + + if (error_packed != GIT_ENOTFOUND) + return git__rethrow(error_packed, "Failed to lookup reference"); + + return git__throw(GIT_ENOTFOUND, "Reference not found"); +} + +/* + * 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 error; + + 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) { + /* load the existing packfile */ + if ((error = packed_load(ref->owner)) < GIT_SUCCESS) + return git__rethrow(error, "Failed to delete reference"); + + if (git_hashtable_remove(ref->owner->references.packfile, + ref->name) < GIT_SUCCESS) + return git__throw(GIT_ENOTFOUND, "Reference not found"); + + error = packed_write(ref->owner); + + /* If the reference is loose, we can just remove the reference + * from the filesystem */ + } else { + char full_path[GIT_PATH_MAX]; + git_reference *ref_in_pack; + + git_path_join(full_path, ref->owner->path_repository, ref->name); + + error = p_unlink(full_path); + if (error < GIT_SUCCESS) + goto cleanup; + + /* 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) == GIT_SUCCESS) { + assert((ref_in_pack->flags & GIT_REF_PACKED) != 0); + error = git_reference_delete(ref_in_pack); + } + } + +cleanup: + return error == GIT_SUCCESS ? + GIT_SUCCESS : + git__rethrow(error, "Failed to delete reference"); +} + +int git_reference_delete(git_reference *ref) +{ + int error = reference_delete(ref); + if (error < GIT_SUCCESS) + return error; + + git_reference_free(ref); + return GIT_SUCCESS; +} + + +int git_reference_lookup(git_reference **ref_out, + git_repository *repo, const char *name) { int error; char normalized_name[GIT_REFNAME_MAX]; + git_reference *ref = NULL; assert(ref_out && repo && name); @@ -978,112 +1021,38 @@ int reference_lookup(reference **ref_out, git_repository *repo, const char *name if (error < GIT_SUCCESS) return git__rethrow(error, "Failed to lookup reference"); - /* First, check has been previously loaded and cached */ - *ref_out = git_hashtable_lookup(repo->references.loose_cache, normalized_name); - if (*ref_out != NULL) - return loose_update(*ref_out); - - /* Then check if there is a loose file for that reference.*/ - error = loose_lookup(ref_out, repo, normalized_name, 0); - - /* If the file exists, we store it on the cache */ - if (error == GIT_SUCCESS) - return git_hashtable_insert(repo->references.loose_cache, (*ref_out)->name, (*ref_out)); - - /* The loose lookup has failed, but not because the reference wasn't found; - * probably the loose reference is corrupted. this is bad. */ - if (error != GIT_ENOTFOUND) - return git__rethrow(error, "Failed to lookup reference"); - - /* - * If we cannot find a loose reference, we look into the packfile - * Load the packfile first if it hasn't been loaded - */ - /* load all the packed references */ - error = packed_load(repo); + error = reference_create(&ref, repo, normalized_name); if (error < GIT_SUCCESS) return git__rethrow(error, "Failed to lookup reference"); - /* Look up on the packfile */ - *ref_out = git_hashtable_lookup(repo->references.packfile, normalized_name); - if (*ref_out != NULL) - return GIT_SUCCESS; - - /* The reference doesn't exist anywhere */ - return git__throw(GIT_ENOTFOUND, "Failed to lookup reference. Reference doesn't exist"); -} - -int git_reference_lookup(git_reference **ref_out, git_repository *repo, const char *name) -{ - int error; - reference *ref; - - assert(ref_out && repo && name); - - *ref_out = NULL; - - error = reference_lookup(&ref, repo, name); + error = reference_lookup(ref); if (error < GIT_SUCCESS) - return error; - - *ref_out = reference_create_external(ref); - if (*ref_out == NULL) - return GIT_ENOMEM; + return git__rethrow(error, "Failed to lookup reference"); + *ref_out = ref; return GIT_SUCCESS; } /** * Getters */ -static git_rtype ref_type(reference *ref) +git_rtype git_reference_type(git_reference *ref) { assert(ref); - if (ref->type & GIT_REF_OID) + if (ref->flags & GIT_REF_OID) return GIT_REF_OID; - if (ref->type & GIT_REF_SYMBOLIC) + if (ref->flags & GIT_REF_SYMBOLIC) return GIT_REF_SYMBOLIC; return GIT_REF_INVALID; } -git_rtype git_reference_type(git_reference *ref_in) +int git_reference_is_packed(git_reference *ref) { - reference *ref; - - assert(ref_in); - - ref = reference_get_internal(ref_in); - if (ref == NULL) - return GIT_REF_INVALID; - - return ref_type(ref); -} - -int git_reference_is_packed(git_reference *ref_in) -{ - reference *ref; - - assert(ref_in); - - ref = reference_get_internal(ref_in); - if (ref == NULL) - return GIT_REF_INVALID; - - return !!(ref->type & GIT_REF_PACKED); -} - -void git_reference_free(git_reference *ref) -{ - if (ref == NULL) - return; - - if (ref->name) - git__free(ref->name); - - git__free(ref); + assert(ref); + return !!(ref->flags & GIT_REF_PACKED); } const char *git_reference_name(git_reference *ref) @@ -1098,288 +1067,158 @@ git_repository *git_reference_owner(git_reference *ref) return ref->owner; } -static const git_oid *ref_oid(reference *ref) +const git_oid *git_reference_oid(git_reference *ref) { assert(ref); - if ((ref->type & GIT_REF_OID) == 0) + if ((ref->flags & GIT_REF_OID) == 0) return NULL; - if (loose_update(ref) < GIT_SUCCESS) - return NULL; - - return &((reference_oid *)ref)->oid; + return &ref->target.oid; } -const git_oid *git_reference_oid(git_reference *ref_in) -{ - reference *ref; - - assert(ref_in); - - ref = reference_get_internal(ref_in); - if (ref == NULL) - return NULL; - - return ref_oid(ref); -} - -static const char *ref_target(reference *ref) +const char *git_reference_target(git_reference *ref) { assert(ref); - if ((ref->type & GIT_REF_SYMBOLIC) == 0) + if ((ref->flags & GIT_REF_SYMBOLIC) == 0) return NULL; - if (loose_update(ref) < GIT_SUCCESS) - return NULL; - - return ((reference_symbolic *)ref)->target; + return ref->target.symbolic; } -const char *git_reference_target(git_reference *ref_in) -{ - reference *ref; - - assert(ref_in); - - ref = reference_get_internal(ref_in); - if (ref == NULL) - return NULL; - - return ref_target(ref); -} - -static int reference_create_symbolic(reference **ref_out, git_repository *repo, const char *name, const char *target, int force) +int git_reference_create_symbolic( + git_reference **ref_out, + git_repository *repo, + const char *name, + const char *target, + int force) { char normalized[GIT_REFNAME_MAX]; - int error = GIT_SUCCESS, updated = 0; - reference *ref = NULL; - void *old_ref = NULL; + int ref_exists, error = GIT_SUCCESS; + git_reference *ref = NULL; - if (reference_lookup(&ref, repo, name) == GIT_SUCCESS && !force) - return git__throw(GIT_EEXISTS, "Failed to create symbolic reference. Reference already exists"); + error = normalize_name(normalized, sizeof(normalized), name, 0); + if (error < GIT_SUCCESS) + goto cleanup; - /* - * If they old ref was of the same type, then we can just update - * it (once we've checked that the target is valid). Otherwise we - * need a new reference because we can't make a symbolic ref out - * of an oid one. - * If if didn't exist, then we need to create a new one anyway. - */ - if (ref && ref->type & GIT_REF_SYMBOLIC){ - updated = 1; + if ((error = reference_exists(&ref_exists, repo, normalized) < GIT_SUCCESS)) + return git__rethrow(error, "Failed to create symbolic reference"); + + if (ref_exists && !force) + return git__throw(GIT_EEXISTS, + "Failed to create symbolic reference. Reference already exists"); + + error = reference_create(&ref, repo, normalized); + if (error < GIT_SUCCESS) + goto cleanup; + + ref->flags |= GIT_REF_SYMBOLIC; + + /* set the target; this will normalize the name automatically + * and write the reference on disk */ + error = git_reference_set_target(ref, target); + if (error < GIT_SUCCESS) + goto cleanup; + + if (ref_out == NULL) { + git_reference_free(ref); } else { - ref = NULL; - error = reference_create(&ref, repo, name, GIT_REF_SYMBOLIC); - if (error < GIT_SUCCESS) - goto cleanup; + *ref_out = ref; } - /* The target can aither be the name of an object id reference or the name of another symbolic reference */ - error = normalize_name(normalized, sizeof(normalized), target, 0); - if (error < GIT_SUCCESS) - goto cleanup; - - /* set the target; this will write the reference on disk */ - error = reference_set_target(ref, normalized); - if (error < GIT_SUCCESS) - goto cleanup; - - /* - * If we didn't update the ref, then we need to insert or replace - * it in the loose cache. If we replaced a ref, free it. - */ - if (!updated){ - error = git_hashtable_insert2(repo->references.loose_cache, ref->name, ref, &old_ref); - if (error < GIT_SUCCESS) - goto cleanup; - - if (old_ref != NULL) - reference_free((reference *)old_ref); - } - - *ref_out = ref; - - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create symbolic reference"); - -cleanup: - reference_free(ref); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create symbolic reference"); -} - -int git_reference_create_symbolic(git_reference **ref_out, git_repository *repo, const char *name, const char *target, int force) -{ - int error; - reference *ref; - - error = reference_create_symbolic(&ref, repo, name, target, force); - if (error < GIT_SUCCESS) - return error; - - *ref_out = reference_create_external(ref); - if (*ref_out == NULL) - return GIT_ENOMEM; - return GIT_SUCCESS; + +cleanup: + git_reference_free(ref); + return git__rethrow(error, "Failed to create symbolic reference"); } -static int reference_create_oid(reference **ref_out, git_repository *repo, const char *name, const git_oid *id, int force) +int git_reference_create_oid( + git_reference **ref_out, + git_repository *repo, + const char *name, + const git_oid *id, + int force) { - int error = GIT_SUCCESS, updated = 0; - reference *ref = NULL; - void *old_ref = NULL; + int error = GIT_SUCCESS, ref_exists; + git_reference *ref = NULL; + char normalized[GIT_REFNAME_MAX]; - if(reference_lookup(&ref, repo, name) == GIT_SUCCESS && !force) - return git__throw(GIT_EEXISTS, "Failed to create reference OID. Reference already exists"); + error = normalize_name(normalized, sizeof(normalized), name, 1); + if (error < GIT_SUCCESS) + goto cleanup; + + if ((error = reference_exists(&ref_exists, repo, normalized) < GIT_SUCCESS)) + return git__rethrow(error, "Failed to create OID reference"); + + if (ref_exists && !force) + return git__throw(GIT_EEXISTS, + "Failed to create OID reference. Reference already exists"); if ((error = reference_available(repo, name, NULL)) < GIT_SUCCESS) return git__rethrow(error, "Failed to create reference"); - /* - * If they old ref was of the same type, then we can just update - * it (once we've checked that the target is valid). Otherwise we - * need a new reference because we can't make a symbolic ref out - * of an oid one. - * If if didn't exist, then we need to create a new one anyway. - */ - if (ref && ref-> type & GIT_REF_OID){ - updated = 1; - } else { - ref = NULL; - error = reference_create(&ref, repo, name, GIT_REF_OID); - if (error < GIT_SUCCESS) - goto cleanup; - } - - /* set the oid; this will write the reference on disk */ - error = reference_set_oid(ref, id); + error = reference_create(&ref, repo, name); if (error < GIT_SUCCESS) goto cleanup; - if(!updated){ - error = git_hashtable_insert2(repo->references.loose_cache, ref->name, ref, &old_ref); - if (error < GIT_SUCCESS) - goto cleanup; + ref->flags |= GIT_REF_OID; - if (old_ref != NULL) - reference_free((reference *)old_ref); + /* set the oid; this will write the reference on disk */ + error = git_reference_set_oid(ref, id); + if (error < GIT_SUCCESS) + goto cleanup; + + if (ref_out == NULL) { + git_reference_free(ref); + } else { + *ref_out = ref; } - *ref_out = ref; - - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference OID"); + return GIT_SUCCESS; cleanup: - reference_free(ref); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference OID"); -} - -int git_reference_create_oid(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id, int force) -{ - int error; - reference *ref; - - error = reference_create_oid(&ref, repo, name, id, force); - if (error < GIT_SUCCESS) - return error; - - *ref_out = reference_create_external(ref); - if (*ref_out == NULL) - return GIT_ENOMEM; - - return GIT_SUCCESS; + git_reference_free(ref); + return git__rethrow(error, "Failed to create reference OID"); } /* * Change the OID target of a reference. * - * For loose references, just change the oid in memory - * and overwrite the file in disk. + * For both loose and packed references, just change + * the oid in memory and (over)write the file in disk. * - * For packed files, this is not pretty: - * For performance reasons, we write the new reference - * loose on disk (it replaces the old on the packfile), - * but we cannot invalidate the pointer to the reference, - * and most importantly, the `packfile` object must stay - * consistent with the representation of the packfile - * on disk. This is what we need to: - * - * 1. Copy the reference - * 2. Change the oid on the original - * 3. Write the original to disk - * 4. Write the original to the loose cache - * 5. Replace the original with the copy (old reference) in the packfile cache + * We do not repack packed references because of performance + * reasons. */ -int reference_set_oid(reference *ref, const git_oid *id) +int git_reference_set_oid(git_reference *ref, const git_oid *id) { - reference_oid *ref_oid; - reference_oid *ref_old = NULL; int error = GIT_SUCCESS; - if ((ref->type & GIT_REF_OID) == 0) - return git__throw(GIT_EINVALIDREFSTATE, "Failed to set OID target of reference. Not an OID reference"); - - ref_oid = (reference_oid *)ref; + if ((ref->flags & GIT_REF_OID) == 0) + return git__throw(GIT_EINVALIDREFSTATE, + "Failed to set OID target of reference. Not an OID reference"); assert(ref->owner); /* Don't let the user create references to OIDs that * don't exist in the ODB */ if (!git_odb_exists(git_repository_database(ref->owner), id)) - return git__throw(GIT_ENOTFOUND, "Failed to set OID target of reference. OID doesn't exist in ODB"); + return git__throw(GIT_ENOTFOUND, + "Failed to set OID target of reference. OID doesn't exist in ODB"); - /* duplicate the reference; - * this copy will stay on the packfile cache */ - if (ref->type & GIT_REF_PACKED) { - ref_old = git__malloc(sizeof(reference_oid)); - if (ref_old == NULL) - return GIT_ENOMEM; - - ref_old->ref.name = git__strdup(ref->name); - if (ref_old->ref.name == NULL) { - git__free(ref_old); - return GIT_ENOMEM; - } - } - - git_oid_cpy(&ref_oid->oid, id); - ref->type &= ~GIT_REF_HAS_PEEL; + /* Update the OID value on `ref` */ + git_oid_cpy(&ref->target.oid, id); error = loose_write(ref); if (error < GIT_SUCCESS) goto cleanup; - if (ref->type & GIT_REF_PACKED) { - /* insert the original on the loose cache */ - error = git_hashtable_insert(ref->owner->references.loose_cache, ref->name, ref); - if (error < GIT_SUCCESS) - goto cleanup; - - ref->type &= ~GIT_REF_PACKED; - - /* replace the original in the packfile with the copy */ - error = git_hashtable_insert(ref->owner->references.packfile, ref_old->ref.name, ref_old); - if (error < GIT_SUCCESS) - goto cleanup; - } - return GIT_SUCCESS; cleanup: - reference_free((reference *)ref_old); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to set OID target of reference"); -} - -int git_reference_set_oid(git_reference *ref_in, const git_oid *target) -{ - reference *ref; - - ref = reference_get_internal(ref_in); - if (ref == NULL) - return git__throw(GIT_ENOTFOUND, "Failed to rename reference. Reference `%s` doesn't exist", ref->name); - - return reference_set_oid(ref, target); + return git__rethrow(error, "Failed to set OID target of reference"); } /* @@ -1389,35 +1228,29 @@ int git_reference_set_oid(git_reference *ref_in, const git_oid *target) * a pack. We just change the target in memory * and overwrite the file on disk. */ -int reference_set_target(reference *ref, const char *target) +int git_reference_set_target(git_reference *ref, const char *target) { - reference_symbolic *ref_sym; + int error; + char normalized[GIT_REFNAME_MAX]; - if ((ref->type & GIT_REF_SYMBOLIC) == 0) - return git__throw(GIT_EINVALIDREFSTATE, "Failed to set reference target. Not a symbolic reference"); + if ((ref->flags & GIT_REF_SYMBOLIC) == 0) + return git__throw(GIT_EINVALIDREFSTATE, + "Failed to set reference target. Not a symbolic reference"); - ref_sym = (reference_symbolic *)ref; + error = normalize_name(normalized, sizeof(normalized), target, 0); + if (error < GIT_SUCCESS) + return git__rethrow(error, + "Failed to set reference target. Invalid target name"); - git__free(ref_sym->target); - ref_sym->target = git__strdup(target); - if (ref_sym->target == NULL) + git__free(ref->target.symbolic); + ref->target.symbolic = git__strdup(normalized); + if (ref->target.symbolic == NULL) return GIT_ENOMEM; return loose_write(ref); } -int git_reference_set_target(git_reference *ref_in, const char *target) -{ - reference *ref; - - ref = reference_get_internal(ref_in); - if (ref == NULL) - return git__throw(GIT_ENOTFOUND, "Failed to rename reference. Reference `%s` doesn't exist", ref->name); - - return reference_set_target(ref, target); -} - -int git_reference_rename(git_reference *ref_in, const char *new_name, int force) +int git_reference_rename(git_reference *ref, const char *new_name, int force) { int error; @@ -1425,71 +1258,59 @@ int git_reference_rename(git_reference *ref_in, const char *new_name, int force) char normalized[GIT_REFNAME_MAX]; const char *head_target = NULL; - reference *ref = NULL, *new_ref = NULL, *head = NULL, *tmp_ref = NULL; + git_reference *existing_ref = NULL, *head = NULL; - assert(ref_in); + error = normalize_name(normalized, sizeof(normalized), + new_name, ref->flags & GIT_REF_OID); - ref = reference_get_internal(ref_in); - if (ref == NULL) - return git__throw(GIT_ENOTFOUND, "Failed to rename reference. Reference `%s` doesn't exist", ref->name); - - error = normalize_name(normalized, sizeof(normalized), new_name, ref->type & GIT_REF_OID); if (error < GIT_SUCCESS) return git__rethrow(error, "Failed to rename reference. Invalid name"); new_name = normalized; - error = reference_lookup(&new_ref, ref->owner, new_name); - if (error == GIT_SUCCESS) { - if (!force) - return git__throw(GIT_EEXISTS, "Failed to rename reference. Reference already exists"); + /* If we are forcing the rename, try to lookup a reference with the + * new one. If the lookup succeeds, we need to delete that ref + * before the renaming can proceed */ + if (force) { + error = git_reference_lookup(&existing_ref, ref->owner, new_name); - error = reference_delete(new_ref); - } - - if (error < GIT_SUCCESS) { - git_path_join(aux_path, ref->owner->path_repository, new_name); - /* If we couldn't read the reference because it doesn't - * exist it's ok - otherwise return */ - if (git_futils_isfile(aux_path) == GIT_SUCCESS) + if (error == GIT_SUCCESS) { + error = git_reference_delete(existing_ref); + if (error < GIT_SUCCESS) + return git__rethrow(error, + "Failed to rename reference. " + "The existing reference cannot be deleted"); + } 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) - return git__rethrow(error, "Failed to rename reference. Reference already exists"); - - /* - * First, we backup the reference targets. Just keeping the old - * reference won't work, since we may have to remove it to create - * the new reference, e.g. when renaming foo/bar -> foo. - */ - - if (ref->type & GIT_REF_SYMBOLIC) - tmp_ref = git__malloc(sizeof(reference_symbolic)); - else - tmp_ref = git__malloc(sizeof(reference_oid)); - - if (tmp_ref == NULL) - return GIT_ENOMEM; - - tmp_ref->name = git__strdup(ref->name); - tmp_ref->type = ref->type; - tmp_ref->owner = ref->owner; - - if (ref->type & GIT_REF_SYMBOLIC) - ((reference_symbolic *)tmp_ref)->target = git__strdup(((reference_symbolic *)ref)->target); - else - ((reference_oid *)tmp_ref)->oid = ((reference_oid *)ref)->oid; + return git__rethrow(error, + "Failed to rename reference. Reference already exists"); /* * Now delete the old ref and remove an possibly existing directory - * named `new_name`. + * 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 ((error = reference_delete(ref)) < GIT_SUCCESS) goto cleanup; - git_path_join(aux_path, tmp_ref->owner->path_repository, new_name); + git_path_join(aux_path, ref->owner->path_repository, new_name); if (git_futils_exists(aux_path) == GIT_SUCCESS) { if (git_futils_isdir(aux_path) == GIT_SUCCESS) { if ((error = git_futils_rmdir_r(aux_path, 0)) < GIT_SUCCESS) @@ -1513,8 +1334,7 @@ int git_reference_rename(git_reference *ref_in, const char *new_name, int force) * TODO * */ - - git_path_join_n(aux_path, 3, tmp_ref->owner->path_repository, "logs", tmp_ref->name); + git_path_join_n(aux_path, 3, ref->owner->path_repository, "logs", ref->name); if (git_futils_isfile(aux_path) == GIT_SUCCESS) { if ((error = p_unlink(aux_path)) < GIT_SUCCESS) goto rollback; @@ -1523,174 +1343,107 @@ int git_reference_rename(git_reference *ref_in, const char *new_name, int force) /* * Finally we can create the new reference. */ - if (tmp_ref->type & GIT_REF_SYMBOLIC) { - if ((error = reference_create_symbolic(&new_ref, tmp_ref->owner, new_name, ((reference_symbolic *)tmp_ref)->target, 0)) < GIT_SUCCESS) - goto rollback; + if (ref->flags & GIT_REF_SYMBOLIC) { + error = git_reference_create_symbolic( + NULL, ref->owner, new_name, ref->target.symbolic, 0); } else { - if ((error = reference_create_oid(&new_ref, tmp_ref->owner, new_name, &((reference_oid *)tmp_ref)->oid, 0)) < GIT_SUCCESS) - goto rollback; + error = git_reference_create_oid( + NULL, ref->owner, new_name, &ref->target.oid, 0); + } + + if (error < GIT_SUCCESS) + goto cleanup; + + /* + * Check if we have to update HEAD. + */ + error = git_reference_lookup(&head, ref->owner, GIT_HEAD_FILE); + if (error < GIT_SUCCESS) + goto cleanup; + + head_target = git_reference_target(head); + + if (head_target && !strcmp(head_target, ref->name)) { + error = git_reference_create_symbolic( + &head, ref->owner, "HEAD", new_name, 1); + + if (error < GIT_SUCCESS) + goto cleanup; } /* * Change the name of the reference given by the user. */ - git__free(ref_in->name); - ref_in->name = git__strdup(new_ref->name); + git__free(ref->name); + ref->name = git__strdup(new_name); - if ((error = git_hashtable_insert(new_ref->owner->references.loose_cache, new_ref->name, new_ref)) < GIT_SUCCESS) - goto rollback; - - /* - * Check if we have to update HEAD. - */ - - if ((error = reference_lookup(&head, new_ref->owner, GIT_HEAD_FILE)) < GIT_SUCCESS) - goto cleanup; - - head_target = ref_target(head); - - if (head_target && !strcmp(head_target, tmp_ref->name)) - if ((error = reference_create_symbolic(&head, new_ref->owner, "HEAD", new_ref->name, 1)) < GIT_SUCCESS) - goto rollback; + /* The reference is no longer packed */ + ref->flags &= ~GIT_REF_PACKED; cleanup: - reference_free(tmp_ref); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to rename reference"); + /* We no longer need the newly created reference nor the head */ + git_reference_free(head); + return error == GIT_SUCCESS ? + GIT_SUCCESS : + git__rethrow(error, "Failed to rename reference"); rollback: /* * Try to create the old reference again. */ - if (ref->type & GIT_REF_SYMBOLIC) - error = reference_create_symbolic(&new_ref, tmp_ref->owner, tmp_ref->name, ((reference_symbolic *)tmp_ref)->target, 0); + if (ref->flags & GIT_REF_SYMBOLIC) + error = git_reference_create_symbolic( + NULL, ref->owner, ref->name, ref->target.symbolic, 0); else - error = reference_create_oid(&new_ref, ref->owner, tmp_ref->name, &((reference_oid *)tmp_ref)->oid, 0); - - git__free(ref_in->name); - ref_in->name = git__strdup(tmp_ref->name); - - reference_free(tmp_ref); + error = git_reference_create_oid( + NULL, ref->owner, ref->name, &ref->target.oid, 0); return error == GIT_SUCCESS ? git__rethrow(GIT_ERROR, "Failed to rename reference. Did rollback") : git__rethrow(error, "Failed to rename reference. Failed to rollback"); } -/* - * Delete a reference. - * - * If the reference is packed, this is an expensive - * operation. We need to remove the reference from - * the memory cache and then rewrite the whole pack - * - * If the reference is loose, we remove it on - * the filesystem and update the in-memory cache - * accordingly. We also make sure that an older version - * of it doesn't exist as a packed reference. If this - * is the case, this packed reference is removed as well. - * - * This obviously invalidates the `ref` pointer. - */ -int reference_delete(reference *ref) +int git_reference_resolve(git_reference **ref_out, git_reference *ref) { - int error; - reference *reference; + int error, i = 0; + git_repository *repo; assert(ref); - if (ref->type & GIT_REF_PACKED) { - /* load the existing packfile */ - if ((error = packed_load(ref->owner)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to delete reference"); + *ref_out = NULL; + repo = ref->owner; - if (git_hashtable_remove(ref->owner->references.packfile, ref->name) < GIT_SUCCESS) - return git__throw(GIT_ENOTFOUND, "Reference not found"); + /* If the reference is already resolved, we need to return a + * copy. Instead of duplicating `ref`, we look it up again to + * ensure the copy is out to date */ + if (ref->flags & GIT_REF_OID) + return git_reference_lookup(ref_out, ref->owner, ref->name); - error = packed_write(ref->owner); - } else { - char full_path[GIT_PATH_MAX]; - git_path_join(full_path, ref->owner->path_repository, ref->name); - git_hashtable_remove(ref->owner->references.loose_cache, ref->name); - error = p_unlink(full_path); + /* Otherwise, keep iterating until the reference is resolved */ + for (i = 0; i < MAX_NESTING_LEVEL; ++i) { + git_reference *new_ref; + + error = git_reference_lookup(&new_ref, repo, ref->target.symbolic); if (error < GIT_SUCCESS) - goto cleanup; + return git__rethrow(error, "Failed to resolve reference"); - /* When deleting a loose reference, we have to ensure that an older - * packed version of it doesn't exist - */ - if (!reference_lookup(&reference, ref->owner, ref->name)) { - assert((reference->type & GIT_REF_PACKED) != 0); - error = reference_delete(reference); + /* Free intermediate references, except for the original one + * we've received */ + if (i > 0) + git_reference_free(ref); + + ref = new_ref; + + /* When the reference we've just looked up is an OID, we've + * successfully resolved the symbolic ref */ + if (ref->flags & GIT_REF_OID) { + *ref_out = ref; + return GIT_SUCCESS; } } -cleanup: - reference_free(ref); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to delete reference"); -} - -int git_reference_delete(git_reference *ref_in) -{ - reference *ref; - - ref = reference_get_internal(ref_in); - if (ref == NULL) - return git__throw(GIT_ENOTFOUND, "Failed to delete reference. Reference `%s` doesn't exist", ref_in->name); - - git_reference_free(ref_in); - - return reference_delete(ref); -} - -static int reference_resolve(reference **resolved_ref, reference *ref) -{ - git_repository *repo; - int error, i; - - assert(resolved_ref && ref); - *resolved_ref = NULL; - - if ((error = loose_update(ref)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to resolve reference"); - - repo = ref->owner; - - for (i = 0; i < MAX_NESTING_LEVEL; ++i) { - reference_symbolic *ref_sym; - - *resolved_ref = ref; - - if (ref->type & GIT_REF_OID) - return GIT_SUCCESS; - - ref_sym = (reference_symbolic *)ref; - if ((error = reference_lookup(&ref, repo, ref_sym->target)) < GIT_SUCCESS) - return error; - } - - return git__throw(GIT_ENOMEM, "Failed to resolve reference. Reference is too nested"); -} - -int git_reference_resolve(git_reference **resolved_ref, git_reference *ref_in) -{ - int error; - reference *ref = NULL, *out = NULL; - - *resolved_ref = NULL; - - ref = reference_get_internal(ref_in); - if (ref == NULL) - return git__throw(GIT_ENOTFOUND, "Failed to resolve reference. Reference `%s` doesn't exist", ref_in->name); - - error = reference_resolve(&out, ref); - if (error < GIT_SUCCESS) - return error; - - *resolved_ref = reference_create_external(out); - if (*resolved_ref == NULL) - return GIT_ENOMEM; - - return GIT_SUCCESS; + return git__throw(GIT_ENOMEM, + "Failed to resolve reference. Reference is too nested"); } int git_reference_packall(git_repository *repo) @@ -1709,7 +1462,11 @@ int git_reference_packall(git_repository *repo) return packed_write(repo); } -int git_reference_foreach(git_repository *repo, unsigned int list_flags, int (*callback)(const char *, void *), void *payload) +int git_reference_foreach( + git_repository *repo, + unsigned int list_flags, + int (*callback)(const char *, void *), + void *payload) { int error; struct dirent_list_data data; @@ -1725,7 +1482,8 @@ int git_reference_foreach(git_repository *repo, unsigned int list_flags, int (*c GIT_HASHTABLE_FOREACH(repo->references.packfile, ref_name, _unused, if ((error = callback(ref_name, payload)) < GIT_SUCCESS) - return git__throw(error, "Failed to list references. User callback failed"); + return git__throw(error, + "Failed to list references. User callback failed"); ); } @@ -1738,7 +1496,6 @@ int git_reference_foreach(git_repository *repo, unsigned int list_flags, int (*c data.callback = callback; data.callback_payload = payload; - git_path_join(refs_path, repo->path_repository, GIT_REFS_DIR); return git_futils_direach(refs_path, GIT_PATH_MAX, _dirent_loose_listall, &data); } @@ -1748,7 +1505,10 @@ static int cb__reflist_add(const char *ref, void *data) return git_vector_insert((git_vector *)data, git__strdup(ref)); } -int git_reference_listall(git_strarray *array, git_repository *repo, unsigned int list_flags) +int git_reference_listall( + git_strarray *array, + git_repository *repo, + unsigned int list_flags) { int error; git_vector ref_list; @@ -1761,7 +1521,8 @@ int git_reference_listall(git_strarray *array, git_repository *repo, unsigned in if (git_vector_init(&ref_list, 8, NULL) < GIT_SUCCESS) return GIT_ENOMEM; - error = git_reference_foreach(repo, list_flags, &cb__reflist_add, (void *)&ref_list); + error = git_reference_foreach( + repo, list_flags, &cb__reflist_add, (void *)&ref_list); if (error < GIT_SUCCESS) { git_vector_free(&ref_list); @@ -1773,48 +1534,39 @@ int git_reference_listall(git_strarray *array, git_repository *repo, unsigned in return GIT_SUCCESS; } -int git_repository__refcache_init(git_refcache *refs) +int git_reference_reload(git_reference *ref) { - assert(refs); + int error = reference_lookup(ref); - refs->loose_cache = git_hashtable_alloc( - default_table_size, - reftable_hash, - (git_hash_keyeq_ptr)(&git__strcmp_cb)); + if (error < GIT_SUCCESS) { + git_reference_free(ref); + return git__rethrow(error, "Failed to reload reference"); + } - /* packfile loaded lazily */ - refs->packfile = NULL; - refs->packfile_time = 0; - - return (refs->loose_cache) ? GIT_SUCCESS : GIT_ENOMEM; + return GIT_SUCCESS; } + void git_repository__refcache_free(git_refcache *refs) { - reference *reference; - const void *GIT_UNUSED(_unused); - assert(refs); - GIT_HASHTABLE_FOREACH(refs->loose_cache, _unused, reference, - reference_free(reference); - ); - - git_hashtable_free(refs->loose_cache); - if (refs->packfile) { + const void *GIT_UNUSED(_unused); + struct packref *reference; + GIT_HASHTABLE_FOREACH(refs->packfile, _unused, reference, - reference_free(reference); + free(reference); ); git_hashtable_free(refs->packfile); } } -static int check_valid_ref_char(char ch) +static int is_valid_ref_char(char ch) { if ((unsigned) ch <= ' ') - return GIT_ERROR; + return 0; switch (ch) { case '~': @@ -1824,13 +1576,17 @@ static int check_valid_ref_char(char ch) case '?': case '[': case '*': - return GIT_ERROR; + return 0; default: - return GIT_SUCCESS; + return 1; } } -static int normalize_name(char *buffer_out, size_t out_size, const char *name, int is_oid_ref) +static int normalize_name( + char *buffer_out, + size_t out_size, + const char *name, + int is_oid_ref) { const char *name_end, *buffer_out_start; const char *current; @@ -1847,26 +1603,33 @@ static int normalize_name(char *buffer_out, size_t out_size, const char *name, i /* A refname can not be empty */ if (name_end == name) - return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name is empty"); + return git__throw(GIT_EINVALIDREFNAME, + "Failed to normalize name. Reference name is empty"); /* A refname can not end with a dot or a slash */ if (*(name_end - 1) == '.' || *(name_end - 1) == '/') - return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name ends with dot or slash"); + return git__throw(GIT_EINVALIDREFNAME, + "Failed to normalize name. Reference name ends with dot or slash"); while (current < name_end && out_size) { - if (check_valid_ref_char(*current)) - return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name contains invalid characters"); + if (!is_valid_ref_char(*current)) + return git__throw(GIT_EINVALIDREFNAME, + "Failed to normalize name. " + "Reference name contains invalid characters"); if (buffer_out > buffer_out_start) { char prev = *(buffer_out - 1); /* A refname can not start with a dot nor contain a double dot */ if (*current == '.' && ((prev == '.') || (prev == '/'))) - return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name starts with a dot or contains a double dot"); + return git__throw(GIT_EINVALIDREFNAME, + "Failed to normalize name. " + "Reference name starts with a dot or contains a double dot"); /* '@{' is forbidden within a refname */ if (*current == '{' && prev == '@') - return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name contains '@{'"); + return git__throw(GIT_EINVALIDREFNAME, + "Failed to normalize name. Reference name contains '@{'"); /* Prevent multiple slashes from being added to the output */ if (*current == '/' && prev == '/') { @@ -1888,13 +1651,18 @@ static int normalize_name(char *buffer_out, size_t out_size, const char *name, i /* 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) && strcmp(name, GIT_MERGE_HEAD_FILE) - && strcmp(name, GIT_FETCH_HEAD_FILE))) - return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name contains no slashes"); + 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) + return git__throw(GIT_EINVALIDREFNAME, + "Failed to normalize name. Reference name contains no slashes"); /* A refname can not end with ".lock" */ if (!git__suffixcmp(name, GIT_FILELOCK_EXTENSION)) - return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name ends with '.lock'"); + return git__throw(GIT_EINVALIDREFNAME, + "Failed to normalize name. Reference name ends with '.lock'"); *buffer_out = '\0'; @@ -1904,17 +1672,25 @@ static int normalize_name(char *buffer_out, size_t out_size, const char *name, i */ if (is_oid_ref && !(git__prefixcmp(buffer_out_start, GIT_REFS_DIR) || strcmp(buffer_out_start, GIT_HEAD_FILE))) - return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name does not start with 'refs/'"); + return git__throw(GIT_EINVALIDREFNAME, + "Failed to normalize name. " + "Reference name does not start with 'refs/'"); return GIT_SUCCESS; } -int git_reference__normalize_name(char *buffer_out, size_t out_size, const char *name) +int git_reference__normalize_name( + char *buffer_out, + size_t out_size, + const char *name) { return normalize_name(buffer_out, out_size, name, 0); } -int git_reference__normalize_name_oid(char *buffer_out, size_t out_size, const char *name) +int git_reference__normalize_name_oid( + char *buffer_out, + size_t out_size, + const char *name) { return normalize_name(buffer_out, out_size, name, 1); } diff --git a/src/refs.h b/src/refs.h index db0f5c4df..02e336e54 100644 --- a/src/refs.h +++ b/src/refs.h @@ -33,19 +33,23 @@ #define GIT_REFNAME_MAX 1024 struct git_reference { + unsigned int flags; git_repository *owner; char *name; + time_t mtime; + + union { + git_oid oid; + char *symbolic; + } target; }; typedef struct { git_hashtable *packfile; - git_hashtable *loose_cache; time_t packfile_time; } git_refcache; - void git_repository__refcache_free(git_refcache *refs); -int git_repository__refcache_init(git_refcache *refs); int git_reference__normalize_name(char *buffer_out, size_t out_size, const char *name); int git_reference__normalize_name_oid(char *buffer_out, size_t out_size, const char *name); diff --git a/src/repository.c b/src/repository.c index 6c75aa190..f8195e2d9 100644 --- a/src/repository.c +++ b/src/repository.c @@ -172,11 +172,6 @@ static git_repository *repository_alloc(void) return NULL; } - if (git_repository__refcache_init(&repo->references) < GIT_SUCCESS) { - git__free(repo); - return NULL; - } - return repo; } @@ -777,7 +772,7 @@ int git_repository_head_orphan(git_repository *repo) int git_repository_is_empty(git_repository *repo) { - git_reference *head, *branch; + git_reference *head = NULL, *branch = NULL; int error; error = git_reference_lookup(&head, repo, "HEAD"); diff --git a/tests/t10-refs.c b/tests/t10-refs.c index 12632b02b..e5e722992 100644 --- a/tests/t10-refs.c +++ b/tests/t10-refs.c @@ -143,22 +143,23 @@ BEGIN_TEST(readsym2, "lookup the HEAD and resolve the master branch") must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); must_pass(git_reference_lookup(&reference, repo, head_tracker_sym_ref_name)); - must_pass(git_reference_resolve(&resolved_ref, reference)); - comp_base_ref = resolved_ref; + must_pass(git_reference_resolve(&comp_base_ref, reference)); + git_reference_free(reference); must_pass(git_reference_lookup(&reference, repo, GIT_HEAD_FILE)); must_pass(git_reference_resolve(&resolved_ref, reference)); must_pass(git_oid_cmp(git_reference_oid(comp_base_ref), git_reference_oid(resolved_ref))); + git_reference_free(reference); + git_reference_free(resolved_ref); must_pass(git_reference_lookup(&reference, repo, current_head_target)); must_pass(git_reference_resolve(&resolved_ref, reference)); must_pass(git_oid_cmp(git_reference_oid(comp_base_ref), git_reference_oid(resolved_ref))); - - git_repository_free(repo); - git_reference_free(reference); git_reference_free(resolved_ref); + git_reference_free(comp_base_ref); + git_repository_free(repo); END_TEST BEGIN_TEST(readsym3, "lookup the master branch and then the HEAD") @@ -902,6 +903,9 @@ BEGIN_TEST(delete1, "can delete a just packed reference") /* Pack all existing references */ must_pass(git_reference_packall(repo)); + /* Reload the reference from disk */ + must_pass(git_reference_reload(ref)); + /* Ensure it's a packed reference */ must_be_true(git_reference_is_packed(ref) == 1);