mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-21 10:17:20 +00:00
Added git_reference__normalize_name() along with tests.
This commit is contained in:
parent
83403e99ba
commit
aa2120e9da
@ -53,5 +53,6 @@ typedef SSIZE_T ssize_t;
|
||||
#include "bswap.h"
|
||||
|
||||
#define GIT_PATH_MAX 4096
|
||||
#define GIT_FILELOCK_EXTENSION ".lock\0"
|
||||
|
||||
#endif /* INCLUDE_common_h__ */
|
||||
|
@ -140,6 +140,7 @@ typedef struct git_reference git_reference;
|
||||
|
||||
/** Basic type of any Git reference. */
|
||||
typedef enum {
|
||||
GIT_REF_ANY = -2, /** Reference can be an object id reference or a symbolic reference */
|
||||
GIT_REF_INVALID = -1, /** Invalid reference */
|
||||
GIT_REF_OID = 1, /** A reference which points at an object id */
|
||||
GIT_REF_SYMBOLIC = 2, /** A reference which points at another reference */
|
||||
|
104
src/refs.c
104
src/refs.c
@ -571,12 +571,13 @@ error_cleanup:
|
||||
int git_repository_lookup_ref(git_reference **ref_out, git_repository *repo, const char *name)
|
||||
{
|
||||
int error;
|
||||
char normalized_name[GIT_PATH_MAX];
|
||||
|
||||
assert(ref_out && repo && name);
|
||||
|
||||
*ref_out = NULL;
|
||||
|
||||
error = check_refname(name);
|
||||
error = git_reference__normalize_name(normalized_name, name, GIT_REF_ANY);
|
||||
if (error < GIT_SUCCESS)
|
||||
return error;
|
||||
|
||||
@ -584,7 +585,7 @@ int git_repository_lookup_ref(git_reference **ref_out, git_repository *repo, con
|
||||
* First, check if the reference is on the local cache;
|
||||
* references on the cache are assured to be up-to-date
|
||||
*/
|
||||
*ref_out = git_hashtable_lookup(repo->references.cache, name);
|
||||
*ref_out = git_hashtable_lookup(repo->references.cache, normalized_name);
|
||||
if (*ref_out != NULL)
|
||||
return GIT_SUCCESS;
|
||||
|
||||
@ -593,7 +594,7 @@ int git_repository_lookup_ref(git_reference **ref_out, git_repository *repo, con
|
||||
* If the file exists, we parse it and store it on the
|
||||
* cache.
|
||||
*/
|
||||
error = lookup_loose_ref(ref_out, repo, name);
|
||||
error = lookup_loose_ref(ref_out, repo, normalized_name);
|
||||
|
||||
if (error == GIT_SUCCESS)
|
||||
return GIT_SUCCESS;
|
||||
@ -618,7 +619,7 @@ int git_repository_lookup_ref(git_reference **ref_out, git_repository *repo, con
|
||||
return error;
|
||||
|
||||
/* check the cache again -- hopefully the reference will be there */
|
||||
*ref_out = git_hashtable_lookup(repo->references.cache, name);
|
||||
*ref_out = git_hashtable_lookup(repo->references.cache, normalized_name);
|
||||
if (*ref_out != NULL)
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
@ -653,4 +654,99 @@ void git_repository__refcache_free(git_refcache *refs)
|
||||
git_hashtable_free(refs->cache);
|
||||
}
|
||||
|
||||
static int check_valid_ref_char(char ch)
|
||||
{
|
||||
if (ch <= ' ')
|
||||
return GIT_ERROR;
|
||||
|
||||
switch (ch) {
|
||||
case '~':
|
||||
case '^':
|
||||
case ':':
|
||||
case '\\':
|
||||
case '?':
|
||||
case '[':
|
||||
return GIT_ERROR;
|
||||
break;
|
||||
|
||||
default:
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
int git_reference__normalize_name(char *buffer_out, const char *name, git_rtype type)
|
||||
{
|
||||
int error = GIT_SUCCESS;
|
||||
const char *name_end, *buffer_out_start;
|
||||
char *current;
|
||||
int contains_a_slash = 0;
|
||||
|
||||
assert(name && buffer_out);
|
||||
|
||||
buffer_out_start = buffer_out;
|
||||
current = (char *)name;
|
||||
name_end = name + strlen(name);
|
||||
|
||||
if (type == GIT_REF_INVALID)
|
||||
return GIT_EINVALIDTYPE;
|
||||
|
||||
/* A refname can not be empty */
|
||||
if (name_end == name)
|
||||
return GIT_EINVALIDREFNAME;
|
||||
|
||||
/* A refname can not end with a dot or a slash */
|
||||
if (*(name_end - 1) == '.' || *(name_end - 1) == '/')
|
||||
return GIT_EINVALIDREFNAME;
|
||||
|
||||
while (current < name_end) {
|
||||
if (check_valid_ref_char(*current))
|
||||
return GIT_EINVALIDREFNAME;
|
||||
|
||||
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_EINVALIDREFNAME;
|
||||
|
||||
/* '@{' is forbidden within a refname */
|
||||
if (*current == '{' && prev == '@')
|
||||
return GIT_EINVALIDREFNAME;
|
||||
|
||||
/* Prevent multiple slashes from being added to the output */
|
||||
if (*current == '/' && prev == '/') {
|
||||
current++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (*current == '/') {
|
||||
/* Slashes are not authorized in symbolic reference name */
|
||||
if (type == GIT_REF_SYMBOLIC) {
|
||||
return GIT_EINVALIDREFNAME;
|
||||
}
|
||||
|
||||
contains_a_slash = 1;
|
||||
}
|
||||
|
||||
*buffer_out++ = *current++;
|
||||
}
|
||||
|
||||
/* Object id refname have to contain at least one slash */
|
||||
if (type == GIT_REF_OID && !contains_a_slash)
|
||||
return GIT_EINVALIDREFNAME;
|
||||
|
||||
/* A refname can not end with ".lock" */
|
||||
if (!git__suffixcmp(name, GIT_FILELOCK_EXTENSION))
|
||||
return GIT_EINVALIDREFNAME;
|
||||
|
||||
*buffer_out = '\0';
|
||||
|
||||
/* For object id references, name has to start with refs/(heads|tags|remotes) */
|
||||
if (type == GIT_REF_OID && !(!git__prefixcmp(buffer_out_start, GIT_REFS_HEADS_DIR) ||
|
||||
!git__prefixcmp(buffer_out_start, GIT_REFS_TAGS_DIR) || !git__prefixcmp(buffer_out_start, GIT_REFS_REMOTES_DIR)))
|
||||
return GIT_EINVALIDREFNAME;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
#define GIT_REFS_DIR "refs/"
|
||||
#define GIT_REFS_HEADS_DIR GIT_REFS_DIR "heads/"
|
||||
#define GIT_REFS_TAGS_DIR GIT_REFS_DIR "tags/"
|
||||
#define GIT_REFS_REMOTES_DIR GIT_REFS_DIR "remotes/"
|
||||
|
||||
#define GIT_SYMREF "ref: "
|
||||
#define GIT_PACKEDREFS_FILE "packed-refs"
|
||||
@ -37,5 +38,6 @@ typedef struct {
|
||||
|
||||
void git_repository__refcache_free(git_refcache *refs);
|
||||
int git_repository__refcache_init(git_refcache *refs);
|
||||
int git_reference__normalize_name(char *buffer_out, const char *name, git_rtype type);
|
||||
|
||||
#endif
|
||||
|
@ -279,6 +279,63 @@ BEGIN_TEST("createref", create_new_object_id_ref)
|
||||
must_pass(gitfo_unlink(ref_path)); /* TODO: replace with git_reference_delete() when available */
|
||||
END_TEST
|
||||
|
||||
static int ensure_refname_normalized(git_rtype ref_type, const char *input_refname, const char *expected_refname)
|
||||
{
|
||||
int error = GIT_SUCCESS;
|
||||
char buffer_out[GIT_PATH_MAX];
|
||||
|
||||
error = git_reference__normalize_name(buffer_out, input_refname, ref_type);
|
||||
if (error < GIT_SUCCESS)
|
||||
return error;
|
||||
|
||||
if (expected_refname == NULL)
|
||||
return error;
|
||||
|
||||
if (strcmp(buffer_out, expected_refname))
|
||||
error = GIT_ERROR;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
BEGIN_TEST("normalizeref", normalize_unknown_ref_type)
|
||||
must_fail(ensure_refname_normalized(GIT_REF_INVALID, "a", NULL));
|
||||
END_TEST
|
||||
|
||||
BEGIN_TEST("normalizeref", normalize_object_id_ref)
|
||||
must_fail(ensure_refname_normalized(GIT_REF_OID, "a", NULL));
|
||||
must_fail(ensure_refname_normalized(GIT_REF_OID, "", NULL));
|
||||
must_fail(ensure_refname_normalized(GIT_REF_OID, "refs/heads/a/", NULL));
|
||||
must_fail(ensure_refname_normalized(GIT_REF_OID, "refs/heads/a.", NULL));
|
||||
must_fail(ensure_refname_normalized(GIT_REF_OID, "refs/heads/a.lock", NULL));
|
||||
must_fail(ensure_refname_normalized(GIT_REF_OID, "refs/dummy/a", NULL));
|
||||
must_pass(ensure_refname_normalized(GIT_REF_OID, "refs/tags/a", "refs/tags/a"));
|
||||
must_pass(ensure_refname_normalized(GIT_REF_OID, "refs/heads/a/b", "refs/heads/a/b"));
|
||||
must_pass(ensure_refname_normalized(GIT_REF_OID, "refs/heads/a./b", "refs/heads/a./b"));
|
||||
must_fail(ensure_refname_normalized(GIT_REF_OID, "refs/heads/foo?bar", NULL));
|
||||
must_fail(ensure_refname_normalized(GIT_REF_OID, "refs/heads\foo", NULL));
|
||||
must_pass(ensure_refname_normalized(GIT_REF_OID, "refs/heads/v@ation", "refs/heads/v@ation"));
|
||||
must_pass(ensure_refname_normalized(GIT_REF_OID, "refs///heads///a", "refs/heads/a"));
|
||||
must_fail(ensure_refname_normalized(GIT_REF_OID, "refs/heads/.a/b", NULL));
|
||||
must_fail(ensure_refname_normalized(GIT_REF_OID, "refs/heads/foo/../bar", NULL));
|
||||
must_fail(ensure_refname_normalized(GIT_REF_OID, "refs/heads/foo..bar", NULL));
|
||||
must_fail(ensure_refname_normalized(GIT_REF_OID, "refs/heads/./foo", NULL));
|
||||
must_fail(ensure_refname_normalized(GIT_REF_OID, "refs/heads/v@{ation", NULL));
|
||||
END_TEST
|
||||
|
||||
BEGIN_TEST("normalizeref", normalize_symbolic_ref)
|
||||
must_pass(ensure_refname_normalized(GIT_REF_SYMBOLIC, "a", "a"));
|
||||
must_fail(ensure_refname_normalized(GIT_REF_SYMBOLIC, "", NULL));
|
||||
must_fail(ensure_refname_normalized(GIT_REF_SYMBOLIC, "a/b", NULL));
|
||||
must_fail(ensure_refname_normalized(GIT_REF_SYMBOLIC, "heads\foo", NULL));
|
||||
END_TEST
|
||||
|
||||
|
||||
BEGIN_TEST("normalizeref", normalize_any_ref) /* Slash related rules do not apply, neither do 'refs' prefix related rules */
|
||||
must_pass(ensure_refname_normalized(GIT_REF_ANY, "a", "a"));
|
||||
must_pass(ensure_refname_normalized(GIT_REF_ANY, "a/b", "a/b"));
|
||||
must_pass(ensure_refname_normalized(GIT_REF_ANY, "refs///heads///a", "refs/heads/a"));
|
||||
END_TEST
|
||||
|
||||
git_testsuite *libgit2_suite_refs(void)
|
||||
{
|
||||
git_testsuite *suite = git_testsuite_new("References");
|
||||
@ -293,6 +350,10 @@ git_testsuite *libgit2_suite_refs(void)
|
||||
ADD_TEST(suite, "readpackedref", packed_exists_but_more_recent_loose_reference_is_retrieved);
|
||||
ADD_TEST(suite, "createref", create_new_symbolic_ref);
|
||||
ADD_TEST(suite, "createref", create_new_object_id_ref);
|
||||
ADD_TEST(suite, "normalizeref", normalize_unknown_ref_type);
|
||||
ADD_TEST(suite, "normalizeref", normalize_object_id_ref);
|
||||
ADD_TEST(suite, "normalizeref", normalize_symbolic_ref);
|
||||
ADD_TEST(suite, "normalizeref", normalize_any_ref);
|
||||
|
||||
return suite;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user