mirror of
https://github.com/go-gitea/gitea.git
synced 2026-06-15 20:25:18 +02:00
Compare commits
6 Commits
76f8d122fe
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7997c1ccad | ||
|
|
0eba0e371f | ||
|
|
052feee34a | ||
|
|
b4cb192fba | ||
|
|
1363b097e2 | ||
|
|
d2186ecd03 |
2
.github/workflows/cron-renovate.yml
vendored
2
.github/workflows/cron-renovate.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: renovatebot/github-action@693b9ef15eec82123529a37c782242f091365961 # v46.1.14
|
||||
- uses: renovatebot/github-action@8217b3fc286df088d7c27f3255fe8414463bc0fd # v46.1.15
|
||||
with:
|
||||
renovate-version: ${{ env.RENOVATE_VERSION }}
|
||||
configurationFile: renovate.json5
|
||||
|
||||
2
.github/workflows/pull-db-tests.yml
vendored
2
.github/workflows/pull-db-tests.yml
vendored
@@ -131,7 +131,7 @@ jobs:
|
||||
ports:
|
||||
- "7700:7700"
|
||||
redis:
|
||||
image: redis:latest@sha256:e74c9b933d78e2829583d88f92793f4524752a15ac59c8baff2dd5ed000b7432
|
||||
image: redis:latest@sha256:a505f8b9d8ac3ff7b0848055b4abf1901d6d77606774aa1e38bd37f1197ed2b5
|
||||
options: >- # wait until redis has started
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 5s
|
||||
|
||||
@@ -222,13 +222,12 @@ Here's the history of the owners and the time they served:
|
||||
- [Lunny Xiao](https://gitea.com/lunny) - 2016, 2017, [2018](https://github.com/go-gitea/gitea/issues/3255), [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872), 2023
|
||||
- [Kim Carlbäcker](https://github.com/bkcsoft) - 2016, 2017
|
||||
- [Thomas Boerger](https://gitea.com/tboerger) - 2016, 2017
|
||||
- [Lauris Bukšis-Haberkorns](https://gitea.com/lafriks) - [2018](https://github.com/go-gitea/gitea/issues/3255), [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801)
|
||||
- [Lauris Bukšis](https://gitea.com/lafriks) - [2018](https://github.com/go-gitea/gitea/issues/3255), [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), 2025
|
||||
- [Matti Ranta](https://gitea.com/techknowlogick) - [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872), 2023
|
||||
- [Andrew Thornton](https://gitea.com/zeripath) - [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872), 2023
|
||||
- [6543](https://gitea.com/6543) - 2023, 2025
|
||||
- [John Olheiser](https://gitea.com/jolheiser) - 2023, 2024
|
||||
- [Jason Song](https://gitea.com/wolfogre) - 2023
|
||||
- [lafriks](https://gitea.com/lafriks) <lauris@nix.lv> - 2025
|
||||
|
||||
## Governance Compensation
|
||||
|
||||
|
||||
2
go.mod
2
go.mod
@@ -105,6 +105,7 @@ require (
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.5
|
||||
golang.org/x/crypto v0.53.0
|
||||
golang.org/x/image v0.42.0
|
||||
golang.org/x/mod v0.37.0
|
||||
golang.org/x/net v0.56.0
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
golang.org/x/sync v0.21.0
|
||||
@@ -267,7 +268,6 @@ require (
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
go4.org v0.0.0-20260112195520-a5071408f32f // indirect
|
||||
golang.org/x/mod v0.37.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
golang.org/x/tools v0.45.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad // indirect
|
||||
|
||||
@@ -70,9 +70,11 @@ func (c *CommitMessage) MessageTrailer() CommitMessageTrailerValues {
|
||||
|
||||
var commitMessageTrailerSplit = sync.OnceValue(func() *regexp.Regexp {
|
||||
// the sep is either something like "\n---\n" or "\n\n" in the body, or at the start of the body like "---\n"
|
||||
return regexp.MustCompile(`(?s)^(?P<content>.*?)(?P<sep>^|^\n|^-{3,}\n|\n-{3,}\n|\n\n)(?P<trailer>(?:[A-Za-z0-9][-A-Za-z0-9]*:[^\n]*\n?)*)$`)
|
||||
return regexp.MustCompile(`(?s)^(?P<content>.*?)(?P<sep>^|^\n|^-{3,}\n+|\n-{3,}\n+|\n\n)(?P<trailer>(?:[A-Za-z0-9][-A-Za-z0-9]*:[^\n]*\n?)*\n*)$`)
|
||||
})
|
||||
|
||||
// CommitMessageSplitTrailer tries to split the message by the trailer separator
|
||||
// content + sep + trailer will reconstruct the original message
|
||||
func CommitMessageSplitTrailer(s string) (content, sep, trailer string) {
|
||||
s = util.NormalizeStringEOL(s)
|
||||
re := commitMessageTrailerSplit()
|
||||
|
||||
@@ -26,8 +26,10 @@ func TestCommitMessageTrailer(t *testing.T) {
|
||||
{"a", "a", "", ""},
|
||||
{"a\n\nk", "a\n\nk", "", ""},
|
||||
{"a\n\nk:v", "a", "\n\n", "k:v"},
|
||||
{"a\n\nk:v\n\n", "a", "\n\n", "k:v\n\n"},
|
||||
{"a\n--\nk:v", "a\n--\nk:v", "", ""},
|
||||
{"a\n---\nk:v", "a", "\n---\n", "k:v"},
|
||||
{"a\n\n---\n\nk:v", "a\n", "\n---\n\n", "k:v"},
|
||||
|
||||
{"k: v", "", "", "k: v"},
|
||||
{"\nk:v", "", "\n", "k:v"},
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -20,6 +22,7 @@ const (
|
||||
|
||||
var (
|
||||
ErrInvalidStructure = util.NewInvalidArgumentErrorf("package has invalid structure")
|
||||
ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid")
|
||||
ErrGoModFileTooLarge = util.NewInvalidArgumentErrorf("go.mod file is too large")
|
||||
)
|
||||
|
||||
@@ -54,6 +57,13 @@ func ParsePackage(r io.ReaderAt, size int64) (*Package, error) {
|
||||
Name: strings.TrimSuffix(nameAndVersion, "@"+parts[1]),
|
||||
Version: versionParts[0],
|
||||
}
|
||||
|
||||
// the version is taken verbatim from the zip path and later written
|
||||
// one per line into the @v/list proxy response, so it has to be a
|
||||
// valid module version (no newlines or other stray characters)
|
||||
if !semver.IsValid(p.Version) {
|
||||
return nil, ErrInvalidVersion
|
||||
}
|
||||
}
|
||||
|
||||
if len(versionParts) > 1 {
|
||||
|
||||
@@ -59,6 +59,16 @@ func TestParsePackage(t *testing.T) {
|
||||
assert.Equal(t, "module gitea.com/go-gitea/gitea", p.GoMod)
|
||||
})
|
||||
|
||||
t.Run("InvalidVersion", func(t *testing.T) {
|
||||
data := createArchive(map[string][]byte{
|
||||
packageName + "@v1.0.0\nv99.0.0/go.mod": []byte("module " + packageName),
|
||||
})
|
||||
|
||||
p, err := ParsePackage(data, int64(data.Len()))
|
||||
assert.Nil(t, p)
|
||||
assert.ErrorIs(t, err, ErrInvalidVersion)
|
||||
})
|
||||
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
data := createArchive(map[string][]byte{
|
||||
packageName + "@" + packageVersion + "/subdir/go.mod": []byte("invalid"),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@11.5.2",
|
||||
"packageManager": "pnpm@11.5.3",
|
||||
"engines": {
|
||||
"node": ">= 22.18.0",
|
||||
"pnpm": ">= 11.0.0"
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
git_service "gitea.dev/services/git"
|
||||
)
|
||||
|
||||
// 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
|
||||
// ---
|
||||
// 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:
|
||||
// - application/json
|
||||
// - text/plain
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
@@ -33,9 +38,16 @@ func CompareDiff(ctx *context.APIContext) {
|
||||
// required: true
|
||||
// - name: basehead
|
||||
// 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
|
||||
// required: true
|
||||
// - name: output
|
||||
// in: query
|
||||
// description: return the raw comparison as `diff` or `patch` instead of JSON
|
||||
// type: string
|
||||
// enum:
|
||||
// - diff
|
||||
// - patch
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Compare"
|
||||
@@ -57,6 +69,16 @@ func CompareDiff(ctx *context.APIContext) {
|
||||
}
|
||||
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")
|
||||
files := ctx.FormString("files") == "" || ctx.FormBool("files")
|
||||
|
||||
@@ -88,3 +110,20 @@ func CompareDiff(ctx *context.APIContext) {
|
||||
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
|
||||
func (cpi *comparePageInfoType) parseCompareInfo(ctx *context.Context) error {
|
||||
func (cpi *comparePageInfoType) parseCompareInfo(ctx *context.Context, compareParam string) error {
|
||||
baseRepo := ctx.Repo.Repository
|
||||
fileOnly := ctx.FormBool("file-only")
|
||||
|
||||
// 1 Parse compare router param
|
||||
compareReq := common.ParseCompareRouterParam(ctx.PathParam("*"))
|
||||
compareReq := common.ParseCompareRouterParam(compareParam)
|
||||
|
||||
// remove the check when we support compare with carets
|
||||
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
|
||||
func CompareDiff(ctx *context.Context) {
|
||||
comparePageInfo := newComparePageInfo()
|
||||
err := comparePageInfo.parseCompareInfo(ctx)
|
||||
err := comparePageInfo.parseCompareInfo(ctx, ctx.PathParam("*"))
|
||||
if errors.Is(err, util.ErrNotExist) || errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
@@ -605,6 +605,45 @@ func CompareDiff(ctx *context.Context) {
|
||||
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) {
|
||||
ci := cpi.compareInfo
|
||||
if cpi.allowCreatePull {
|
||||
|
||||
@@ -1310,7 +1310,7 @@ func CompareAndPullRequestPost(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.CreateIssueForm)
|
||||
repo := ctx.Repo.Repository
|
||||
comparePageInfo := newComparePageInfo()
|
||||
err := comparePageInfo.parseCompareInfo(ctx)
|
||||
err := comparePageInfo.parseCompareInfo(ctx, ctx.PathParam("*"))
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.JSONErrorNotFound()
|
||||
return
|
||||
|
||||
@@ -1269,9 +1269,12 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
||||
m.Get("/commit/*", context.RepoRefByType(git.RefTypeCommit), repo.TreeViewNodes)
|
||||
})
|
||||
m.Get("/compare", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff)
|
||||
m.Combo("/compare/*", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists).
|
||||
Get(repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff).
|
||||
Post(reqSignIn, context.RepoMustNotBeArchived(), reqUnitPullsReader, repo.MustAllowPulls, web.Bind(forms.CreateIssueForm{}), repo.SetWhitespaceBehavior, repo.CompareAndPullRequestPost)
|
||||
m.PathGroup("/compare/*", func(g *web.RouterPathGroup) {
|
||||
g.MatchPath("GET", "/<basehead:*>.diff", repo.MustBeNotEmpty, repo.DownloadCompareDiff)
|
||||
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)
|
||||
}, optSignIn, context.RepoAssignment, reqUnitCodeReader)
|
||||
// end "/{username}/{reponame}": repo code: find, compare, list
|
||||
|
||||
@@ -4,14 +4,15 @@
|
||||
package pull
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
@@ -767,8 +768,6 @@ func CloseRepoBranchesPulls(ctx context.Context, doer *user_model.User, repo *re
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
var commitMessageTrailersPattern = regexp.MustCompile(`(?:^|\n\n)(?:[\w-]+[ \t]*:[^\n]+\n*(?:[ \t]+[^\n]+\n*)*)+$`)
|
||||
|
||||
// GetSquashMergeCommitMessages returns the commit messages between head and merge base (if there is one)
|
||||
func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequest) string {
|
||||
if err := pr.LoadIssue(ctx); err != nil {
|
||||
@@ -819,54 +818,44 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ
|
||||
return ""
|
||||
}
|
||||
|
||||
posterSig := pr.Issue.Poster.NewGitSig().String()
|
||||
mergeMessage := strings.TrimSpace(pr.Issue.Content) // use PR's title and description as squash commit message
|
||||
if setting.Repository.PullRequest.PopulateSquashCommentWithCommitMessages {
|
||||
mergeMessage = formatSquashMergeCommitMessages(limitedCommits) // use PR's commit messages as squash commit message
|
||||
}
|
||||
coAuthors := collectSquashMergeCommitCoAuthors(ctx, gitRepo, pr, headCommitRef, mergeBaseRef, limit, limitedCommits)
|
||||
return buildSquashMergeCommitMessages(mergeMessage, coAuthors)
|
||||
}
|
||||
|
||||
uniqueAuthors := make(container.Set[string])
|
||||
authors := make([]string, 0, len(limitedCommits))
|
||||
stringBuilder := strings.Builder{}
|
||||
|
||||
if !setting.Repository.PullRequest.PopulateSquashCommentWithCommitMessages {
|
||||
// use PR's title and description as squash commit message
|
||||
message := strings.TrimSpace(pr.Issue.Content)
|
||||
stringBuilder.WriteString(message)
|
||||
if stringBuilder.Len() > 0 {
|
||||
stringBuilder.WriteRune('\n')
|
||||
if !commitMessageTrailersPattern.MatchString(message) {
|
||||
// TODO: this trailer check doesn't work with the separator line added below for the co-authors
|
||||
stringBuilder.WriteRune('\n')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// use PR's commit messages as squash commit message
|
||||
// commits list is in reverse chronological order
|
||||
maxMsgSize := setting.Repository.PullRequest.DefaultMergeMessageSize
|
||||
for _, commit := range slices.Backward(limitedCommits) {
|
||||
msg := strings.TrimSpace(commit.MessageUTF8())
|
||||
if msg == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// This format follows GitHub's squash commit message style,
|
||||
// even if there are other "* " in the commit message body, they are written as-is.
|
||||
// Maybe, ideally, we should indent those lines too.
|
||||
_, _ = fmt.Fprintf(&stringBuilder, "* %s\n\n", msg)
|
||||
if maxMsgSize > 0 && stringBuilder.Len() >= maxMsgSize {
|
||||
tmp := stringBuilder.String()
|
||||
wasValidUtf8 := utf8.ValidString(tmp)
|
||||
tmp = tmp[:maxMsgSize] + "..."
|
||||
if wasValidUtf8 {
|
||||
// If the message was valid UTF-8 before truncation, ensure it remains valid after truncation
|
||||
// For non-utf8 messages, we can't do much about it, end users should use utf-8 as much as possible
|
||||
tmp = strings.ToValidUTF8(tmp, "")
|
||||
}
|
||||
stringBuilder.Reset()
|
||||
stringBuilder.WriteString(tmp)
|
||||
break
|
||||
}
|
||||
}
|
||||
func buildSquashMergeCommitMessages(mergeMessage string, coAuthors []string) string {
|
||||
if len(coAuthors) == 0 {
|
||||
return mergeMessage
|
||||
}
|
||||
|
||||
// collect co-authors
|
||||
msgContent, msgSep, msgTrailer := git.CommitMessageSplitTrailer(mergeMessage)
|
||||
if (msgSep == "" || msgSep == "\n\n") && msgTrailer == "" {
|
||||
msgContent = strings.TrimRightFunc(msgContent, unicode.IsSpace)
|
||||
msgSep = "\n\n---------\n\n"
|
||||
}
|
||||
var sb strings.Builder
|
||||
sb.WriteString(msgContent)
|
||||
sb.WriteString(msgSep)
|
||||
if msgTrailer = strings.TrimSpace(msgTrailer); msgTrailer != "" {
|
||||
sb.WriteString(msgTrailer)
|
||||
sb.WriteRune('\n')
|
||||
}
|
||||
for _, author := range coAuthors {
|
||||
sb.WriteString(git.CoAuthoredByTrailer + ": ")
|
||||
sb.WriteString(author)
|
||||
sb.WriteRune('\n')
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func collectSquashMergeCommitCoAuthors(ctx context.Context, gitRepo *git.Repository, pr *issues_model.PullRequest, headCommitRef, mergeBaseRef git.RefName, limitFirst int, limitedCommits []*git.Commit) []string {
|
||||
posterSig := pr.Issue.Poster.NewGitSig().String()
|
||||
uniqueAuthors := make(container.Set[string])
|
||||
authors := make([]string, 0, len(limitedCommits))
|
||||
|
||||
for _, commit := range limitedCommits {
|
||||
authorString := commit.Author.String()
|
||||
if uniqueAuthors.Add(authorString) && authorString != posterSig {
|
||||
@@ -880,14 +869,14 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ
|
||||
}
|
||||
|
||||
// collect the remaining authors
|
||||
if limit >= 0 && setting.Repository.PullRequest.DefaultMergeMessageAllAuthors {
|
||||
skip := limit
|
||||
limit = 30
|
||||
if limitFirst >= 0 && setting.Repository.PullRequest.DefaultMergeMessageAllAuthors {
|
||||
skip := limitFirst
|
||||
batchLimit := 30
|
||||
for {
|
||||
commits, err := gitRepo.CommitsBetween(headCommitRef, mergeBaseRef, limit, skip)
|
||||
commits, err := gitRepo.CommitsBetween(headCommitRef, mergeBaseRef, batchLimit, skip)
|
||||
if err != nil {
|
||||
log.Error("Unable to get commits between: %s %s Error: %v", pr.HeadBranch, pr.MergeBase, err)
|
||||
return ""
|
||||
return authors
|
||||
}
|
||||
if len(commits) == 0 {
|
||||
break
|
||||
@@ -901,22 +890,46 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ
|
||||
}
|
||||
}
|
||||
}
|
||||
skip += limit
|
||||
skip += batchLimit
|
||||
}
|
||||
}
|
||||
return authors
|
||||
}
|
||||
|
||||
func formatSquashMergeCommitMessages(commits []*git.Commit) string {
|
||||
maxMsgSize := setting.Repository.PullRequest.DefaultMergeMessageSize
|
||||
sb := &bytes.Buffer{}
|
||||
// commits list is in reverse chronological order
|
||||
for _, commit := range slices.Backward(commits) {
|
||||
msg := strings.TrimSpace(commit.MessageUTF8())
|
||||
if msg == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// This format follows GitHub's squash commit message style,
|
||||
// even if there are other "* " in the commit message body, they are written as-is.
|
||||
// Maybe, ideally, we should indent those lines too.
|
||||
_, _ = fmt.Fprintf(sb, "* %s\n\n", msg)
|
||||
if maxMsgSize > 0 && sb.Len() >= maxMsgSize {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if stringBuilder.Len() > 0 && len(authors) > 0 {
|
||||
// TODO: this separator line doesn't work with the trailer check (commitMessageTrailersPattern) above
|
||||
stringBuilder.WriteString("---------\n\n")
|
||||
buf := bytes.TrimSpace(sb.Bytes())
|
||||
if maxMsgSize > 0 && len(buf) > maxMsgSize {
|
||||
buf = buf[:maxMsgSize]
|
||||
for {
|
||||
r, sz := utf8.DecodeLastRune(buf)
|
||||
if r == utf8.RuneError && sz == 1 {
|
||||
buf = buf[:len(buf)-1]
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
buf = append(buf, '.', '.', '.')
|
||||
}
|
||||
|
||||
for _, author := range authors {
|
||||
stringBuilder.WriteString(git.CoAuthoredByTrailer + ": ")
|
||||
stringBuilder.WriteString(author)
|
||||
stringBuilder.WriteRune('\n')
|
||||
}
|
||||
|
||||
return stringBuilder.String()
|
||||
buf = append(buf, '\n', '\n')
|
||||
return util.UnsafeBytesToString(buf)
|
||||
}
|
||||
|
||||
// GetIssuesAllCommitStatus returns a map of issue ID to a list of all statuses for the most recent commit as well as a map of issue ID to only the commit's latest status
|
||||
|
||||
@@ -11,28 +11,33 @@ import (
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unit"
|
||||
"gitea.dev/models/unittest"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/gitrepo"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TODO TestPullRequest_PushToBaseRepo
|
||||
|
||||
func TestPullRequest_CommitMessageTrailersPattern(t *testing.T) {
|
||||
// Not a valid trailer section
|
||||
assert.False(t, commitMessageTrailersPattern.MatchString(""))
|
||||
assert.False(t, commitMessageTrailersPattern.MatchString("No trailer."))
|
||||
assert.False(t, commitMessageTrailersPattern.MatchString("Signed-off-by: Bob <bob@example.com>\nNot a trailer due to following text."))
|
||||
assert.False(t, commitMessageTrailersPattern.MatchString("Message body not correctly separated from trailer section by empty line.\nSigned-off-by: Bob <bob@example.com>"))
|
||||
// Valid trailer section
|
||||
assert.True(t, commitMessageTrailersPattern.MatchString("Signed-off-by: Bob <bob@example.com>"))
|
||||
assert.True(t, commitMessageTrailersPattern.MatchString("Signed-off-by: Bob <bob@example.com>\nOther-Trailer: Value"))
|
||||
assert.True(t, commitMessageTrailersPattern.MatchString("Message body correctly separated from trailer section by empty line.\n\nSigned-off-by: Bob <bob@example.com>"))
|
||||
assert.True(t, commitMessageTrailersPattern.MatchString("Multiple trailers.\n\nSigned-off-by: Bob <bob@example.com>\nOther-Trailer: Value"))
|
||||
assert.True(t, commitMessageTrailersPattern.MatchString("Newline after trailer section.\n\nSigned-off-by: Bob <bob@example.com>\n"))
|
||||
assert.True(t, commitMessageTrailersPattern.MatchString("No space after colon is accepted.\n\nSigned-off-by:Bob <bob@example.com>"))
|
||||
assert.True(t, commitMessageTrailersPattern.MatchString("Additional whitespace is accepted.\n\nSigned-off-by \t : \tBob <bob@example.com> "))
|
||||
assert.True(t, commitMessageTrailersPattern.MatchString("Folded value.\n\nFolded-trailer: This is\n a folded\n trailer value\nOther-Trailer: Value"))
|
||||
func TestPullRequest_FormatSquashMergeCommitMessages(t *testing.T) {
|
||||
oldest := &git.Commit{CommitMessage: git.CommitMessage{MessageRaw: "commit msg 1"}}
|
||||
newest := &git.Commit{CommitMessage: git.CommitMessage{MessageRaw: "commit msg 2\n\nCommit description."}}
|
||||
|
||||
defer test.MockVariableValue(&setting.Repository.PullRequest.DefaultMergeMessageSize, 0)()
|
||||
|
||||
assert.Equal(t, "* commit msg 1\n\n* commit msg 2\n\nCommit description.\n\n", formatSquashMergeCommitMessages([]*git.Commit{newest, oldest}))
|
||||
|
||||
utf8Msg := &git.Commit{CommitMessage: git.CommitMessage{MessageRaw: "🌞"}}
|
||||
setting.Repository.PullRequest.DefaultMergeMessageSize = 3
|
||||
assert.Equal(t, "* ...\n\n", formatSquashMergeCommitMessages([]*git.Commit{utf8Msg}))
|
||||
setting.Repository.PullRequest.DefaultMergeMessageSize = 4
|
||||
assert.Equal(t, "* ...\n\n", formatSquashMergeCommitMessages([]*git.Commit{utf8Msg}))
|
||||
setting.Repository.PullRequest.DefaultMergeMessageSize = 5
|
||||
assert.Equal(t, "* ...\n\n", formatSquashMergeCommitMessages([]*git.Commit{utf8Msg}))
|
||||
setting.Repository.PullRequest.DefaultMergeMessageSize = 6
|
||||
assert.Equal(t, "* 🌞\n\n", formatSquashMergeCommitMessages([]*git.Commit{utf8Msg}))
|
||||
}
|
||||
|
||||
func TestPullRequest_GetDefaultMergeMessage_InternalTracker(t *testing.T) {
|
||||
@@ -88,3 +93,27 @@ func TestPullRequest_GetDefaultMergeMessage_ExternalTracker(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "Merge pull request 'issue3' (#3) from user2/repo2:branch2 into master", mergeMessage)
|
||||
}
|
||||
|
||||
func TestBuildSquashMergeCommitMessages(t *testing.T) {
|
||||
cases := []struct {
|
||||
msg string
|
||||
coAuthors []string
|
||||
expected string
|
||||
}{
|
||||
{"title", nil, "title"},
|
||||
{"title", []string{"the-user"}, "title\n\n---------\n\nCo-authored-by: the-user\n"},
|
||||
{"title\n\n", []string{"the-user"}, "title\n\n---------\n\nCo-authored-by: the-user\n"},
|
||||
{"title\n\nKey: val", []string{"the-user"}, "title\n\nKey: val\nCo-authored-by: the-user\n"},
|
||||
{"title\n\n----\nKey: val", []string{"the-user"}, "title\n\n----\nKey: val\nCo-authored-by: the-user\n"},
|
||||
{"title\n\n----\nKey: val\n\n", []string{"the-user"}, "title\n\n----\nKey: val\nCo-authored-by: the-user\n"},
|
||||
|
||||
{"title\n\nbody", nil, "title\n\nbody"},
|
||||
{"title\n\nbody", []string{"the-user"}, "title\n\nbody\n\n---------\n\nCo-authored-by: the-user\n"},
|
||||
{"title\n\nbody\n\nKey: val", []string{"the-user"}, "title\n\nbody\n\nKey: val\nCo-authored-by: the-user\n"},
|
||||
{"title\n\nbody\n\n----\nKey: val", []string{"the-user"}, "title\n\nbody\n\n----\nKey: val\nCo-authored-by: the-user\n"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
msg := buildSquashMergeCommitMessages(c.msg, c.coAuthors)
|
||||
assert.Equal(t, c.expected, msg, "msg: %s", c.msg)
|
||||
}
|
||||
}
|
||||
|
||||
16
templates/swagger/v1_json.tmpl
generated
16
templates/swagger/v1_json.tmpl
generated
@@ -8108,8 +8108,10 @@
|
||||
},
|
||||
"/repos/{owner}/{repo}/compare/{basehead}": {
|
||||
"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": [
|
||||
"application/json"
|
||||
"application/json",
|
||||
"text/plain"
|
||||
],
|
||||
"tags": [
|
||||
"repository"
|
||||
@@ -8133,10 +8135,20 @@
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"diff",
|
||||
"patch"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "return the raw comparison as `diff` or `patch` instead of JSON",
|
||||
"name": "output",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
||||
15
templates/swagger/v1_openapi3_json.tmpl
generated
15
templates/swagger/v1_openapi3_json.tmpl
generated
@@ -19468,6 +19468,7 @@
|
||||
},
|
||||
"/repos/{owner}/{repo}/compare/{basehead}": {
|
||||
"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",
|
||||
"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",
|
||||
"name": "basehead",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"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": {
|
||||
|
||||
@@ -6,6 +6,7 @@ package integration
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
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) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
|
||||
@@ -1272,7 +1272,7 @@ Commit description.
|
||||
commitMessage: `loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong message`,
|
||||
},
|
||||
},
|
||||
expectedMessage: `* looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo...`,
|
||||
expectedMessage: "* looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo...\n\n",
|
||||
},
|
||||
{
|
||||
name: "Test Co-authored-by",
|
||||
|
||||
Reference in New Issue
Block a user