From 77e06d7e8547e82336489b7cdeb04294ed3d6015 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Mon, 17 Sep 2012 07:11:32 +0200 Subject: [PATCH] refs: introduce git_reference_is_valid_name() --- include/git2/refs.h | 13 ++- src/refs.c | 95 ++++++++++++------- src/refs.h | 1 - src/revparse.c | 5 + tests-clar/refs/create.c | 2 +- tests-clar/refs/isvalidname.c | 23 +++++ tests-clar/refs/lookup.c | 2 +- tests-clar/refs/normalize.c | 27 ++---- tests-clar/refs/read.c | 4 +- .../{head-tracker => HEAD_TRACKER} | 0 .../.gitted/{head-tracker => HEAD_TRACKER} | 0 11 files changed, 112 insertions(+), 60 deletions(-) create mode 100644 tests-clar/refs/isvalidname.c rename tests-clar/resources/testrepo.git/{head-tracker => HEAD_TRACKER} (100%) rename tests-clar/resources/testrepo/.gitted/{head-tracker => HEAD_TRACKER} (100%) diff --git a/include/git2/refs.h b/include/git2/refs.h index acca69215..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), @@ -452,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 ef8300aba..e71d8c60e 100644 --- a/src/refs.c +++ b/src/refs.c @@ -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) @@ -1611,6 +1611,26 @@ static int ensure_segment_validity(const char *name) 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, @@ -1620,37 +1640,42 @@ int git_reference__normalize_name( char *current; int segment_len, segments_count = 0, error = -1; - - assert(name && buf); + unsigned int process_flags; + bool normalize = (buf != NULL); + assert(name); + process_flags = flags; current = (char *)name; - git_buf_clear(buf); + if (normalize) + git_buf_clear(buf); while (true) { segment_len = ensure_segment_validity(current); if (segment_len < 0) { - if ((flags & GIT_REF_FORMAT_REFSPEC_PATTERN) && + if ((process_flags & GIT_REF_FORMAT_REFSPEC_PATTERN) && current[0] == '*' && (current[1] == '\0' || current[1] == '/')) { /* Accept one wildcard as a full refname component. */ - flags &= ~GIT_REF_FORMAT_REFSPEC_PATTERN; + process_flags &= ~GIT_REF_FORMAT_REFSPEC_PATTERN; segment_len = 1; } else goto cleanup; } if (segment_len > 0) { - int cur_len = git_buf_len(buf); + 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)); + 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 (git_buf_oom(buf)) - goto cleanup; } if (current[segment_len] == '\0') @@ -1660,7 +1685,7 @@ int git_reference__normalize_name( } /* A refname can not be empty */ - if (git_buf_len(buf) == 0) + if (segment_len == 0 && segments_count == 0) goto cleanup; /* A refname can not end with "." */ @@ -1675,15 +1700,17 @@ int git_reference__normalize_name( if (!git__suffixcmp(name, GIT_FILELOCK_EXTENSION)) 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) && - segments_count < 2 && - strcmp(name, GIT_HEAD_FILE) != 0 && - strcmp(name, GIT_MERGE_HEAD_FILE) != 0 && - strcmp(name, GIT_FETCH_HEAD_FILE) != 0) - return -1; + 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; @@ -1736,19 +1763,6 @@ int git_reference__normalize_name_lax( 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) @@ -1937,3 +1951,12 @@ cleanup: git_reference_free(resolved); return error; } + +int git_reference_is_valid_name( + const char *refname) +{ + return git_reference__normalize_name( + NULL, + refname, + GIT_REF_FORMAT_ALLOW_ONELEVEL) == 0; +} diff --git a/src/refs.h b/src/refs.h index 0674d8799..010503452 100644 --- a/src/refs.h +++ b/src/refs.h @@ -54,7 +54,6 @@ typedef struct { void git_repository__refcache_free(git_refcache *refs); int git_reference__normalize_name_lax(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(git_buf *buf, const char *name, unsigned int flags); int git_reference__update(git_repository *repo, const git_oid *oid, const char *ref_name); 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/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 db1096476..a144ef5c0 100644 --- a/tests-clar/refs/normalize.c +++ b/tests-clar/refs/normalize.c @@ -39,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) @@ -62,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( @@ -98,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"); @@ -128,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( @@ -311,9 +302,9 @@ 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 \ @@ -332,7 +323,7 @@ void test_refs_normalize__refspec_pattern(void) ensure_refname_invalid( GIT_REF_FORMAT_REFSPEC_PATTERN, "foo"); ensure_refname_normalized( - ONE_LEVEL_AND_REFSPEC, "foo", "foo"); + ONE_LEVEL_AND_REFSPEC, "FOO", "FOO"); ensure_refname_normalized( GIT_REF_FORMAT_REFSPEC_PATTERN, "foo/bar", "foo/bar"); 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