diff --git a/modules/git/tree.go b/modules/git/tree.go
index f6fdff97d0400..9135a1edc918c 100644
--- a/modules/git/tree.go
+++ b/modules/git/tree.go
@@ -73,3 +73,14 @@ func (repo *Repository) GetTreePathLatestCommit(refName, treePath string) (*Comm
}
return repo.GetCommit(strings.TrimSpace(stdout))
}
+
+// RevParse resolves a revision reference to other git-related objects
+func (repo *Repository) RevParse(ref, file string) (string, error) {
+ stdout, _, err := NewCommand("rev-parse").
+ AddDynamicArguments(ref+":"+file).
+ RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
+ if err != nil {
+ return "", err
+ }
+ return strings.TrimSpace(stdout), nil
+}
diff --git a/routers/common/compare.go b/routers/common/compare.go
index 4d1cc2f0d8908..736f73db0e0e0 100644
--- a/routers/common/compare.go
+++ b/routers/common/compare.go
@@ -18,4 +18,5 @@ type CompareInfo struct {
BaseBranch string
HeadBranch string
DirectComparison bool
+ RawDiffType git.RawDiffType
}
diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go
index 8b99dd95da109..351236ac29f3d 100644
--- a/routers/web/repo/compare.go
+++ b/routers/web/repo/compare.go
@@ -228,10 +228,31 @@ func ParseCompareInfo(ctx *context.Context) *common.CompareInfo {
)
infoPath = ctx.PathParam("*")
+
var infos []string
+
if infoPath == "" {
infos = []string{baseRepo.DefaultBranch, baseRepo.DefaultBranch}
} else {
+ // check if head is a branch or tag only if infoPath ends with .diff or .patch
+ if strings.HasSuffix(infoPath, ".diff") || strings.HasSuffix(infoPath, ".patch") {
+ infos = strings.SplitN(infoPath, "...", 2)
+ if len(infos) != 2 {
+ infos = strings.SplitN(infoPath, "..", 2) // match github behavior
+ }
+ ref2IsBranch := gitrepo.IsBranchExist(ctx, ctx.Repo.Repository, infos[1])
+ ref2IsTag := gitrepo.IsTagExist(ctx, ctx.Repo.Repository, infos[1])
+ if !ref2IsBranch && !ref2IsTag {
+ if strings.HasSuffix(infoPath, ".diff") {
+ ci.RawDiffType = git.RawDiffNormal
+ infoPath = strings.TrimSuffix(infoPath, ".diff")
+ } else if strings.HasSuffix(infoPath, ".patch") {
+ ci.RawDiffType = git.RawDiffPatch
+ infoPath = strings.TrimSuffix(infoPath, ".patch")
+ }
+ }
+ }
+
infos = strings.SplitN(infoPath, "...", 2)
if len(infos) != 2 {
if infos = strings.SplitN(infoPath, "..", 2); len(infos) == 2 {
@@ -729,6 +750,7 @@ func CompareDiff(ctx *context.Context) {
return
}
+ ctx.Data["PageIsCompareDiff"] = true
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
ctx.Data["DirectComparison"] = ci.DirectComparison
ctx.Data["OtherCompareSeparator"] = ".."
@@ -743,6 +765,16 @@ func CompareDiff(ctx *context.Context) {
return
}
+ if ci.RawDiffType != "" {
+ err := git.GetRepoRawDiffForFile(ci.HeadGitRepo, ci.BaseBranch, ci.HeadBranch, ci.RawDiffType, "", ctx.Resp)
+ if err != nil {
+ ctx.ServerError("GetRepoRawDiffForFile", err)
+ return
+ }
+ ctx.Resp.Flush()
+ return
+ }
+
baseTags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
if err != nil {
ctx.ServerError("GetTagNamesByRepoID", err)
diff --git a/templates/repo/diff/options_dropdown.tmpl b/templates/repo/diff/options_dropdown.tmpl
index 8d08e7ad46021..03519165db6dc 100644
--- a/templates/repo/diff/options_dropdown.tmpl
+++ b/templates/repo/diff/options_dropdown.tmpl
@@ -10,6 +10,9 @@
{{else if .Commit.ID.String}}
{{ctx.Locale.Tr "repo.diff.download_patch"}}
{{ctx.Locale.Tr "repo.diff.download_diff"}}
+ {{else if $.PageIsCompareDiff}}
+ {{ctx.Locale.Tr "repo.diff.download_patch"}}
+ {{ctx.Locale.Tr "repo.diff.download_diff"}}
{{end}}
{{ctx.Locale.Tr "repo.pulls.expand_files"}}
{{ctx.Locale.Tr "repo.pulls.collapse_files"}}
diff --git a/tests/integration/compare_test.go b/tests/integration/compare_test.go
index 0648777fede0e..6aefbd0e40c55 100644
--- a/tests/integration/compare_test.go
+++ b/tests/integration/compare_test.go
@@ -9,10 +9,12 @@ import (
"net/url"
"strings"
"testing"
+ "time"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/test"
repo_service "code.gitea.io/gitea/services/repository"
"code.gitea.io/gitea/tests"
@@ -158,3 +160,99 @@ func TestCompareCodeExpand(t *testing.T) {
}
})
}
+
+func TestCompareRawDiffNormal(t *testing.T) {
+ onGiteaRun(t, func(t *testing.T, u *url.URL) {
+ user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
+ repo, err := repo_service.CreateRepositoryDirectly(db.DefaultContext, user1, user1, repo_service.CreateRepoOptions{
+ Name: "test_raw_diff",
+ Readme: "Default",
+ AutoInit: true,
+ DefaultBranch: "main",
+ }, true)
+ assert.NoError(t, err)
+ session := loginUser(t, user1.Name)
+
+ r, _ := gitrepo.OpenRepository(db.DefaultContext, repo)
+
+ oldRef, _ := r.GetBranchCommit(repo.DefaultBranch)
+ oldBlobRef, _ := r.RevParse(oldRef.ID.String(), "README.md")
+
+ testEditFile(t, session, user1.Name, repo.Name, "main", "README.md", strings.Repeat("a\n", 2))
+
+ newRef, _ := r.GetBranchCommit(repo.DefaultBranch)
+ newBlobRef, _ := r.RevParse(newRef.ID.String(), "README.md")
+
+ req := NewRequest(t, "GET", fmt.Sprintf("/user1/test_raw_diff/compare/%s...%s.diff", oldRef.ID.String(), newRef.ID.String()))
+ resp := session.MakeRequest(t, req, http.StatusOK)
+
+ expected := fmt.Sprintf(`diff --git a/README.md b/README.md
+index %s..%s 100644
+--- a/README.md
++++ b/README.md
+@@ -1,2 +1,2 @@
+-# test_raw_diff
+-
++a
++a
+`, oldBlobRef[:7], newBlobRef[:7])
+ assert.Equal(t, expected, resp.Body.String())
+ })
+}
+
+func TestCompareRawDiffPatch(t *testing.T) {
+ onGiteaRun(t, func(t *testing.T, u *url.URL) {
+ user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
+ repo, err := repo_service.CreateRepositoryDirectly(db.DefaultContext, user1, user1, repo_service.CreateRepoOptions{
+ Name: "test_raw_diff",
+ Readme: "Default",
+ AutoInit: true,
+ DefaultBranch: "main",
+ }, true)
+ assert.NoError(t, err)
+ session := loginUser(t, user1.Name)
+
+ r, _ := gitrepo.OpenRepository(db.DefaultContext, repo)
+
+ // Get the old commit and blob reference
+ oldRef, _ := r.GetBranchCommit(repo.DefaultBranch)
+ oldBlobRef, _ := r.RevParse(oldRef.ID.String(), "README.md")
+
+ resp := testEditFile(t, session, user1.Name, repo.Name, "main", "README.md", strings.Repeat("a\n", 2))
+
+ newRef, _ := r.GetBranchCommit(repo.DefaultBranch)
+ newBlobRef, _ := r.RevParse(newRef.ID.String(), "README.md")
+
+ // Get the last modified time from the response header
+ respTs, _ := time.Parse(time.RFC1123, resp.Result().Header.Get("Last-Modified"))
+ respTs = respTs.In(time.Local)
+
+ // Format the timestamp to match the expected format in the patch
+ customFormat := "Mon, 02 Jan 2006 15:04:05 -0700"
+ respTsStr := respTs.Format(customFormat)
+
+ req := NewRequest(t, "GET", fmt.Sprintf("/user1/test_raw_diff/compare/%s...%s.patch", oldRef.ID.String(), newRef.ID.String()))
+ resp = session.MakeRequest(t, req, http.StatusOK)
+
+ expected := fmt.Sprintf(`From %s Mon Sep 17 00:00:00 2001
+From: User One
+Date: %s
+Subject: [PATCH] Update README.md
+
+---
+ README.md | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/README.md b/README.md
+index %s..%s 100644
+--- a/README.md
++++ b/README.md
+@@ -1,2 +1,2 @@
+-# test_raw_diff
+-
++a
++a
+`, newRef.ID.String(), respTsStr, oldBlobRef[:7], newBlobRef[:7])
+ assert.Equal(t, expected, resp.Body.String())
+ })
+}