mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-08 00:49:11 +00:00
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:
@@ -14,7 +14,10 @@ import (
|
||||
"gitea.dev/models/unittest"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/git/gitcmd"
|
||||
"gitea.dev/modules/gitrepo"
|
||||
"gitea.dev/modules/test"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/routers/common"
|
||||
repo_service "gitea.dev/services/repository"
|
||||
"gitea.dev/tests"
|
||||
|
||||
@@ -135,6 +138,73 @@ func TestCompareBranches(t *testing.T) {
|
||||
inspectCompare(t, htmlDoc, diffCount, diffChanges)
|
||||
}
|
||||
|
||||
func TestCompareWithRefSuffix(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
// remove-files-b^ resolves to the tip's parent, so the test.txt added by the tip is excluded
|
||||
req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv...remove-files-b^")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
inspectCompare(t, htmlDoc, 2, []string{"link_hi", "test.csv"})
|
||||
|
||||
// a suffix resolves to a commit rather than a branch, so the page offers no pull request to create
|
||||
assert.Equal(t, 0, htmlDoc.doc.Find(".pullrequest-form").Length())
|
||||
|
||||
// the same suffix on the direct ".." comparison resolves to the same commit
|
||||
req = NewRequest(t, "GET", "/user2/repo20/compare/add-csv..remove-files-b^")
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc = NewHTMLParser(t, resp.Body)
|
||||
inspectCompare(t, htmlDoc, 2, []string{"link_hi", "test.csv"})
|
||||
|
||||
// a ~N suffix on the base side resolves and renders the compare page, but also
|
||||
// resolves to a commit rather than a branch, so no pull request form is offered
|
||||
req = NewRequest(t, "GET", "/user2/repo20/compare/add-csv~1...remove-files-b")
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
assert.True(t, test.IsNormalPageCompleted(resp.Body.String()))
|
||||
htmlDoc = NewHTMLParser(t, resp.Body)
|
||||
assert.Equal(t, 0, htmlDoc.doc.Find(".pullrequest-form").Length())
|
||||
|
||||
// the web handler folds an unsupported (^{...}) and an unresolvable (~50) suffix alike into 404
|
||||
for _, basehead := range []string{
|
||||
"add-csv...remove-files-b~50",
|
||||
"add-csv...remove-files-b^{/Add}",
|
||||
"add-csv^{/Add}...remove-files-b",
|
||||
} {
|
||||
req = NewRequest(t, "GET", "/user2/repo20/compare/"+basehead).SetHeader("Accept", "text/html")
|
||||
resp = session.MakeRequest(t, req, http.StatusNotFound)
|
||||
assert.True(t, test.IsNormalPageCompleted(resp.Body.String()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveRefWithSuffixContract(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 31})
|
||||
gitRepo, err := gitrepo.OpenRepository(t.Context(), repo)
|
||||
require.NoError(t, err)
|
||||
defer gitRepo.Close()
|
||||
|
||||
// a nil error guarantees a usable RefName
|
||||
ref, err := common.ResolveRefWithSuffix(gitRepo, "add-csv", "^")
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, ref)
|
||||
// a ref resolved with a suffix must be a commit SHA, not a branch ref
|
||||
// (branch refs would break "New Pull Request" logic)
|
||||
assert.False(t, ref.IsBranch(), "ref with suffix must not resolve to a branch")
|
||||
|
||||
// a missing ref and an unresolvable suffix both report not-found instead of an empty RefName
|
||||
for _, tc := range []struct{ oriRef, suffix string }{
|
||||
{"does-not-exist", ""},
|
||||
{"add-csv", "~50"},
|
||||
} {
|
||||
ref, err := common.ResolveRefWithSuffix(gitRepo, tc.oriRef, tc.suffix)
|
||||
assert.ErrorIs(t, err, util.ErrNotExist, "ref %q suffix %q", tc.oriRef, tc.suffix)
|
||||
assert.Empty(t, ref, "ref %q suffix %q", tc.oriRef, tc.suffix)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompareBranchesNoCommonMergeBase(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user