summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authornulltoken <emeric.fermas@gmail.com>2012-09-11 12:06:57 +0200
committernulltoken <emeric.fermas@gmail.com>2012-09-25 07:49:14 +0200
commitc030ada7ff7f9c93a2287ca2f57173d66fbff88a (patch)
treec83775cdcbf93ffab1947851d833e5c2ac3bff42
parentd75074f4c02e8d8928d20261a891d94d26d41ea7 (diff)
downloadlibgit2-c030ada7ff7f9c93a2287ca2f57173d66fbff88a.tar.gz
refs: make git_reference_normalize_name() accept refspec pattern
-rw-r--r--include/git2/refs.h2
-rw-r--r--src/refs.c186
-rw-r--r--src/refs.h4
-rw-r--r--tests-clar/refs/normalize.c63
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");
+}