diff --git a/include/git2/refdb.h b/include/git2/refdb.h index 0586b119e..76b8fda0d 100644 --- a/include/git2/refdb.h +++ b/include/git2/refdb.h @@ -35,7 +35,12 @@ GIT_EXTERN(git_reference *) git_reference__alloc( git_refdb *refdb, const char *name, const git_oid *oid, - const char *symbolic); + const git_oid *peel); + +GIT_EXTERN(git_reference *) git_reference__alloc_symbolic( + git_refdb *refdb, + const char *name, + const char *target); /** * Create a new reference database with no backends. diff --git a/include/git2/refs.h b/include/git2/refs.h index e0451ba82..1ff0d4544 100644 --- a/include/git2/refs.h +++ b/include/git2/refs.h @@ -132,6 +132,17 @@ GIT_EXTERN(int) git_reference_create(git_reference **out, git_repository *repo, */ GIT_EXTERN(const git_oid *) git_reference_target(const git_reference *ref); +/** + * Return the peeled OID target of this reference. + * + * This peeled OID only applies to direct references that point to + * a hard Tag object: it is the result of peeling such Tag. + * + * @param ref The reference + * @return a pointer to the oid if available, NULL otherwise + */ +GIT_EXTERN(const git_oid *) git_reference_target_peel(const git_reference *ref); + /** * Get full name to the reference pointed to by a symbolic reference. * diff --git a/src/refdb_fs.c b/src/refdb_fs.c index f00bd72a0..730148a8f 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -430,7 +430,7 @@ static int loose_lookup( goto done; } - *out = git_reference__alloc(backend->refdb, ref_name, NULL, target); + *out = git_reference__alloc_symbolic(backend->refdb, ref_name, target); } else { if ((error = loose_parse_oid(&oid, &ref_file)) < 0) goto done; @@ -484,7 +484,8 @@ static int packed_lookup( if ((error = packed_map_entry(&entry, &pos, backend, ref_name)) < 0) return error; - if ((*out = git_reference__alloc(backend->refdb, ref_name, &entry->oid, NULL)) == NULL) + if ((*out = git_reference__alloc(backend->refdb, ref_name, + &entry->oid, &entry->peel)) == NULL) return -1; return 0; @@ -644,7 +645,7 @@ static int loose_write(refdb_fs_backend *backend, const git_reference *ref) if (ref->type == GIT_REF_OID) { char oid[GIT_OID_HEXSZ + 1]; - git_oid_fmt(oid, &ref->target.oid); + git_oid_fmt(oid, &ref->target.direct.oid); oid[GIT_OID_HEXSZ] = '\0'; git_filebuf_printf(&file, "%s\n", oid); diff --git a/src/refs.c b/src/refs.c index b1f679632..290b89b41 100644 --- a/src/refs.c +++ b/src/refs.c @@ -31,37 +31,62 @@ enum { GIT_PACKREF_WAS_LOOSE = 2 }; +static git_reference *alloc_ref(git_refdb *refdb, const char *name) +{ + git_reference *ref; + size_t namelen = strlen(name); + + if ((ref = git__calloc(1, sizeof(git_reference) + namelen + 1)) == NULL) + return NULL; + + ref->db = refdb; + memcpy(ref->name, name, namelen + 1); + + return ref; +} + +git_reference *git_reference__alloc_symbolic( + git_refdb *refdb, + const char *name, + const char *target) +{ + git_reference *ref; + + assert(refdb && name && target); + + ref = alloc_ref(refdb, name); + if (!ref) + return NULL; + + ref->type = GIT_REF_SYMBOLIC; + + if ((ref->target.symbolic = git__strdup(target)) == NULL) { + git__free(ref); + return NULL; + } + + return ref; +} git_reference *git_reference__alloc( git_refdb *refdb, const char *name, const git_oid *oid, - const char *symbolic) + const git_oid *peel) { git_reference *ref; - size_t namelen; - assert(refdb && name && ((oid && !symbolic) || (!oid && symbolic))); + assert(refdb && name && oid); - namelen = strlen(name); - - if ((ref = git__calloc(1, sizeof(git_reference) + namelen + 1)) == NULL) + ref = alloc_ref(refdb, name); + if (!ref) return NULL; - if (oid) { - ref->type = GIT_REF_OID; - git_oid_cpy(&ref->target.oid, oid); - } else { - ref->type = GIT_REF_SYMBOLIC; + ref->type = GIT_REF_OID; + git_oid_cpy(&ref->target.direct.oid, oid); - if ((ref->target.symbolic = git__strdup(symbolic)) == NULL) { - git__free(ref); - return NULL; - } - } - - ref->db = refdb; - memcpy(ref->name, name, namelen + 1); + if (peel != NULL) + git_oid_cpy(&ref->target.direct.peel, peel); return ref; } @@ -71,13 +96,8 @@ void git_reference_free(git_reference *reference) if (reference == NULL) return; - if (reference->type == GIT_REF_SYMBOLIC) { + if (reference->type == GIT_REF_SYMBOLIC) git__free(reference->target.symbolic); - reference->target.symbolic = NULL; - } - - reference->db = NULL; - reference->type = GIT_REF_INVALID; git__free(reference); } @@ -302,7 +322,17 @@ const git_oid *git_reference_target(const git_reference *ref) if (ref->type != GIT_REF_OID) return NULL; - return &ref->target.oid; + return &ref->target.direct.oid; +} + +const git_oid *git_reference_target_peel(const git_reference *ref) +{ + assert(ref); + + if (ref->type != GIT_REF_OID || git_oid_iszero(&ref->target.direct.peel)) + return NULL; + + return &ref->target.direct.peel; } const char *git_reference_symbolic_target(const git_reference *ref) @@ -335,8 +365,15 @@ static int reference__create( (error = reference_can_write(repo, normalized, NULL, force)) < 0 || (error = git_repository_refdb__weakptr(&refdb, repo)) < 0) return error; + + if (oid != NULL) { + assert(symbolic == NULL); + ref = git_reference__alloc(refdb, name, oid, NULL); + } else { + ref = git_reference__alloc_symbolic(refdb, name, symbolic); + } - if ((ref = git_reference__alloc(refdb, name, oid, symbolic)) == NULL) + if (ref == NULL) return -1; if ((error = git_refdb_write(refdb, ref)) < 0) { @@ -437,8 +474,6 @@ int git_reference_rename( char normalized[GIT_REFNAME_MAX]; bool should_head_be_updated = false; git_reference *result = NULL; - git_oid *oid; - const char *symbolic; int error = 0; int reference_has_log; @@ -447,7 +482,8 @@ int git_reference_rename( 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 || + 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; @@ -455,14 +491,15 @@ int git_reference_rename( * Create the new reference. */ if (ref->type == GIT_REF_OID) { - oid = &ref->target.oid; - symbolic = NULL; + result = git_reference__alloc(ref->db, new_name, + &ref->target.direct.oid, &ref->target.direct.peel); + } else if (ref->type == GIT_REF_SYMBOLIC) { + result = git_reference__alloc_symbolic(ref->db, new_name, ref->target.symbolic); } else { - oid = NULL; - symbolic = ref->target.symbolic; + assert(0); } - - if ((result = git_reference__alloc(ref->db, new_name, oid, symbolic)) == NULL) + + if (result == NULL) return -1; /* Check if we have to update HEAD. */ @@ -509,11 +546,17 @@ on_error: int git_reference_resolve(git_reference **ref_out, const git_reference *ref) { - if (ref->type == GIT_REF_OID) + switch (git_reference_type(ref)) { + case GIT_REF_OID: return git_reference_lookup(ref_out, ref->db->repo, ref->name); - else - return git_reference_lookup_resolved(ref_out, ref->db->repo, - ref->target.symbolic, -1); + + case GIT_REF_SYMBOLIC: + return git_reference_lookup_resolved(ref_out, ref->db->repo, ref->target.symbolic, -1); + + default: + giterr_set(GITERR_REFERENCE, "Invalid reference"); + return -1; + } } int git_reference_foreach( @@ -778,16 +821,20 @@ int git_reference__normalize_name_lax( int git_reference_cmp(git_reference *ref1, git_reference *ref2) { + git_ref_t type1, type2; assert(ref1 && ref2); - /* let's put symbolic refs before OIDs */ - if (ref1->type != ref2->type) - return (ref1->type == GIT_REF_SYMBOLIC) ? -1 : 1; + type1 = git_reference_type(ref1); + type2 = git_reference_type(ref2); - if (ref1->type == GIT_REF_SYMBOLIC) + /* let's put symbolic refs before OIDs */ + if (type1 != type2) + return (type1 == GIT_REF_SYMBOLIC) ? -1 : 1; + + if (type1 == GIT_REF_SYMBOLIC) return strcmp(ref1->target.symbolic, ref2->target.symbolic); - return git_oid_cmp(&ref1->target.oid, &ref2->target.oid); + return git_oid_cmp(&ref1->target.direct.oid, &ref2->target.direct.oid); } static int reference__update_terminal( @@ -905,15 +952,6 @@ static int peel_error(int error, git_reference *ref, const char* 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, @@ -925,10 +963,22 @@ int git_reference_peel( assert(ref); - if ((error = git_reference_resolve(&resolved, ref)) < 0) - return peel_error(error, ref, "Cannot resolve reference"); + if (ref->type == GIT_REF_OID) { + resolved = ref; + } else { + if ((error = git_reference_resolve(&resolved, ref)) < 0) + return peel_error(error, ref, "Cannot resolve reference"); + } - if ((error = reference_target(&target, resolved)) < 0) { + if (!git_oid_iszero(&resolved->target.direct.peel)) { + error = git_object_lookup(&target, + git_reference_owner(ref), &resolved->target.direct.peel, GIT_OBJ_ANY); + } else { + error = git_object_lookup(&target, + git_reference_owner(ref), &resolved->target.direct.oid, GIT_OBJ_ANY); + } + + if (error < 0) { peel_error(error, ref, "Cannot retrieve reference target"); goto cleanup; } @@ -940,7 +990,10 @@ int git_reference_peel( cleanup: git_object_free(target); - git_reference_free(resolved); + + if (resolved != ref) + git_reference_free(resolved); + return error; } diff --git a/src/refs.h b/src/refs.h index 7d63c3fbd..b0aa56a54 100644 --- a/src/refs.h +++ b/src/refs.h @@ -49,11 +49,14 @@ struct git_reference { git_refdb *db; - git_ref_t type; union { - git_oid oid; + struct { + git_oid oid; + git_oid peel; + } direct; + char *symbolic; } target; diff --git a/tests-clar/refdb/inmemory.c b/tests-clar/refdb/inmemory.c index 6f5651964..2cccd8eb2 100644 --- a/tests-clar/refdb/inmemory.c +++ b/tests-clar/refdb/inmemory.c @@ -135,7 +135,7 @@ int foreach_test(const char *ref_name, void *payload) else if (*i == 2) cl_git_pass(git_oid_fromstr(&expected, "763d71aadf09a7951596c9746c024e7eece7c7af")); - cl_assert(git_oid_cmp(&expected, &ref->target.oid) == 0); + cl_assert(git_oid_cmp(&expected, git_reference_target(ref)) == 0); ++(*i); @@ -176,7 +176,7 @@ int delete_test(const char *ref_name, void *payload) cl_git_pass(git_reference_lookup(&ref, repo, ref_name)); cl_git_pass(git_oid_fromstr(&expected, "e90810b8df3e80c413d903f631643c716887138d")); - cl_assert(git_oid_cmp(&expected, &ref->target.oid) == 0); + cl_assert(git_oid_cmp(&expected, git_reference_target(ref)) == 0); ++(*i); diff --git a/tests-clar/refdb/testdb.c b/tests-clar/refdb/testdb.c index a8e7ba5fe..e60f6790e 100644 --- a/tests-clar/refdb/testdb.c +++ b/tests-clar/refdb/testdb.c @@ -98,12 +98,16 @@ static int refdb_test_backend__lookup( git_vector_foreach(&backend->refs, i, entry) { if (strcmp(entry->name, ref_name) == 0) { - const git_oid *oid = - entry->type == GIT_REF_OID ? &entry->target.oid : NULL; - const char *symbolic = - entry->type == GIT_REF_SYMBOLIC ? entry->target.symbolic : NULL; - - if ((*out = git_reference__alloc(backend->refdb, ref_name, oid, symbolic)) == NULL) + + if (entry->type == GIT_REF_OID) { + *out = git_reference__alloc(backend->refdb, ref_name, + &entry->target.oid, NULL); + } else if (entry->type == GIT_REF_SYMBOLIC) { + *out = git_reference__alloc_symbolic(backend->refdb, ref_name, + entry->target.symbolic); + } + + if (*out == NULL) return -1; return 0;