From c030ada7ff7f9c93a2287ca2f57173d66fbff88a Mon Sep 17 00:00:00 2001 From: nulltoken Date: Tue, 11 Sep 2012 12:06:57 +0200 Subject: refs: make git_reference_normalize_name() accept refspec pattern --- include/git2/refs.h | 2 - src/refs.c | 186 +++++++++++++++++++++++++++----------------- src/refs.h | 4 +- tests-clar/refs/normalize.c | 63 ++++++++++++++- 4 files changed, 176 insertions(+), 79 deletions(-) diff --git a/include/git2/refs.h b/include/git2/refs.h index 73b32a9e2..acca69215 100644 --- a/include/git2/refs.h +++ b/include/git2/refs.h @@ -414,8 +414,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. * diff --git a/src/refs.c b/src/refs.c index 74c40e850..ef8300aba 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) @@ -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,148 @@ static int is_valid_ref_char(char ch) } } -int git_reference_normalize_name( - char *buffer_out, - size_t buffer_size, - const char *name, - unsigned int flags) +static int ensure_segment_validity(const char *name) { - const char *name_end, *buffer_out_start; - const char *current; - int contains_a_slash = 0; + const char *current = name; + char prev = '\0'; - assert(name && buffer_out); + if (*current == '.') + return -1; /* Refname starts with "." */ - if (flags & GIT_REF_FORMAT_REFSPEC_PATTERN) { - giterr_set(GITERR_INVALID, "Unimplemented"); - return -1; - } + for (current = name; ; current++) { + if (*current == '\0' || *current == '/') + break; - buffer_out_start = buffer_out; - current = name; - name_end = name + strlen(name); + if (!is_valid_ref_char(*current)) + return -1; /* Illegal character in refname */ - /* Terminating null byte */ - buffer_size--; + if (prev == '.' && *current == '.') + return -1; /* Refname contains ".." */ - /* A refname can not be empty */ - if (name_end == name) - goto invalid_name; + if (prev == '@' && *current == '{') + return -1; /* Refname contains "@{" */ - /* A refname can not end with a dot or a slash */ - if (*(name_end - 1) == '.' || *(name_end - 1) == '/') - goto invalid_name; + prev = *current; + } - while (current < name_end && buffer_size > 0) { - if (!is_valid_ref_char(*current)) - goto invalid_name; + return current - name; +} + +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 - if (buffer_out > buffer_out_start) { - char prev = *(buffer_out - 1); + char *current; + int segment_len, segments_count = 0, error = -1; + + assert(name && buf); + + current = (char *)name; + + git_buf_clear(buf); + + while (true) { + segment_len = ensure_segment_validity(current); + if (segment_len < 0) { + if ((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; + segment_len = 1; + } else + goto cleanup; + } - /* A refname can not start with a dot nor contain a double dot */ - if (*current == '.' && ((prev == '.') || (prev == '/'))) - goto invalid_name; + if (segment_len > 0) { + int cur_len = git_buf_len(buf); - /* '@{' is forbidden within a refname */ - if (*current == '{' && prev == '@') - goto invalid_name; + git_buf_joinpath(buf, git_buf_cstr(buf), current); + git_buf_truncate(buf, + cur_len + segment_len + (segments_count ? 1 : 0)); - /* Prevent multiple slashes from being added to the output */ - if (*current == '/' && prev == '/') { - current++; - continue; - } - } + segments_count++; - if (*current == '/') { - if (buffer_out > buffer_out_start) - contains_a_slash = 1; - else { - current++; - continue; - } + if (git_buf_oom(buf)) + goto cleanup; } - *buffer_out++ = *current++; - buffer_size--; - } + if (current[segment_len] == '\0') + break; - if (current < name_end) { - giterr_set( - GITERR_REFERENCE, - "The provided buffer is too short to hold the normalization of '%s'", name); - return GIT_EBUFS; + current += segment_len + 1; } + /* A refname can not be empty */ + if (git_buf_len(buf) == 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; + /* 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 && + 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) - goto invalid_name; + return -1; - /* A refname can not end with ".lock" */ - if (!git__suffixcmp(name, GIT_FILELOCK_EXTENSION)) - goto invalid_name; + error = 0; - *buffer_out = '\0'; +cleanup: + if (error) + giterr_set( + GITERR_REFERENCE, + "The given reference name '%s' is not valid", name); - return 0; + return error; +} -invalid_name: - giterr_set( +int git_reference_normalize_name( + char *buffer_out, + size_t buffer_size, + const char *name, + unsigned int flags) +{ + git_buf buf = GIT_BUF_INIT; + int error; + + if ((error = git_reference__normalize_name(&buf, name, flags)) < 0) + goto cleanup; + + if (git_buf_len(&buf) > buffer_size - 1) { + giterr_set( GITERR_REFERENCE, - "The given reference name '%s' is not valid", name); - return -1; + "The provided buffer is too short to hold the normalization of '%s'", name); + error = GIT_EBUFS; + goto cleanup; + } + + git_buf_copy_cstr(buffer_out, buffer_size, &buf); + + error = 0; + +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) diff --git a/src/refs.h b/src/refs.h index 082350278..0674d8799 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_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/tests-clar/refs/normalize.c b/tests-clar/refs/normalize.c index 4e80e4b0b..db1096476 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]; @@ -115,7 +116,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( @@ -314,3 +315,57 @@ void test_refs_normalize__buffer_has_to_be_big_enough_to_hold_the_normalized_ver cl_git_fail(git_reference_normalize_name( 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"); +} -- cgit v1.2.1