feat(api): support ref suffixes in compare (#38148)

Compare API requests with a `^` or `~N` revision suffix (for example
`compare/main...feature^`) were rejected with `400 Unsupported
comparison syntax: ref with suffix`. The fix resolves the suffix to a
commit before comparing, so `base...head^` and `~N` work on either side,
the same as git.

Only `^`/`~N` navigation is resolved. Pull request creation still
requires plain branch refs, and the web compare page keeps rejecting
suffixes since its branch selectors need separate UI work.

Closes #33943
This commit is contained in:
Eyüp Can Akman
2026-06-24 08:38:02 +03:00
committed by GitHub
parent 59d4825a95
commit ef927f9fa3
11 changed files with 246 additions and 34 deletions

View File

@@ -228,13 +228,16 @@ func (ref RefName) RefWebLinkPath() string {
return string(refType) + "/" + util.PathEscapeSegments(ref.ShortName())
}
func ParseRefSuffix(ref string) (string, string) {
func ParseRefSuffix(ref string) (refName, refSuffix string) {
// Partially support https://git-scm.com/docs/gitrevisions
if idx := strings.Index(ref, "@{"); idx != -1 {
return ref[:idx], ref[idx:]
suffixIdx := -1 // earliest suffix mark, so a combined suffix like "main~2^" stays intact
for _, mark := range []string{"@{", "^", "~"} {
if idx := strings.Index(ref, mark); idx != -1 && (suffixIdx == -1 || idx < suffixIdx) {
suffixIdx = idx
}
}
if idx := strings.Index(ref, "^"); idx != -1 {
return ref[:idx], ref[idx:]
if suffixIdx == -1 {
return ref, ""
}
return ref, ""
return ref[:suffixIdx], ref[suffixIdx:]
}

View File

@@ -37,3 +37,22 @@ func TestRefWebLinkPath(t *testing.T) {
assert.Equal(t, "tag/foo", RefName("refs/tags/foo").RefWebLinkPath())
assert.Equal(t, "commit/c0ffee", RefName("c0ffee").RefWebLinkPath())
}
func TestParseRefSuffix(t *testing.T) {
cases := []struct {
ref, name, suffix string
}{
{"main", "main", ""},
{"main^", "main", "^"},
{"main^2", "main", "^2"},
{"main~3", "main", "~3"},
{"main@{yesterday}", "main", "@{yesterday}"},
{"main~2^", "main", "~2^"},
{"main^~2", "main", "^~2"},
}
for _, c := range cases {
name, suffix := ParseRefSuffix(c.ref)
assert.Equal(t, c.name, name, "ref: %s", c.ref)
assert.Equal(t, c.suffix, suffix, "ref: %s", c.ref)
}
}