Skip to content

Commit 5bdb78f

Browse files
committed
refs: Handle normalizing negative refspecs
Upstream commit 2adb7bc. Negative refspecs were added in Git v2.29.0 which allows refspecs to be prefixed with `^`[^1]. Currently, the library is unable to normalize negative refspecs which causes errors in different tools that rely on `libgit2`. Specifically, when the library attempts to parse and normalize a negative refspec, it returns a `GIT_EINVALIDSPEC` code. Add the ability to correctly normalize negative refspecs. While this PR will not update the behavior of `fetch`, or other actions that rely on negative refspecs, this allows us to be able to successfully parse and normalize them. A future change will handle updating the individual actions. [^1]: git/git@c0192df
1 parent c9693eb commit 5bdb78f

File tree

1 file changed

+19
-6
lines changed

1 file changed

+19
-6
lines changed

src/refs.c

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -813,17 +813,20 @@ static int is_valid_ref_char(char ch)
813813
}
814814
}
815815

816-
static int ensure_segment_validity(const char *name, char may_contain_glob)
816+
static int ensure_segment_validity(const char *name, char may_contain_glob, bool allow_caret_prefix)
817817
{
818818
const char *current = name;
819+
const char *start = current;
819820
char prev = '\0';
820821
const int lock_len = (int)strlen(GIT_FILELOCK_EXTENSION);
821822
int segment_len;
822823

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

826-
for (current = name; ; current++) {
829+
for (current = start; ; current++) {
827830
if (*current == '\0' || *current == '/')
828831
break;
829832

@@ -855,7 +858,7 @@ static int ensure_segment_validity(const char *name, char may_contain_glob)
855858
return segment_len;
856859
}
857860

858-
static bool is_all_caps_and_underscore(const char *name, size_t len)
861+
static bool is_valid_normalized_name(const char *name, size_t len)
859862
{
860863
size_t i;
861864
char c;
@@ -865,6 +868,9 @@ static bool is_all_caps_and_underscore(const char *name, size_t len)
865868
for (i = 0; i < len; i++)
866869
{
867870
c = name[i];
871+
if (i == 0 && c == '^')
872+
continue; /* The first character is allowed to be "^" for negative refspecs */
873+
868874
if ((c < 'A' || c > 'Z') && c != '_')
869875
return false;
870876
}
@@ -885,6 +891,7 @@ int git_reference__normalize_name(
885891
int segment_len, segments_count = 0, error = GIT_EINVALIDSPEC;
886892
unsigned int process_flags;
887893
bool normalize = (buf != NULL);
894+
bool allow_caret_prefix = true;
888895
bool validate = (flags & GIT_REFERENCE_FORMAT__VALIDATION_DISABLE) == 0;
889896

890897
#ifdef GIT_USE_ICONV
@@ -922,7 +929,7 @@ int git_reference__normalize_name(
922929
while (true) {
923930
char may_contain_glob = process_flags & GIT_REFERENCE_FORMAT_REFSPEC_PATTERN;
924931

925-
segment_len = ensure_segment_validity(current, may_contain_glob);
932+
segment_len = ensure_segment_validity(current, may_contain_glob, allow_caret_prefix);
926933
if (segment_len < 0)
927934
goto cleanup;
928935

@@ -958,6 +965,12 @@ int git_reference__normalize_name(
958965
break;
959966

960967
current += segment_len + 1;
968+
969+
/*
970+
* A caret prefix is only allowed in the first segment to signify a
971+
* negative refspec.
972+
*/
973+
allow_caret_prefix = false;
961974
}
962975

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

978991
if ((segments_count == 1 ) &&
979992
!(flags & GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND) &&
980-
!(is_all_caps_and_underscore(name, (size_t)segment_len) ||
993+
!(is_valid_normalized_name(name, (size_t)segment_len) ||
981994
((flags & GIT_REFERENCE_FORMAT_REFSPEC_PATTERN) && !strcmp("*", name))))
982995
goto cleanup;
983996

984997
if ((segments_count > 1)
985-
&& (is_all_caps_and_underscore(name, strchr(name, '/') - name)))
998+
&& (is_valid_normalized_name(name, strchr(name, '/') - name)))
986999
goto cleanup;
9871000

9881001
error = 0;

0 commit comments

Comments
 (0)