summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authornulltoken <emeric.fermas@gmail.com>2011-02-10 15:08:00 +0100
committerVicent Marti <tanoku@gmail.com>2011-03-03 20:23:48 +0200
commitaa2120e9da45017c6fe0126d6e9b1ee20ff40037 (patch)
treebfd83227689fc95eb5fdfa4958d2066c121ee425
parent83403e99bae5fe415f30cc834e66c2e589ca46e0 (diff)
downloadlibgit2-aa2120e9da45017c6fe0126d6e9b1ee20ff40037.tar.gz
Added git_reference__normalize_name() along with tests.
-rw-r--r--src/common.h1
-rw-r--r--src/git2/types.h1
-rw-r--r--src/refs.c104
-rw-r--r--src/refs.h2
-rw-r--r--tests/t10-refs.c61
5 files changed, 165 insertions, 4 deletions
diff --git a/src/common.h b/src/common.h
index 1ca00471b..1aede7367 100644
--- a/src/common.h
+++ b/src/common.h
@@ -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__ */
diff --git a/src/git2/types.h b/src/git2/types.h
index 4f66742f0..7bf4d189e 100644
--- a/src/git2/types.h
+++ b/src/git2/types.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 */
diff --git a/src/refs.c b/src/refs.c
index b95ec70cf..46589e04d 100644
--- a/src/refs.c
+++ b/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;
+}
diff --git a/src/refs.h b/src/refs.h
index 70196aa95..2e9f340b8 100644
--- a/src/refs.h
+++ b/src/refs.h
@@ -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
diff --git a/tests/t10-refs.c b/tests/t10-refs.c
index 2d055c978..45ee09a79 100644
--- a/tests/t10-refs.c
+++ b/tests/t10-refs.c
@@ -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;
}