Commit aa2120e9 by nulltoken Committed by Vicent Marti

Added git_reference__normalize_name() along with tests.

parent 83403e99
......@@ -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 */
......
......@@ -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;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment