@@ -13,6 +13,7 @@ import (
1313 "regexp"
1414 "strings"
1515 "time"
16+ "unicode/utf8"
1617
1718 "code.gitea.io/gitea/models/db"
1819 git_model "code.gitea.io/gitea/models/git"
@@ -838,64 +839,66 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ
838839 stringBuilder := strings.Builder {}
839840
840841 if ! setting .Repository .PullRequest .PopulateSquashCommentWithCommitMessages {
842+ // use PR's title and description as squash commit message
841843 message := strings .TrimSpace (pr .Issue .Content )
842844 stringBuilder .WriteString (message )
843845 if stringBuilder .Len () > 0 {
844846 stringBuilder .WriteRune ('\n' )
845847 if ! commitMessageTrailersPattern .MatchString (message ) {
848+ // TODO: this trailer check doesn't work with the separator line added below for the co-authors
846849 stringBuilder .WriteRune ('\n' )
847850 }
848851 }
849- }
850-
851- // commits list is in reverse chronological order
852- first := true
853- for i := len (commits ) - 1 ; i >= 0 ; i -- {
854- commit := commits [i ]
855-
856- if setting .Repository .PullRequest .PopulateSquashCommentWithCommitMessages {
857- maxSize := setting .Repository .PullRequest .DefaultMergeMessageSize
858- if maxSize < 0 || stringBuilder .Len () < maxSize {
859- var toWrite []byte
860- if first {
861- first = false
862- toWrite = []byte (strings .TrimPrefix (commit .CommitMessage , pr .Issue .Title ))
863- } else {
864- toWrite = []byte (commit .CommitMessage )
865- }
866-
867- if len (toWrite ) > maxSize - stringBuilder .Len () && maxSize > - 1 {
868- toWrite = append (toWrite [:maxSize - stringBuilder .Len ()], "..." ... )
869- }
870- if _ , err := stringBuilder .Write (toWrite ); err != nil {
871- log .Error ("Unable to write commit message Error: %v" , err )
872- return ""
873- }
852+ } else {
853+ // use PR's commit messages as squash commit message
854+ // commits list is in reverse chronological order
855+ maxMsgSize := setting .Repository .PullRequest .DefaultMergeMessageSize
856+ for i := len (commits ) - 1 ; i >= 0 ; i -- {
857+ commit := commits [i ]
858+ msg := strings .TrimSpace (commit .CommitMessage )
859+ if msg == "" {
860+ continue
861+ }
874862
875- if _ , err := stringBuilder .WriteRune ('\n' ); err != nil {
876- log .Error ("Unable to write commit message Error: %v" , err )
877- return ""
863+ // This format follows GitHub's squash commit message style,
864+ // even if there are other "* " in the commit message body, they are written as-is.
865+ // Maybe, ideally, we should indent those lines too.
866+ _ , _ = fmt .Fprintf (& stringBuilder , "* %s\n \n " , msg )
867+ if maxMsgSize > 0 && stringBuilder .Len () >= maxMsgSize {
868+ tmp := stringBuilder .String ()
869+ wasValidUtf8 := utf8 .ValidString (tmp )
870+ tmp = tmp [:maxMsgSize ] + "..."
871+ if wasValidUtf8 {
872+ // If the message was valid UTF-8 before truncation, ensure it remains valid after truncation
873+ // For non-utf8 messages, we can't do much about it, end users should use utf-8 as much as possible
874+ tmp = strings .ToValidUTF8 (tmp , "" )
878875 }
876+ stringBuilder .Reset ()
877+ stringBuilder .WriteString (tmp )
878+ break
879879 }
880880 }
881+ }
881882
883+ // collect co-authors
884+ for _ , commit := range commits {
882885 authorString := commit .Author .String ()
883886 if uniqueAuthors .Add (authorString ) && authorString != posterSig {
884887 // Compare use account as well to avoid adding the same author multiple times
885- // times when email addresses are private or multiple emails are used.
888+ // when email addresses are private or multiple emails are used.
886889 commitUser , _ := user_model .GetUserByEmail (ctx , commit .Author .Email )
887890 if commitUser == nil || commitUser .ID != pr .Issue .Poster .ID {
888891 authors = append (authors , authorString )
889892 }
890893 }
891894 }
892895
893- // Consider collecting the remaining authors
896+ // collect the remaining authors
894897 if limit >= 0 && setting .Repository .PullRequest .DefaultMergeMessageAllAuthors {
895898 skip := limit
896899 limit = 30
897900 for {
898- commits , err : = gitRepo .CommitsBetweenLimit (headCommit , mergeBase , limit , skip )
901+ commits , err = gitRepo .CommitsBetweenLimit (headCommit , mergeBase , limit , skip )
899902 if err != nil {
900903 log .Error ("Unable to get commits between: %s %s Error: %v" , pr .HeadBranch , pr .MergeBase , err )
901904 return ""
@@ -916,19 +919,15 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ
916919 }
917920 }
918921
922+ if stringBuilder .Len () > 0 && len (authors ) > 0 {
923+ // TODO: this separator line doesn't work with the trailer check (commitMessageTrailersPattern) above
924+ stringBuilder .WriteString ("---------\n \n " )
925+ }
926+
919927 for _ , author := range authors {
920- if _ , err := stringBuilder .WriteString ("Co-authored-by: " ); err != nil {
921- log .Error ("Unable to write to string builder Error: %v" , err )
922- return ""
923- }
924- if _ , err := stringBuilder .WriteString (author ); err != nil {
925- log .Error ("Unable to write to string builder Error: %v" , err )
926- return ""
927- }
928- if _ , err := stringBuilder .WriteRune ('\n' ); err != nil {
929- log .Error ("Unable to write to string builder Error: %v" , err )
930- return ""
931- }
928+ stringBuilder .WriteString ("Co-authored-by: " )
929+ stringBuilder .WriteString (author )
930+ stringBuilder .WriteRune ('\n' )
932931 }
933932
934933 return stringBuilder .String ()
0 commit comments