diff options
23 files changed, 600 insertions, 168 deletions
diff --git a/include/git2/odb.h b/include/git2/odb.h index 1919f61a0..c6e73571b 100644 --- a/include/git2/odb.h +++ b/include/git2/odb.h @@ -279,8 +279,10 @@ GIT_EXTERN(int) git_odb_hash(git_oid *id, const void *data, size_t len, git_otyp /** * Read a file from disk and fill a git_oid with the object id * that the file would have if it were written to the Object - * Database as an object of the given type. Similar functionality - * to git.git's `git hash-object` without the `-w` flag. + * Database as an object of the given type (w/o applying filters). + * Similar functionality to git.git's `git hash-object` without + * the `-w` flag, however, with the --no-filters flag. + * If you need filters, see git_repository_hashfile. * * @param out oid structure the result is written into. * @param path file to read and determine object id for 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/checkout.c b/src/checkout.c index ea5e79abd..7cf9fe033 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -59,7 +59,7 @@ static int blob_content_to_file( mode_t entry_filemode, git_checkout_opts *opts) { - int error, nb_filters = 0; + int error = -1, nb_filters = 0; mode_t file_mode = opts->file_mode; bool dont_free_filtered = false; git_buf unfiltered = GIT_BUF_INIT, filtered = GIT_BUF_INIT; 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,118 +1584,175 @@ 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; +} - if (buffer_out > buffer_out_start) { - char prev = *(buffer_out - 1); +static bool is_all_caps_and_underscore(const char *name, int len) +{ + int i; + char c; + + assert(name && len > 0); - /* A refname can not start with a dot nor contain a double dot */ - if (*current == '.' && ((prev == '.') || (prev == '/'))) - goto invalid_name; + for (i = 0; i < len; i++) + { + c = name[i]; + if ((c < 'A' || c > 'Z') && c != '_') + return false; + } - /* '@{' is forbidden within a refname */ - if (*current == '{' && prev == '@') - goto invalid_name; + if (*name == '_' || name[len - 1] == '_') + return false; - /* Prevent multiple slashes from being added to the output */ - if (*current == '/' && prev == '/') { - current++; - continue; - } + 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 (*current == '/') { - if (buffer_out > buffer_out_start) - contains_a_slash = 1; - else { - current++; - continue; + 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++; } - *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; } - /* 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; + /* 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 invalid_name; + goto cleanup; - *buffer_out = '\0'; + if ((segments_count == 1 ) && !(flags & GIT_REF_FORMAT_ALLOW_ONELEVEL)) + goto cleanup; - return 0; + if ((segments_count == 1 ) && + !(is_all_caps_and_underscore(name, segment_len) || + ((flags & GIT_REF_FORMAT_REFSPEC_PATTERN) && !strcmp("*", name)))) + goto cleanup; -invalid_name: - giterr_set( - GITERR_REFERENCE, - "The given reference name '%s' is not valid", name); - return -1; + 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( +int git_reference_normalize_name( char *buffer_out, - size_t out_size, - const char *name) + size_t buffer_size, + const char *name, + unsigned int flags) { - return git_reference_normalize_name( - buffer_out, - out_size, - name, - GIT_REF_FORMAT_ALLOW_ONELEVEL); + 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 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_oid( +int git_reference__normalize_name_lax( char *buffer_out, size_t out_size, const char *name) @@ -1704,9 +1761,8 @@ int git_reference__normalize_name_oid( buffer_out, out_size, name, - GIT_REF_FORMAT_NORMAL); + GIT_REF_FORMAT_ALLOW_ONELEVEL); } - #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) { @@ -408,7 +408,7 @@ static int tag_list_cb(const char *tag_name, void *payload) filter = (tag_filter_data *)payload; if (!*filter->pattern || p_fnmatch(filter->pattern, tag_name + GIT_REFS_TAGS_DIR_LEN, 0) == 0) - return git_vector_insert(filter->taglist, git__strdup(tag_name)); + return git_vector_insert(filter->taglist, git__strdup(tag_name + GIT_REFS_TAGS_DIR_LEN)); return 0; } 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/object/tag/list.c b/tests-clar/object/tag/list.c new file mode 100644 index 000000000..6d5a24347 --- /dev/null +++ b/tests-clar/object/tag/list.c @@ -0,0 +1,115 @@ +#include "clar_libgit2.h" + +#include "tag.h" + +static git_repository *g_repo; + +#define MAX_USED_TAGS 6 + +struct pattern_match_t +{ + const char* pattern; + const size_t expected_matches; + const char* expected_results[MAX_USED_TAGS]; +}; + +// Helpers +static void ensure_tag_pattern_match(git_repository *repo, + const struct pattern_match_t* data) +{ + int already_found[MAX_USED_TAGS] = { 0 }; + git_strarray tag_list; + int error = 0; + size_t sucessfully_found = 0; + size_t i, j; + + cl_assert(data->expected_matches <= MAX_USED_TAGS); + + if ((error = git_tag_list_match(&tag_list, data->pattern, repo)) < 0) + goto exit; + + if (tag_list.count != data->expected_matches) + { + error = GIT_ERROR; + goto exit; + } + + // we have to be prepared that tags come in any order. + for (i = 0; i < tag_list.count; i++) + { + for (j = 0; j < data->expected_matches; j++) + { + if (!already_found[j] && !strcmp(data->expected_results[j], tag_list.strings[i])) + { + already_found[j] = 1; + sucessfully_found++; + break; + } + } + } + cl_assert_equal_i((int)sucessfully_found, (int)data->expected_matches); + +exit: + git_strarray_free(&tag_list); + cl_git_pass(error); +} + +// Fixture setup and teardown +void test_object_tag_list__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_object_tag_list__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_object_tag_list__list_all(void) +{ + // list all tag names from the repository + git_strarray tag_list; + + cl_git_pass(git_tag_list(&tag_list, g_repo)); + + cl_assert_equal_i((int)tag_list.count, 6); + + git_strarray_free(&tag_list); +} + +static const struct pattern_match_t matches[] = { + // All tags, including a packed one and two namespaced ones. + { "", 6, { "e90810b", "point_to_blob", "test", "packed-tag", "foo/bar", "foo/foo/bar" } }, + + // beginning with + { "t*", 1, { "test" } }, + + // ending with + { "*b", 2, { "e90810b", "point_to_blob" } }, + + // exact match + { "e", 0 }, + { "e90810b", 1, { "e90810b" } }, + + // either or + { "e90810[ab]", 1, { "e90810b" } }, + + // glob in the middle + { "foo/*/bar", 1, { "foo/foo/bar" } }, + + // The matching of '*' is based on plain string matching analog to the regular expression ".*" + // => a '/' in the tag name has no special meaning. + // Compare to `git tag -l "*bar"` + { "*bar", 2, { "foo/bar", "foo/foo/bar" } }, + + // End of list + { NULL } +}; + +void test_object_tag_list__list_by_pattern(void) +{ + // list all tag names from the repository matching a specified pattern + size_t i = 0; + while (matches[i].pattern) + ensure_tag_pattern_match(g_repo, &matches[i++]); +} diff --git a/tests-clar/object/tag/read.c b/tests-clar/object/tag/read.c index 6a0ad8a23..4dd5cc253 100644 --- a/tests-clar/object/tag/read.c +++ b/tests-clar/object/tag/read.c @@ -10,27 +10,6 @@ static const char *badly_tagged_commit = "e90810b8df3e80c413d903f631643c71688713 static git_repository *g_repo; - -// Helpers -static void ensure_tag_pattern_match(git_repository *repo, - const char *pattern, - const size_t expected_matches) -{ - git_strarray tag_list; - int error = 0; - - if ((error = git_tag_list_match(&tag_list, pattern, repo)) < 0) - goto exit; - - if (tag_list.count != expected_matches) - error = GIT_ERROR; - -exit: - git_strarray_free(&tag_list); - cl_git_pass(error); -} - - // Fixture setup and teardown void test_object_tag_read__initialize(void) { @@ -74,30 +53,6 @@ void test_object_tag_read__parse(void) git_commit_free(commit); } -void test_object_tag_read__list(void) -{ - // list all tag names from the repository - git_strarray tag_list; - - cl_git_pass(git_tag_list(&tag_list, g_repo)); - - cl_assert(tag_list.count == 3); - - git_strarray_free(&tag_list); -} - -void test_object_tag_read__list_pattern(void) -{ - // list all tag names from the repository matching a specified pattern - ensure_tag_pattern_match(g_repo, "", 3); - ensure_tag_pattern_match(g_repo, "*", 3); - ensure_tag_pattern_match(g_repo, "t*", 1); - ensure_tag_pattern_match(g_repo, "*b", 2); - ensure_tag_pattern_match(g_repo, "e", 0); - ensure_tag_pattern_match(g_repo, "e90810b", 1); - ensure_tag_pattern_match(g_repo, "e90810[ab]", 1); -} - void test_object_tag_read__parse_without_tagger(void) { // read and parse a tag without a tagger field 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/list.c b/tests-clar/refs/list.c index 2daa3941e..3948b2b7a 100644 --- a/tests-clar/refs/list.c +++ b/tests-clar/refs/list.c @@ -33,10 +33,10 @@ void test_refs_list__all(void) printf("# %s\n", ref_list.strings[i]); }*/ - /* We have exactly 9 refs in total if we include the packed ones: + /* We have exactly 12 refs in total if we include the packed ones: * there is a reference that exists both in the packfile and as * loose, but we only list it once */ - cl_assert_equal_i((int)ref_list.count, 10); + cl_assert_equal_i((int)ref_list.count, 13); git_strarray_free(&ref_list); } @@ -62,7 +62,7 @@ void test_refs_list__do_not_retrieve_references_which_name_end_with_a_lock_exten "144344043ba4d4a405da03de3844aa829ae8be0e\n"); cl_git_pass(git_reference_list(&ref_list, g_repo, GIT_REF_LISTALL)); - cl_assert_equal_i((int)ref_list.count, 10); + cl_assert_equal_i((int)ref_list.count, 13); git_strarray_free(&ref_list); } 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) @@ -62,6 +53,8 @@ void test_refs_normalize__cannot_normalize_any_direct_reference_name(void) 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( GIT_REF_FORMAT_NORMAL, "refs/heads/a."); @@ -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 index 40d876b4c..40d876b4c 100644 --- a/tests-clar/resources/testrepo.git/head-tracker +++ b/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 index 40d876b4c..40d876b4c 100644 --- a/tests-clar/resources/testrepo/.gitted/head-tracker +++ b/tests-clar/resources/testrepo/.gitted/HEAD_TRACKER diff --git a/tests-clar/resources/testrepo/.gitted/packed-refs b/tests-clar/resources/testrepo/.gitted/packed-refs index 52f5e876f..6018a19d2 100644 --- a/tests-clar/resources/testrepo/.gitted/packed-refs +++ b/tests-clar/resources/testrepo/.gitted/packed-refs @@ -1,3 +1,4 @@ # pack-refs with: peeled 41bc8c69075bbdb46c5c6f0566cc8cc5b46e8bd9 refs/heads/packed 5b5b025afb0b4c913b4c338a42934a3863bf3644 refs/heads/packed-test +b25fa35b38051e4ae45d4222e795f9df2e43f1d1 refs/tags/packed-tag diff --git a/tests-clar/resources/testrepo/.gitted/refs/tags/foo/bar b/tests-clar/resources/testrepo/.gitted/refs/tags/foo/bar new file mode 100644 index 000000000..6ee952a03 --- /dev/null +++ b/tests-clar/resources/testrepo/.gitted/refs/tags/foo/bar @@ -0,0 +1 @@ +b25fa35b38051e4ae45d4222e795f9df2e43f1d1 diff --git a/tests-clar/resources/testrepo/.gitted/refs/tags/foo/foo/bar b/tests-clar/resources/testrepo/.gitted/refs/tags/foo/foo/bar new file mode 100644 index 000000000..6ee952a03 --- /dev/null +++ b/tests-clar/resources/testrepo/.gitted/refs/tags/foo/foo/bar @@ -0,0 +1 @@ +b25fa35b38051e4ae45d4222e795f9df2e43f1d1 |
