summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/git2/odb.h6
-rw-r--r--include/git2/refs.h15
-rw-r--r--src/checkout.c2
-rw-r--r--src/refs.c244
-rw-r--r--src/refs.h6
-rw-r--r--src/refspec.c113
-rw-r--r--src/refspec.h4
-rw-r--r--src/revparse.c5
-rw-r--r--src/tag.c2
-rw-r--r--tests-clar/network/refspecs.c83
-rw-r--r--tests-clar/object/tag/list.c115
-rw-r--r--tests-clar/object/tag/read.c45
-rw-r--r--tests-clar/refs/create.c2
-rw-r--r--tests-clar/refs/isvalidname.c23
-rw-r--r--tests-clar/refs/list.c6
-rw-r--r--tests-clar/refs/lookup.c2
-rw-r--r--tests-clar/refs/normalize.c88
-rw-r--r--tests-clar/refs/read.c4
-rw-r--r--tests-clar/resources/testrepo.git/HEAD_TRACKER (renamed from tests-clar/resources/testrepo.git/head-tracker)0
-rw-r--r--tests-clar/resources/testrepo/.gitted/HEAD_TRACKER (renamed from tests-clar/resources/testrepo/.gitted/head-tracker)0
-rw-r--r--tests-clar/resources/testrepo/.gitted/packed-refs1
-rw-r--r--tests-clar/resources/testrepo/.gitted/refs/tags/foo/bar1
-rw-r--r--tests-clar/resources/testrepo/.gitted/refs/tags/foo/foo/bar1
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) {
diff --git a/src/tag.c b/src/tag.c
index 6495d470f..ae9d0a895 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -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