mirror of
https://github.com/go-gitea/gitea
synced 2025-08-09 02:58:20 +00:00
@@ -3,8 +3,6 @@
|
||||
|
||||
package git
|
||||
|
||||
import "path"
|
||||
|
||||
// CommitInfo describes the first commit with the provided entry
|
||||
type CommitInfo struct {
|
||||
Entry *TreeEntry
|
||||
@@ -12,15 +10,14 @@ type CommitInfo struct {
|
||||
SubmoduleFile *CommitSubmoduleFile
|
||||
}
|
||||
|
||||
func getCommitInfoSubmoduleFile(repoLink string, entry *TreeEntry, commit *Commit, treePathDir string) (*CommitSubmoduleFile, error) {
|
||||
fullPath := path.Join(treePathDir, entry.Name())
|
||||
func GetCommitInfoSubmoduleFile(repoLink, fullPath string, commit *Commit, refCommitID ObjectID) (*CommitSubmoduleFile, error) {
|
||||
submodule, err := commit.GetSubModule(fullPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if submodule == nil {
|
||||
// unable to find submodule from ".gitmodules" file
|
||||
return NewCommitSubmoduleFile(repoLink, fullPath, "", entry.ID.String()), nil
|
||||
return NewCommitSubmoduleFile(repoLink, fullPath, "", refCommitID.String()), nil
|
||||
}
|
||||
return NewCommitSubmoduleFile(repoLink, fullPath, submodule.URL, entry.ID.String()), nil
|
||||
return NewCommitSubmoduleFile(repoLink, fullPath, submodule.URL, refCommitID.String()), nil
|
||||
}
|
||||
|
@@ -73,7 +73,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, repoLink string, commit *
|
||||
|
||||
// If the entry is a submodule, add a submodule file for this
|
||||
if entry.IsSubModule() {
|
||||
commitsInfo[i].SubmoduleFile, err = getCommitInfoSubmoduleFile(repoLink, entry, commit, treePath)
|
||||
commitsInfo[i].SubmoduleFile, err = GetCommitInfoSubmoduleFile(repoLink, path.Join(treePath, entry.Name()), commit, entry.ID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@@ -67,7 +67,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, repoLink string, commit *
|
||||
|
||||
// If the entry is a submodule, add a submodule file for this
|
||||
if entry.IsSubModule() {
|
||||
commitsInfo[i].SubmoduleFile, err = getCommitInfoSubmoduleFile(repoLink, entry, commit, treePath)
|
||||
commitsInfo[i].SubmoduleFile, err = GetCommitInfoSubmoduleFile(repoLink, path.Join(treePath, entry.Name()), commit, entry.ID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@@ -125,9 +125,9 @@ func TestEntries_GetCommitsInfo(t *testing.T) {
|
||||
t.Run("NonExistingSubmoduleAsNil", func(t *testing.T) {
|
||||
commit, err := bareRepo1.GetCommit("HEAD")
|
||||
require.NoError(t, err)
|
||||
tree, err := commit.GetTreeEntryByPath("file1.txt")
|
||||
treeEntry, err := commit.GetTreeEntryByPath("file1.txt")
|
||||
require.NoError(t, err)
|
||||
cisf, err := getCommitInfoSubmoduleFile("/any/repo-link", tree, commit, "")
|
||||
cisf, err := GetCommitInfoSubmoduleFile("/any/repo-link", "file1.txt", commit, treeEntry.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &CommitSubmoduleFile{
|
||||
repoLink: "/any/repo-link",
|
||||
|
@@ -42,7 +42,7 @@ func (sf *CommitSubmoduleFile) getWebLinkInTargetRepo(ctx context.Context, moreL
|
||||
return nil
|
||||
}
|
||||
if strings.HasPrefix(sf.refURL, "../") {
|
||||
targetLink := path.Join(sf.repoLink, path.Dir(sf.fullPath), sf.refURL)
|
||||
targetLink := path.Join(sf.repoLink, sf.refURL)
|
||||
return &SubmoduleWebLink{RepoWebLink: targetLink, CommitWebLink: targetLink + moreLinkPath}
|
||||
}
|
||||
if !sf.parsed {
|
||||
|
@@ -32,7 +32,7 @@ func TestCommitSubmoduleLink(t *testing.T) {
|
||||
assert.Equal(t, "/subpath/user/repo", wl.RepoWebLink)
|
||||
assert.Equal(t, "/subpath/user/repo/tree/aaaa", wl.CommitWebLink)
|
||||
|
||||
sf = NewCommitSubmoduleFile("/subpath/any/repo-home-link", "dir/submodule", "../../../user/repo", "aaaa")
|
||||
sf = NewCommitSubmoduleFile("/subpath/any/repo-home-link", "dir/submodule", "../../user/repo", "aaaa")
|
||||
wl = sf.SubmoduleWebLinkCompare(t.Context(), "1111", "2222")
|
||||
assert.Equal(t, "/subpath/user/repo", wl.RepoWebLink)
|
||||
assert.Equal(t, "/subpath/user/repo/compare/1111...2222", wl.CommitWebLink)
|
||||
|
@@ -11,6 +11,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// ObjectCache provides thread-safe cache operations.
|
||||
@@ -106,3 +108,16 @@ func HashFilePathForWebUI(s string) string {
|
||||
_, _ = h.Write([]byte(s))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func SplitCommitTitleBody(commitMessage string, titleRuneLimit int) (title, body string) {
|
||||
title, body, _ = strings.Cut(commitMessage, "\n")
|
||||
title, title2 := util.EllipsisTruncateRunes(title, titleRuneLimit)
|
||||
if title2 != "" {
|
||||
if body == "" {
|
||||
body = title2
|
||||
} else {
|
||||
body = title2 + "\n" + body
|
||||
}
|
||||
}
|
||||
return title, body
|
||||
}
|
||||
|
@@ -15,3 +15,17 @@ func TestHashFilePathForWebUI(t *testing.T) {
|
||||
HashFilePathForWebUI("foobar"),
|
||||
)
|
||||
}
|
||||
|
||||
func TestSplitCommitTitleBody(t *testing.T) {
|
||||
title, body := SplitCommitTitleBody("啊bcdefg", 4)
|
||||
assert.Equal(t, "啊…", title)
|
||||
assert.Equal(t, "…bcdefg", body)
|
||||
|
||||
title, body = SplitCommitTitleBody("abcdefg\n1234567", 4)
|
||||
assert.Equal(t, "a…", title)
|
||||
assert.Equal(t, "…bcdefg\n1234567", body)
|
||||
|
||||
title, body = SplitCommitTitleBody("abcdefg\n1234567", 100)
|
||||
assert.Equal(t, "abcdefg", title)
|
||||
assert.Equal(t, "1234567", body)
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@ func IsLikelyEllipsisLeftPart(s string) bool {
|
||||
return strings.HasSuffix(s, utf8Ellipsis) || strings.HasSuffix(s, asciiEllipsis)
|
||||
}
|
||||
|
||||
func ellipsisGuessDisplayWidth(r rune) int {
|
||||
func ellipsisDisplayGuessWidth(r rune) int {
|
||||
// To make the truncated string as long as possible,
|
||||
// CJK/emoji chars are considered as 2-ASCII width but not 3-4 bytes width.
|
||||
// Here we only make the best guess (better than counting them in bytes),
|
||||
@@ -48,13 +48,17 @@ func ellipsisGuessDisplayWidth(r rune) int {
|
||||
// It appends "…" or "..." at the end of truncated string.
|
||||
// It guarantees the length of the returned runes doesn't exceed the limit.
|
||||
func EllipsisDisplayString(str string, limit int) string {
|
||||
s, _, _, _ := ellipsisDisplayString(str, limit)
|
||||
s, _, _, _ := ellipsisDisplayString(str, limit, ellipsisDisplayGuessWidth)
|
||||
return s
|
||||
}
|
||||
|
||||
// EllipsisDisplayStringX works like EllipsisDisplayString while it also returns the right part
|
||||
func EllipsisDisplayStringX(str string, limit int) (left, right string) {
|
||||
left, offset, truncated, encounterInvalid := ellipsisDisplayString(str, limit)
|
||||
return ellipsisDisplayStringX(str, limit, ellipsisDisplayGuessWidth)
|
||||
}
|
||||
|
||||
func ellipsisDisplayStringX(str string, limit int, widthGuess func(rune) int) (left, right string) {
|
||||
left, offset, truncated, encounterInvalid := ellipsisDisplayString(str, limit, widthGuess)
|
||||
if truncated {
|
||||
right = str[offset:]
|
||||
r, _ := utf8.DecodeRune(UnsafeStringToBytes(right))
|
||||
@@ -68,7 +72,7 @@ func EllipsisDisplayStringX(str string, limit int) (left, right string) {
|
||||
return left, right
|
||||
}
|
||||
|
||||
func ellipsisDisplayString(str string, limit int) (res string, offset int, truncated, encounterInvalid bool) {
|
||||
func ellipsisDisplayString(str string, limit int, widthGuess func(rune) int) (res string, offset int, truncated, encounterInvalid bool) {
|
||||
if len(str) <= limit {
|
||||
return str, len(str), false, false
|
||||
}
|
||||
@@ -81,7 +85,7 @@ func ellipsisDisplayString(str string, limit int) (res string, offset int, trunc
|
||||
for i, r := range str {
|
||||
encounterInvalid = encounterInvalid || r == utf8.RuneError
|
||||
pos = i
|
||||
runeWidth := ellipsisGuessDisplayWidth(r)
|
||||
runeWidth := widthGuess(r)
|
||||
if used+runeWidth+3 > limit {
|
||||
break
|
||||
}
|
||||
@@ -96,7 +100,7 @@ func ellipsisDisplayString(str string, limit int) (res string, offset int, trunc
|
||||
if nextCnt >= 4 {
|
||||
break
|
||||
}
|
||||
nextWidth += ellipsisGuessDisplayWidth(r)
|
||||
nextWidth += widthGuess(r)
|
||||
nextCnt++
|
||||
}
|
||||
if nextCnt <= 3 && used+nextWidth <= limit {
|
||||
@@ -114,6 +118,10 @@ func ellipsisDisplayString(str string, limit int) (res string, offset int, trunc
|
||||
return str[:offset] + ellipsis, offset, true, encounterInvalid
|
||||
}
|
||||
|
||||
func EllipsisTruncateRunes(str string, limit int) (left, right string) {
|
||||
return ellipsisDisplayStringX(str, limit, func(r rune) int { return 1 })
|
||||
}
|
||||
|
||||
// TruncateRunes returns a truncated string with given rune limit,
|
||||
// it returns input string if its rune length doesn't exceed the limit.
|
||||
func TruncateRunes(str string, limit int) string {
|
||||
|
@@ -29,7 +29,7 @@ func TestEllipsisGuessDisplayWidth(t *testing.T) {
|
||||
t.Run(c.r, func(t *testing.T) {
|
||||
w := 0
|
||||
for _, r := range c.r {
|
||||
w += ellipsisGuessDisplayWidth(r)
|
||||
w += ellipsisDisplayGuessWidth(r)
|
||||
}
|
||||
assert.Equal(t, c.want, w, "hex=% x", []byte(c.r))
|
||||
})
|
||||
|
Reference in New Issue
Block a user