mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-08 14:03:24 +09:00
feat: add raw diff/patch endpoint for repository comparisons (#37632)
## Summary
Adds `GET
/repos/{owner}/{repo}/compare/{basehead}.{diffType:diff|patch}`,
mirroring the existing `/git/commits/{sha}.{diffType}` endpoint but for
comparisons between two arbitrary refs.
The new endpoint streams a raw unified diff or `git format-patch` output
between any two refs:
GET /repos/{owner}/{repo}/compare/main...feature.diff
GET /repos/{owner}/{repo}/compare/v1.0..v1.1.patch
GET /repos/{owner}/{repo}/compare/abc1234...def5678.diff
Resolves #5561, #13416 and #17165.
AI was used while creating this PR. Automated tests were added as per
the contribution policy.
---------
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
bircni
Claude Opus 4.8
parent
b4cb192fba
commit
052feee34a
@@ -11,6 +11,7 @@ import (
|
|||||||
api "gitea.dev/modules/structs"
|
api "gitea.dev/modules/structs"
|
||||||
"gitea.dev/services/context"
|
"gitea.dev/services/context"
|
||||||
"gitea.dev/services/convert"
|
"gitea.dev/services/convert"
|
||||||
|
git_service "gitea.dev/services/git"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CompareDiff compare two branches or commits
|
// CompareDiff compare two branches or commits
|
||||||
@@ -18,8 +19,12 @@ func CompareDiff(ctx *context.APIContext) {
|
|||||||
// swagger:operation GET /repos/{owner}/{repo}/compare/{basehead} repository repoCompareDiff
|
// swagger:operation GET /repos/{owner}/{repo}/compare/{basehead} repository repoCompareDiff
|
||||||
// ---
|
// ---
|
||||||
// summary: Get commit comparison information
|
// summary: Get commit comparison information
|
||||||
|
// description: |
|
||||||
|
// By default returns JSON commit comparison information. The raw diff or patch can be
|
||||||
|
// requested with the `output` query parameter set to `diff` or `patch` respectively.
|
||||||
// produces:
|
// produces:
|
||||||
// - application/json
|
// - application/json
|
||||||
|
// - text/plain
|
||||||
// parameters:
|
// parameters:
|
||||||
// - name: owner
|
// - name: owner
|
||||||
// in: path
|
// in: path
|
||||||
@@ -33,9 +38,16 @@ func CompareDiff(ctx *context.APIContext) {
|
|||||||
// required: true
|
// required: true
|
||||||
// - name: basehead
|
// - name: basehead
|
||||||
// in: path
|
// in: path
|
||||||
// description: compare two branches or commits
|
// description: compare two refs as `base...head` (or `base..head`); refs may be branches, tags, full or short SHAs, including branch names that contain slashes.
|
||||||
// type: string
|
// type: string
|
||||||
// required: true
|
// required: true
|
||||||
|
// - name: output
|
||||||
|
// in: query
|
||||||
|
// description: return the raw comparison as `diff` or `patch` instead of JSON
|
||||||
|
// type: string
|
||||||
|
// enum:
|
||||||
|
// - diff
|
||||||
|
// - patch
|
||||||
// responses:
|
// responses:
|
||||||
// "200":
|
// "200":
|
||||||
// "$ref": "#/responses/Compare"
|
// "$ref": "#/responses/Compare"
|
||||||
@@ -57,6 +69,16 @@ func CompareDiff(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
defer closer()
|
defer closer()
|
||||||
|
|
||||||
|
// ?output=diff|patch returns the raw output, otherwise the JSON comparison is returned.
|
||||||
|
switch ctx.FormString("output") {
|
||||||
|
case "diff":
|
||||||
|
downloadCompareDiffOrPatch(ctx, compareInfo, false)
|
||||||
|
return
|
||||||
|
case "patch":
|
||||||
|
downloadCompareDiffOrPatch(ctx, compareInfo, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
verification := ctx.FormString("verification") == "" || ctx.FormBool("verification")
|
verification := ctx.FormString("verification") == "" || ctx.FormBool("verification")
|
||||||
files := ctx.FormString("files") == "" || ctx.FormBool("files")
|
files := ctx.FormString("files") == "" || ctx.FormBool("files")
|
||||||
|
|
||||||
@@ -88,3 +110,20 @@ func CompareDiff(ctx *context.APIContext) {
|
|||||||
Commits: apiCommits,
|
Commits: apiCommits,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// downloadCompareDiffOrPatch writes a comparison's raw diff or patch to the response.
|
||||||
|
func downloadCompareDiffOrPatch(ctx *context.APIContext, compareInfo *git_service.CompareInfo, patch bool) {
|
||||||
|
ctx.Resp.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
compareArg := compareInfo.BaseCommitID + compareInfo.CompareSeparator + compareInfo.HeadCommitID
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if patch {
|
||||||
|
err = compareInfo.HeadGitRepo.GetPatch(compareArg, ctx.Resp)
|
||||||
|
} else {
|
||||||
|
err = compareInfo.HeadGitRepo.GetDiff(compareArg, ctx.Resp)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -201,12 +201,12 @@ func newComparePageInfo() *comparePageInfoType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parseCompareInfo parse compare info between two commit for preparing comparing references
|
// parseCompareInfo parse compare info between two commit for preparing comparing references
|
||||||
func (cpi *comparePageInfoType) parseCompareInfo(ctx *context.Context) error {
|
func (cpi *comparePageInfoType) parseCompareInfo(ctx *context.Context, compareParam string) error {
|
||||||
baseRepo := ctx.Repo.Repository
|
baseRepo := ctx.Repo.Repository
|
||||||
fileOnly := ctx.FormBool("file-only")
|
fileOnly := ctx.FormBool("file-only")
|
||||||
|
|
||||||
// 1 Parse compare router param
|
// 1 Parse compare router param
|
||||||
compareReq := common.ParseCompareRouterParam(ctx.PathParam("*"))
|
compareReq := common.ParseCompareRouterParam(compareParam)
|
||||||
|
|
||||||
// remove the check when we support compare with carets
|
// remove the check when we support compare with carets
|
||||||
if compareReq.BaseOriRefSuffix != "" {
|
if compareReq.BaseOriRefSuffix != "" {
|
||||||
@@ -545,7 +545,7 @@ func getBranchesAndTagsForRepo(ctx gocontext.Context, repo *repo_model.Repositor
|
|||||||
// CompareDiff show different from one commit to another commit
|
// CompareDiff show different from one commit to another commit
|
||||||
func CompareDiff(ctx *context.Context) {
|
func CompareDiff(ctx *context.Context) {
|
||||||
comparePageInfo := newComparePageInfo()
|
comparePageInfo := newComparePageInfo()
|
||||||
err := comparePageInfo.parseCompareInfo(ctx)
|
err := comparePageInfo.parseCompareInfo(ctx, ctx.PathParam("*"))
|
||||||
if errors.Is(err, util.ErrNotExist) || errors.Is(err, util.ErrInvalidArgument) {
|
if errors.Is(err, util.ErrNotExist) || errors.Is(err, util.ErrInvalidArgument) {
|
||||||
ctx.NotFound(nil)
|
ctx.NotFound(nil)
|
||||||
return
|
return
|
||||||
@@ -605,6 +605,45 @@ func CompareDiff(ctx *context.Context) {
|
|||||||
ctx.HTML(http.StatusOK, tplCompare)
|
ctx.HTML(http.StatusOK, tplCompare)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DownloadCompareDiff render a comparison's raw unified diff
|
||||||
|
func DownloadCompareDiff(ctx *context.Context) {
|
||||||
|
downloadCompareDiffOrPatch(ctx, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DownloadComparePatch render a comparison as a git format-patch
|
||||||
|
func DownloadComparePatch(ctx *context.Context) {
|
||||||
|
downloadCompareDiffOrPatch(ctx, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadCompareDiffOrPatch(ctx *context.Context, patch bool) {
|
||||||
|
// The route captures `basehead` separately so the `.diff`/`.patch` suffix is
|
||||||
|
// stripped from the catch-all `*` param parseCompareInfo would otherwise read.
|
||||||
|
cpi := newComparePageInfo()
|
||||||
|
if err := cpi.parseCompareInfo(ctx, ctx.PathParam("basehead")); err != nil {
|
||||||
|
if errors.Is(err, util.ErrNotExist) || errors.Is(err, util.ErrInvalidArgument) {
|
||||||
|
ctx.NotFound(nil)
|
||||||
|
} else {
|
||||||
|
ctx.ServerError("ParseCompareInfo", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ci := cpi.compareInfo
|
||||||
|
|
||||||
|
ctx.Resp.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
compareArg := ci.BaseCommitID + ci.CompareSeparator + ci.HeadCommitID
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if patch {
|
||||||
|
err = ci.HeadGitRepo.GetPatch(compareArg, ctx.Resp)
|
||||||
|
} else {
|
||||||
|
err = ci.HeadGitRepo.GetDiff(compareArg, ctx.Resp)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("DownloadCompareDiffOrPatch", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (cpi *comparePageInfoType) prepareCreatePullRequestPage(ctx *context.Context) {
|
func (cpi *comparePageInfoType) prepareCreatePullRequestPage(ctx *context.Context) {
|
||||||
ci := cpi.compareInfo
|
ci := cpi.compareInfo
|
||||||
if cpi.allowCreatePull {
|
if cpi.allowCreatePull {
|
||||||
|
|||||||
@@ -1310,7 +1310,7 @@ func CompareAndPullRequestPost(ctx *context.Context) {
|
|||||||
form := web.GetForm(ctx).(*forms.CreateIssueForm)
|
form := web.GetForm(ctx).(*forms.CreateIssueForm)
|
||||||
repo := ctx.Repo.Repository
|
repo := ctx.Repo.Repository
|
||||||
comparePageInfo := newComparePageInfo()
|
comparePageInfo := newComparePageInfo()
|
||||||
err := comparePageInfo.parseCompareInfo(ctx)
|
err := comparePageInfo.parseCompareInfo(ctx, ctx.PathParam("*"))
|
||||||
if errors.Is(err, util.ErrNotExist) {
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
ctx.JSONErrorNotFound()
|
ctx.JSONErrorNotFound()
|
||||||
return
|
return
|
||||||
|
|||||||
+6
-3
@@ -1269,9 +1269,12 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
|||||||
m.Get("/commit/*", context.RepoRefByType(git.RefTypeCommit), repo.TreeViewNodes)
|
m.Get("/commit/*", context.RepoRefByType(git.RefTypeCommit), repo.TreeViewNodes)
|
||||||
})
|
})
|
||||||
m.Get("/compare", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff)
|
m.Get("/compare", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff)
|
||||||
m.Combo("/compare/*", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists).
|
m.PathGroup("/compare/*", func(g *web.RouterPathGroup) {
|
||||||
Get(repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff).
|
g.MatchPath("GET", "/<basehead:*>.diff", repo.MustBeNotEmpty, repo.DownloadCompareDiff)
|
||||||
Post(reqSignIn, context.RepoMustNotBeArchived(), reqUnitPullsReader, repo.MustAllowPulls, web.Bind(forms.CreateIssueForm{}), repo.SetWhitespaceBehavior, repo.CompareAndPullRequestPost)
|
g.MatchPath("GET", "/<basehead:*>.patch", repo.MustBeNotEmpty, repo.DownloadComparePatch)
|
||||||
|
g.MatchPath("GET", "/<*:*>", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff)
|
||||||
|
g.MatchPath("POST", "/<*:*>", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists, reqSignIn, context.RepoMustNotBeArchived(), reqUnitPullsReader, repo.MustAllowPulls, web.Bind(forms.CreateIssueForm{}), repo.SetWhitespaceBehavior, repo.CompareAndPullRequestPost)
|
||||||
|
})
|
||||||
m.Get("/pulls/new/*", repo.PullsNewRedirect)
|
m.Get("/pulls/new/*", repo.PullsNewRedirect)
|
||||||
}, optSignIn, context.RepoAssignment, reqUnitCodeReader)
|
}, optSignIn, context.RepoAssignment, reqUnitCodeReader)
|
||||||
// end "/{username}/{reponame}": repo code: find, compare, list
|
// end "/{username}/{reponame}": repo code: find, compare, list
|
||||||
|
|||||||
Generated
+14
-2
@@ -8108,8 +8108,10 @@
|
|||||||
},
|
},
|
||||||
"/repos/{owner}/{repo}/compare/{basehead}": {
|
"/repos/{owner}/{repo}/compare/{basehead}": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"description": "By default returns JSON commit comparison information. The raw diff or patch can be\nrequested with the `output` query parameter set to `diff` or `patch` respectively.\n",
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json",
|
||||||
|
"text/plain"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"repository"
|
"repository"
|
||||||
@@ -8133,10 +8135,20 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "compare two branches or commits",
|
"description": "compare two refs as `base...head` (or `base..head`); refs may be branches, tags, full or short SHAs, including branch names that contain slashes.",
|
||||||
"name": "basehead",
|
"name": "basehead",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"diff",
|
||||||
|
"patch"
|
||||||
|
],
|
||||||
|
"type": "string",
|
||||||
|
"description": "return the raw comparison as `diff` or `patch` instead of JSON",
|
||||||
|
"name": "output",
|
||||||
|
"in": "query"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
|
|||||||
Generated
+14
-1
@@ -19468,6 +19468,7 @@
|
|||||||
},
|
},
|
||||||
"/repos/{owner}/{repo}/compare/{basehead}": {
|
"/repos/{owner}/{repo}/compare/{basehead}": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"description": "By default returns JSON commit comparison information. The raw diff or patch can be\nrequested with the `output` query parameter set to `diff` or `patch` respectively.\n",
|
||||||
"operationId": "repoCompareDiff",
|
"operationId": "repoCompareDiff",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -19489,13 +19490,25 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "compare two branches or commits",
|
"description": "compare two refs as `base...head` (or `base..head`); refs may be branches, tags, full or short SHAs, including branch names that contain slashes.",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"name": "basehead",
|
"name": "basehead",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "return the raw comparison as `diff` or `patch` instead of JSON",
|
||||||
|
"in": "query",
|
||||||
|
"name": "output",
|
||||||
|
"schema": {
|
||||||
|
"enum": [
|
||||||
|
"diff",
|
||||||
|
"patch"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package integration
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
auth_model "gitea.dev/models/auth"
|
auth_model "gitea.dev/models/auth"
|
||||||
@@ -62,3 +63,113 @@ func TestAPICompareBranches(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIDownloadCompareDiffOrPatch(t *testing.T) {
|
||||||
|
onGiteaRun(t, func(t *testing.T, _ *url.URL) {
|
||||||
|
session := loginUser(t, "user2")
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
|
||||||
|
t.Run("BranchToBranchDiff", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo20/compare/add-csv...remove-files-b?output=diff").AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
assert.Equal(t, "text/plain; charset=utf-8", resp.Header().Get("Content-Type"))
|
||||||
|
body := resp.Body.String()
|
||||||
|
assert.Contains(t, body, "diff --git ")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("BranchToBranchPatch", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo20/compare/add-csv...remove-files-b?output=patch").AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
assert.Equal(t, "text/plain; charset=utf-8", resp.Header().Get("Content-Type"))
|
||||||
|
body := resp.Body.String()
|
||||||
|
assert.True(t, strings.HasPrefix(body, "From "), "patch output should start with a format-patch header, got: %q", body[:min(40, len(body))])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("CommitToCommitDiff", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo20/compare/808038d2f71b0ab02099...c8e31bc7688741a5287f?output=diff").AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
assert.Contains(t, resp.Body.String(), "diff --git ")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("BranchToCommitDiff", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
// 8babce96... is the head of remove-files-b; pairing it with add-csv guarantees a non-empty diff.
|
||||||
|
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo20/compare/add-csv...8babce967f21b9dfa6987f943b91093dac58a4f0?output=diff").AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
assert.Contains(t, resp.Body.String(), "diff --git ")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("TwoDotSeparator", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo20/compare/add-csv..remove-files-b?output=diff").AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
assert.Contains(t, resp.Body.String(), "diff --git ")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("SlashedBranchName", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
// user2/repo1's `feature/1` branch contains a slash; the route must match it
|
||||||
|
// without URL-encoding. master and feature/1 happen to share a SHA in the fixture,
|
||||||
|
// so we only assert the route resolves (200 OK) rather than checking diff content.
|
||||||
|
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/compare/master...feature/1?output=diff").AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
assert.Equal(t, "text/plain; charset=utf-8", resp.Header().Get("Content-Type"))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("UnknownOutputReturnsJSON", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
// Only "diff"/"patch" switch to raw output; any other value falls through to JSON.
|
||||||
|
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo20/compare/add-csv...remove-files-b?output=foo").AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
apiResp := DecodeJSON(t, resp, &api.Compare{})
|
||||||
|
assert.Equal(t, 2, apiResp.TotalCommits)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("SingleRefImplicitBase", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
// No `...`/`..` separator: parseCompareInfo defaults the base to the
|
||||||
|
// repo's PR target branch (master for repo20) and compares it against
|
||||||
|
// the given head.
|
||||||
|
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo20/compare/add-csv?output=diff").AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
assert.Equal(t, "text/plain; charset=utf-8", resp.Header().Get("Content-Type"))
|
||||||
|
assert.Contains(t, resp.Body.String(), "diff --git ")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("PrivateRepoAnonymous", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
// repo16 is private; an unauthenticated request must not leak its existence.
|
||||||
|
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo16/compare/master...good-sign?output=diff")
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("CrossRepoFork", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
user13 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 13})
|
||||||
|
repo11 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 11})
|
||||||
|
user13Sess := loginUser(t, "user13")
|
||||||
|
user13Token := getTokenForLoggedInUser(t, user13Sess, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
|
||||||
|
_, err := createFileInBranch(user13, repo11, createFileInBranchOptions{OldBranch: "master", NewBranch: "cross-repo-diff"}, map[string]string{"hello.txt": "hi\n"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", "/api/v1/repos/user12/repo10/compare/master...user13:cross-repo-diff?output=diff").AddTokenAuth(user13Token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
assert.Equal(t, "text/plain; charset=utf-8", resp.Header().Get("Content-Type"))
|
||||||
|
assert.Contains(t, resp.Body.String(), "diff --git ")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -167,6 +167,53 @@ Hello from 2
|
|||||||
assert.Equal(t, 0, htmlDoc.doc.Find(".pullrequest-form").Length())
|
assert.Equal(t, 0, htmlDoc.doc.Find(".pullrequest-form").Length())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCompareDownloadDiffOrPatch(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
session := loginUser(t, "user2")
|
||||||
|
|
||||||
|
t.Run("BranchToBranchDiff", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv...remove-files-b.diff")
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
assert.Equal(t, "text/plain; charset=utf-8", resp.Header().Get("Content-Type"))
|
||||||
|
assert.Contains(t, resp.Body.String(), "diff --git ")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("BranchToBranchPatch", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv...remove-files-b.patch")
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
assert.Equal(t, "text/plain; charset=utf-8", resp.Header().Get("Content-Type"))
|
||||||
|
assert.True(t, strings.HasPrefix(resp.Body.String(), "From "), "patch output should start with a format-patch header")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("SingleRefImplicitBase", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv.diff")
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
assert.Contains(t, resp.Body.String(), "diff --git ")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("InvalidBaseRef", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", "/user2/repo20/compare/does-not-exist...remove-files-b.diff")
|
||||||
|
session.MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("PrivateRepoAnonymous", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
// repo16 is private; an unauthenticated request must not leak its existence.
|
||||||
|
req := NewRequest(t, "GET", "/user2/repo16/compare/master...good-sign.diff")
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestCompareCodeExpand(t *testing.T) {
|
func TestCompareCodeExpand(t *testing.T) {
|
||||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
|
|||||||
Reference in New Issue
Block a user