diff --git a/include/git2/refs.h b/include/git2/refs.h index 73b32a9e2..10b73f0c9 100644 --- a/include/git2/refs.h +++ b/include/git2/refs.h @@ -392,7 +392,8 @@ enum { /** * Control whether one-level refnames are accepted * (i.e., refnames that do not contain multiple /-separated - * components) + * components). Those are expected to be written only using + * uppercase letters and underscore (FETCH_HEAD, ...) */ GIT_REF_FORMAT_ALLOW_ONELEVEL = (1 << 0), @@ -414,8 +415,6 @@ enum { * Once normalized, if the reference name is valid, it will be * returned in the user allocated buffer. * - * TODO: Implement handling of GIT_REF_FORMAT_REFSPEC_PATTERN - * * @param buffer_out The user allocated buffer where the * normalized name will be stored. * @@ -454,6 +453,16 @@ GIT_EXTERN(int) git_reference_peel( git_reference *ref, git_otype type); +/** + * Ensure the reference name is well-formed. + * + * @param refname name to be checked. + * + * @return 1 if the reference name is acceptable; 0 if it isn't + */ +GIT_EXTERN(int) git_reference_is_valid_name( + const char *refname); + /** @} */ GIT_END_DECL #endif diff --git a/src/refs.c b/src/refs.c index 74c40e850..693870a0b 100644 --- a/src/refs.c +++ b/src/refs.c @@ -1098,7 +1098,7 @@ int git_reference_lookup_resolved( scan->name = git__calloc(GIT_REFNAME_MAX + 1, sizeof(char)); GITERR_CHECK_ALLOC(scan->name); - if ((result = git_reference__normalize_name( + if ((result = git_reference__normalize_name_lax( scan->name, GIT_REFNAME_MAX, name)) < 0) { @@ -1200,7 +1200,7 @@ int git_reference_create_symbolic( char normalized[GIT_REFNAME_MAX]; git_reference *ref = NULL; - if (git_reference__normalize_name( + if (git_reference__normalize_name_lax( normalized, sizeof(normalized), name) < 0) @@ -1239,7 +1239,7 @@ int git_reference_create_oid( git_reference *ref = NULL; char normalized[GIT_REFNAME_MAX]; - if (git_reference__normalize_name_oid( + if (git_reference__normalize_name_lax( normalized, sizeof(normalized), name) < 0) @@ -1322,7 +1322,7 @@ int git_reference_set_target(git_reference *ref, const char *target) return -1; } - if (git_reference__normalize_name( + if (git_reference__normalize_name_lax( normalized, sizeof(normalized), target)) @@ -1584,106 +1584,175 @@ static int is_valid_ref_char(char ch) } } +static int ensure_segment_validity(const char *name) +{ + const char *current = name; + char prev = '\0'; + + if (*current == '.') + return -1; /* Refname starts with "." */ + + for (current = name; ; current++) { + if (*current == '\0' || *current == '/') + break; + + if (!is_valid_ref_char(*current)) + return -1; /* Illegal character in refname */ + + if (prev == '.' && *current == '.') + return -1; /* Refname contains ".." */ + + if (prev == '@' && *current == '{') + return -1; /* Refname contains "@{" */ + + prev = *current; + } + + return current - name; +} + +static bool is_all_caps_and_underscore(const char *name, int len) +{ + int i; + char c; + + assert(name && len > 0); + + for (i = 0; i < len; i++) + { + c = name[i]; + if ((c < 'A' || c > 'Z') && c != '_') + return false; + } + + if (*name == '_' || name[len - 1] == '_') + return false; + + return true; +} + +int git_reference__normalize_name( + git_buf *buf, + const char *name, + unsigned int flags) +{ + // Inspired from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/refs.c#L36-100 + + char *current; + int segment_len, segments_count = 0, error = -1; + unsigned int process_flags; + bool normalize = (buf != NULL); + assert(name); + + process_flags = flags; + current = (char *)name; + + if (normalize) + git_buf_clear(buf); + + while (true) { + segment_len = ensure_segment_validity(current); + if (segment_len < 0) { + if ((process_flags & GIT_REF_FORMAT_REFSPEC_PATTERN) && + current[0] == '*' && + (current[1] == '\0' || current[1] == '/')) { + /* Accept one wildcard as a full refname component. */ + process_flags &= ~GIT_REF_FORMAT_REFSPEC_PATTERN; + segment_len = 1; + } else + goto cleanup; + } + + if (segment_len > 0) { + if (normalize) { + int cur_len = git_buf_len(buf); + + git_buf_joinpath(buf, git_buf_cstr(buf), current); + git_buf_truncate(buf, + cur_len + segment_len + (segments_count ? 1 : 0)); + + if (git_buf_oom(buf)) + goto cleanup; + } + + segments_count++; + } + + if (current[segment_len] == '\0') + break; + + current += segment_len + 1; + } + + /* A refname can not be empty */ + if (segment_len == 0 && segments_count == 0) + goto cleanup; + + /* A refname can not end with "." */ + if (current[segment_len - 1] == '.') + goto cleanup; + + /* A refname can not end with "/" */ + if (current[segment_len - 1] == '/') + goto cleanup; + + /* A refname can not end with ".lock" */ + if (!git__suffixcmp(name, GIT_FILELOCK_EXTENSION)) + goto cleanup; + + if ((segments_count == 1 ) && !(flags & GIT_REF_FORMAT_ALLOW_ONELEVEL)) + goto cleanup; + + if ((segments_count == 1 ) && + !(is_all_caps_and_underscore(name, segment_len) || + ((flags & GIT_REF_FORMAT_REFSPEC_PATTERN) && !strcmp("*", name)))) + goto cleanup; + + if ((segments_count > 1) + && (is_all_caps_and_underscore(name, strchr(name, '/') - name))) + goto cleanup; + + error = 0; + +cleanup: + if (error) + giterr_set( + GITERR_REFERENCE, + "The given reference name '%s' is not valid", name); + + return error; +} + int git_reference_normalize_name( char *buffer_out, size_t buffer_size, const char *name, unsigned int flags) { - const char *name_end, *buffer_out_start; - const char *current; - int contains_a_slash = 0; + git_buf buf = GIT_BUF_INIT; + int error; - assert(name && buffer_out); + if ((error = git_reference__normalize_name(&buf, name, flags)) < 0) + goto cleanup; - if (flags & GIT_REF_FORMAT_REFSPEC_PATTERN) { - giterr_set(GITERR_INVALID, "Unimplemented"); - return -1; - } - - buffer_out_start = buffer_out; - current = name; - name_end = name + strlen(name); - - /* Terminating null byte */ - buffer_size--; - - /* A refname can not be empty */ - if (name_end == name) - goto invalid_name; - - /* A refname can not end with a dot or a slash */ - if (*(name_end - 1) == '.' || *(name_end - 1) == '/') - goto invalid_name; - - while (current < name_end && buffer_size > 0) { - if (!is_valid_ref_char(*current)) - goto invalid_name; - - 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 == '/'))) - goto invalid_name; - - /* '@{' is forbidden within a refname */ - if (*current == '{' && prev == '@') - goto invalid_name; - - /* Prevent multiple slashes from being added to the output */ - if (*current == '/' && prev == '/') { - current++; - continue; - } - } - - if (*current == '/') { - if (buffer_out > buffer_out_start) - contains_a_slash = 1; - else { - current++; - continue; - } - } - - *buffer_out++ = *current++; - buffer_size--; - } - - if (current < name_end) { + if (git_buf_len(&buf) > buffer_size - 1) { giterr_set( GITERR_REFERENCE, "The provided buffer is too short to hold the normalization of '%s'", name); - return GIT_EBUFS; + error = GIT_EBUFS; + goto cleanup; } - /* 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 (!(flags & GIT_REF_FORMAT_ALLOW_ONELEVEL) && - !contains_a_slash && - strcmp(name, GIT_HEAD_FILE) != 0 && - strcmp(name, GIT_MERGE_HEAD_FILE) != 0 && - strcmp(name, GIT_FETCH_HEAD_FILE) != 0) - goto invalid_name; + git_buf_copy_cstr(buffer_out, buffer_size, &buf); - /* A refname can not end with ".lock" */ - if (!git__suffixcmp(name, GIT_FILELOCK_EXTENSION)) - goto invalid_name; + error = 0; - *buffer_out = '\0'; - - return 0; - -invalid_name: - giterr_set( - GITERR_REFERENCE, - "The given reference name '%s' is not valid", name); - return -1; +cleanup: + git_buf_free(&buf); + return error; } -int git_reference__normalize_name( +int git_reference__normalize_name_lax( char *buffer_out, size_t out_size, const char *name) @@ -1694,19 +1763,6 @@ int git_reference__normalize_name( name, GIT_REF_FORMAT_ALLOW_ONELEVEL); } - -int git_reference__normalize_name_oid( - char *buffer_out, - size_t out_size, - const char *name) -{ - return git_reference_normalize_name( - buffer_out, - out_size, - name, - GIT_REF_FORMAT_NORMAL); -} - #define GIT_REF_TYPEMASK (GIT_REF_OID | GIT_REF_SYMBOLIC) int git_reference_cmp(git_reference *ref1, git_reference *ref2) @@ -1895,3 +1951,19 @@ cleanup: git_reference_free(resolved); return error; } + +int git_reference__is_valid_name( + const char *refname, + unsigned int flags) +{ + giterr_clear(); + return git_reference__normalize_name(NULL, refname, flags) == 0; +} + +int git_reference_is_valid_name( + const char *refname) +{ + return git_reference__is_valid_name( + refname, + GIT_REF_FORMAT_ALLOW_ONELEVEL); +} diff --git a/src/refs.h b/src/refs.h index 082350278..54359f07b 100644 --- a/src/refs.h +++ b/src/refs.h @@ -11,6 +11,7 @@ #include "git2/oid.h" #include "git2/refs.h" #include "strmap.h" +#include "buffer.h" #define GIT_REFS_DIR "refs/" #define GIT_REFS_HEADS_DIR GIT_REFS_DIR "heads/" @@ -52,8 +53,9 @@ typedef struct { void git_repository__refcache_free(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); +int git_reference__normalize_name_lax(char *buffer_out, size_t out_size, const char *name); +int git_reference__normalize_name(git_buf *buf, const char *name, unsigned int flags); +int git_reference__is_valid_name(const char *refname, unsigned int flags); int git_reference__update(git_repository *repo, const git_oid *oid, const char *ref_name); /** diff --git a/src/refspec.c b/src/refspec.c index b6b1158b7..1265c566c 100644 --- a/src/refspec.c +++ b/src/refspec.c @@ -11,6 +11,119 @@ #include "refspec.h" #include "util.h" #include "posix.h" +#include "refs.h" + +int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch) +{ + // Ported from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/remote.c#L518-636 + + size_t llen; + int is_glob = 0; + const char *lhs, *rhs; + int flags; + + assert(refspec && input); + + memset(refspec, 0x0, sizeof(git_refspec)); + + lhs = input; + if (*lhs == '+') { + refspec->force = 1; + lhs++; + } + + rhs = strrchr(lhs, ':'); + + /* + * Before going on, special case ":" (or "+:") as a refspec + * for matching refs. + */ + if (!is_fetch && rhs == lhs && rhs[1] == '\0') { + refspec->matching = 1; + return 0; + } + + if (rhs) { + size_t rlen = strlen(++rhs); + is_glob = (1 <= rlen && strchr(rhs, '*')); + refspec->dst = git__strndup(rhs, rlen); + } + + llen = (rhs ? (size_t)(rhs - lhs - 1) : strlen(lhs)); + if (1 <= llen && memchr(lhs, '*', llen)) { + if ((rhs && !is_glob) || (!rhs && is_fetch)) + goto invalid; + is_glob = 1; + } else if (rhs && is_glob) + goto invalid; + + refspec->pattern = is_glob; + refspec->src = git__strndup(lhs, llen); + flags = GIT_REF_FORMAT_ALLOW_ONELEVEL + | (is_glob ? GIT_REF_FORMAT_REFSPEC_PATTERN : 0); + + if (is_fetch) { + /* + * LHS + * - empty is allowed; it means HEAD. + * - otherwise it must be a valid looking ref. + */ + if (!*refspec->src) + ; /* empty is ok */ + else if (!git_reference__is_valid_name(refspec->src, flags)) + goto invalid; + /* + * RHS + * - missing is ok, and is same as empty. + * - empty is ok; it means not to store. + * - otherwise it must be a valid looking ref. + */ + if (!refspec->dst) + ; /* ok */ + else if (!*refspec->dst) + ; /* ok */ + else if (!git_reference__is_valid_name(refspec->dst, flags)) + goto invalid; + } else { + /* + * LHS + * - empty is allowed; it means delete. + * - when wildcarded, it must be a valid looking ref. + * - otherwise, it must be an extended SHA-1, but + * there is no existing way to validate this. + */ + if (!*refspec->src) + ; /* empty is ok */ + else if (is_glob) { + if (!git_reference__is_valid_name(refspec->src, flags)) + goto invalid; + } + else { + ; /* anything goes, for now */ + } + /* + * RHS + * - missing is allowed, but LHS then must be a + * valid looking ref. + * - empty is not allowed. + * - otherwise it must be a valid looking ref. + */ + if (!refspec->dst) { + if (!git_reference__is_valid_name(refspec->src, flags)) + goto invalid; + } else if (!*refspec->dst) { + goto invalid; + } else { + if (!git_reference__is_valid_name(refspec->dst, flags)) + goto invalid; + } + } + + return 0; + + invalid: + return -1; +} int git_refspec_parse(git_refspec *refspec, const char *str) { diff --git a/src/refspec.h b/src/refspec.h index 2db504910..2f46b3e59 100644 --- a/src/refspec.h +++ b/src/refspec.h @@ -20,6 +20,10 @@ struct git_refspec { }; int git_refspec_parse(struct git_refspec *refspec, const char *str); +int git_refspec__parse( + struct git_refspec *refspec, + const char *str, + bool is_fetch); /** * Transform a reference to its target following the refspec's rules, diff --git a/src/revparse.c b/src/revparse.c index 17266b944..5e2db99cd 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -50,6 +50,11 @@ static int disambiguate_refname(git_reference **out, git_repository *repo, const if ((error = git_buf_printf(&refnamebuf, formatters[i], git_buf_cstr(&name))) < 0) goto cleanup; + if (!git_reference_is_valid_name(git_buf_cstr(&refnamebuf))) { + error = GIT_ENOTFOUND; + continue; + } + error = git_reference_lookup_resolved(&ref, repo, git_buf_cstr(&refnamebuf), -1); if (!error) { diff --git a/tests-clar/network/refspecs.c b/tests-clar/network/refspecs.c new file mode 100644 index 000000000..bfe0af48c --- /dev/null +++ b/tests-clar/network/refspecs.c @@ -0,0 +1,83 @@ +#include "clar_libgit2.h" +#include "refspec.h" +#include "remote.h" + +static void assert_refspec(unsigned int direction, const char *input, bool is_expected_to_be_valid) +{ + git_refspec refspec; + int error; + + error = git_refspec__parse(&refspec, input, direction == GIT_DIR_FETCH); + + if (is_expected_to_be_valid) + cl_assert_equal_i(0, error); + else + cl_assert_equal_i(GIT_ERROR, error); +} + +void test_network_refspecs__parsing(void) +{ + // Ported from https://github.com/git/git/blob/abd2bde78bd994166900290434a2048e660dabed/t/t5511-refspec.sh + + assert_refspec(GIT_DIR_PUSH, "", false); + assert_refspec(GIT_DIR_PUSH, ":", true); + assert_refspec(GIT_DIR_PUSH, "::", false); + assert_refspec(GIT_DIR_PUSH, "+:", true); + + assert_refspec(GIT_DIR_FETCH, "", true); + assert_refspec(GIT_DIR_PUSH, ":", true); + assert_refspec(GIT_DIR_FETCH, "::", false); + + assert_refspec(GIT_DIR_PUSH, "refs/heads/*:refs/remotes/frotz/*", true); + assert_refspec(GIT_DIR_PUSH, "refs/heads/*:refs/remotes/frotz", false); + assert_refspec(GIT_DIR_PUSH, "refs/heads:refs/remotes/frotz/*", false); + assert_refspec(GIT_DIR_PUSH, "refs/heads/master:refs/remotes/frotz/xyzzy", true); + + /* + * These have invalid LHS, but we do not have a formal "valid sha-1 + * expression syntax checker" so they are not checked with the current + * code. They will be caught downstream anyway, but we may want to + * have tighter check later... + */ + //assert_refspec(GIT_DIR_PUSH, "refs/heads/master::refs/remotes/frotz/xyzzy", false); + //assert_refspec(GIT_DIR_PUSH, "refs/heads/maste :refs/remotes/frotz/xyzzy", false); + + assert_refspec(GIT_DIR_FETCH, "refs/heads/*:refs/remotes/frotz/*", true); + assert_refspec(GIT_DIR_FETCH, "refs/heads/*:refs/remotes/frotz", false); + assert_refspec(GIT_DIR_FETCH, "refs/heads:refs/remotes/frotz/*", false); + assert_refspec(GIT_DIR_FETCH, "refs/heads/master:refs/remotes/frotz/xyzzy", true); + assert_refspec(GIT_DIR_FETCH, "refs/heads/master::refs/remotes/frotz/xyzzy", false); + assert_refspec(GIT_DIR_FETCH, "refs/heads/maste :refs/remotes/frotz/xyzzy", false); + + assert_refspec(GIT_DIR_PUSH, "master~1:refs/remotes/frotz/backup", true); + assert_refspec(GIT_DIR_FETCH, "master~1:refs/remotes/frotz/backup", false); + assert_refspec(GIT_DIR_PUSH, "HEAD~4:refs/remotes/frotz/new", true); + assert_refspec(GIT_DIR_FETCH, "HEAD~4:refs/remotes/frotz/new", false); + + assert_refspec(GIT_DIR_PUSH, "HEAD", true); + assert_refspec(GIT_DIR_FETCH, "HEAD", true); + assert_refspec(GIT_DIR_PUSH, "refs/heads/ nitfol", false); + assert_refspec(GIT_DIR_FETCH, "refs/heads/ nitfol", false); + + assert_refspec(GIT_DIR_PUSH, "HEAD:", false); + assert_refspec(GIT_DIR_FETCH, "HEAD:", true); + assert_refspec(GIT_DIR_PUSH, "refs/heads/ nitfol:", false); + assert_refspec(GIT_DIR_FETCH, "refs/heads/ nitfol:", false); + + assert_refspec(GIT_DIR_PUSH, ":refs/remotes/frotz/deleteme", true); + assert_refspec(GIT_DIR_FETCH, ":refs/remotes/frotz/HEAD-to-me", true); + assert_refspec(GIT_DIR_PUSH, ":refs/remotes/frotz/delete me", false); + assert_refspec(GIT_DIR_FETCH, ":refs/remotes/frotz/HEAD to me", false); + + assert_refspec(GIT_DIR_FETCH, "refs/heads/*/for-linus:refs/remotes/mine/*-blah", false); + assert_refspec(GIT_DIR_PUSH, "refs/heads/*/for-linus:refs/remotes/mine/*-blah", false); + + assert_refspec(GIT_DIR_FETCH, "refs/heads*/for-linus:refs/remotes/mine/*", false); + assert_refspec(GIT_DIR_PUSH, "refs/heads*/for-linus:refs/remotes/mine/*", false); + + assert_refspec(GIT_DIR_FETCH, "refs/heads/*/*/for-linus:refs/remotes/mine/*", false); + assert_refspec(GIT_DIR_PUSH, "refs/heads/*/*/for-linus:refs/remotes/mine/*", false); + + assert_refspec(GIT_DIR_FETCH, "refs/heads/*/for-linus:refs/remotes/mine/*", true); + assert_refspec(GIT_DIR_PUSH, "refs/heads/*/for-linus:refs/remotes/mine/*", true); +} diff --git a/tests-clar/refs/create.c b/tests-clar/refs/create.c index 2e42cb607..af5b203a3 100644 --- a/tests-clar/refs/create.c +++ b/tests-clar/refs/create.c @@ -27,7 +27,7 @@ void test_refs_create__symbolic(void) git_oid id; git_buf ref_path = GIT_BUF_INIT; - const char *new_head_tracker = "another-head-tracker"; + const char *new_head_tracker = "ANOTHER_HEAD_TRACKER"; git_oid_fromstr(&id, current_master_tip); diff --git a/tests-clar/refs/isvalidname.c b/tests-clar/refs/isvalidname.c new file mode 100644 index 000000000..99761de32 --- /dev/null +++ b/tests-clar/refs/isvalidname.c @@ -0,0 +1,23 @@ +#include "clar_libgit2.h" + +void test_refs_isvalidname__can_detect_invalid_formats(void) +{ + cl_assert_equal_i(false, git_reference_is_valid_name("refs/tags/0.17.0^{}")); + cl_assert_equal_i(false, git_reference_is_valid_name("TWO/LEVELS")); + cl_assert_equal_i(false, git_reference_is_valid_name("ONE.LEVEL")); + cl_assert_equal_i(false, git_reference_is_valid_name("HEAD/")); + cl_assert_equal_i(false, git_reference_is_valid_name("NO_TRAILING_UNDERSCORE_")); + cl_assert_equal_i(false, git_reference_is_valid_name("_NO_LEADING_UNDERSCORE")); + cl_assert_equal_i(false, git_reference_is_valid_name("HEAD/aa")); + cl_assert_equal_i(false, git_reference_is_valid_name("lower_case")); + cl_assert_equal_i(false, git_reference_is_valid_name("")); +} + +void test_refs_isvalidname__wont_hopefully_choke_on_valid_formats(void) +{ + cl_assert_equal_i(true, git_reference_is_valid_name("refs/tags/0.17.0")); + cl_assert_equal_i(true, git_reference_is_valid_name("refs/LEVELS")); + cl_assert_equal_i(true, git_reference_is_valid_name("HEAD")); + cl_assert_equal_i(true, git_reference_is_valid_name("ONE_LEVEL")); + cl_assert_equal_i(true, git_reference_is_valid_name("refs/stash")); +} diff --git a/tests-clar/refs/lookup.c b/tests-clar/refs/lookup.c index ab563ac2b..71ab1b7b8 100644 --- a/tests-clar/refs/lookup.c +++ b/tests-clar/refs/lookup.c @@ -25,7 +25,7 @@ void test_refs_lookup__with_resolve(void) cl_assert(git_reference_cmp(a, b) == 0); git_reference_free(b); - cl_git_pass(git_reference_lookup_resolved(&b, g_repo, "head-tracker", 5)); + cl_git_pass(git_reference_lookup_resolved(&b, g_repo, "HEAD_TRACKER", 5)); cl_assert(git_reference_cmp(a, b) == 0); git_reference_free(b); diff --git a/tests-clar/refs/normalize.c b/tests-clar/refs/normalize.c index 4e80e4b0b..a144ef5c0 100644 --- a/tests-clar/refs/normalize.c +++ b/tests-clar/refs/normalize.c @@ -5,9 +5,10 @@ #include "reflog.h" // Helpers -static void ensure_refname_normalized(unsigned int flags, - const char *input_refname, - const char *expected_refname) +static void ensure_refname_normalized( + unsigned int flags, + const char *input_refname, + const char *expected_refname) { char buffer_out[GIT_REFNAME_MAX]; @@ -38,17 +39,7 @@ void test_refs_normalize__can_normalize_a_direct_reference_name(void) ensure_refname_normalized( GIT_REF_FORMAT_NORMAL, "refs/heads/v@ation", "refs/heads/v@ation"); ensure_refname_normalized( - GIT_REF_FORMAT_NORMAL, "/refs///heads///a", "refs/heads/a"); -} - -void test_refs_normalize__can_normalize_some_specific_one_level_direct_reference_names(void) -{ - ensure_refname_normalized( - GIT_REF_FORMAT_NORMAL, "HEAD", "HEAD"); - ensure_refname_normalized( - GIT_REF_FORMAT_NORMAL, "MERGE_HEAD", "MERGE_HEAD"); - ensure_refname_normalized( - GIT_REF_FORMAT_NORMAL, "FETCH_HEAD", "FETCH_HEAD"); + GIT_REF_FORMAT_NORMAL, "refs///heads///a", "refs/heads/a"); } void test_refs_normalize__cannot_normalize_any_direct_reference_name(void) @@ -61,6 +52,8 @@ void test_refs_normalize__cannot_normalize_any_direct_reference_name(void) GIT_REF_FORMAT_NORMAL, "//a"); ensure_refname_invalid( GIT_REF_FORMAT_NORMAL, ""); + ensure_refname_invalid( + GIT_REF_FORMAT_NORMAL, "/refs/heads/a/"); ensure_refname_invalid( GIT_REF_FORMAT_NORMAL, "refs/heads/a/"); ensure_refname_invalid( @@ -97,9 +90,9 @@ void test_refs_normalize__symbolic(void) GIT_REF_FORMAT_ALLOW_ONELEVEL, "///"); ensure_refname_normalized( - GIT_REF_FORMAT_ALLOW_ONELEVEL, "a", "a"); + GIT_REF_FORMAT_ALLOW_ONELEVEL, "ALL_CAPS_AND_UNDERSCORES", "ALL_CAPS_AND_UNDERSCORES"); ensure_refname_normalized( - GIT_REF_FORMAT_ALLOW_ONELEVEL, "a/b", "a/b"); + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/MixedCasing", "refs/MixedCasing"); ensure_refname_normalized( GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs///heads///a", "refs/heads/a"); @@ -115,7 +108,7 @@ void test_refs_normalize__symbolic(void) * See https://github.com/spearce/JGit/commit/e4bf8f6957bbb29362575d641d1e77a02d906739 */ void test_refs_normalize__jgit_suite(void) { - // tests borrowed from JGit + // tests borrowed from JGit /* EmptyString */ ensure_refname_invalid( @@ -127,10 +120,9 @@ void test_refs_normalize__jgit_suite(void) ensure_refname_invalid( GIT_REF_FORMAT_NORMAL, "master"); ensure_refname_normalized( - GIT_REF_FORMAT_ALLOW_ONELEVEL, "heads/master", "heads/master"); + GIT_REF_FORMAT_NORMAL, "heads/master", "heads/master"); /* ValidHead */ - ensure_refname_normalized( GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master", "refs/heads/master"); ensure_refname_normalized( @@ -310,7 +302,61 @@ void test_refs_normalize__buffer_has_to_be_big_enough_to_hold_the_normalized_ver char buffer_out[21]; cl_git_pass(git_reference_normalize_name( - buffer_out, 21, "//refs//heads/long///name", GIT_REF_FORMAT_NORMAL)); + buffer_out, 21, "refs//heads///long///name", GIT_REF_FORMAT_NORMAL)); cl_git_fail(git_reference_normalize_name( - buffer_out, 20, "//refs//heads/long///name", GIT_REF_FORMAT_NORMAL)); + buffer_out, 20, "refs//heads///long///name", GIT_REF_FORMAT_NORMAL)); +} + +#define ONE_LEVEL_AND_REFSPEC \ + GIT_REF_FORMAT_ALLOW_ONELEVEL \ + | GIT_REF_FORMAT_REFSPEC_PATTERN + +void test_refs_normalize__refspec_pattern(void) +{ + ensure_refname_invalid( + GIT_REF_FORMAT_REFSPEC_PATTERN, "heads/*foo/bar"); + ensure_refname_invalid( + GIT_REF_FORMAT_REFSPEC_PATTERN, "heads/foo*/bar"); + ensure_refname_invalid( + GIT_REF_FORMAT_REFSPEC_PATTERN, "heads/f*o/bar"); + + ensure_refname_invalid( + GIT_REF_FORMAT_REFSPEC_PATTERN, "foo"); + ensure_refname_normalized( + ONE_LEVEL_AND_REFSPEC, "FOO", "FOO"); + + ensure_refname_normalized( + GIT_REF_FORMAT_REFSPEC_PATTERN, "foo/bar", "foo/bar"); + ensure_refname_normalized( + ONE_LEVEL_AND_REFSPEC, "foo/bar", "foo/bar"); + + ensure_refname_normalized( + GIT_REF_FORMAT_REFSPEC_PATTERN, "*/foo", "*/foo"); + ensure_refname_normalized( + ONE_LEVEL_AND_REFSPEC, "*/foo", "*/foo"); + + ensure_refname_normalized( + GIT_REF_FORMAT_REFSPEC_PATTERN, "foo/*/bar", "foo/*/bar"); + ensure_refname_normalized( + ONE_LEVEL_AND_REFSPEC, "foo/*/bar", "foo/*/bar"); + + ensure_refname_invalid( + GIT_REF_FORMAT_REFSPEC_PATTERN, "*"); + ensure_refname_normalized( + ONE_LEVEL_AND_REFSPEC, "*", "*"); + + ensure_refname_invalid( + GIT_REF_FORMAT_REFSPEC_PATTERN, "foo/*/*"); + ensure_refname_invalid( + ONE_LEVEL_AND_REFSPEC, "foo/*/*"); + + ensure_refname_invalid( + GIT_REF_FORMAT_REFSPEC_PATTERN, "*/foo/*"); + ensure_refname_invalid( + ONE_LEVEL_AND_REFSPEC, "*/foo/*"); + + ensure_refname_invalid( + GIT_REF_FORMAT_REFSPEC_PATTERN, "*/*/foo"); + ensure_refname_invalid( + ONE_LEVEL_AND_REFSPEC, "*/*/foo"); } diff --git a/tests-clar/refs/read.c b/tests-clar/refs/read.c index f33658754..6ab6bf586 100644 --- a/tests-clar/refs/read.c +++ b/tests-clar/refs/read.c @@ -6,7 +6,7 @@ static const char *loose_tag_ref_name = "refs/tags/e90810b"; static const char *non_existing_tag_ref_name = "refs/tags/i-do-not-exist"; -static const char *head_tracker_sym_ref_name = "head-tracker"; +static const char *head_tracker_sym_ref_name = "HEAD_TRACKER"; static const char *current_head_target = "refs/heads/master"; static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"; static const char *packed_head_name = "refs/heads/packed"; @@ -221,7 +221,7 @@ void test_refs_read__unfound_return_ENOTFOUND(void) { git_reference *reference; - cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&reference, g_repo, "test/master")); + cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&reference, g_repo, "TEST_MASTER")); cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&reference, g_repo, "refs/test/master")); cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&reference, g_repo, "refs/tags/test/master")); cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&reference, g_repo, "refs/tags/test/farther/master")); diff --git a/tests-clar/resources/testrepo.git/head-tracker b/tests-clar/resources/testrepo.git/HEAD_TRACKER similarity index 100% rename from tests-clar/resources/testrepo.git/head-tracker rename to tests-clar/resources/testrepo.git/HEAD_TRACKER diff --git a/tests-clar/resources/testrepo/.gitted/head-tracker b/tests-clar/resources/testrepo/.gitted/HEAD_TRACKER similarity index 100% rename from tests-clar/resources/testrepo/.gitted/head-tracker rename to tests-clar/resources/testrepo/.gitted/HEAD_TRACKER