0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-05-23 02:05:27 +02:00

fix: Invalid UTF-8 commit messages in JSON API responses (#37542)

This commit is contained in:
Nicolas 2026-05-07 16:19:45 +02:00 committed by GitHub
parent 2200ed7499
commit c9b9e376fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
54 changed files with 221 additions and 215 deletions

View File

@ -261,7 +261,7 @@ func UpdateBranch(ctx context.Context, repoID, pusherID int64, branchName string
Cols("commit_id, commit_message, pusher_id, commit_time, is_deleted, updated_unix"). Cols("commit_id, commit_message, pusher_id, commit_time, is_deleted, updated_unix").
Update(&Branch{ Update(&Branch{
CommitID: commit.ID.String(), CommitID: commit.ID.String(),
CommitMessage: commit.Summary(), CommitMessage: commit.MessageTitle(),
PusherID: pusherID, PusherID: pusherID,
CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()), CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()),
IsDeleted: false, IsDeleted: false,

View File

@ -36,7 +36,7 @@ func TestAddDeletedBranch(t *testing.T) {
commit := &git.Commit{ commit := &git.Commit{
ID: git.MustIDFromString(secondBranch.CommitID), ID: git.MustIDFromString(secondBranch.CommitID),
CommitMessage: secondBranch.CommitMessage, CommitMessage: git.CommitMessage{MessageRaw: secondBranch.CommitMessage},
Committer: &git.Signature{ Committer: &git.Signature{
When: secondBranch.CommitTime.AsLocalTime(), When: secondBranch.CommitTime.AsLocalTime(),
}, },

View File

@ -89,7 +89,10 @@ func ToUTF8(content []byte, opts ConvertOpts) []byte {
encoding, _ := charset.Lookup(charsetLabel) encoding, _ := charset.Lookup(charsetLabel)
if encoding == nil { if encoding == nil {
setting.PanicInDevOrTesting("unsupported detected charset %q, it shouldn't happen", charsetLabel) setting.PanicInDevOrTesting("unsupported detected charset %q, it shouldn't happen", charsetLabel)
return content if opts.ErrorReturnOrigin {
return content
}
return bytes.ToValidUTF8(content, opts.ErrorReplacement)
} }
var decoded []byte var decoded []byte

View File

@ -11,19 +11,28 @@ import (
"os/exec" "os/exec"
"strings" "strings"
"code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/git/gitcmd" "code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
type CommitMessage struct {
MessageRaw string
messageUTF8 *string
messageTitle *string
messageBody *string
}
// Commit represents a git commit. // Commit represents a git commit.
type Commit struct { type Commit struct {
Tree // FIXME: bad design, this field can be nil if the commit is from "last commit cache" Tree // FIXME: bad design, this field can be nil if the commit is from "last commit cache"
ID ObjectID CommitMessage
Author *Signature // never nil
Committer *Signature // never nil ID ObjectID
CommitMessage string Author *Signature // never nil
Signature *CommitSignature Committer *Signature // never nil
Signature *CommitSignature
Parents []ObjectID // ID strings Parents []ObjectID // ID strings
submoduleCache *ObjectCache[*SubModule] submoduleCache *ObjectCache[*SubModule]
@ -35,19 +44,28 @@ type CommitSignature struct {
Payload string Payload string
} }
// Message returns the commit message. Same as retrieving CommitMessage directly. func (c *CommitMessage) MessageUTF8() string {
func (c *Commit) Message() string { if c.messageUTF8 == nil {
// FIXME: GIT-COMMIT-MESSAGE-ENCODING: this logic is not right bs := charset.ToUTF8(util.UnsafeStringToBytes(c.MessageRaw), charset.ConvertOpts{ErrorReplacement: []byte{'?'}})
// * When need to use commit message in templates/database, it should be valid UTF-8 c.messageUTF8 = new(util.UnsafeBytesToString(bs))
// * When need to get the original commit message, it should just use "c.CommitMessage" }
// It's not easy to refactor at the moment, many templates need to be updated and tested return *c.messageUTF8
return c.CommitMessage
} }
// Summary returns first line of commit message. func (c *CommitMessage) MessageTitle() string {
// The string is forced to be valid UTF8 if c.messageTitle == nil {
func (c *Commit) Summary() string { s, _, _ := strings.Cut(strings.TrimSpace(c.MessageUTF8()), "\n")
return strings.ToValidUTF8(strings.Split(strings.TrimSpace(c.CommitMessage), "\n")[0], "?") c.messageTitle = new(strings.TrimSpace(s))
}
return *c.messageTitle
}
func (c *CommitMessage) MessageBody() string {
if c.messageBody == nil {
_, s, _ := strings.Cut(strings.TrimSpace(c.MessageUTF8()), "\n")
c.messageBody = new(strings.TrimSpace(s))
}
return *c.messageBody
} }
// ParentID returns oid of n-th parent (0-based index). // ParentID returns oid of n-th parent (0-based index).

View File

@ -66,7 +66,7 @@ func convertPGPSignature(c *object.Commit) *CommitSignature {
func convertCommit(c *object.Commit) *Commit { func convertCommit(c *object.Commit) *Commit {
return &Commit{ return &Commit{
ID: ParseGogitHash(c.Hash), ID: ParseGogitHash(c.Hash),
CommitMessage: c.Message, CommitMessage: CommitMessage{MessageRaw: c.Message},
Committer: &c.Committer, Committer: &c.Committer,
Author: &c.Author, Author: &c.Author,
Signature: convertPGPSignature(c), Signature: convertPGPSignature(c),

View File

@ -92,7 +92,7 @@ func CommitFromReader(gitRepo *Repository, objectID ObjectID, reader io.Reader)
} }
} }
commit.CommitMessage = messageSB.String() commit.MessageRaw = messageSB.String()
if commit.Signature != nil { if commit.Signature != nil {
commit.Signature.Payload = payloadSB.String() commit.Signature.Payload = payloadSB.String()
} }

View File

@ -95,7 +95,7 @@ signed commit`, commitFromReader.Signature.Payload)
commitFromReader2, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString+"\n\n")) commitFromReader2, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString+"\n\n"))
assert.NoError(t, err) assert.NoError(t, err)
commitFromReader.CommitMessage += "\n\n" commitFromReader.CommitMessage.MessageRaw += "\n\n"
commitFromReader.Signature.Payload += "\n\n" commitFromReader.Signature.Payload += "\n\n"
assert.Equal(t, commitFromReader, commitFromReader2) assert.Equal(t, commitFromReader, commitFromReader2)
} }

View File

@ -91,7 +91,7 @@ empty commit`, commitFromReader.Signature.Payload)
commitFromReader2, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString+"\n\n")) commitFromReader2, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString+"\n\n"))
assert.NoError(t, err) assert.NoError(t, err)
commitFromReader.CommitMessage += "\n\n" commitFromReader.CommitMessage.MessageRaw += "\n\n"
commitFromReader.Signature.Payload += "\n\n" commitFromReader.Signature.Payload += "\n\n"
assert.Equal(t, commitFromReader, commitFromReader2) assert.Equal(t, commitFromReader, commitFromReader2)
} }
@ -154,11 +154,20 @@ ISO-8859-1`, commitFromReader.Signature.Payload)
commitFromReader2, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString+"\n\n")) commitFromReader2, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString+"\n\n"))
assert.NoError(t, err) assert.NoError(t, err)
commitFromReader.CommitMessage += "\n\n" commitFromReader.CommitMessage.MessageRaw += "\n\n"
commitFromReader.Signature.Payload += "\n\n" commitFromReader.Signature.Payload += "\n\n"
assert.Equal(t, commitFromReader, commitFromReader2) assert.Equal(t, commitFromReader, commitFromReader2)
} }
func TestCommitMessageSanitizesInvalidUTF8(t *testing.T) {
commit := &Commit{
CommitMessage: CommitMessage{MessageRaw: "title \xff\n\n\n\nbody \xff\n\n\n"},
}
assert.Equal(t, "title ÿ", commit.MessageTitle())
assert.Equal(t, "body ÿ", commit.MessageBody())
assert.Equal(t, "title ÿ\n\n\n\nbody ÿ\n\n\n", commit.MessageUTF8())
}
func TestHasPreviousCommit(t *testing.T) { func TestHasPreviousCommit(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")

View File

@ -111,8 +111,6 @@ func (p *Parser) parseRef(refBlock string) (map[string]string, error) {
len(fields), len(p.format.fieldNames)) len(fields), len(p.format.fieldNames))
} }
for i, field := range fields { for i, field := range fields {
field = strings.TrimSpace(field)
var fieldKey string var fieldKey string
var fieldVal string var fieldVal string
before, after, ok := strings.Cut(field, " ") before, after, ok := strings.Cut(field, " ")

View File

@ -116,12 +116,12 @@ func TestParser(t *testing.T) {
}, },
{ {
"refname:short": "v0.0.2", "refname:short": "v0.0.2",
"contents": "Update CI config (#651)", "contents": "Update CI config (#651)\n\n",
"author": "John Doe <john.doe@foo.com> 1521643174 +0000", "author": "John Doe <john.doe@foo.com> 1521643174 +0000",
}, },
{ {
"refname:short": "v0.0.3", "refname:short": "v0.0.3",
"contents": "Fixed code sample for bash completion (#687)", "contents": "Fixed code sample for bash completion (#687)\n\n",
"author": "Foo Baz <foo@baz.com> 1524836750 +0200", "author": "Foo Baz <foo@baz.com> 1524836750 +0200",
}, },
}, },

View File

@ -10,7 +10,6 @@ import (
"bytes" "bytes"
"io" "io"
"sort" "sort"
"strings"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd" "code.gitea.io/gitea/modules/git/gitcmd"
@ -97,7 +96,7 @@ func findLFSFileFunc(repo *git.Repository, objectID git.ObjectID, revListReader
result := LFSResult{ result := LFSResult{
Name: curPath + fname, Name: curPath + fname,
SHA: curCommit.ID.String(), SHA: curCommit.ID.String(),
Summary: strings.Split(strings.TrimSpace(curCommit.CommitMessage), "\n")[0], Summary: curCommit.MessageTitle(),
When: curCommit.Author.When, When: curCommit.Author.When,
ParentHashes: curCommit.Parents, ParentHashes: curCommit.Parents,
} }

View File

@ -54,7 +54,7 @@ func TestGetTagCommitWithSignature(t *testing.T) {
assert.NotNil(t, commit) assert.NotNil(t, commit)
assert.NotNil(t, commit.Signature) assert.NotNil(t, commit.Signature)
// test that signature is not in message // test that signature is not in message
assert.Equal(t, "signed-commit\n", commit.CommitMessage) assert.Equal(t, "signed-commit\n", commit.CommitMessage.MessageRaw)
} }
func TestGetCommitWithBadCommitID(t *testing.T) { func TestGetCommitWithBadCommitID(t *testing.T) {

View File

@ -176,15 +176,14 @@ func parseTagRef(ref map[string]string) (tag *Tag, err error) {
} }
tag.Tagger = parseSignatureFromCommitLine(ref["creator"]) tag.Tagger = parseSignatureFromCommitLine(ref["creator"])
tag.Message = ref["contents"] tag.MessageRaw = ref["contents"]
// strip any signature if present in contents field // strip any signature if present in contents field
_, tag.Message, _ = parsePayloadSignature(util.UnsafeStringToBytes(tag.Message), 0) _, tag.MessageRaw, _ = parsePayloadSignature(util.UnsafeStringToBytes(tag.MessageRaw), 0)
// annotated tag with GPG signature // annotated tag with GPG signature
if tag.Type == "tag" && ref["contents:signature"] != "" { if tag.Type == "tag" && ref["contents:signature"] != "" {
payload := fmt.Sprintf("object %s\ntype commit\ntag %s\ntagger %s\n\n%s\n", payload := fmt.Sprintf("object %s\ntype commit\ntag %s\ntagger %s\n\n%s", tag.Object, tag.Name, ref["creator"], tag.MessageRaw)
tag.Object, tag.Name, ref["creator"], strings.TrimSpace(tag.Message))
tag.Signature = &CommitSignature{ tag.Signature = &CommitSignature{
Signature: ref["contents:signature"], Signature: ref["contents:signature"],
Payload: payload, Payload: payload,

View File

@ -64,12 +64,12 @@ func (repo *Repository) getTag(tagID ObjectID, name string) (*Tag, error) {
return nil, err return nil, err
} }
tag := &Tag{ tag := &Tag{
Name: name, Name: name,
ID: tagID, ID: tagID,
Object: commitID, Object: commitID,
Type: tp, Type: tp,
Tagger: commit.Committer, Tagger: commit.Committer,
Message: commit.Message(), CommitMessage: CommitMessage{MessageRaw: commit.CommitMessage.MessageRaw},
} }
repo.tagCache.Set(tagID.String(), tag) repo.tagCache.Set(tagID.String(), tag)
@ -86,12 +86,12 @@ func (repo *Repository) getTag(tagID ObjectID, name string) (*Tag, error) {
} }
tag := &Tag{ tag := &Tag{
Name: name, Name: name,
ID: tagID, ID: tagID,
Object: commitID.Type().MustID(gogitTag.Target[:]), Object: commitID.Type().MustID(gogitTag.Target[:]),
Type: tp, Type: tp,
Tagger: &gogitTag.Tagger, Tagger: &gogitTag.Tagger,
Message: gogitTag.Message, CommitMessage: CommitMessage{MessageRaw: gogitTag.Message},
} }
repo.tagCache.Set(tagID.String(), tag) repo.tagCache.Set(tagID.String(), tag)

View File

@ -71,12 +71,12 @@ func (repo *Repository) getTag(tagID ObjectID, name string) (*Tag, error) {
return nil, err return nil, err
} }
tag := &Tag{ tag := &Tag{
Name: name, Name: name,
ID: tagID, ID: tagID,
Object: commitID, Object: commitID,
Type: tp, Type: tp,
Tagger: commit.Committer, Tagger: commit.Committer,
Message: commit.Message(), CommitMessage: commit.CommitMessage,
} }
repo.tagCache.Set(tagID.String(), tag) repo.tagCache.Set(tagID.String(), tag)

View File

@ -211,13 +211,13 @@ func TestRepository_parseTagRef(t *testing.T) {
}, },
want: &Tag{ want: &Tag{
Name: "v1.9.1", Name: "v1.9.1",
ID: MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"), ID: MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"),
Object: MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"), Object: MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"),
Type: "commit", Type: "commit",
Tagger: parseSignatureFromCommitLine("Foo Bar <foo@bar.com> 1565789218 +0300"), Tagger: parseSignatureFromCommitLine("Foo Bar <foo@bar.com> 1565789218 +0300"),
Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n", CommitMessage: CommitMessage{MessageRaw: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n"},
Signature: nil, Signature: nil,
}, },
}, },
@ -240,13 +240,13 @@ func TestRepository_parseTagRef(t *testing.T) {
}, },
want: &Tag{ want: &Tag{
Name: "v0.0.1", Name: "v0.0.1",
ID: MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"), ID: MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"),
Object: MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"), Object: MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"),
Type: "tag", Type: "tag",
Tagger: parseSignatureFromCommitLine("Foo Bar <foo@bar.com> 1565789218 +0300"), Tagger: parseSignatureFromCommitLine("Foo Bar <foo@bar.com> 1565789218 +0300"),
Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n", CommitMessage: CommitMessage{MessageRaw: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n"},
Signature: nil, Signature: nil,
}, },
}, },
@ -263,6 +263,7 @@ func TestRepository_parseTagRef(t *testing.T) {
* add changelog of v1.9.1 * add changelog of v1.9.1
* Update CHANGELOG.md * Update CHANGELOG.md
-----BEGIN PGP SIGNATURE----- -----BEGIN PGP SIGNATURE-----
aBCGzBAABCgAdFiEEyWRwv/q1Q6IjSv+D4IPOwzt33PoFAmI8jbIACgkQ4IPOwzt3 aBCGzBAABCgAdFiEEyWRwv/q1Q6IjSv+D4IPOwzt33PoFAmI8jbIACgkQ4IPOwzt3
@ -298,12 +299,12 @@ qbHDASXl
}, },
want: &Tag{ want: &Tag{
Name: "v0.0.1", Name: "v0.0.1",
ID: MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"), ID: MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"),
Object: MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"), Object: MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"),
Type: "tag", Type: "tag",
Tagger: parseSignatureFromCommitLine("Foo Bar <foo@bar.com> 1565789218 +0300"), Tagger: parseSignatureFromCommitLine("Foo Bar <foo@bar.com> 1565789218 +0300"),
Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md", CommitMessage: CommitMessage{MessageRaw: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n"},
Signature: &CommitSignature{ Signature: &CommitSignature{
Signature: `-----BEGIN PGP SIGNATURE----- Signature: `-----BEGIN PGP SIGNATURE-----

View File

@ -12,12 +12,13 @@ import (
// Tag represents a Git tag. // Tag represents a Git tag.
type Tag struct { type Tag struct {
CommitMessage
Name string Name string
ID ObjectID ID ObjectID
Object ObjectID // The id of this commit object Object ObjectID // The id of this commit object
Type string Type string
Tagger *Signature Tagger *Signature
Message string
Signature *CommitSignature Signature *CommitSignature
} }
@ -87,7 +88,7 @@ func parseTagData(objectFormat ObjectFormat, data []byte) (*Tag, error) {
pos += eol + 1 pos += eol + 1
} }
payload, msg, sign := parsePayloadSignature(data, pos) payload, msg, sign := parsePayloadSignature(data, pos)
tag.Message = msg tag.MessageRaw = msg
if len(sign) > 0 { if len(sign) > 0 {
tag.Signature = &CommitSignature{Signature: sign, Payload: payload} tag.Signature = &CommitSignature{Signature: sign, Payload: payload}
} }

View File

@ -28,7 +28,6 @@ tagger Lucas Michot <lucas@semalead.com> 1484491741 +0100
Object: MustIDFromString("3b114ab800c6432ad42387ccf6bc8d4388a2885a"), Object: MustIDFromString("3b114ab800c6432ad42387ccf6bc8d4388a2885a"),
Type: "commit", Type: "commit",
Tagger: &Signature{Name: "Lucas Michot", Email: "lucas@semalead.com", When: time.Unix(1484491741, 0).In(time.FixedZone("", 3600))}, Tagger: &Signature{Name: "Lucas Michot", Email: "lucas@semalead.com", When: time.Unix(1484491741, 0).In(time.FixedZone("", 3600))},
Message: "",
Signature: nil, Signature: nil,
}, },
}, },
@ -43,13 +42,13 @@ o
ono`, ono`,
expected: Tag{ expected: Tag{
Name: "", Name: "",
ID: Sha1ObjectFormat.EmptyObjectID(), ID: Sha1ObjectFormat.EmptyObjectID(),
Object: MustIDFromString("7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc"), Object: MustIDFromString("7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc"),
Type: "commit", Type: "commit",
Tagger: &Signature{Name: "Lucas Michot", Email: "lucas@semalead.com", When: time.Unix(1484553735, 0).In(time.FixedZone("", 3600))}, Tagger: &Signature{Name: "Lucas Michot", Email: "lucas@semalead.com", When: time.Unix(1484553735, 0).In(time.FixedZone("", 3600))},
Message: "test message\no\n\nono", CommitMessage: CommitMessage{MessageRaw: "test message\no\n\nono"},
Signature: nil, Signature: nil,
}, },
}, },
{ {
@ -64,12 +63,12 @@ dummy signature
-----END SSH SIGNATURE----- -----END SSH SIGNATURE-----
`, `,
expected: Tag{ expected: Tag{
Name: "", Name: "",
ID: Sha1ObjectFormat.EmptyObjectID(), ID: Sha1ObjectFormat.EmptyObjectID(),
Object: MustIDFromString("7cdf42c0b1cc763ab7e4c33c47a24e27c66bfaaa"), Object: MustIDFromString("7cdf42c0b1cc763ab7e4c33c47a24e27c66bfaaa"),
Type: "commit", Type: "commit",
Tagger: &Signature{Name: "dummy user", Email: "dummy-email@example.com", When: time.Unix(1484491741, 0).In(time.FixedZone("", 3600))}, Tagger: &Signature{Name: "dummy user", Email: "dummy-email@example.com", When: time.Unix(1484491741, 0).In(time.FixedZone("", 3600))},
Message: "dummy message", CommitMessage: CommitMessage{MessageRaw: "dummy message"},
Signature: &CommitSignature{ Signature: &CommitSignature{
Signature: `-----BEGIN SSH SIGNATURE----- Signature: `-----BEGIN SSH SIGNATURE-----
dummy signature dummy signature
@ -93,5 +92,5 @@ dummy message`,
tag, err := parseTagData(Sha1ObjectFormat, []byte("type commit\n\nfoo\n-----BEGIN SSH SIGNATURE-----\ncorrupted...")) tag, err := parseTagData(Sha1ObjectFormat, []byte("type commit\n\nfoo\n-----BEGIN SSH SIGNATURE-----\ncorrupted..."))
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "foo\n-----BEGIN SSH SIGNATURE-----\ncorrupted...", tag.Message) assert.Equal(t, "foo\n-----BEGIN SSH SIGNATURE-----\ncorrupted...", tag.CommitMessage.MessageRaw)
} }

View File

@ -97,7 +97,7 @@ func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository,
RepoID: repo.ID, RepoID: repo.ID,
Name: branch, Name: branch,
CommitID: commit.ID.String(), CommitID: commit.ID.String(),
CommitMessage: commit.Summary(), CommitMessage: commit.MessageTitle(),
PusherID: doerID, PusherID: doerID,
CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()), CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()),
}) })
@ -112,7 +112,7 @@ func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository,
RepoID: repo.ID, RepoID: repo.ID,
Name: branch, Name: branch,
CommitID: commit.ID.String(), CommitID: commit.ID.String(),
CommitMessage: commit.Summary(), CommitMessage: commit.MessageTitle(),
PusherID: doerID, PusherID: doerID,
CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()), CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()),
}) })

View File

@ -152,7 +152,7 @@ func (pc *PushCommits) AvatarLink(ctx context.Context, email string) string {
func CommitToPushCommit(commit *git.Commit) *PushCommit { func CommitToPushCommit(commit *git.Commit) *PushCommit {
return &PushCommit{ return &PushCommit{
Sha1: commit.ID.String(), Sha1: commit.ID.String(),
Message: commit.Message(), Message: commit.MessageUTF8(),
AuthorEmail: commit.Author.Email, AuthorEmail: commit.Author.Email,
AuthorName: commit.Author.Name, AuthorName: commit.Author.Name,
CommitterEmail: commit.Committer.Email, CommitterEmail: commit.Committer.Email,

View File

@ -145,7 +145,7 @@ func TestCommitToPushCommit(t *testing.T) {
ID: sha1, ID: sha1,
Author: sig, Author: sig,
Committer: sig, Committer: sig,
CommitMessage: "Commit Message", CommitMessage: git.CommitMessage{MessageRaw: "Commit Message"},
}) })
assert.Equal(t, hexString, pushCommit.Sha1) assert.Equal(t, hexString, pushCommit.Sha1)
assert.Equal(t, "Commit Message", pushCommit.Message) assert.Equal(t, "Commit Message", pushCommit.Message)
@ -176,13 +176,13 @@ func TestListToPushCommits(t *testing.T) {
ID: hash1, ID: hash1,
Author: sig, Author: sig,
Committer: sig, Committer: sig,
CommitMessage: "Message1", CommitMessage: git.CommitMessage{MessageRaw: "Message1"},
}, },
{ {
ID: hash2, ID: hash2,
Author: sig, Author: sig,
Committer: sig, Committer: sig,
CommitMessage: "Message2", CommitMessage: git.CommitMessage{MessageRaw: "Message2"},
}, },
} }

View File

@ -132,10 +132,9 @@ func newFuncMapWebPage() template.FuncMap {
// ----------------------------------------------------------------- // -----------------------------------------------------------------
// misc (TODO: move them to MiscUtils to avoid bloating the main func map) // misc (TODO: move them to MiscUtils to avoid bloating the main func map)
"ActionContent2Commits": ActionContent2Commits, "ActionContent2Commits": ActionContent2Commits,
"IsMultilineCommitMessage": isMultilineCommitMessage, "CommentMustAsDiff": gitdiff.CommentMustAsDiff,
"CommentMustAsDiff": gitdiff.CommentMustAsDiff, "MirrorRemoteAddress": mirrorRemoteAddress,
"MirrorRemoteAddress": mirrorRemoteAddress,
"FilenameIsImage": filenameIsImage, "FilenameIsImage": filenameIsImage,
"TabSizeClass": tabSizeClass, "TabSizeClass": tabSizeClass,

View File

@ -52,11 +52,6 @@ func sortArrow(normSort, revSort, urlSort string, isDefault bool) template.HTML
return "" return ""
} }
// isMultilineCommitMessage checks to see if a commit message contains multiple lines.
func isMultilineCommitMessage(msg string) bool {
return strings.Count(strings.TrimSpace(msg), "\n") >= 1
}
// Actioner describes an action // Actioner describes an action
type Actioner interface { type Actioner interface {
GetOpType() activities_model.ActionType GetOpType() activities_model.ActionType

View File

@ -79,20 +79,14 @@ func (ut *RenderUtils) RenderCommitMessageLinkSubject(msg, urlDefault string, re
// RenderCommitBody extracts the body of a commit message without its title. // RenderCommitBody extracts the body of a commit message without its title.
func (ut *RenderUtils) RenderCommitBody(msg string, repo *repo.Repository) template.HTML { func (ut *RenderUtils) RenderCommitBody(msg string, repo *repo.Repository) template.HTML {
msgLine := strings.TrimSpace(msg) _, body, _ := strings.Cut(strings.TrimSpace(msg), "\n")
lineEnd := strings.IndexByte(msgLine, '\n') body = strings.TrimFunc(body, unicode.IsSpace)
if lineEnd > 0 { if body == "" {
msgLine = msgLine[lineEnd+1:]
} else {
return ""
}
msgLine = strings.TrimLeftFunc(msgLine, unicode.IsSpace)
if len(msgLine) == 0 {
return "" return ""
} }
rctx := renderhelper.NewRenderContextRepoComment(ut.ctx, repo) rctx := renderhelper.NewRenderContextRepoComment(ut.ctx, repo)
htmlContent := template.HTML(template.HTMLEscapeString(msgLine)) htmlContent := template.HTML(template.HTMLEscapeString(body))
renderedMessage, err := markup.PostProcessCommitMessage(rctx, htmlContent) renderedMessage, err := markup.PostProcessCommitMessage(rctx, htmlContent)
if err != nil { if err != nil {
log.Error("PostProcessCommitMessage: %v", err) log.Error("PostProcessCommitMessage: %v", err)

View File

@ -4,7 +4,6 @@
package feed package feed
import ( import (
"strings"
"time" "time"
"code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/repo"
@ -39,14 +38,14 @@ func ShowBranchFeed(ctx *context.Context, repo *repo.Repository, formatType stri
for _, commit := range commits { for _, commit := range commits {
feed.Items = append(feed.Items, &feeds.Item{ feed.Items = append(feed.Items, &feeds.Item{
Id: commit.ID.String(), Id: commit.ID.String(),
Title: strings.TrimSpace(strings.Split(commit.Message(), "\n")[0]), Title: commit.MessageTitle(),
Link: &feeds.Link{Href: repo.HTMLURL() + "/commit/" + commit.ID.String()}, Link: &feeds.Link{Href: repo.HTMLURL() + "/commit/" + commit.ID.String()},
Author: &feeds.Author{ Author: &feeds.Author{
Name: commit.Author.Name, Name: commit.Author.Name,
Email: commit.Author.Email, Email: commit.Author.Email,
}, },
Description: commit.Message(), Description: commit.MessageUTF8(), // TODO: description can be shorten content
Content: commit.Message(), Content: commit.MessageUTF8(),
Created: commit.Committer.When, Created: commit.Committer.When,
}) })
} }

View File

@ -4,7 +4,6 @@
package feed package feed
import ( import (
"strings"
"time" "time"
"code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/repo"
@ -46,14 +45,14 @@ func ShowFileFeed(ctx *context.Context, repo *repo.Repository, formatType string
for _, commit := range commits { for _, commit := range commits {
feed.Items = append(feed.Items, &feeds.Item{ feed.Items = append(feed.Items, &feeds.Item{
Id: commit.ID.String(), Id: commit.ID.String(),
Title: strings.TrimSpace(strings.Split(commit.Message(), "\n")[0]), Title: commit.MessageTitle(),
Link: &feeds.Link{Href: repo.HTMLURL() + "/commit/" + commit.ID.String()}, Link: &feeds.Link{Href: repo.HTMLURL() + "/commit/" + commit.ID.String()},
Author: &feeds.Author{ Author: &feeds.Author{
Name: commit.Author.Name, Name: commit.Author.Name,
Email: commit.Author.Email, Email: commit.Author.Email,
}, },
Description: commit.Message(), Description: commit.MessageUTF8(), // TODO: description can be shorten content
Content: commit.Message(), Content: commit.MessageUTF8(),
Created: commit.Committer.When, Created: commit.Committer.When,
}) })
} }

View File

@ -231,7 +231,7 @@ func renderBlameFillFirstBlameRow(repoLink string, avatarUtils *templates.Avatar
br.PreviousSha = part.PreviousSha br.PreviousSha = part.PreviousSha
br.PreviousShaURL = fmt.Sprintf("%s/blame/commit/%s/%s", repoLink, url.PathEscape(part.PreviousSha), util.PathEscapeSegments(part.PreviousPath)) br.PreviousShaURL = fmt.Sprintf("%s/blame/commit/%s/%s", repoLink, url.PathEscape(part.PreviousSha), util.PathEscapeSegments(part.PreviousPath))
br.CommitURL = fmt.Sprintf("%s/commit/%s", repoLink, url.PathEscape(part.Sha)) br.CommitURL = fmt.Sprintf("%s/commit/%s", repoLink, url.PathEscape(part.Sha))
br.CommitMessage = commit.CommitMessage br.CommitMessage = commit.MessageUTF8()
br.CommitSince = templates.TimeSince(commit.Author.When) br.CommitSince = templates.TimeSince(commit.Author.When)
} }

View File

@ -349,7 +349,7 @@ func Diff(ctx *context.Context) {
parentCommitID = parentCommit.ID.String() parentCommitID = parentCommit.ID.String()
} }
setCompareContext(ctx, parentCommit, commit, userName, repoName) setCompareContext(ctx, parentCommit, commit, userName, repoName)
ctx.Data["Title"] = commit.Summary() + " · " + base.ShortSha(commitID) ctx.Data["Title"] = commit.MessageTitle() + " · " + base.ShortSha(commitID)
ctx.Data["Commit"] = commit ctx.Data["Commit"] = commit
ctx.Data["Diff"] = diff ctx.Data["Diff"] = diff
ctx.Data["DiffBlobExcerptData"] = diffBlobExcerptData ctx.Data["DiffBlobExcerptData"] = diffBlobExcerptData

View File

@ -391,17 +391,14 @@ func prepareNewPullRequestTitleContent(ci *git_service.CompareInfo, commits []*g
if useFirstCommitAsTitle { if useFirstCommitAsTitle {
// the "commits" are from "ShowPrettyFormatLogToList", which is ordered from newest to oldest, here take the oldest one // the "commits" are from "ShowPrettyFormatLogToList", which is ordered from newest to oldest, here take the oldest one
c := commits[len(commits)-1] c := commits[len(commits)-1]
title = strings.TrimSpace(c.UserCommit.Summary()) title = c.UserCommit.MessageTitle()
} else { } else {
title = autoTitleFromBranchName(ci.HeadRef.ShortName()) title = autoTitleFromBranchName(ci.HeadRef.ShortName())
} }
if len(commits) == 1 { if len(commits) == 1 {
// FIXME: GIT-COMMIT-MESSAGE-ENCODING: try to convert the encoding for commit message explicitly, ideally it should be done by a git commit struct method
c := commits[0] c := commits[0]
_, content, _ = strings.Cut(strings.TrimSpace(c.UserCommit.CommitMessage), "\n") content = c.MessageBody()
content = strings.TrimSpace(content)
content = string(charset.ToUTF8([]byte(content), charset.ConvertOpts{}))
} }
var titleTrailer string var titleTrailer string

View File

@ -6,7 +6,6 @@ package repo
import ( import (
"strings" "strings"
"testing" "testing"
"unicode/utf8"
asymkey_model "code.gitea.io/gitea/models/asymkey" asymkey_model "code.gitea.io/gitea/models/asymkey"
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
@ -55,7 +54,7 @@ func TestNewPullRequestTitleContent(t *testing.T) {
SignCommit: &asymkey_model.SignCommit{ SignCommit: &asymkey_model.SignCommit{
UserCommit: &user_model.UserCommit{ UserCommit: &user_model.UserCommit{
Commit: &git.Commit{ Commit: &git.Commit{
CommitMessage: msg, CommitMessage: git.CommitMessage{MessageRaw: msg},
}, },
}, },
}, },
@ -99,9 +98,9 @@ func TestNewPullRequestTitleContent(t *testing.T) {
assert.Equal(t, "title-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa…", title) assert.Equal(t, "title-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa…", title)
assert.Equal(t, "…aaaaaaaaa\n", content) assert.Equal(t, "…aaaaaaaaa\n", content)
title, content = prepareNewPullRequestTitleContent(ci, []*git_model.SignCommitWithStatuses{mockCommit("a\xf0\xf0\xf0\nb\xf0\xf0\xf0")}, setting.RepoPRTitleSourceFirstCommit) title, content = prepareNewPullRequestTitleContent(ci, []*git_model.SignCommitWithStatuses{mockCommit("title \xf0\nbody \xf0")}, setting.RepoPRTitleSourceFirstCommit)
assert.Equal(t, "a?", title) // FIXME: GIT-COMMIT-MESSAGE-ENCODING: "title" doesn't use the same charset converting logic as "content" assert.Equal(t, "title ð", title)
assert.Equal(t, "b"+string(utf8.RuneError)+string(utf8.RuneError), content) assert.Equal(t, "body ð", content)
} }
func TestAutoTitleFromBranchName(t *testing.T) { func TestAutoTitleFromBranchName(t *testing.T) {

View File

@ -6,7 +6,6 @@ package repo
import ( import (
"bytes" "bytes"
"net/http" "net/http"
"strings"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/gitrepo"
@ -33,10 +32,10 @@ func CherryPick(ctx *context.Context) {
if ctx.FormString("cherry-pick-type") == "revert" { if ctx.FormString("cherry-pick-type") == "revert" {
ctx.Data["CherryPickType"] = "revert" ctx.Data["CherryPickType"] = "revert"
ctx.Data["commit_summary"] = "revert " + ctx.PathParam("sha") ctx.Data["commit_summary"] = "revert " + ctx.PathParam("sha")
ctx.Data["commit_message"] = "revert " + cherryPickCommit.Message() ctx.Data["commit_message"] = "revert " + cherryPickCommit.MessageUTF8()
} else { } else {
ctx.Data["CherryPickType"] = "cherry-pick" ctx.Data["CherryPickType"] = "cherry-pick"
ctx.Data["commit_summary"], ctx.Data["commit_message"], _ = strings.Cut(cherryPickCommit.Message(), "\n") ctx.Data["commit_summary"], ctx.Data["commit_message"] = cherryPickCommit.MessageTitle(), cherryPickCommit.MessageBody()
} }
ctx.HTML(http.StatusOK, tplCherryPick) ctx.HTML(http.StatusOK, tplCherryPick)

View File

@ -675,7 +675,7 @@ func TestWebhook(ctx *context.Context) {
ID: objectFormat.EmptyObjectID(), ID: objectFormat.EmptyObjectID(),
Author: ghost.NewGitSig(), Author: ghost.NewGitSig(),
Committer: ghost.NewGitSig(), Committer: ghost.NewGitSig(),
CommitMessage: "This is a fake commit", CommitMessage: git.CommitMessage{MessageRaw: "This is a fake commit"},
} }
} }
@ -683,7 +683,7 @@ func TestWebhook(ctx *context.Context) {
apiCommit := &api.PayloadCommit{ apiCommit := &api.PayloadCommit{
ID: commit.ID.String(), ID: commit.ID.String(),
Message: commit.Message(), Message: commit.MessageUTF8(),
URL: ctx.Repo.Repository.HTMLURL() + "/commit/" + url.PathEscape(commit.ID.String()), URL: ctx.Repo.Repository.HTMLURL() + "/commit/" + url.PathEscape(commit.ID.String()),
Author: &api.PayloadUser{ Author: &api.PayloadUser{
Name: commit.Author.Name, Name: commit.Author.Name,

View File

@ -256,7 +256,7 @@ func skipWorkflows(ctx context.Context, input *notifyInput, commit *git.Commit)
log.Debug("repo %s: skipped run for pr %v because of %s string", input.Repo.RelativePath(), input.PullRequest.Issue.ID, s) log.Debug("repo %s: skipped run for pr %v because of %s string", input.Repo.RelativePath(), input.PullRequest.Issue.ID, s)
return true return true
} }
if strings.Contains(commit.CommitMessage, s) { if strings.Contains(commit.MessageRaw, s) {
log.Debug("repo %s with commit %s: skipped run because of %s string", input.Repo.RelativePath(), commit.ID, s) log.Debug("repo %s with commit %s: skipped run because of %s string", input.Repo.RelativePath(), commit.ID, s)
return true return true
} }
@ -320,7 +320,7 @@ func handleWorkflows(
for _, dwf := range detectedWorkflows { for _, dwf := range detectedWorkflows {
run := &actions_model.ActionRun{ run := &actions_model.ActionRun{
Title: strings.SplitN(commit.CommitMessage, "\n", 2)[0], Title: commit.MessageTitle(),
RepoID: input.Repo.ID, RepoID: input.Repo.ID,
Repo: input.Repo, Repo: input.Repo,
OwnerID: input.Repo.OwnerID, OwnerID: input.Repo.OwnerID,
@ -483,7 +483,7 @@ func handleSchedules(
} }
run := &actions_model.ActionSchedule{ run := &actions_model.ActionSchedule{
Title: strings.SplitN(commit.CommitMessage, "\n", 2)[0], Title: commit.MessageTitle(),
RepoID: input.Repo.ID, RepoID: input.Repo.ID,
Repo: input.Repo, Repo: input.Repo,
OwnerID: input.Repo.OwnerID, OwnerID: input.Repo.OwnerID,

View File

@ -5,7 +5,6 @@ package actions
import ( import (
"fmt" "fmt"
"strings"
actions_model "code.gitea.io/gitea/models/actions" actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
@ -98,7 +97,7 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re
var entry *git.TreeEntry var entry *git.TreeEntry
run := &actions_model.ActionRun{ run := &actions_model.ActionRun{
Title: strings.SplitN(runTargetCommit.CommitMessage, "\n", 2)[0], Title: runTargetCommit.MessageTitle(),
RepoID: repo.ID, RepoID: repo.ID,
Repo: repo, Repo: repo,
OwnerID: repo.OwnerID, OwnerID: repo.OwnerID,

View File

@ -150,17 +150,13 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get commit %s in repository: %s Error: %w", opts.NewCommitIDs[i], repo.FullName(), err) return nil, fmt.Errorf("failed to get commit %s in repository: %s Error: %w", opts.NewCommitIDs[i], repo.FullName(), err)
} }
} // create a new pull request
if title == "" {
// create a new pull request title = commit.MessageTitle()
if title == "" { }
title = strings.Split(commit.CommitMessage, "\n")[0] if description == "" {
} description = commit.MessageBody()
if description == "" { }
_, description, _ = strings.Cut(commit.CommitMessage, "\n\n")
}
if description == "" {
description = title
} }
prIssue := &issues_model.Issue{ prIssue := &issues_model.Issue{

View File

@ -11,7 +11,6 @@ import (
"net/url" "net/url"
"path" "path"
"strconv" "strconv"
"strings"
"time" "time"
actions_model "code.gitea.io/gitea/models/actions" actions_model "code.gitea.io/gitea/models/actions"
@ -215,7 +214,7 @@ func ToTag(repo *repo_model.Repository, t *git.Tag) *api.Tag {
return &api.Tag{ return &api.Tag{
Name: t.Name, Name: t.Name,
Message: strings.TrimSpace(t.Message), Message: t.MessageUTF8(),
ID: t.ID.String(), ID: t.ID.String(),
Commit: ToCommitMeta(repo, t), Commit: ToCommitMeta(repo, t),
ZipballURL: zipballURL, ZipballURL: zipballURL,
@ -769,7 +768,7 @@ func ToAnnotatedTag(ctx context.Context, repo *repo_model.Repository, t *git.Tag
Tag: t.Name, Tag: t.Name,
SHA: t.ID.String(), SHA: t.ID.String(),
Object: ToAnnotatedTagObject(repo, c), Object: ToAnnotatedTagObject(repo, c),
Message: t.Message, Message: t.MessageUTF8(),
URL: repo.APIURL() + "/git/tags/" + t.ID.String(), URL: repo.APIURL() + "/git/tags/" + t.ID.String(),
Tagger: ToCommitUser(t.Tagger), Tagger: ToCommitUser(t.Tagger),
Verification: ToVerification(ctx, c), Verification: ToVerification(ctx, c),

View File

@ -56,7 +56,7 @@ func ToPayloadCommit(ctx context.Context, repo *repo_model.Repository, c *git.Co
return &api.PayloadCommit{ return &api.PayloadCommit{
ID: c.ID.String(), ID: c.ID.String(),
Message: c.Message(), Message: c.MessageUTF8(),
URL: repo.HTMLURL() + "/commit/" + c.ID.String(), URL: repo.HTMLURL() + "/commit/" + c.ID.String(),
Author: &api.PayloadUser{ Author: &api.PayloadUser{
Name: c.Author.Name, Name: c.Author.Name,
@ -171,7 +171,7 @@ func ToCommit(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Rep
}, },
Date: commit.Committer.When.Format(time.RFC3339), Date: commit.Committer.When.Format(time.RFC3339),
}, },
Message: commit.Message(), Message: commit.MessageUTF8(),
Tree: &api.CommitMeta{ Tree: &api.CommitMeta{
URL: repo.APIURL() + "/git/trees/" + url.PathEscape(commit.ID.String()), URL: repo.APIURL() + "/git/trees/" + url.PathEscape(commit.ID.String()),
SHA: commit.ID.String(), SHA: commit.ID.String(),

View File

@ -21,12 +21,12 @@ func TestToCommitMeta(t *testing.T) {
sha1 := git.Sha1ObjectFormat sha1 := git.Sha1ObjectFormat
signature := &git.Signature{Name: "Test Signature", Email: "test@email.com", When: time.Unix(0, 0)} signature := &git.Signature{Name: "Test Signature", Email: "test@email.com", When: time.Unix(0, 0)}
tag := &git.Tag{ tag := &git.Tag{
Name: "Test Tag", Name: "Test Tag",
ID: sha1.EmptyObjectID(), ID: sha1.EmptyObjectID(),
Object: sha1.EmptyObjectID(), Object: sha1.EmptyObjectID(),
Type: "Test Type", Type: "Test Type",
Tagger: signature, Tagger: signature,
Message: "Test Message", CommitMessage: git.CommitMessage{MessageRaw: "Test Message"},
} }
commitMeta := ToCommitMeta(headRepo, tag) commitMeta := ToCommitMeta(headRepo, tag)

View File

@ -28,7 +28,7 @@ func ToWikiCommit(commit *git.Commit) *api.WikiCommit {
}, },
Date: commit.Committer.When.UTC().Format(time.RFC3339), Date: commit.Committer.When.UTC().Format(time.RFC3339),
}, },
Message: commit.CommitMessage, Message: commit.MessageUTF8(),
} }
} }

View File

@ -847,7 +847,7 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ
maxMsgSize := setting.Repository.PullRequest.DefaultMergeMessageSize maxMsgSize := setting.Repository.PullRequest.DefaultMergeMessageSize
for i := len(commits) - 1; i >= 0; i-- { for i := len(commits) - 1; i >= 0; i-- {
commit := commits[i] commit := commits[i]
msg := strings.TrimSpace(commit.CommitMessage) msg := strings.TrimSpace(commit.MessageUTF8())
if msg == "" { if msg == "" {
continue continue
} }
@ -1074,7 +1074,7 @@ func GetPullCommits(ctx context.Context, baseGitRepo *git.Repository, doer *user
} }
commits = append(commits, CommitInfo{ commits = append(commits, CommitInfo{
Summary: commit.Summary(), Summary: commit.MessageTitle(),
CommitterOrAuthorName: committerOrAuthorName, CommitterOrAuthorName: committerOrAuthorName,
ID: commit.ID.String(), ID: commit.ID.String(),
ShortSha: base.ShortSha(commit.ID.String()), ShortSha: base.ShortSha(commit.ID.String()),

View File

@ -359,7 +359,7 @@ func SyncBranchesToDB(ctx context.Context, repoID, pusherID int64, branchNames,
RepoID: repoID, RepoID: repoID,
Name: branchName, Name: branchName,
CommitID: commit.ID.String(), CommitID: commit.ID.String(),
CommitMessage: commit.Summary(), CommitMessage: commit.MessageTitle(),
PusherID: pusherID, PusherID: pusherID,
CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()), CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()),
}) })

View File

@ -179,7 +179,7 @@ func getFileContentsByEntryInternal(ctx context.Context, repo *repo_model.Reposi
} }
} }
if opts.IncludeCommitMessage { if opts.IncludeCommitMessage {
contentsResponse.LastCommitMessage = new(lastCommit.Message()) contentsResponse.LastCommitMessage = new(lastCommit.MessageUTF8())
} }
} }

View File

@ -110,7 +110,7 @@ func GetFileCommitResponse(repo *repo_model.Repository, commit *git.Commit) (*ap
}, },
Date: commit.Committer.When.UTC().Format(time.RFC3339), Date: commit.Committer.When.UTC().Format(time.RFC3339),
}, },
Message: commit.Message(), Message: commit.MessageUTF8(),
Tree: &api.CommitMeta{ Tree: &api.CommitMeta{
URL: commitTreeURL.String(), URL: commitTreeURL.String(),
SHA: commit.Tree.ID.String(), SHA: commit.Tree.ID.String(),

View File

@ -402,7 +402,7 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
} }
rel, has := relMap[lowerTag] rel, has := relMap[lowerTag]
title, note := git.SplitCommitTitleBody(tag.Message, 255) title, note := git.SplitCommitTitleBody(tag.MessageUTF8(), 255)
if !has { if !has {
rel = &repo_model.Release{ rel = &repo_model.Release{
RepoID: repo.ID, RepoID: repo.ID,

View File

@ -18,8 +18,7 @@
<meta property="og:title" content="{{.Title}}"> <meta property="og:title" content="{{.Title}}">
<meta property="og:url" content="{{ctx.AppFullLink $.Link}}"> <meta property="og:url" content="{{ctx.AppFullLink $.Link}}">
{{if and .PageIsDiff .Commit}} {{if and .PageIsDiff .Commit}}
{{- $commitMessageParts := StringUtils.Cut .Commit.Message "\n" -}} {{- $commitMessageBody := .Commit.MessageBody -}}
{{- $commitMessageBody := index $commitMessageParts 1 -}}
{{- if $commitMessageBody -}} {{- if $commitMessageBody -}}
<meta property="og:description" content="{{StringUtils.EllipsisString $commitMessageBody 300}}"> <meta property="og:description" content="{{StringUtils.EllipsisString $commitMessageBody 300}}">
{{- end -}} {{- end -}}

View File

@ -65,11 +65,12 @@
{{end -}} {{end -}}
{{if eq .ActionName "push"}} {{if eq .ActionName "push"}}
<ul> <ul>
{{range .Comment.Commits}} {{$repoURL := $.Comment.Issue.PullRequest.BaseRepo.HTMLURL}}
{{range $commit := $.Comment.Commits}}
<li> <li>
<a href="{{$.Comment.Issue.PullRequest.BaseRepo.HTMLURL}}/commit/{{.ID}}"> <a href="{{$repoURL}}/commit/{{$commit.ID}}">
{{ShortSha .ID.String}} {{ShortSha $commit.ID.String}}
</a> - {{.Summary}} </a> - {{$commit.MessageTitle}}
</li> </li>
{{end}} {{end}}
</ul> </ul>

View File

@ -5,7 +5,7 @@
<div class="ui container fluid padded"> <div class="ui container fluid padded">
<div class="ui top attached header clearing segment tw-relative commit-header"> <div class="ui top attached header clearing segment tw-relative commit-header">
<div class="tw-flex tw-mb-4 tw-gap-1"> <div class="tw-flex tw-mb-4 tw-gap-1">
<h3 class="tw-mb-0 tw-flex-1"><span class="commit-summary" title="{{.Commit.Summary}}">{{ctx.RenderUtils.RenderCommitMessage .Commit.Message $.Repository}}</span>{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses "AdditionalClasses" "tw-inline"}}</h3> <h3 class="tw-mb-0 tw-flex-1"><span class="commit-summary" title="{{.Commit.MessageTitle}}">{{ctx.RenderUtils.RenderCommitMessage .Commit.MessageUTF8 $.Repository}}</span>{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses "AdditionalClasses" "tw-inline"}}</h3>
{{if not $.PageIsWiki}} {{if not $.PageIsWiki}}
<div class="commit-header-buttons"> <div class="commit-header-buttons">
<a class="ui primary tiny button" href="{{.SourcePath}}"> <a class="ui primary tiny button" href="{{.SourcePath}}">
@ -119,8 +119,8 @@
</div> </div>
{{end}} {{end}}
</div> </div>
{{if IsMultilineCommitMessage .Commit.Message}} {{if .Commit.MessageBody}}
<pre class="commit-body">{{ctx.RenderUtils.RenderCommitBody .Commit.Message $.Repository}}</pre> <pre class="commit-body">{{ctx.RenderUtils.RenderCommitBody .Commit.MessageUTF8 $.Repository}}</pre>
{{end}} {{end}}
{{template "repo/commit_load_branches_and_tags" .}} {{template "repo/commit_load_branches_and_tags" .}}
</div> </div>

View File

@ -11,7 +11,7 @@
</thead> </thead>
<tbody class="commit-list"> <tbody class="commit-list">
{{$commitRepoLink := $.RepoLink}}{{if $.CommitRepoLink}}{{$commitRepoLink = $.CommitRepoLink}}{{end}} {{$commitRepoLink := $.RepoLink}}{{if $.CommitRepoLink}}{{$commitRepoLink = $.CommitRepoLink}}{{end}}
{{range .Commits}} {{range $commit := .Commits}}
<tr> <tr>
<td class="author"> <td class="author">
<span class="author-wrapper"> <span class="author-wrapper">
@ -38,18 +38,22 @@
<td class="message"> <td class="message">
<span class="message-wrapper"> <span class="message-wrapper">
{{if $.PageIsWiki}} {{if $.PageIsWiki}}
<span class="commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{.Summary | ctx.RenderUtils.RenderEmoji}}</span> <span class="commit-summary {{if gt $commit.ParentCount 1}} grey text{{end}}" title="{{$commit.MessageTitle}}">
{{$commit.MessageTitle | ctx.RenderUtils.RenderEmoji}}
</span>
{{else}} {{else}}
{{$commitLink:= printf "%s/commit/%s" $commitRepoLink (PathEscape .ID.String)}} {{$commitLink:= printf "%s/commit/%s" $commitRepoLink (PathEscape $commit.ID.String)}}
<span class="commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{ctx.RenderUtils.RenderCommitMessageLinkSubject .Message $commitLink $.Repository}}</span> <span class="commit-summary {{if gt $commit.ParentCount 1}} grey text{{end}}" title="{{$commit.MessageTitle}}">
{{ctx.RenderUtils.RenderCommitMessageLinkSubject $commit.MessageUTF8 $commitLink $.Repository}}
</span>
{{end}} {{end}}
</span> </span>
{{if IsMultilineCommitMessage .Message}} {{if $commit.MessageBody}}
<button class="ui button ellipsis-button" aria-expanded="false" data-global-click="onRepoEllipsisButtonClick">...</button> <button class="ui button ellipsis-button" aria-expanded="false" data-global-click="onRepoEllipsisButtonClick">...</button>
{{end}} {{end}}
{{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses}} {{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses}}
{{if IsMultilineCommitMessage .Message}} {{if $commit.MessageBody}}
<pre class="commit-body tw-hidden">{{ctx.RenderUtils.RenderCommitBody .Message $.Repository}}</pre> <pre class="commit-body tw-hidden">{{ctx.RenderUtils.RenderCommitBody $commit.MessageUTF8 $.Repository}}</pre>
{{end}} {{end}}
{{if $.CommitsTagsMap}} {{if $.CommitsTagsMap}}
{{range (index $.CommitsTagsMap .ID.String)}} {{range (index $.CommitsTagsMap .ID.String)}}

View File

@ -1,6 +1,6 @@
{{$index := 0}} {{$index := 0}}
<div class="timeline-item commits-list"> <div class="timeline-item commits-list">
{{range .comment.Commits}} {{range $commit := .comment.Commits}}
{{$tag := printf "%s-%d" $.comment.HashTag $index}} {{$tag := printf "%s-%d" $.comment.HashTag $index}}
{{$index = Eval $index "+" 1}} {{$index = Eval $index "+" 1}}
<div class="flex-text-block" id="{{$tag}}">{{/*singular-commit*/}} <div class="flex-text-block" id="{{$tag}}">{{/*singular-commit*/}}
@ -14,11 +14,11 @@
{{$commitBaseLink := printf "%s/commit" $.comment.Issue.PullRequest.BaseRepo.Link}} {{$commitBaseLink := printf "%s/commit" $.comment.Issue.PullRequest.BaseRepo.Link}}
{{$commitLink:= printf "%s/%s" $commitBaseLink (PathEscape .ID.String)}} {{$commitLink:= printf "%s/%s" $commitBaseLink (PathEscape .ID.String)}}
<span class="tw-flex-1 tw-font-mono gt-ellipsis" title="{{.Summary}}"> <span class="tw-flex-1 tw-font-mono gt-ellipsis" title="{{$commit.MessageTitle}}">
{{- ctx.RenderUtils.RenderCommitMessageLinkSubject .Message $commitLink $.comment.Issue.PullRequest.BaseRepo -}} {{- ctx.RenderUtils.RenderCommitMessageLinkSubject $commit.MessageUTF8 $commitLink $.comment.Issue.PullRequest.BaseRepo -}}
</span> </span>
{{if IsMultilineCommitMessage .Message}} {{if $commit.MessageBody}}
<button class="ui button ellipsis-button show-panel toggle" data-panel="[data-singular-commit-body-for='{{$tag}}']">...</button> <button class="ui button ellipsis-button show-panel toggle" data-panel="[data-singular-commit-body-for='{{$tag}}']">...</button>
{{end}} {{end}}
@ -27,9 +27,9 @@
{{template "repo/commit_sign_badge" dict "Commit" . "CommitBaseLink" $commitBaseLink "CommitSignVerification" .Verification}} {{template "repo/commit_sign_badge" dict "Commit" . "CommitBaseLink" $commitBaseLink "CommitSignVerification" .Verification}}
</span> </span>
</div> </div>
{{if IsMultilineCommitMessage .Message}} {{if $commit.MessageBody}}
<pre class="commit-body tw-ml-[33px] tw-hidden" data-singular-commit-body-for="{{$tag}}"> <pre class="commit-body tw-ml-[33px] tw-hidden" data-singular-commit-body-for="{{$tag}}">
{{- ctx.RenderUtils.RenderCommitBody .Message $.comment.Issue.PullRequest.BaseRepo -}} {{- ctx.RenderUtils.RenderCommitBody $commit.MessageUTF8 $.comment.Issue.PullRequest.BaseRepo -}}
</pre> </pre>
{{end}} {{end}}
{{end}} {{end}}

View File

@ -17,10 +17,11 @@
{{template "repo/commit_statuses" dict "Status" .LatestCommitStatus "Statuses" .LatestCommitStatuses}} {{template "repo/commit_statuses" dict "Status" .LatestCommitStatus "Statuses" .LatestCommitStatuses}}
{{$commitLink:= printf "%s/commit/%s" .RepoLink (PathEscape .LatestCommit.ID.String)}} {{$commitLink:= printf "%s/commit/%s" .RepoLink (PathEscape .LatestCommit.ID.String)}}
<span class="grey commit-summary" title="{{.LatestCommit.Summary}}"><span class="message-wrapper">{{ctx.RenderUtils.RenderCommitMessageLinkSubject .LatestCommit.Message $commitLink $.Repository}}</span> <span class="grey commit-summary" title="{{.LatestCommit.MessageTitle}}">
{{if IsMultilineCommitMessage .LatestCommit.Message}} <span class="message-wrapper">{{ctx.RenderUtils.RenderCommitMessageLinkSubject .LatestCommit.MessageUTF8 $commitLink $.Repository}}</span>
{{if .LatestCommit.MessageBody}}
<button class="ui button ellipsis-button" aria-expanded="false" data-global-click="onRepoEllipsisButtonClick">...</button> <button class="ui button ellipsis-button" aria-expanded="false" data-global-click="onRepoEllipsisButtonClick">...</button>
<pre class="commit-body tw-hidden">{{ctx.RenderUtils.RenderCommitBody .LatestCommit.Message $.Repository}}</pre> <pre class="commit-body tw-hidden">{{ctx.RenderUtils.RenderCommitBody .LatestCommit.MessageUTF8 $.Repository}}</pre>
{{end}} {{end}}
</span> </span>
{{end}} {{end}}

View File

@ -6,7 +6,7 @@
</h4> </h4>
<table id="lfs-files-find-table" class="ui attached segment single line table"> <table id="lfs-files-find-table" class="ui attached segment single line table">
<tbody> <tbody>
{{range .Results}} {{range $lfsItem := .Results}}
<tr> <tr>
<td> <td>
{{svg "octicon-file"}} {{svg "octicon-file"}}
@ -14,8 +14,8 @@
</td> </td>
<td class="message"> <td class="message">
<span class="truncate"> <span class="truncate">
<a href="{{$.RepoLink}}/commit/{{.SHA}}" title="{{.Summary}}"> <a href="{{$.RepoLink}}/commit/{{.SHA}}" title="{{$lfsItem.Summary}}">
{{.Summary | ctx.RenderUtils.RenderEmoji}} {{$lfsItem.Summary | ctx.RenderUtils.RenderEmoji}}
</a> </a>
</span> </span>
</td> </td>

View File

@ -56,7 +56,7 @@
<div class="repo-file-cell message commit-summary {{if not $commit}}notready{{end}}"> <div class="repo-file-cell message commit-summary {{if not $commit}}notready{{end}}">
{{if $commit}} {{if $commit}}
{{$commitLink := printf "%s/commit/%s" $.RepoLink (PathEscape $commit.ID.String)}} {{$commitLink := printf "%s/commit/%s" $.RepoLink (PathEscape $commit.ID.String)}}
{{ctx.RenderUtils.RenderCommitMessageLinkSubject $commit.Message $commitLink $.Repository}} {{ctx.RenderUtils.RenderCommitMessageLinkSubject $commit.MessageUTF8 $commitLink $.Repository}}
{{else}} {{else}}
{{/* will be loaded again by LastCommitLoaderURL */}} {{/* will be loaded again by LastCommitLoaderURL */}}
{{end}} {{end}}

View File

@ -88,13 +88,13 @@
{{$repoLink := (.GetRepoLink ctx)}} {{$repoLink := (.GetRepoLink ctx)}}
{{$repo := .Repo}} {{$repo := .Repo}}
<div class="tw-flex tw-flex-col tw-gap-1"> <div class="tw-flex tw-flex-col tw-gap-1">
{{range $push.Commits}} {{range $pushCommit := $push.Commits}}
{{$commitLink := printf "%s/commit/%s" $repoLink .Sha1}} {{$commitLink := printf "%s/commit/%s" $repoLink .Sha1}}
<div class="flex-text-block"> <div class="flex-text-block">
<img loading="lazy" alt class="ui avatar" src="{{$push.AvatarLink ctx .AuthorEmail}}" title="{{.AuthorName}}" width="16" height="16"> <img loading="lazy" alt class="ui avatar" src="{{$push.AvatarLink ctx .AuthorEmail}}" title="{{.AuthorName}}" width="16" height="16">
<a class="ui sha label" href="{{$commitLink}}">{{ShortSha .Sha1}}</a> <a class="ui sha label" href="{{$commitLink}}">{{ShortSha .Sha1}}</a>
<span class="tw-inline-block tw-truncate"> <span class="tw-inline-block tw-truncate">
{{ctx.RenderUtils.RenderCommitMessage .Message $repo}} {{ctx.RenderUtils.RenderCommitMessage $pushCommit.Message $repo}}
</span> </span>
</div> </div>
{{end}} {{end}}

View File

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestAPIRepoTags(t *testing.T) { func TestAPIRepoTags(t *testing.T) {
@ -35,24 +36,22 @@ func TestAPIRepoTags(t *testing.T) {
assert.Len(t, tags, 1) assert.Len(t, tags, 1)
assert.Equal(t, "v1.1", tags[0].Name) assert.Equal(t, "v1.1", tags[0].Name)
assert.Equal(t, "Initial commit", tags[0].Message) assert.Equal(t, "Initial commit\n", tags[0].Message)
assert.Equal(t, "65f1bf27bc3bf70f64657658635e66094edbcb4d", tags[0].Commit.SHA) assert.Equal(t, "65f1bf27bc3bf70f64657658635e66094edbcb4d", tags[0].Commit.SHA)
assert.Equal(t, setting.AppURL+"api/v1/repos/user2/repo1/git/commits/65f1bf27bc3bf70f64657658635e66094edbcb4d", tags[0].Commit.URL) assert.Equal(t, setting.AppURL+"api/v1/repos/user2/repo1/git/commits/65f1bf27bc3bf70f64657658635e66094edbcb4d", tags[0].Commit.URL)
assert.Equal(t, setting.AppURL+"user2/repo1/archive/v1.1.zip", tags[0].ZipballURL) assert.Equal(t, setting.AppURL+"user2/repo1/archive/v1.1.zip", tags[0].ZipballURL)
assert.Equal(t, setting.AppURL+"user2/repo1/archive/v1.1.tar.gz", tags[0].TarballURL) assert.Equal(t, setting.AppURL+"user2/repo1/archive/v1.1.tar.gz", tags[0].TarballURL)
newTag := createNewTagUsingAPI(t, token, user.Name, repoName, "gitea/22", "", "nice!\nand some text") newTag := createNewTagUsingAPI(t, token, user.Name, repoName, "gitea/22", "", "nice!\nand some text")
assert.Equal(t, "nice!\nand some text\n", newTag.Message) // git message standard: there will always be a newline at the end of the message
resp = MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
tags = DecodeJSON(t, resp, []*api.Tag{}) tags = DecodeJSON(t, resp, []*api.Tag{})
assert.Len(t, tags, 2) require.Len(t, tags, 2)
for _, tag := range tags { respTag := tags[0]
if tag.Name != "v1.1" { assert.Equal(t, newTag.Name, respTag.Name)
assert.Equal(t, newTag.Name, tag.Name) assert.Equal(t, newTag.Message, respTag.Message)
assert.Equal(t, newTag.Message, tag.Message) assert.Equal(t, newTag.Commit.SHA, respTag.Commit.SHA)
assert.Equal(t, "nice!\nand some text", tag.Message)
assert.Equal(t, newTag.Commit.SHA, tag.Commit.SHA)
}
}
// get created tag // get created tag
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/tags/%s", user.Name, repoName, newTag.Name). req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/tags/%s", user.Name, repoName, newTag.Name).