Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion include/git2/refspec.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,16 @@ GIT_EXTERN(int) git_refspec_force(const git_refspec *refspec);
GIT_EXTERN(git_direction) git_refspec_direction(const git_refspec *spec);

/**
* Check if a refspec's source descriptor matches a reference
* Check if a refspec's source descriptor matches a negative reference
*
* @param refspec the refspec
* @param refname the name of the reference to check
* @return 1 if the refspec matches, 0 otherwise
*/
GIT_EXTERN(int) git_refspec_src_matches_negative(const git_refspec *refspec, const char *refname);

/**
* Check if a refspec's source descriptor matches a reference
*
* @param refspec the refspec
* @param refname the name of the reference to check
Expand Down
25 changes: 19 additions & 6 deletions src/refs.c
Original file line number Diff line number Diff line change
Expand Up @@ -813,17 +813,20 @@ static int is_valid_ref_char(char ch)
}
}

static int ensure_segment_validity(const char *name, char may_contain_glob)
static int ensure_segment_validity(const char *name, char may_contain_glob, bool allow_caret_prefix)
{
const char *current = name;
const char *start = current;
char prev = '\0';
const int lock_len = (int)strlen(GIT_FILELOCK_EXTENSION);
int segment_len;

if (*current == '.')
return -1; /* Refname starts with "." */
if (allow_caret_prefix && *current == '^')
start++;

for (current = name; ; current++) {
for (current = start; ; current++) {
if (*current == '\0' || *current == '/')
break;

Expand Down Expand Up @@ -855,7 +858,7 @@ static int ensure_segment_validity(const char *name, char may_contain_glob)
return segment_len;
}

static bool is_all_caps_and_underscore(const char *name, size_t len)
static bool is_valid_normalized_name(const char *name, size_t len)
{
size_t i;
char c;
Expand All @@ -865,6 +868,9 @@ static bool is_all_caps_and_underscore(const char *name, size_t len)
for (i = 0; i < len; i++)
{
c = name[i];
if (i == 0 && c == '^')
continue; /* The first character is allowed to be "^" for negative refspecs */

if ((c < 'A' || c > 'Z') && c != '_')
return false;
}
Expand All @@ -885,6 +891,7 @@ int git_reference__normalize_name(
int segment_len, segments_count = 0, error = GIT_EINVALIDSPEC;
unsigned int process_flags;
bool normalize = (buf != NULL);
bool allow_caret_prefix = true;
bool validate = (flags & GIT_REFERENCE_FORMAT__VALIDATION_DISABLE) == 0;

#ifdef GIT_USE_ICONV
Expand Down Expand Up @@ -922,7 +929,7 @@ int git_reference__normalize_name(
while (true) {
char may_contain_glob = process_flags & GIT_REFERENCE_FORMAT_REFSPEC_PATTERN;

segment_len = ensure_segment_validity(current, may_contain_glob);
segment_len = ensure_segment_validity(current, may_contain_glob, allow_caret_prefix);
if (segment_len < 0)
goto cleanup;

Expand Down Expand Up @@ -958,6 +965,12 @@ int git_reference__normalize_name(
break;

current += segment_len + 1;

/*
* A caret prefix is only allowed in the first segment to signify a
* negative refspec.
*/
allow_caret_prefix = false;
}

/* A refname can not be empty */
Expand All @@ -977,12 +990,12 @@ int git_reference__normalize_name(

if ((segments_count == 1 ) &&
!(flags & GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND) &&
!(is_all_caps_and_underscore(name, (size_t)segment_len) ||
!(is_valid_normalized_name(name, (size_t)segment_len) ||
((flags & GIT_REFERENCE_FORMAT_REFSPEC_PATTERN) && !strcmp("*", name))))
goto cleanup;

if ((segments_count > 1)
&& (is_all_caps_and_underscore(name, strchr(name, '/') - name)))
&& (is_valid_normalized_name(name, strchr(name, '/') - name)))
goto cleanup;

error = 0;
Expand Down
29 changes: 28 additions & 1 deletion src/refspec.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch)
int is_glob = 0;
const char *lhs, *rhs;
int flags;
bool is_neg_refspec = false;

assert(refspec && input);

Expand All @@ -33,6 +34,9 @@ int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch)
refspec->force = 1;
lhs++;
}
if (*lhs == '^') {
is_neg_refspec = true;
}

rhs = strrchr(lhs, ':');

Expand Down Expand Up @@ -61,7 +65,14 @@ int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch)

llen = (rhs ? (size_t)(rhs - lhs - 1) : strlen(lhs));
if (1 <= llen && memchr(lhs, '*', llen)) {
if ((rhs && !is_glob) || (!rhs && is_fetch))
/*
* If the lefthand side contains a glob, then one of the following must be
* true, otherwise the spec is invalid
* 1) the rhs exists and also contains a glob
* 2) it is a negative refspec (i.e. no rhs)
* 3) the rhs doesn't exist and we're fetching
*/
if ((rhs && !is_glob) || (rhs && is_neg_refspec) || (!rhs && is_fetch && !is_neg_refspec))
goto invalid;
is_glob = 1;
} else if (rhs && is_glob)
Expand Down Expand Up @@ -208,6 +219,14 @@ int git_refspec_force(const git_refspec *refspec)
return refspec->force;
}

int git_refspec_src_matches_negative(const git_refspec *refspec, const char *refname)
{
if (refspec == NULL || refspec->src == NULL || !git_refspec_is_negative(refspec))
return false;

return (wildmatch(refspec->src + 1, refname, 0) == 0);
}

int git_refspec_src_matches(const git_refspec *refspec, const char *refname)
{
if (refspec == NULL || refspec->src == NULL)
Expand All @@ -216,6 +235,14 @@ int git_refspec_src_matches(const git_refspec *refspec, const char *refname)
return (wildmatch(refspec->src, refname, 0) == 0);
}

int git_refspec_is_negative(const git_refspec *spec)
{
GIT_ASSERT_ARG(spec);
GIT_ASSERT_ARG(spec->src);

return (spec->src[0] == '^' && spec->dst == NULL);
}

int git_refspec_dst_matches(const git_refspec *refspec, const char *refname)
{
if (refspec == NULL || refspec->dst == NULL)
Expand Down
8 changes: 8 additions & 0 deletions src/refspec.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ int git_refspec__serialize(git_buf *out, const git_refspec *refspec);
*/
int git_refspec_is_wildcard(const git_refspec *spec);

/**
* Determines if a refspec is a negative refspec.
*
* @param spec the refspec
* @return 1 if the refspec is a negative, 0 otherwise
*/
int git_refspec_is_negative(const git_refspec *spec);

/**
* DWIM `spec` with `refs` existing on the remote, append the dwim'ed
* result in `out`.
Expand Down
8 changes: 6 additions & 2 deletions src/remote.c
Original file line number Diff line number Diff line change
Expand Up @@ -2112,17 +2112,21 @@ int git_remote_is_valid_name(
git_refspec *git_remote__matching_refspec(git_remote *remote, const char *refname)
{
git_refspec *spec;
git_refspec *match = NULL;
size_t i;

git_vector_foreach(&remote->active_refspecs, i, spec) {
if (spec->push)
continue;

if (git_refspec_is_negative(spec) && git_refspec_src_matches_negative(spec, refname))
return NULL;

if (git_refspec_src_matches(spec, refname))
return spec;
match = spec;
}

return NULL;
return match;
}

git_refspec *git_remote__matching_dst_refspec(git_remote *remote, const char *refname)
Expand Down