Merge branch 'main' into ssh-mirror-migrations

This commit is contained in:
pomidorry
2026-06-05 18:42:07 +03:00
committed by GitHub
30 changed files with 232 additions and 156 deletions
-5
View File
File diff suppressed because one or more lines are too long
+1 -2
View File
@@ -23,7 +23,7 @@ require (
github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347
github.com/ProtonMail/go-crypto v1.4.1
github.com/PuerkitoBio/goquery v1.12.0
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.3
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.10.0
github.com/alecthomas/chroma/v2 v2.25.0
github.com/aws/aws-sdk-go-v2/credentials v1.19.16
github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.14
@@ -195,7 +195,6 @@ require (
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/flatbuffers v25.12.19+incompatible // indirect
github.com/google/go-querystring v1.2.0 // indirect
github.com/google/go-tpm v0.9.8 // indirect
+2 -4
View File
@@ -71,8 +71,8 @@ github.com/RoaringBitmap/roaring/v2 v2.16.0 h1:Kys1UNf49d5W8Tq3bpuAhIr/Z8/yPB+59
github.com/RoaringBitmap/roaring/v2 v2.16.0/go.mod h1:eq4wdNXxtJIS/oikeCzdX1rBzek7ANzbth041hrU8Q4=
github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4=
github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk=
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.3 h1:ikrUPushSxbpr9mmDhSgz1KyUTChjwkDrxY/jzv2OTQ=
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.3/go.mod h1:bnXbvnI9Mfqdj4L3Y9aCsB1A/ztuYLNRzgVKsJFgR/U=
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.10.0 h1:LvK7+C6qgz8BPnmn7xGekf8vTkcqTvyBBHaLYIMxx0g=
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.10.0/go.mod h1:I28hc9eaiqKCoOB+9Wh/P1IOScfm0xCgyWC6DAo0lmo=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
@@ -367,8 +367,6 @@ github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v1.9.3 h1:dNPSXeXv6HCq2jdyWfjgmhBdqnR6PRO3m/G05nvpPC8=
github.com/gomodule/redigo v1.9.3/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs=
github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
+2 -4
View File
@@ -46,10 +46,8 @@ func syncGitConfig(ctx context.Context) (err error) {
return err
}
if DefaultFeatures().CheckVersionAtLeast("2.10") {
if err := configSet(ctx, "receive.advertisePushOptions", "true"); err != nil {
return err
}
if err := configSet(ctx, "receive.advertisePushOptions", "true"); err != nil {
return err
}
if DefaultFeatures().CheckVersionAtLeast("2.18") {
+1 -8
View File
@@ -22,7 +22,7 @@ import (
"github.com/hashicorp/go-version"
)
const RequiredVersion = "2.6.0" // the minimum Git version required
const RequiredVersion = "2.13.0" // the minimum Git version required
type Features struct {
gitVersion *version.Version
@@ -173,13 +173,6 @@ func InitFull() (err error) {
if err = InitSimple(); err != nil {
return err
}
if setting.LFS.StartServer {
if !DefaultFeatures().CheckVersionAtLeast("2.1.2") {
return errors.New("LFS server support requires Git >= 2.1.2")
}
}
return syncGitConfig(context.Background())
}
+1 -7
View File
@@ -16,13 +16,7 @@ import (
// GetRemoteAddress returns remote url of git repository in the repoPath with special remote name
func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (string, error) {
var cmd *gitcmd.Command
if DefaultFeatures().CheckVersionAtLeast("2.7") {
cmd = gitcmd.NewCommand("remote", "get-url").AddDynamicArguments(remoteName)
} else {
cmd = gitcmd.NewCommand("config", "--get").AddDynamicArguments("remote." + remoteName + ".url")
}
cmd := gitcmd.NewCommand("remote", "get-url").AddDynamicArguments(remoteName)
result, _, err := cmd.WithDir(repoPath).RunStdString(ctx)
if err != nil {
return "", err
+31 -49
View File
@@ -7,7 +7,6 @@ package git
import (
"bytes"
"io"
"os"
"strconv"
"strings"
@@ -222,17 +221,20 @@ type CommitsByFileAndRangeOptions struct {
Page int
Since string
Until string
// when using FollowRename, there is no quick way to know the total count, so use hasMore to indicate if there are more commits to load
FollowRename bool
}
// CommitsByFileAndRange return the commits according revision file and the page
func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions) ([]*Commit, error) {
gitCmd := gitcmd.NewCommand("rev-list").
AddOptionFormat("--max-count=%d", setting.Git.CommitsRangeSize).
func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions) (commits []*Commit, hasMore bool, _ error) {
limit := setting.Git.CommitsRangeSize
gitCmd := gitcmd.NewCommand("--no-pager", "log").
AddArguments("--pretty=tformat:%H").
AddOptionFormat("--max-count=%d", limit+1).
AddOptionFormat("--skip=%d", (opts.Page-1)*setting.Git.CommitsRangeSize)
gitCmd.AddDynamicArguments(opts.Revision)
if opts.Not != "" {
gitCmd.AddOptionValues("--not", opts.Not)
if opts.FollowRename {
gitCmd.AddArguments("--follow")
}
if opts.Since != "" {
gitCmd.AddOptionFormat("--since=%s", opts.Since)
@@ -240,9 +242,12 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
if opts.Until != "" {
gitCmd.AddOptionFormat("--until=%s", opts.Until)
}
gitCmd.AddDynamicArguments(opts.Revision)
if opts.Not != "" {
gitCmd.AddOptionValues("--not", opts.Not)
}
gitCmd.AddDashesAndList(opts.File)
var commits []*Commit
stdoutReader, stdoutReaderClose := gitCmd.MakeStdoutPipe()
defer stdoutReaderClose()
err := gitCmd.WithDir(repo.Path).
@@ -274,7 +279,12 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
}
}).
RunWithStderr(repo.Ctx)
return commits, err
hasMore = len(commits) > limit
if hasMore {
commits = commits[:limit]
}
return commits, hasMore, err
}
// FilesCountBetween return the number of files changed between two commits
@@ -398,7 +408,7 @@ func (repo *Repository) commitsBefore(id ObjectID, limit int) ([]*Commit, error)
commits := make([]*Commit, 0, len(formattedLog))
for _, commit := range formattedLog {
branches, err := repo.getBranches(os.Environ(), commit.ID.String(), 2)
branches, err := repo.getBranches(nil, commit.ID.String(), 2)
if err != nil {
return nil, err
}
@@ -422,46 +432,17 @@ func (repo *Repository) getCommitsBeforeLimit(id ObjectID, num int) ([]*Commit,
}
func (repo *Repository) getBranches(env []string, commitID string, limit int) ([]string, error) {
if DefaultFeatures().CheckVersionAtLeast("2.7.0") {
stdout, _, err := gitcmd.NewCommand("for-each-ref", "--format=%(refname:strip=2)").
AddOptionFormat("--count=%d", limit).
AddOptionValues("--contains", commitID, BranchPrefix).
WithDir(repo.Path).
WithEnv(env).
RunStdString(repo.Ctx)
if err != nil {
return nil, err
}
branches := strings.Fields(stdout)
return branches, nil
}
stdout, _, err := gitcmd.NewCommand("branch").
stdout, _, err := gitcmd.NewCommand("for-each-ref", "--format=%(refname:strip=2)").
AddOptionFormat("--count=%d", limit).
AddOptionValues("--contains", commitID).
WithDir(repo.Path).
AddArguments(BranchPrefix).
WithEnv(env).
WithDir(repo.Path).
RunStdString(repo.Ctx)
if err != nil {
return nil, err
}
refs := strings.Split(stdout, "\n")
var maxNum int
if len(refs) > limit {
maxNum = limit
} else {
maxNum = len(refs) - 1
}
branches := make([]string, maxNum)
for i, ref := range refs[:maxNum] {
parts := strings.Fields(ref)
branches[i] = parts[len(parts)-1]
}
return branches, nil
return strings.Fields(stdout), nil
}
// GetCommitsFromIDs get commits from commit IDs
@@ -504,16 +485,17 @@ func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID s
parts := bytes.SplitSeq(bytes.TrimSpace(stdout), []byte{'\n'})
// check the commits one by one until we find a commit contained by another branch
// check the commits one by one until we find a commit contained by another branch,
// and we think this commit is the divergence point
for commitID := range parts {
branches, err := repo.getBranches(env, string(commitID), 2)
for part := range parts {
commitID := string(part)
branches, err := repo.getBranches(env, commitID, 2)
if err != nil {
return "", err
}
for _, b := range branches {
if b != branch {
return string(commitID), nil
return commitID, nil
}
}
}
+46 -4
View File
@@ -4,10 +4,11 @@
package git
import (
"os"
"path/filepath"
"strings"
"testing"
"gitea.dev/modules/git/gitcmd"
"gitea.dev/modules/setting"
"gitea.dev/modules/test"
@@ -36,7 +37,7 @@ func TestRepository_GetCommitBranches(t *testing.T) {
for _, testCase := range testCases {
commit, err := bareRepo1.GetCommit(testCase.CommitID)
assert.NoError(t, err)
branches, err := bareRepo1.getBranches(os.Environ(), commit.ID.String(), 2)
branches, err := bareRepo1.getBranches(nil, commit.ID.String(), 2)
assert.NoError(t, err)
assert.Equal(t, testCase.ExpectedBranches, branches)
}
@@ -140,11 +141,52 @@ func TestCommitsByFileAndRange(t *testing.T) {
defer bareRepo1.Close()
// "foo" has 3 commits in "master" branch
commits, err := bareRepo1.CommitsByFileAndRange(CommitsByFileAndRangeOptions{Revision: "master", File: "foo", Page: 1})
commits, hasMore, err := bareRepo1.CommitsByFileAndRange(CommitsByFileAndRangeOptions{Revision: "master", File: "foo", Page: 1})
require.NoError(t, err)
assert.True(t, hasMore)
assert.Len(t, commits, 2)
commits, err = bareRepo1.CommitsByFileAndRange(CommitsByFileAndRangeOptions{Revision: "master", File: "foo", Page: 2})
commits, hasMore, err = bareRepo1.CommitsByFileAndRange(CommitsByFileAndRangeOptions{Revision: "master", File: "foo", Page: 2})
require.NoError(t, err)
assert.Len(t, commits, 1)
assert.False(t, hasMore)
repoFollowRenameDir := filepath.Join(t.TempDir(), "repo.git")
require.NoError(t, gitcmd.NewCommand("init").AddDynamicArguments(repoFollowRenameDir).Run(t.Context()))
_, _, runErr := gitcmd.NewCommand("fast-import").WithDir(repoFollowRenameDir).WithStdinBytes([]byte(strings.TrimSpace(`
blob
mark :1
data 0
reset refs/heads/master
commit refs/heads/master
mark :2
author Chi-Iroh <user@example.com> 1778660718 +0200
committer Chi-Iroh <user@example.com> 1778660718 +0200
data 10
Add a.txt
M 100644 :1 a.txt
commit refs/heads/master
mark :3
author Chi-Iroh <user@example.com> 1778660741 +0200
committer Chi-Iroh <user@example.com> 1778660741 +0200
data 22
Rename a.txt to b.txt
from :2
D a.txt
M 100644 :1 b.txt
`))).RunStdString(t.Context())
require.NoError(t, runErr)
repoFollowRename, err := OpenRepository(t.Context(), repoFollowRenameDir)
require.NoError(t, err)
defer repoFollowRename.Close()
commits, _, err = repoFollowRename.CommitsByFileAndRange(CommitsByFileAndRangeOptions{Revision: "master", File: "b.txt", Page: 1})
require.NoError(t, err)
assert.Len(t, commits, 1)
commits, _, err = repoFollowRename.CommitsByFileAndRange(CommitsByFileAndRangeOptions{Revision: "master", File: "b.txt", Page: 1, FollowRename: true})
require.NoError(t, err)
assert.Len(t, commits, 2)
}
+8 -9
View File
@@ -37,10 +37,11 @@ type Paginator struct {
total int // total rows count, -1 means unknown
totalPages int // total pages count, -1 means unknown
current int // current page number
curRows int // current page rows count
pagingNum int // how many rows in one page
numPages int // how many pages to show on the UI
hasNext *bool // used for total=-1 ("unlimited paging")
}
// New initialize a new pagination calculation and returns a Paginator as result.
@@ -60,15 +61,13 @@ func New(total, pagingNum, current, numPages int) *Paginator {
}
}
func (p *Paginator) SetCurRows(rows int) {
func (p *Paginator) SetUnlimitedPaging(curRows int, hasNext bool) {
// For "unlimited paging", we need to know the rows of current page to determine if there is a next page.
// There is still an edge case: when curRows==pagingNum, then the "next page" will be an empty page.
// Ideally we should query one more row to determine if there is really a next page, but it's impossible in current framework.
p.curRows = rows
if p.total == -1 && p.current == 1 && !p.HasNext() {
p.hasNext = &hasNext
if p.total == -1 && p.current == 1 && !hasNext {
// if there is only one page for the "unlimited paging", set total rows/pages count
// then the tmpl could decide to hide the nav bar.
p.total = rows
p.total = curRows
p.totalPages = util.Iif(p.total == 0, 0, 1)
}
}
@@ -92,8 +91,8 @@ func (p *Paginator) Previous() int {
// HasNext returns true if there is a next page relative to current page.
func (p *Paginator) HasNext() bool {
if p.total == -1 {
return p.curRows >= p.pagingNum
if p.hasNext != nil {
return *p.hasNext
}
return p.current*p.pagingNum < p.total
}
+2 -2
View File
@@ -60,7 +60,7 @@ func (r *Reader) Close() error {
type SeekableWriter struct {
buf []byte
n int
w seekable.Writer
w *seekable.Writer
}
var _ io.WriteCloser = (*SeekableWriter)(nil)
@@ -114,7 +114,7 @@ func (w *SeekableWriter) Close() error {
}
type SeekableReader struct {
r seekable.Reader
r *seekable.Reader
c func() error
}
+1
View File
@@ -1333,6 +1333,7 @@
"repo.editor.fork_branch_exists": "Branch \"%s\" already exists in your fork. Please choose a new branch name.",
"repo.commits.desc": "Browse source code change history.",
"repo.commits.commits": "Commits",
"repo.commits.history_enable_follow_renames": "Include renames",
"repo.commits.no_commits": "No commits in common. \"%s\" and \"%s\" have entirely different histories.",
"repo.commits.nothing_to_compare": "There are no differences to show.",
"repo.commits.search.tooltip": "You can prefix keywords with \"author:\", \"committer:\", \"after:\", or \"before:\", e.g. \"revert author:Alice before:2019-01-13\".",
+1
View File
@@ -1321,6 +1321,7 @@
"repo.editor.fork_branch_exists": "Tá brainse \"%s\" ann cheana féin i do fhorc. Roghnaigh ainm brainse nua le do thoil.",
"repo.commits.desc": "Brabhsáil stair athraithe cód foinse.",
"repo.commits.commits": "Tiomáintí",
"repo.commits.history_enable_follow_renames": "Cuir athainmneacha san áireamh",
"repo.commits.no_commits": "Níl aon ghealltanas i gcoiteann. Tá stair iomlán difriúil ag \"%s\" agus \"%s\".",
"repo.commits.nothing_to_compare": "Níl aon difríochtaí le taispeáint.",
"repo.commits.search.tooltip": "Is féidir eochairfhocail a réamhfhostú le “údar:”, “committer:”, “after:”, nó “before:”, e.g. \"fill an t-údar:Alice roimh: 2019-01-13\".",
+6
View File
@@ -306,6 +306,10 @@ func ListBranches(ctx *context.APIContext) {
// in: query
// description: page size of results
// type: integer
// - name: q
// in: query
// description: branch name substring to filter by
// type: string
// responses:
// "200":
// "$ref": "#/responses/BranchList"
@@ -314,6 +318,7 @@ func ListBranches(ctx *context.APIContext) {
var apiBranches []*api.Branch
listOptions := utils.GetListOptions(ctx)
keyword := ctx.FormString("q")
if !ctx.Repo.Repository.IsEmpty {
if ctx.Repo.GitRepo == nil {
@@ -325,6 +330,7 @@ func ListBranches(ctx *context.APIContext) {
ListOptions: listOptions,
RepoID: ctx.Repo.Repository.ID,
IsDeletedBranch: optional.Some(false),
Keyword: keyword,
}
var err error
totalNumOfBranches, err = db.Count[git_model.Branch](ctx, branchOpts)
+1 -1
View File
@@ -262,7 +262,7 @@ func GetAllCommits(ctx *context.APIContext) {
return
}
commits, err = ctx.Repo.GitRepo.CommitsByFileAndRange(
commits, _, err = ctx.Repo.GitRepo.CommitsByFileAndRange(
git.CommitsByFileAndRangeOptions{
Revision: sha,
File: path,
+1 -1
View File
@@ -435,7 +435,7 @@ func ListPageRevisions(ctx *context.APIContext) {
page := max(ctx.FormInt("page"), 1)
// get Commit Count
commitsHistory, err := wikiRepo.CommitsByFileAndRange(
commitsHistory, _, err := wikiRepo.CommitsByFileAndRange(
git.CommitsByFileAndRangeOptions{
Revision: ctx.Repo.Repository.DefaultWikiBranch,
File: pageFilename,
+1 -1
View File
@@ -20,7 +20,7 @@ func ShowFileFeed(ctx *context.Context, repo *repo.Repository, formatType string
if len(fileName) == 0 {
return
}
commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange(
commits, _, err := ctx.Repo.GitRepo.CommitsByFileAndRange(
git.CommitsByFileAndRangeOptions{
Revision: ctx.Repo.RefFullName.ShortName(), // FIXME: legacy code used ShortName
File: fileName,
+31 -16
View File
@@ -214,37 +214,52 @@ func FileHistory(ctx *context.Context) {
return
}
commitsCount, err := gitrepo.FileCommitsCount(ctx, ctx.Repo.Repository, ctx.Repo.RefFullName.ShortName(), ctx.Repo.TreePath)
if err != nil {
ctx.ServerError("FileCommitsCount", err)
return
} else if commitsCount == 0 {
ctx.NotFound(nil)
return
}
followRename := ctx.FormBool("follow-rename")
ctx.Data["ShowFollowRename"] = true
ctx.Data["FollowRenameChecked"] = followRename
page := max(ctx.FormInt("page"), 1)
commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange(
commits, hasMore, err := ctx.Repo.GitRepo.CommitsByFileAndRange(
git.CommitsByFileAndRangeOptions{
Revision: ctx.Repo.RefFullName.ShortName(), // FIXME: legacy code used ShortName
File: ctx.Repo.TreePath,
Page: page,
Revision: ctx.Repo.RefFullName.ShortName(), // FIXME: legacy code used ShortName
File: ctx.Repo.TreePath,
Page: page,
FollowRename: followRename,
})
if err != nil {
ctx.ServerError("CommitsByFileAndRange", err)
return
}
var commitsCount int64
if followRename {
// there is no quick method to know the total count when "follow rename"
commitsCount = -1
} else {
commitsCount, err = gitrepo.FileCommitsCount(ctx, ctx.Repo.Repository, ctx.Repo.RefFullName.ShortName(), ctx.Repo.TreePath)
if err != nil {
ctx.ServerError("FileCommitsCount", err)
return
}
}
if len(commits) == 0 {
ctx.NotFound(nil)
return
}
ctx.Data["FileTreePath"] = ctx.Repo.TreePath
ctx.Data["CommitCount"] = commitsCount
ctx.Data["Commits"], err = processGitCommits(ctx, commits)
if err != nil {
ctx.ServerError("processGitCommits", err)
return
}
ctx.Data["FileTreePath"] = ctx.Repo.TreePath
ctx.Data["CommitCount"] = commitsCount
pager := context.NewPagination(commitsCount, setting.Git.CommitsRangeSize, page, 5)
if commitsCount == -1 {
pager.WithUnlimitedPaging(len(commits), hasMore)
}
pager.AddParamFromRequest(ctx.Req)
ctx.Data["Page"] = pager
ctx.HTML(http.StatusOK, tplCommits)
+1 -1
View File
@@ -351,7 +351,7 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry)
page := max(ctx.FormInt("page"), 1)
// get Commit Count
commitsHistory, err := wikiGitRepo.CommitsByFileAndRange(
commitsHistory, _, err := wikiGitRepo.CommitsByFileAndRange(
git.CommitsByFileAndRangeOptions{
Revision: ctx.Repo.Repository.DefaultWikiBranch,
File: pageFilename,
+6 -5
View File
@@ -111,6 +111,7 @@ func Dashboard(ctx *context.Context) {
prepareHeatmapURL(ctx)
pageSize := setting.UI.User.RepoPagingNum
feeds, count, err := feed_service.GetFeedsForDashboard(ctx, activities_model.GetFeedsOptions{
RequestedUser: ctxUser,
RequestedTeam: ctx.Org.Team,
@@ -119,17 +120,17 @@ func Dashboard(ctx *context.Context) {
OnlyPerformedBy: false,
IncludeDeleted: false,
Date: ctx.FormString("date"),
ListOptions: db.ListOptions{
Page: page,
PageSize: setting.UI.FeedPagingNum,
},
ListOptions: db.ListOptions{Page: page, PageSize: pageSize},
})
if err != nil {
ctx.ServerError("GetFeeds", err)
return
}
pager := context.NewPagination(count, setting.UI.FeedPagingNum, page, 5).WithCurRows(len(feeds))
// FIXME: UNLIMITE-PAGING-ONE-MORE-ROW: here is still an edge case: when curRows==pagingNum, then the "next page" will be an empty page.
// Ideally we should query one more row to determine if there is really a next page, but it's impossible in current framework.
pager := context.NewPagination(count, pageSize, page, 5).WithUnlimitedPaging(len(feeds), len(feeds) == pageSize)
pager.AddParamFromRequest(ctx.Req)
ctx.Data["Page"] = pager
ctx.Data["Feeds"] = feeds
+2 -1
View File
@@ -312,7 +312,8 @@ func prepareUserProfileTabData(ctx *context.Context, profileDbRepo *repo_model.R
pager := context.NewPagination(total, pagingNum, page, 5)
if tab == "activity" {
pager.WithCurRows(curRows)
// FIXME: UNLIMITE-PAGING-ONE-MORE-ROW: see another comment
pager.WithUnlimitedPaging(curRows, curRows == pagingNum)
}
pager.AddParamFromRequest(ctx.Req)
ctx.Data["Page"] = pager
+2 -2
View File
@@ -33,8 +33,8 @@ func NewPagination(total int64, pagingNum, current, numPages int) *Pagination {
return p
}
func (p *Pagination) WithCurRows(n int) *Pagination {
p.Paginater.SetCurRows(n)
func (p *Pagination) WithUnlimitedPaging(curRows int, hasNext bool) *Pagination {
p.Paginater.SetUnlimitedPaging(curRows, hasNext)
return p
}
+20
View File
@@ -32,4 +32,24 @@ func TestPagination(t *testing.T) {
params.Del("foo")
v, _ = url.ParseQuery(string(p.GetParams()))
assert.Equal(t, params, v)
p = NewPagination(-1, 1, 1, 1)
p.WithUnlimitedPaging(0, false)
assert.Zero(t, p.Paginater.TotalPages())
assert.False(t, p.Paginater.HasNext())
p = NewPagination(-1, 1, 1, 1)
p.WithUnlimitedPaging(10, false)
assert.Equal(t, 1, p.Paginater.TotalPages()) // first page, no next, so it should know that the total page number is 1
assert.False(t, p.Paginater.HasNext())
p = NewPagination(-1, 1, 2, 1)
p.WithUnlimitedPaging(10, false)
assert.Equal(t, -1, p.Paginater.TotalPages())
assert.False(t, p.Paginater.HasNext())
p = NewPagination(-1, 1, 1, 1)
p.WithUnlimitedPaging(10, true)
assert.Equal(t, -1, p.Paginater.TotalPages())
assert.True(t, p.Paginater.HasNext())
}
+18 -9
View File
@@ -1,21 +1,30 @@
<h4 class="ui top attached header commits-table flex-left-right">
<div class="commits-table-left flex-text-block">
{{if or .PageIsCommits (gt .CommitCount 0)}}
{{.CommitCount}} {{ctx.Locale.Tr "repo.commits.commits"}}
<div class="ui top attached header flex-left-right">
<div class="flex-text-block">
{{if .Commits}}
{{if gt .CommitCount 0}}{{.CommitCount}}{{end}}{{/* CommitCount is -1 when "follow rename" */}}
{{ctx.Locale.Tr "repo.commits.commits"}}
{{else if .IsNothingToCompare}}
{{ctx.Locale.Tr "repo.commits.nothing_to_compare"}}
{{else}}
{{ctx.Locale.Tr "repo.commits.no_commits" $.BaseBranch $.HeadBranch}}
{{end}}
</div>
{{if .IsDiffCompare}}
<div class="commits-table-right tw-whitespace-nowrap">
<div class="flex-text-block">
{{if .ShowFollowRename}}
<div class="ui checkbox">
<input type="checkbox" {{if .FollowRenameChecked}}checked{{end}} data-global-init="initCommitHistoryFollowRename">
<label>{{ctx.Locale.Tr "repo.commits.history_enable_follow_renames"}}</label>
</div>
{{end}}
{{if .IsDiffCompare}}
<div class="flex-text-inline">
<a href="{{$.CommitRepoLink}}/commit/{{.BeforeCommitID | PathEscape}}" class="ui green sha label tw-mx-0">{{template "repo/commits_ref_name" .CompareInfo.BaseRef}}</a>
{{$.CompareInfo.CompareSeparator}}
<a href="{{$.CommitRepoLink}}/commit/{{.AfterCommitID | PathEscape}}" class="ui green sha label tw-mx-0">{{template "repo/commits_ref_name" .CompareInfo.HeadRef}}</a>
</div>
{{end}}
</h4>
{{end}}
</div>
</div>
{{if .PageIsCommits}}
<div class="ui attached segment">
@@ -29,7 +38,7 @@
</div>
{{end}}
{{if and .Commits (gt .CommitCount 0)}}
{{if .Commits}}
{{template "repo/commits_list" .}}
{{end}}
+6
View File
@@ -7241,6 +7241,12 @@
"description": "page size of results",
"name": "limit",
"in": "query"
},
{
"type": "string",
"description": "branch name substring to filter by",
"name": "q",
"in": "query"
}
],
"responses": {
+8
View File
@@ -18313,6 +18313,14 @@
"schema": {
"type": "integer"
}
},
{
"description": "branch name substring to filter by",
"in": "query",
"name": "q",
"schema": {
"type": "string"
}
}
],
"responses": {
+17
View File
@@ -128,3 +128,20 @@ func TestAPIRepoBranchesMirror(t *testing.T) {
assert.NoError(t, err)
assert.JSONEq(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}", string(bs))
}
func TestAPIRepoBranchesSearch(t *testing.T) {
defer tests.PrepareTestEnv(t)()
token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteRepository)
// "test" matches "test_branch" but not "master"
resp := MakeRequest(t, NewRequestf(t, "GET", "/api/v1/repos/org3/repo3/branches?q=test").AddTokenAuth(token), http.StatusOK)
branches := DecodeJSON(t, resp, []api.Branch{})
assert.Len(t, branches, 1)
assert.Equal(t, "test_branch", branches[0].Name)
// no match returns empty list
resp = MakeRequest(t, NewRequestf(t, "GET", "/api/v1/repos/org3/repo3/branches?q=doesnotexist").AddTokenAuth(token), http.StatusOK)
branches = DecodeJSON(t, resp, []api.Branch{})
assert.Empty(t, branches)
}
+4
View File
@@ -323,6 +323,10 @@ func logUnexpectedResponse(t testing.TB, recorder *httptest.ResponseRecorder) {
}
}
// DecodeJSON decodes then response as JSON into typed variable and return it
// HINT: don't use it on existing variable (reuse existing variable):
// if the existing var already contains some values but the new input doesn't, then it leads to wrong test result in edge cases.
// For slice decoding, use: v := DecodeJSON(t, resp, []T{})
func DecodeJSON[T any](t testing.TB, resp *httptest.ResponseRecorder, v T) (ret T) {
t.Helper()
-24
View File
@@ -1816,14 +1816,6 @@ tbody.commit-list {
box-shadow: 0 0.5rem 1rem var(--color-shadow) !important;
}
.commits-table .commits-table-right form {
display: flex;
align-items: center;
gap: 0.75em;
justify-content: center;
flex-wrap: wrap;
}
@media (max-width: 767.98px) {
.repository.view.issue .comment-list,
.repository.view.issue .comment-list .timeline-item {
@@ -1848,22 +1840,6 @@ tbody.commit-list {
flex-basis: auto !important;
margin-bottom: 0.5rem !important;
}
.commits-table {
flex-direction: column;
}
.commits-table .commits-table-left {
align-items: initial !important;
margin-bottom: 6px;
}
.commits-table .commits-table-right form > div:nth-child(1) {
order: 1; /* the "commit search" input */
}
.commits-table .commits-table-right form > div:nth-child(2) {
order: 3; /* the "search all" checkbox */
}
.commits-table .commits-table-right form > button:nth-child(3) {
order: 2; /* the "search" button */
}
.commit-table {
overflow-x: auto;
}
+10
View File
@@ -24,3 +24,13 @@ export function initCommitStatuses() {
});
});
}
export function initCommitFileHistoryFollowRename() {
registerGlobalInitFunc('initCommitHistoryFollowRename', (el: HTMLInputElement) => {
el.addEventListener('change', () => {
const url = new URL(window.location.toString());
url.searchParams.set('follow-rename', `${el.checked}`);
window.location.assign(url.toString());
});
});
}
+2 -1
View File
@@ -21,7 +21,7 @@ import {initMarkupContent} from './markup/content.ts';
import {initRepoFileView} from './features/file-view.ts';
import {initUserExternalLogins, initUserCheckAppUrl} from './features/user-auth.ts';
import {initRepoPullRequestReview, initRepoIssueFilterItemLabel} from './features/repo-issue.ts';
import {initRepoEllipsisButton, initCommitStatuses} from './features/repo-commit.ts';
import {initRepoEllipsisButton, initCommitStatuses, initCommitFileHistoryFollowRename} from './features/repo-commit.ts';
import {initRepoTopicBar} from './features/repo-home.ts';
import {initAdminCommon} from './features/admin/common.ts';
import {initRepoCodeView} from './features/repo-code.ts';
@@ -123,6 +123,7 @@ const initPerformanceTracer = callInitFunctions([
initRepoCodeView,
initBranchSelectorTabs,
initRepoEllipsisButton,
initCommitFileHistoryFollowRename,
initRepoDiffCommitBranchesAndTags,
initRepoEditor,
initRepoGraphGit,