Commit 77e06d7e by nulltoken

refs: introduce git_reference_is_valid_name()

parent c030ada7
...@@ -392,7 +392,8 @@ enum { ...@@ -392,7 +392,8 @@ enum {
/** /**
* Control whether one-level refnames are accepted * Control whether one-level refnames are accepted
* (i.e., refnames that do not contain multiple /-separated * (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), GIT_REF_FORMAT_ALLOW_ONELEVEL = (1 << 0),
...@@ -452,6 +453,16 @@ GIT_EXTERN(int) git_reference_peel( ...@@ -452,6 +453,16 @@ GIT_EXTERN(int) git_reference_peel(
git_reference *ref, git_reference *ref,
git_otype type); 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 GIT_END_DECL
#endif #endif
...@@ -1239,7 +1239,7 @@ int git_reference_create_oid( ...@@ -1239,7 +1239,7 @@ int git_reference_create_oid(
git_reference *ref = NULL; git_reference *ref = NULL;
char normalized[GIT_REFNAME_MAX]; char normalized[GIT_REFNAME_MAX];
if (git_reference__normalize_name_oid( if (git_reference__normalize_name_lax(
normalized, normalized,
sizeof(normalized), sizeof(normalized),
name) < 0) name) < 0)
...@@ -1611,6 +1611,26 @@ static int ensure_segment_validity(const char *name) ...@@ -1611,6 +1611,26 @@ static int ensure_segment_validity(const char *name)
return current - 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( int git_reference__normalize_name(
git_buf *buf, git_buf *buf,
const char *name, const char *name,
...@@ -1620,37 +1640,42 @@ int git_reference__normalize_name( ...@@ -1620,37 +1640,42 @@ int git_reference__normalize_name(
char *current; char *current;
int segment_len, segments_count = 0, error = -1; int segment_len, segments_count = 0, error = -1;
unsigned int process_flags;
assert(name && buf); bool normalize = (buf != NULL);
assert(name);
process_flags = flags;
current = (char *)name; current = (char *)name;
git_buf_clear(buf); if (normalize)
git_buf_clear(buf);
while (true) { while (true) {
segment_len = ensure_segment_validity(current); segment_len = ensure_segment_validity(current);
if (segment_len < 0) { if (segment_len < 0) {
if ((flags & GIT_REF_FORMAT_REFSPEC_PATTERN) && if ((process_flags & GIT_REF_FORMAT_REFSPEC_PATTERN) &&
current[0] == '*' && current[0] == '*' &&
(current[1] == '\0' || current[1] == '/')) { (current[1] == '\0' || current[1] == '/')) {
/* Accept one wildcard as a full refname component. */ /* Accept one wildcard as a full refname component. */
flags &= ~GIT_REF_FORMAT_REFSPEC_PATTERN; process_flags &= ~GIT_REF_FORMAT_REFSPEC_PATTERN;
segment_len = 1; segment_len = 1;
} else } else
goto cleanup; goto cleanup;
} }
if (segment_len > 0) { 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_joinpath(buf, git_buf_cstr(buf), current);
git_buf_truncate(buf, git_buf_truncate(buf,
cur_len + segment_len + (segments_count ? 1 : 0)); cur_len + segment_len + (segments_count ? 1 : 0));
segments_count++; if (git_buf_oom(buf))
goto cleanup;
}
if (git_buf_oom(buf)) segments_count++;
goto cleanup;
} }
if (current[segment_len] == '\0') if (current[segment_len] == '\0')
...@@ -1660,7 +1685,7 @@ int git_reference__normalize_name( ...@@ -1660,7 +1685,7 @@ int git_reference__normalize_name(
} }
/* A refname can not be empty */ /* A refname can not be empty */
if (git_buf_len(buf) == 0) if (segment_len == 0 && segments_count == 0)
goto cleanup; goto cleanup;
/* A refname can not end with "." */ /* A refname can not end with "." */
...@@ -1675,15 +1700,17 @@ int git_reference__normalize_name( ...@@ -1675,15 +1700,17 @@ int git_reference__normalize_name(
if (!git__suffixcmp(name, GIT_FILELOCK_EXTENSION)) if (!git__suffixcmp(name, GIT_FILELOCK_EXTENSION))
goto cleanup; goto cleanup;
/* Object id refname have to contain at least one slash, except if ((segments_count == 1 ) && !(flags & GIT_REF_FORMAT_ALLOW_ONELEVEL))
* for HEAD in a detached state or MERGE_HEAD if we're in the goto cleanup;
* middle of a merge */
if (!(flags & GIT_REF_FORMAT_ALLOW_ONELEVEL) && if ((segments_count == 1 ) &&
segments_count < 2 && !(is_all_caps_and_underscore(name, segment_len) ||
strcmp(name, GIT_HEAD_FILE) != 0 && ((flags & GIT_REF_FORMAT_REFSPEC_PATTERN) && !strcmp("*", name))))
strcmp(name, GIT_MERGE_HEAD_FILE) != 0 && goto cleanup;
strcmp(name, GIT_FETCH_HEAD_FILE) != 0)
return -1; if ((segments_count > 1)
&& (is_all_caps_and_underscore(name, strchr(name, '/') - name)))
goto cleanup;
error = 0; error = 0;
...@@ -1736,19 +1763,6 @@ int git_reference__normalize_name_lax( ...@@ -1736,19 +1763,6 @@ int git_reference__normalize_name_lax(
name, name,
GIT_REF_FORMAT_ALLOW_ONELEVEL); 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) #define GIT_REF_TYPEMASK (GIT_REF_OID | GIT_REF_SYMBOLIC)
int git_reference_cmp(git_reference *ref1, git_reference *ref2) int git_reference_cmp(git_reference *ref1, git_reference *ref2)
...@@ -1937,3 +1951,12 @@ cleanup: ...@@ -1937,3 +1951,12 @@ cleanup:
git_reference_free(resolved); git_reference_free(resolved);
return error; return error;
} }
int git_reference_is_valid_name(
const char *refname)
{
return git_reference__normalize_name(
NULL,
refname,
GIT_REF_FORMAT_ALLOW_ONELEVEL) == 0;
}
...@@ -54,7 +54,6 @@ typedef struct { ...@@ -54,7 +54,6 @@ typedef struct {
void git_repository__refcache_free(git_refcache *refs); 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_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__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); int git_reference__update(git_repository *repo, const git_oid *oid, const char *ref_name);
......
...@@ -50,6 +50,11 @@ static int disambiguate_refname(git_reference **out, git_repository *repo, const ...@@ -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) if ((error = git_buf_printf(&refnamebuf, formatters[i], git_buf_cstr(&name))) < 0)
goto cleanup; 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); error = git_reference_lookup_resolved(&ref, repo, git_buf_cstr(&refnamebuf), -1);
if (!error) { if (!error) {
......
...@@ -27,7 +27,7 @@ void test_refs_create__symbolic(void) ...@@ -27,7 +27,7 @@ void test_refs_create__symbolic(void)
git_oid id; git_oid id;
git_buf ref_path = GIT_BUF_INIT; 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); git_oid_fromstr(&id, current_master_tip);
......
#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"));
}
...@@ -25,7 +25,7 @@ void test_refs_lookup__with_resolve(void) ...@@ -25,7 +25,7 @@ void test_refs_lookup__with_resolve(void)
cl_assert(git_reference_cmp(a, b) == 0); cl_assert(git_reference_cmp(a, b) == 0);
git_reference_free(b); 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); cl_assert(git_reference_cmp(a, b) == 0);
git_reference_free(b); git_reference_free(b);
......
...@@ -39,17 +39,7 @@ void test_refs_normalize__can_normalize_a_direct_reference_name(void) ...@@ -39,17 +39,7 @@ void test_refs_normalize__can_normalize_a_direct_reference_name(void)
ensure_refname_normalized( ensure_refname_normalized(
GIT_REF_FORMAT_NORMAL, "refs/heads/v@ation", "refs/heads/v@ation"); GIT_REF_FORMAT_NORMAL, "refs/heads/v@ation", "refs/heads/v@ation");
ensure_refname_normalized( ensure_refname_normalized(
GIT_REF_FORMAT_NORMAL, "/refs///heads///a", "refs/heads/a"); 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");
} }
void test_refs_normalize__cannot_normalize_any_direct_reference_name(void) void test_refs_normalize__cannot_normalize_any_direct_reference_name(void)
...@@ -63,6 +53,8 @@ void test_refs_normalize__cannot_normalize_any_direct_reference_name(void) ...@@ -63,6 +53,8 @@ void test_refs_normalize__cannot_normalize_any_direct_reference_name(void)
ensure_refname_invalid( ensure_refname_invalid(
GIT_REF_FORMAT_NORMAL, ""); GIT_REF_FORMAT_NORMAL, "");
ensure_refname_invalid( ensure_refname_invalid(
GIT_REF_FORMAT_NORMAL, "/refs/heads/a/");
ensure_refname_invalid(
GIT_REF_FORMAT_NORMAL, "refs/heads/a/"); GIT_REF_FORMAT_NORMAL, "refs/heads/a/");
ensure_refname_invalid( ensure_refname_invalid(
GIT_REF_FORMAT_NORMAL, "refs/heads/a."); GIT_REF_FORMAT_NORMAL, "refs/heads/a.");
...@@ -98,9 +90,9 @@ void test_refs_normalize__symbolic(void) ...@@ -98,9 +90,9 @@ void test_refs_normalize__symbolic(void)
GIT_REF_FORMAT_ALLOW_ONELEVEL, "///"); GIT_REF_FORMAT_ALLOW_ONELEVEL, "///");
ensure_refname_normalized( 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( ensure_refname_normalized(
GIT_REF_FORMAT_ALLOW_ONELEVEL, "a/b", "a/b"); GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/MixedCasing", "refs/MixedCasing");
ensure_refname_normalized( ensure_refname_normalized(
GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs///heads///a", "refs/heads/a"); GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs///heads///a", "refs/heads/a");
...@@ -128,10 +120,9 @@ void test_refs_normalize__jgit_suite(void) ...@@ -128,10 +120,9 @@ void test_refs_normalize__jgit_suite(void)
ensure_refname_invalid( ensure_refname_invalid(
GIT_REF_FORMAT_NORMAL, "master"); GIT_REF_FORMAT_NORMAL, "master");
ensure_refname_normalized( ensure_refname_normalized(
GIT_REF_FORMAT_ALLOW_ONELEVEL, "heads/master", "heads/master"); GIT_REF_FORMAT_NORMAL, "heads/master", "heads/master");
/* ValidHead */ /* ValidHead */
ensure_refname_normalized( ensure_refname_normalized(
GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master", "refs/heads/master"); GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master", "refs/heads/master");
ensure_refname_normalized( ensure_refname_normalized(
...@@ -311,9 +302,9 @@ void test_refs_normalize__buffer_has_to_be_big_enough_to_hold_the_normalized_ver ...@@ -311,9 +302,9 @@ void test_refs_normalize__buffer_has_to_be_big_enough_to_hold_the_normalized_ver
char buffer_out[21]; char buffer_out[21];
cl_git_pass(git_reference_normalize_name( 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( 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 \ #define ONE_LEVEL_AND_REFSPEC \
...@@ -332,7 +323,7 @@ void test_refs_normalize__refspec_pattern(void) ...@@ -332,7 +323,7 @@ void test_refs_normalize__refspec_pattern(void)
ensure_refname_invalid( ensure_refname_invalid(
GIT_REF_FORMAT_REFSPEC_PATTERN, "foo"); GIT_REF_FORMAT_REFSPEC_PATTERN, "foo");
ensure_refname_normalized( ensure_refname_normalized(
ONE_LEVEL_AND_REFSPEC, "foo", "foo"); ONE_LEVEL_AND_REFSPEC, "FOO", "FOO");
ensure_refname_normalized( ensure_refname_normalized(
GIT_REF_FORMAT_REFSPEC_PATTERN, "foo/bar", "foo/bar"); GIT_REF_FORMAT_REFSPEC_PATTERN, "foo/bar", "foo/bar");
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
static const char *loose_tag_ref_name = "refs/tags/e90810b"; 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 *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_head_target = "refs/heads/master";
static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"; static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750";
static const char *packed_head_name = "refs/heads/packed"; static const char *packed_head_name = "refs/heads/packed";
...@@ -221,7 +221,7 @@ void test_refs_read__unfound_return_ENOTFOUND(void) ...@@ -221,7 +221,7 @@ void test_refs_read__unfound_return_ENOTFOUND(void)
{ {
git_reference *reference; 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/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/master"));
cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&reference, g_repo, "refs/tags/test/farther/master")); cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&reference, g_repo, "refs/tags/test/farther/master"));
......
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