Compare commits

...

9 Commits

Author SHA1 Message Date
Lunny Xiao
0676b74b21
Merge bb6645f7a8 into bc50431e8b 2025-10-27 00:06:57 +08:00
Lunny Xiao
bc50431e8b
Upgrade go mail to 0.7.2 (#35748) 2025-10-26 09:52:01 -04:00
GiteaBot
2a6af15448 [skip ci] Updated translations via Crowdin 2025-10-26 00:38:59 +00:00
Lunny Xiao
bb6645f7a8
Merge branch 'main' into lunny/move_commit_related_funcs 2025-10-25 14:37:21 -07:00
Lunny Xiao
f565e97947
Merge branch 'main' into lunny/move_commit_related_funcs 2025-10-17 20:04:55 -07:00
Lunny Xiao
85ff14d162
comments 2025-10-07 11:12:12 -07:00
Lunny Xiao
de436dbdf3
Add a wrap for new batch 2025-10-07 11:01:12 -07:00
Lunny Xiao
7885c77d0f Move commits count to gitrepo 2025-10-07 10:52:48 -07:00
Lunny Xiao
7130ed3310 Move commit file status to gitrepo 2025-10-07 10:48:56 -07:00
57 changed files with 497 additions and 449 deletions

2
go.mod
View File

@ -109,7 +109,7 @@ require (
github.com/ulikunitz/xz v0.5.15
github.com/urfave/cli-docs/v3 v3.0.0-alpha6
github.com/urfave/cli/v3 v3.4.1
github.com/wneessen/go-mail v0.7.1
github.com/wneessen/go-mail v0.7.2
github.com/xeipuuv/gojsonschema v1.2.0
github.com/yohcop/openid-go v1.0.1
github.com/yuin/goldmark v1.7.13

4
go.sum
View File

@ -768,8 +768,8 @@ github.com/urfave/cli/v3 v3.4.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZ
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
github.com/wneessen/go-mail v0.7.1 h1:rvy63sp14N06/kdGqCYwW8Na5gDCXjTQM1E7So4PuKk=
github.com/wneessen/go-mail v0.7.1/go.mod h1:+TkW6QP3EVkgTEqHtVmnAE/1MRhmzb8Y9/W3pweuS+k=
github.com/wneessen/go-mail v0.7.2 h1:xxPnhZ6IZLSgxShebmZ6DPKh1b6OJcoHfzy7UjOkzS8=
github.com/wneessen/go-mail v0.7.2/go.mod h1:+TkW6QP3EVkgTEqHtVmnAE/1MRhmzb8Y9/W3pweuS+k=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=

View File

@ -5,17 +5,13 @@
package git
import (
"bufio"
"bytes"
"context"
"errors"
"io"
"os/exec"
"strconv"
"strings"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
)
@ -130,65 +126,6 @@ func CommitChanges(ctx context.Context, repoPath string, opts CommitChangesOptio
return err
}
// AllCommitsCount returns count of all commits in repository
func AllCommitsCount(ctx context.Context, repoPath string, hidePRRefs bool, files ...string) (int64, error) {
cmd := gitcmd.NewCommand("rev-list")
if hidePRRefs {
cmd.AddArguments("--exclude=" + PullPrefix + "*")
}
cmd.AddArguments("--all", "--count")
if len(files) > 0 {
cmd.AddDashesAndList(files...)
}
stdout, _, err := cmd.WithDir(repoPath).RunStdString(ctx)
if err != nil {
return 0, err
}
return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
}
// CommitsCountOptions the options when counting commits
type CommitsCountOptions struct {
RepoPath string
Not string
Revision []string
RelPath []string
Since string
Until string
}
// CommitsCount returns number of total commits of until given revision.
func CommitsCount(ctx context.Context, opts CommitsCountOptions) (int64, error) {
cmd := gitcmd.NewCommand("rev-list", "--count")
cmd.AddDynamicArguments(opts.Revision...)
if opts.Not != "" {
cmd.AddOptionValues("--not", opts.Not)
}
if len(opts.RelPath) > 0 {
cmd.AddDashesAndList(opts.RelPath...)
}
stdout, _, err := cmd.WithDir(opts.RepoPath).RunStdString(ctx)
if err != nil {
return 0, err
}
return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
}
// CommitsCount returns number of total commits of until current revision.
func (c *Commit) CommitsCount() (int64, error) {
return CommitsCount(c.repo.Ctx, CommitsCountOptions{
RepoPath: c.repo.Path,
Revision: []string{c.ID.String()},
})
}
// CommitsByRange returns the specific page commits before current revision, every page's number default by CommitsRangeSize
func (c *Commit) CommitsByRange(page, pageSize int, not, since, until string) ([]*Commit, error) {
return c.repo.commitsByRangeWithTime(c.ID, page, pageSize, not, since, until)
@ -371,85 +308,6 @@ func (c *Commit) GetBranchName() (string, error) {
return strings.SplitN(strings.TrimSpace(data), "~", 2)[0], nil
}
// CommitFileStatus represents status of files in a commit.
type CommitFileStatus struct {
Added []string
Removed []string
Modified []string
}
// NewCommitFileStatus creates a CommitFileStatus
func NewCommitFileStatus() *CommitFileStatus {
return &CommitFileStatus{
[]string{}, []string{}, []string{},
}
}
func parseCommitFileStatus(fileStatus *CommitFileStatus, stdout io.Reader) {
rd := bufio.NewReader(stdout)
peek, err := rd.Peek(1)
if err != nil {
if err != io.EOF {
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
}
return
}
if peek[0] == '\n' || peek[0] == '\x00' {
_, _ = rd.Discard(1)
}
for {
modifier, err := rd.ReadString('\x00')
if err != nil {
if err != io.EOF {
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
}
return
}
file, err := rd.ReadString('\x00')
if err != nil {
if err != io.EOF {
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
}
return
}
file = file[:len(file)-1]
switch modifier[0] {
case 'A':
fileStatus.Added = append(fileStatus.Added, file)
case 'D':
fileStatus.Removed = append(fileStatus.Removed, file)
case 'M':
fileStatus.Modified = append(fileStatus.Modified, file)
}
}
}
// GetCommitFileStatus returns file status of commit in given repository.
func GetCommitFileStatus(ctx context.Context, repoPath, commitID string) (*CommitFileStatus, error) {
stdout, w := io.Pipe()
done := make(chan struct{})
fileStatus := NewCommitFileStatus()
go func() {
parseCommitFileStatus(fileStatus, stdout)
close(done)
}()
stderr := new(bytes.Buffer)
err := gitcmd.NewCommand("log", "--name-status", "-m", "--pretty=format:", "--first-parent", "--no-renames", "-z", "-1").
AddDynamicArguments(commitID).
WithDir(repoPath).
WithStdout(w).
WithStderr(stderr).
Run(ctx)
w.Close() // Close writer to exit parsing goroutine
if err != nil {
return nil, gitcmd.ConcatenateError(err, stderr.String())
}
<-done
return fileStatus, nil
}
// GetFullCommitID returns full length (40) of commit ID by given short SHA in a repository.
func GetFullCommitID(ctx context.Context, repoPath, shortID string) (string, error) {
commitID, _, err := gitcmd.NewCommand("rev-parse").

View File

@ -14,33 +14,6 @@ import (
"github.com/stretchr/testify/require"
)
func TestCommitsCountSha256(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare_sha256")
commitsCount, err := CommitsCount(t.Context(),
CommitsCountOptions{
RepoPath: bareRepo1Path,
Revision: []string{"f004f41359117d319dedd0eaab8c5259ee2263da839dcba33637997458627fdc"},
})
assert.NoError(t, err)
assert.Equal(t, int64(3), commitsCount)
}
func TestCommitsCountWithoutBaseSha256(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare_sha256")
commitsCount, err := CommitsCount(t.Context(),
CommitsCountOptions{
RepoPath: bareRepo1Path,
Not: "main",
Revision: []string{"branch1"},
})
assert.NoError(t, err)
assert.Equal(t, int64(2), commitsCount)
}
func TestGetFullCommitIDSha256(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare_sha256")
@ -157,39 +130,3 @@ func TestHasPreviousCommitSha256(t *testing.T) {
assert.NoError(t, err)
assert.False(t, selfNot)
}
func TestGetCommitFileStatusMergesSha256(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo6_merge_sha256")
commitFileStatus, err := GetCommitFileStatus(t.Context(), bareRepo1Path, "d2e5609f630dd8db500f5298d05d16def282412e3e66ed68cc7d0833b29129a1")
assert.NoError(t, err)
expected := CommitFileStatus{
[]string{
"add_file.txt",
},
[]string{},
[]string{
"to_modify.txt",
},
}
assert.Equal(t, expected.Added, commitFileStatus.Added)
assert.Equal(t, expected.Removed, commitFileStatus.Removed)
assert.Equal(t, expected.Modified, commitFileStatus.Modified)
expected = CommitFileStatus{
[]string{},
[]string{
"to_remove.txt",
},
[]string{},
}
commitFileStatus, err = GetCommitFileStatus(t.Context(), bareRepo1Path, "da1ded40dc8e5b7c564171f4bf2fc8370487decfb1cb6a99ef28f3ed73d09172")
assert.NoError(t, err)
assert.Equal(t, expected.Added, commitFileStatus.Added)
assert.Equal(t, expected.Removed, commitFileStatus.Removed)
assert.Equal(t, expected.Modified, commitFileStatus.Modified)
}

View File

@ -13,33 +13,6 @@ import (
"github.com/stretchr/testify/require"
)
func TestCommitsCount(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
commitsCount, err := CommitsCount(t.Context(),
CommitsCountOptions{
RepoPath: bareRepo1Path,
Revision: []string{"8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"},
})
assert.NoError(t, err)
assert.Equal(t, int64(3), commitsCount)
}
func TestCommitsCountWithoutBase(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
commitsCount, err := CommitsCount(t.Context(),
CommitsCountOptions{
RepoPath: bareRepo1Path,
Not: "master",
Revision: []string{"branch1"},
})
assert.NoError(t, err)
assert.Equal(t, int64(2), commitsCount)
}
func TestGetFullCommitID(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
@ -212,134 +185,6 @@ func TestHasPreviousCommit(t *testing.T) {
assert.False(t, selfNot)
}
func TestParseCommitFileStatus(t *testing.T) {
type testcase struct {
output string
added []string
removed []string
modified []string
}
kases := []testcase{
{
// Merge commit
output: "MM\x00options/locale/locale_en-US.ini\x00",
modified: []string{
"options/locale/locale_en-US.ini",
},
added: []string{},
removed: []string{},
},
{
// Spaces commit
output: "D\x00b\x00D\x00b b/b\x00A\x00b b/b b/b b/b\x00A\x00b b/b b/b b/b b/b\x00",
removed: []string{
"b",
"b b/b",
},
modified: []string{},
added: []string{
"b b/b b/b b/b",
"b b/b b/b b/b b/b",
},
},
{
// larger commit
output: "M\x00go.mod\x00M\x00go.sum\x00M\x00modules/ssh/ssh.go\x00M\x00vendor/github.com/gliderlabs/ssh/circle.yml\x00M\x00vendor/github.com/gliderlabs/ssh/context.go\x00A\x00vendor/github.com/gliderlabs/ssh/go.mod\x00A\x00vendor/github.com/gliderlabs/ssh/go.sum\x00M\x00vendor/github.com/gliderlabs/ssh/server.go\x00M\x00vendor/github.com/gliderlabs/ssh/session.go\x00M\x00vendor/github.com/gliderlabs/ssh/ssh.go\x00M\x00vendor/golang.org/x/sys/unix/mkerrors.sh\x00M\x00vendor/golang.org/x/sys/unix/syscall_darwin.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_linux.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_dragonfly_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_arm64.go\x00M\x00vendor/modules.txt\x00",
modified: []string{
"go.mod",
"go.sum",
"modules/ssh/ssh.go",
"vendor/github.com/gliderlabs/ssh/circle.yml",
"vendor/github.com/gliderlabs/ssh/context.go",
"vendor/github.com/gliderlabs/ssh/server.go",
"vendor/github.com/gliderlabs/ssh/session.go",
"vendor/github.com/gliderlabs/ssh/ssh.go",
"vendor/golang.org/x/sys/unix/mkerrors.sh",
"vendor/golang.org/x/sys/unix/syscall_darwin.go",
"vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go",
"vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go",
"vendor/golang.org/x/sys/unix/zerrors_freebsd_386.go",
"vendor/golang.org/x/sys/unix/zerrors_freebsd_amd64.go",
"vendor/golang.org/x/sys/unix/zerrors_freebsd_arm.go",
"vendor/golang.org/x/sys/unix/zerrors_freebsd_arm64.go",
"vendor/golang.org/x/sys/unix/zerrors_linux.go",
"vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go",
"vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go",
"vendor/golang.org/x/sys/unix/ztypes_dragonfly_amd64.go",
"vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go",
"vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go",
"vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go",
"vendor/golang.org/x/sys/unix/ztypes_freebsd_arm64.go",
"vendor/golang.org/x/sys/unix/ztypes_netbsd_386.go",
"vendor/golang.org/x/sys/unix/ztypes_netbsd_amd64.go",
"vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go",
"vendor/golang.org/x/sys/unix/ztypes_netbsd_arm64.go",
"vendor/modules.txt",
},
added: []string{
"vendor/github.com/gliderlabs/ssh/go.mod",
"vendor/github.com/gliderlabs/ssh/go.sum",
},
removed: []string{},
},
{
// git 1.7.2 adds an unnecessary \x00 on merge commit
output: "\x00MM\x00options/locale/locale_en-US.ini\x00",
modified: []string{
"options/locale/locale_en-US.ini",
},
added: []string{},
removed: []string{},
},
{
// git 1.7.2 adds an unnecessary \n on normal commit
output: "\nD\x00b\x00D\x00b b/b\x00A\x00b b/b b/b b/b\x00A\x00b b/b b/b b/b b/b\x00",
removed: []string{
"b",
"b b/b",
},
modified: []string{},
added: []string{
"b b/b b/b b/b",
"b b/b b/b b/b b/b",
},
},
}
for _, kase := range kases {
fileStatus := NewCommitFileStatus()
parseCommitFileStatus(fileStatus, strings.NewReader(kase.output))
assert.Equal(t, kase.added, fileStatus.Added)
assert.Equal(t, kase.removed, fileStatus.Removed)
assert.Equal(t, kase.modified, fileStatus.Modified)
}
}
func TestGetCommitFileStatusMerges(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo6_merge")
commitFileStatus, err := GetCommitFileStatus(t.Context(), bareRepo1Path, "022f4ce6214973e018f02bf363bf8a2e3691f699")
assert.NoError(t, err)
expected := CommitFileStatus{
[]string{
"add_file.txt",
},
[]string{
"to_remove.txt",
},
[]string{
"to_modify.txt",
},
}
assert.Equal(t, expected.Added, commitFileStatus.Added)
assert.Equal(t, expected.Removed, commitFileStatus.Removed)
assert.Equal(t, expected.Modified, commitFileStatus.Modified)
}
func Test_GetCommitBranchStart(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
repo, err := OpenRepository(t.Context(), bareRepo1Path)

View File

@ -32,11 +32,6 @@ type GPGSettings struct {
const prettyLogFormat = `--pretty=format:%H`
// GetAllCommitsCount returns count of all commits in repository
func (repo *Repository) GetAllCommitsCount() (int64, error) {
return AllCommitsCount(repo.Ctx, repo.Path, false)
}
func (repo *Repository) ShowPrettyFormatLogToList(ctx context.Context, revisionRange string) ([]*Commit, error) {
// avoid: ambiguous argument 'refs/a...refs/b': unknown revision or path not in the working tree. Use '--': 'git <command> [<revision>...] -- [<file>...]'
logs, _, err := gitcmd.NewCommand("log").AddArguments(prettyLogFormat).

View File

@ -11,7 +11,6 @@ import (
"strconv"
"strings"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/setting"
)
@ -216,16 +215,6 @@ func (repo *Repository) FileChangedBetweenCommits(filename, id1, id2 string) (bo
return len(strings.TrimSpace(string(stdout))) > 0, nil
}
// FileCommitsCount return the number of files at a revision
func (repo *Repository) FileCommitsCount(revision, file string) (int64, error) {
return CommitsCount(repo.Ctx,
CommitsCountOptions{
RepoPath: repo.Path,
Revision: []string{revision},
RelPath: []string{file},
})
}
type CommitsByFileAndRangeOptions struct {
Revision string
File string
@ -433,25 +422,6 @@ func (repo *Repository) CommitsBetweenIDs(last, before string) ([]*Commit, error
return repo.CommitsBetween(lastCommit, beforeCommit)
}
// CommitsCountBetween return numbers of commits between two commits
func (repo *Repository) CommitsCountBetween(start, end string) (int64, error) {
count, err := CommitsCount(repo.Ctx, CommitsCountOptions{
RepoPath: repo.Path,
Revision: []string{start + ".." + end},
})
if err != nil && strings.Contains(err.Error(), "no merge base") {
// future versions of git >= 2.28 are likely to return an error if before and last have become unrelated.
// previously it would return the results of git rev-list before last so let's try that...
return CommitsCount(repo.Ctx, CommitsCountOptions{
RepoPath: repo.Path,
Revision: []string{start, end},
})
}
return count, err
}
// commitsBefore the limit is depth, not total number of returned commits.
func (repo *Repository) commitsBefore(id ObjectID, limit int) ([]*Commit, error) {
cmd := gitcmd.NewCommand("log", prettyLogFormat)
@ -564,23 +534,6 @@ func (repo *Repository) IsCommitInBranch(commitID, branch string) (r bool, err e
return len(stdout) > 0, err
}
func (repo *Repository) AddLastCommitCache(cacheKey, fullName, sha string) error {
if repo.LastCommitCache == nil {
commitsCount, err := cache.GetInt64(cacheKey, func() (int64, error) {
commit, err := repo.GetCommit(sha)
if err != nil {
return 0, err
}
return commit.CommitsCount()
})
if err != nil {
return err
}
repo.LastCommitCache = NewLastCommitCache(commitsCount, fullName, repo, cache.GetCache())
}
return nil
}
// GetCommitBranchStart returns the commit where the branch diverged
func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID string) (string, error) {
cmd := gitcmd.NewCommand("log", prettyLogFormat)

View File

@ -0,0 +1,14 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitrepo
import (
"context"
"code.gitea.io/gitea/modules/git"
)
func NewBatch(ctx context.Context, repo Repository) (*git.Batch, error) {
return git.NewBatch(ctx, repoPath(repo))
}

96
modules/gitrepo/commit.go Normal file
View File

@ -0,0 +1,96 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitrepo
import (
"context"
"strconv"
"strings"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
)
// CommitsCountOptions the options when counting commits
type CommitsCountOptions struct {
Not string
Revision []string
RelPath []string
Since string
Until string
}
// CommitsCount returns number of total commits of until given revision.
func CommitsCount(ctx context.Context, repo Repository, opts CommitsCountOptions) (int64, error) {
cmd := gitcmd.NewCommand("rev-list", "--count")
cmd.AddDynamicArguments(opts.Revision...)
if opts.Not != "" {
cmd.AddOptionValues("--not", opts.Not)
}
if len(opts.RelPath) > 0 {
cmd.AddDashesAndList(opts.RelPath...)
}
stdout, _, err := cmd.WithDir(repoPath(repo)).RunStdString(ctx)
if err != nil {
return 0, err
}
return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
}
// CommitsCountBetween return numbers of commits between two commits
func CommitsCountBetween(ctx context.Context, repo Repository, start, end string) (int64, error) {
count, err := CommitsCount(ctx, repo, CommitsCountOptions{
Revision: []string{start + ".." + end},
})
if err != nil && strings.Contains(err.Error(), "no merge base") {
// future versions of git >= 2.28 are likely to return an error if before and last have become unrelated.
// previously it would return the results of git rev-list before last so let's try that...
return CommitsCount(ctx, repo, CommitsCountOptions{
Revision: []string{start, end},
})
}
return count, err
}
// FileCommitsCount return the number of files at a revision
func FileCommitsCount(ctx context.Context, repo Repository, revision, file string) (int64, error) {
return CommitsCount(ctx, repo,
CommitsCountOptions{
Revision: []string{revision},
RelPath: []string{file},
})
}
// CommitsCountOfCommit returns number of total commits of until current revision.
func CommitsCountOfCommit(ctx context.Context, repo Repository, commitID string) (int64, error) {
return CommitsCount(ctx, repo, CommitsCountOptions{
Revision: []string{commitID},
})
}
// AllCommitsCount returns count of all commits in repository
func AllCommitsCount(ctx context.Context, repo Repository, hidePRRefs bool, files ...string) (int64, error) {
cmd := gitcmd.NewCommand("rev-list")
if hidePRRefs {
cmd.AddArguments("--exclude=" + git.PullPrefix + "*")
}
cmd.AddArguments("--all", "--count")
if len(files) > 0 {
cmd.AddDashesAndList(files...)
}
stdout, err := RunCmdString(ctx, repo, cmd)
if err != nil {
return 0, err
}
return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
}

View File

@ -0,0 +1,93 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitrepo
import (
"bufio"
"bytes"
"context"
"io"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log"
)
// CommitFileStatus represents status of files in a commit.
type CommitFileStatus struct {
Added []string
Removed []string
Modified []string
}
// NewCommitFileStatus creates a CommitFileStatus
func NewCommitFileStatus() *CommitFileStatus {
return &CommitFileStatus{
[]string{}, []string{}, []string{},
}
}
func parseCommitFileStatus(fileStatus *CommitFileStatus, stdout io.Reader) {
rd := bufio.NewReader(stdout)
peek, err := rd.Peek(1)
if err != nil {
if err != io.EOF {
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
}
return
}
if peek[0] == '\n' || peek[0] == '\x00' {
_, _ = rd.Discard(1)
}
for {
modifier, err := rd.ReadString('\x00')
if err != nil {
if err != io.EOF {
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
}
return
}
file, err := rd.ReadString('\x00')
if err != nil {
if err != io.EOF {
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
}
return
}
file = file[:len(file)-1]
switch modifier[0] {
case 'A':
fileStatus.Added = append(fileStatus.Added, file)
case 'D':
fileStatus.Removed = append(fileStatus.Removed, file)
case 'M':
fileStatus.Modified = append(fileStatus.Modified, file)
}
}
}
// GetCommitFileStatus returns file status of commit in given repository.
func GetCommitFileStatus(ctx context.Context, repo Repository, commitID string) (*CommitFileStatus, error) {
stdout, w := io.Pipe()
done := make(chan struct{})
fileStatus := NewCommitFileStatus()
go func() {
parseCommitFileStatus(fileStatus, stdout)
close(done)
}()
stderr := new(bytes.Buffer)
err := gitcmd.NewCommand("log", "--name-status", "-m", "--pretty=format:", "--first-parent", "--no-renames", "-z", "-1").
AddDynamicArguments(commitID).
WithDir(repoPath(repo)).
WithStdout(w).
WithStderr(stderr).
Run(ctx)
w.Close() // Close writer to exit parsing goroutine
if err != nil {
return nil, gitcmd.ConcatenateError(err, stderr.String())
}
<-done
return fileStatus, nil
}

View File

@ -0,0 +1,175 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitrepo
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseCommitFileStatus(t *testing.T) {
type testcase struct {
output string
added []string
removed []string
modified []string
}
kases := []testcase{
{
// Merge commit
output: "MM\x00options/locale/locale_en-US.ini\x00",
modified: []string{
"options/locale/locale_en-US.ini",
},
added: []string{},
removed: []string{},
},
{
// Spaces commit
output: "D\x00b\x00D\x00b b/b\x00A\x00b b/b b/b b/b\x00A\x00b b/b b/b b/b b/b\x00",
removed: []string{
"b",
"b b/b",
},
modified: []string{},
added: []string{
"b b/b b/b b/b",
"b b/b b/b b/b b/b",
},
},
{
// larger commit
output: "M\x00go.mod\x00M\x00go.sum\x00M\x00modules/ssh/ssh.go\x00M\x00vendor/github.com/gliderlabs/ssh/circle.yml\x00M\x00vendor/github.com/gliderlabs/ssh/context.go\x00A\x00vendor/github.com/gliderlabs/ssh/go.mod\x00A\x00vendor/github.com/gliderlabs/ssh/go.sum\x00M\x00vendor/github.com/gliderlabs/ssh/server.go\x00M\x00vendor/github.com/gliderlabs/ssh/session.go\x00M\x00vendor/github.com/gliderlabs/ssh/ssh.go\x00M\x00vendor/golang.org/x/sys/unix/mkerrors.sh\x00M\x00vendor/golang.org/x/sys/unix/syscall_darwin.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_linux.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_dragonfly_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_arm64.go\x00M\x00vendor/modules.txt\x00",
modified: []string{
"go.mod",
"go.sum",
"modules/ssh/ssh.go",
"vendor/github.com/gliderlabs/ssh/circle.yml",
"vendor/github.com/gliderlabs/ssh/context.go",
"vendor/github.com/gliderlabs/ssh/server.go",
"vendor/github.com/gliderlabs/ssh/session.go",
"vendor/github.com/gliderlabs/ssh/ssh.go",
"vendor/golang.org/x/sys/unix/mkerrors.sh",
"vendor/golang.org/x/sys/unix/syscall_darwin.go",
"vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go",
"vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go",
"vendor/golang.org/x/sys/unix/zerrors_freebsd_386.go",
"vendor/golang.org/x/sys/unix/zerrors_freebsd_amd64.go",
"vendor/golang.org/x/sys/unix/zerrors_freebsd_arm.go",
"vendor/golang.org/x/sys/unix/zerrors_freebsd_arm64.go",
"vendor/golang.org/x/sys/unix/zerrors_linux.go",
"vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go",
"vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go",
"vendor/golang.org/x/sys/unix/ztypes_dragonfly_amd64.go",
"vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go",
"vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go",
"vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go",
"vendor/golang.org/x/sys/unix/ztypes_freebsd_arm64.go",
"vendor/golang.org/x/sys/unix/ztypes_netbsd_386.go",
"vendor/golang.org/x/sys/unix/ztypes_netbsd_amd64.go",
"vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go",
"vendor/golang.org/x/sys/unix/ztypes_netbsd_arm64.go",
"vendor/modules.txt",
},
added: []string{
"vendor/github.com/gliderlabs/ssh/go.mod",
"vendor/github.com/gliderlabs/ssh/go.sum",
},
removed: []string{},
},
{
// git 1.7.2 adds an unnecessary \x00 on merge commit
output: "\x00MM\x00options/locale/locale_en-US.ini\x00",
modified: []string{
"options/locale/locale_en-US.ini",
},
added: []string{},
removed: []string{},
},
{
// git 1.7.2 adds an unnecessary \n on normal commit
output: "\nD\x00b\x00D\x00b b/b\x00A\x00b b/b b/b b/b\x00A\x00b b/b b/b b/b b/b\x00",
removed: []string{
"b",
"b b/b",
},
modified: []string{},
added: []string{
"b b/b b/b b/b",
"b b/b b/b b/b b/b",
},
},
}
for _, kase := range kases {
fileStatus := NewCommitFileStatus()
parseCommitFileStatus(fileStatus, strings.NewReader(kase.output))
assert.Equal(t, kase.added, fileStatus.Added)
assert.Equal(t, kase.removed, fileStatus.Removed)
assert.Equal(t, kase.modified, fileStatus.Modified)
}
}
func TestGetCommitFileStatusMerges(t *testing.T) {
bareRepo6 := &mockRepository{path: "repo6_merge"}
commitFileStatus, err := GetCommitFileStatus(t.Context(), bareRepo6, "022f4ce6214973e018f02bf363bf8a2e3691f699")
assert.NoError(t, err)
expected := CommitFileStatus{
[]string{
"add_file.txt",
},
[]string{
"to_remove.txt",
},
[]string{
"to_modify.txt",
},
}
assert.Equal(t, expected.Added, commitFileStatus.Added)
assert.Equal(t, expected.Removed, commitFileStatus.Removed)
assert.Equal(t, expected.Modified, commitFileStatus.Modified)
}
func TestGetCommitFileStatusMergesSha256(t *testing.T) {
bareRepo6Sha256 := &mockRepository{path: "repo6_merge_sha256"}
commitFileStatus, err := GetCommitFileStatus(t.Context(), bareRepo6Sha256, "d2e5609f630dd8db500f5298d05d16def282412e3e66ed68cc7d0833b29129a1")
assert.NoError(t, err)
expected := CommitFileStatus{
[]string{
"add_file.txt",
},
[]string{},
[]string{
"to_modify.txt",
},
}
assert.Equal(t, expected.Added, commitFileStatus.Added)
assert.Equal(t, expected.Removed, commitFileStatus.Removed)
assert.Equal(t, expected.Modified, commitFileStatus.Modified)
expected = CommitFileStatus{
[]string{},
[]string{
"to_remove.txt",
},
[]string{},
}
commitFileStatus, err = GetCommitFileStatus(t.Context(), bareRepo6Sha256, "da1ded40dc8e5b7c564171f4bf2fc8370487decfb1cb6a99ef28f3ed73d09172")
assert.NoError(t, err)
assert.Equal(t, expected.Added, commitFileStatus.Added)
assert.Equal(t, expected.Removed, commitFileStatus.Removed)
assert.Equal(t, expected.Modified, commitFileStatus.Modified)
}

View File

@ -0,0 +1,35 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitrepo
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCommitsCount(t *testing.T) {
bareRepo1 := &mockRepository{path: "repo1_bare"}
commitsCount, err := CommitsCount(t.Context(), bareRepo1,
CommitsCountOptions{
Revision: []string{"8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"},
})
assert.NoError(t, err)
assert.Equal(t, int64(3), commitsCount)
}
func TestCommitsCountWithoutBase(t *testing.T) {
bareRepo1 := &mockRepository{path: "repo1_bare"}
commitsCount, err := CommitsCount(t.Context(), bareRepo1,
CommitsCountOptions{
Not: "master",
Revision: []string{"branch1"},
})
assert.NoError(t, err)
assert.Equal(t, int64(2), commitsCount)
}

View File

@ -218,7 +218,7 @@ func (b *Indexer) addDelete(filename string, repo *repo_model.Repository, batch
func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *internal.RepoChanges) error {
batch := inner_bleve.NewFlushingBatch(b.inner.Indexer, maxBatchSize)
if len(changes.Updates) > 0 {
gitBatch, err := git.NewBatch(ctx, repo.RepoPath())
gitBatch, err := gitrepo.NewBatch(ctx, repo)
if err != nil {
return err
}

View File

@ -210,7 +210,7 @@ func (b *Indexer) addDelete(filename string, repo *repo_model.Repository) elasti
func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *internal.RepoChanges) error {
reqs := make([]elastic.BulkableRequest, 0)
if len(changes.Updates) > 0 {
batch, err := git.NewBatch(ctx, repo.RepoPath())
batch, err := gitrepo.NewBatch(ctx, repo)
if err != nil {
return err
}

View File

@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/cachegroup"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
@ -72,7 +73,7 @@ func ToAPIPayloadCommit(ctx context.Context, emailUsers map[string]*user_model.U
committerUsername = committer.Name
}
fileStatus, err := git.GetCommitFileStatus(ctx, repo.RepoPath(), commit.Sha1)
fileStatus, err := gitrepo.GetCommitFileStatus(ctx, repo, commit.Sha1)
if err != nil {
return nil, fmt.Errorf("FileStatus [commit_sha1: %s]: %w", commit.Sha1, err)
}

View File

@ -3586,6 +3586,7 @@ variables.update.success=Proměnná byla upravena.
logs.always_auto_scroll=Vždy automaticky posouvat logy
logs.always_expand_running=Vždy rozšířit běžící logy
[projects]
deleted.display_name=Odstraněný projekt
type-1.display_name=Samostatný projekt

View File

@ -3645,6 +3645,7 @@ variables.update.success=Die Variable wurde bearbeitet.
logs.always_auto_scroll=Autoscroll für Logs immer aktivieren
logs.always_expand_running=Laufende Logs immer erweitern
[projects]
deleted.display_name=Gelöschtes Projekt
type-1.display_name=Individuelles Projekt

View File

@ -3280,6 +3280,7 @@ variables.update.failed=Αποτυχία επεξεργασίας μεταβλη
variables.update.success=Η μεταβλητή έχει τροποποιηθεί.
[projects]
type-1.display_name=Ατομικό Έργο
type-2.display_name=Έργο Αποθετηρίου

View File

@ -3257,6 +3257,7 @@ variables.update.failed=Error al editar la variable.
variables.update.success=La variable ha sido editada.
[projects]
type-1.display_name=Proyecto individual
type-2.display_name=Proyecto repositorio

View File

@ -2446,6 +2446,7 @@ runs.commit=کامیت
[projects]
[git.filemode]

View File

@ -1693,6 +1693,7 @@ runs.commit=Commit
[projects]
[git.filemode]

View File

@ -3906,6 +3906,7 @@ variables.update.success=La variable a bien été modifiée.
logs.always_auto_scroll=Toujours faire défiler les journaux automatiquement
logs.always_expand_running=Toujours développer les journaux en cours
[projects]
deleted.display_name=Projet supprimé
type-1.display_name=Projet personnel

View File

@ -3914,6 +3914,7 @@ variables.update.success=Tá an t-athróg curtha in eagar.
logs.always_auto_scroll=Logchomhaid scrollaithe uathoibríoch i gcónaí
logs.always_expand_running=Leathnaigh logs reatha i gcónaí
[projects]
deleted.display_name=Tionscadal scriosta
type-1.display_name=Tionscadal Aonair

View File

@ -1605,6 +1605,7 @@ runs.commit=Commit
[projects]
[git.filemode]

View File

@ -1428,6 +1428,7 @@ variables.update.failed=Gagal mengedit variabel.
variables.update.success=Variabel telah diedit.
[projects]
type-1.display_name=Proyek Individu
type-2.display_name=Proyek Repositori

View File

@ -1334,6 +1334,7 @@ runs.commit=Framlag
[projects]
[git.filemode]

View File

@ -2706,6 +2706,7 @@ runs.commit=Commit
[projects]
[git.filemode]

View File

@ -3910,6 +3910,7 @@ variables.update.success=変数を更新しました。
logs.always_auto_scroll=常にログを自動スクロール
logs.always_expand_running=常に実行中のログを展開
[projects]
deleted.display_name=削除されたプロジェクト
type-1.display_name=個人プロジェクト

View File

@ -1554,6 +1554,7 @@ runs.commit=커밋
[projects]
[git.filemode]

View File

@ -3282,6 +3282,7 @@ variables.update.failed=Neizdevās labot mainīgo.
variables.update.success=Mainīgais tika labots.
[projects]
type-1.display_name=Individuālais projekts
type-2.display_name=Repozitorija projekts

View File

@ -2458,6 +2458,7 @@ runs.commit=Commit
[projects]
[git.filemode]

View File

@ -2347,6 +2347,7 @@ runs.commit=Commit
[projects]
[git.filemode]

View File

@ -3615,6 +3615,7 @@ variables.update.failed=Falha ao editar a variável.
variables.update.success=A variável foi editada.
[projects]
deleted.display_name=Excluir Projeto
type-1.display_name=Projeto Individual

View File

@ -3914,6 +3914,7 @@ variables.update.success=A variável foi editada.
logs.always_auto_scroll=Rolar registos de forma automática e permanente
logs.always_expand_running=Expandir sempre os registos que vão rolando
[projects]
deleted.display_name=Planeamento eliminado
type-1.display_name=Planeamento individual

View File

@ -3225,6 +3225,7 @@ variables.update.failed=Не удалось изменить переменну
variables.update.success=Переменная изменена.
[projects]
type-1.display_name=Индивидуальный проект
type-2.display_name=Проект репозитория

View File

@ -2391,6 +2391,7 @@ runs.commit=කැප
[projects]
[git.filemode]

View File

@ -1292,6 +1292,7 @@ runners.labels=Štítky
[projects]
[git.filemode]

View File

@ -1968,6 +1968,7 @@ runs.commit=Commit
[projects]
[git.filemode]

View File

@ -3907,6 +3907,7 @@ variables.update.success=Değişken düzenlendi.
logs.always_auto_scroll=Günlükleri her zaman otomatik kaydır
logs.always_expand_running=Çalıştırma günlüklerini her zaman genişlet
[projects]
deleted.display_name=Silinmiş Proje
type-1.display_name=Kişisel Proje

View File

@ -3428,6 +3428,7 @@ variables.update.success=Змінну відредаговано.
logs.always_auto_scroll=Завжди автоматично прокручувати журнали
logs.always_expand_running=Завжди розгортати поточні журнали
[projects]
deleted.display_name=Видалений проєкт
type-1.display_name=Індивідуальний проєкт

View File

@ -3911,6 +3911,7 @@ variables.update.success=变量已编辑。
logs.always_auto_scroll=总是自动滚动日志
logs.always_expand_running=总是展开运行日志
[projects]
deleted.display_name=已删除项目
type-1.display_name=个人项目

View File

@ -980,6 +980,7 @@ runners.task_list.repository=儲存庫
[projects]
[git.filemode]

View File

@ -3554,6 +3554,7 @@ variables.update.failed=編輯變數失敗。
variables.update.success=已編輯變數。
[projects]
deleted.display_name=已刪除的專案
type-1.display_name=個人專案

View File

@ -13,6 +13,7 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
@ -222,8 +223,7 @@ func GetAllCommits(ctx *context.APIContext) {
}
// Total commit count
commitsCountTotal, err = git.CommitsCount(ctx.Repo.GitRepo.Ctx, git.CommitsCountOptions{
RepoPath: ctx.Repo.GitRepo.Path,
commitsCountTotal, err = gitrepo.CommitsCount(ctx, ctx.Repo.Repository, gitrepo.CommitsCountOptions{
Not: not,
Revision: []string{baseCommit.ID.String()},
Since: since,
@ -245,9 +245,8 @@ func GetAllCommits(ctx *context.APIContext) {
sha = ctx.Repo.Repository.DefaultBranch
}
commitsCountTotal, err = git.CommitsCount(ctx,
git.CommitsCountOptions{
RepoPath: ctx.Repo.GitRepo.Path,
commitsCountTotal, err = gitrepo.CommitsCount(ctx, ctx.Repo.Repository,
gitrepo.CommitsCountOptions{
Not: not,
Revision: []string{sha},
RelPath: []string{path},

View File

@ -1165,7 +1165,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
baseRef := ctx.Repo.GitRepo.UnstableGuessRefByShortName(baseRefToGuess)
headRef := headGitRepo.UnstableGuessRefByShortName(headRefToGuess)
log.Trace("Repo path: %q, base ref: %q->%q, head ref: %q->%q", ctx.Repo.GitRepo.Path, baseRefToGuess, baseRef, headRefToGuess, headRef)
log.Trace("Repo path: %q, base ref: %q->%q, head ref: %q->%q", ctx.Repo.Repository.RelativePath(), baseRefToGuess, baseRef, headRefToGuess, headRef)
baseRefValid := baseRef.IsBranch() || baseRef.IsTag() || git.IsStringLikelyCommitID(git.ObjectFormatFromName(ctx.Repo.Repository.ObjectFormatName), baseRef.ShortName())
headRefValid := headRef.IsBranch() || headRef.IsTag() || git.IsStringLikelyCommitID(git.ObjectFormatFromName(headRepo.ObjectFormatName), headRef.ShortName())

View File

@ -193,7 +193,7 @@ func getWikiPage(ctx *context.APIContext, wikiName wiki_service.WebPath) *api.Wi
}
// get commit count - wiki revisions
commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
commitsCount, _ := gitrepo.FileCommitsCount(ctx, ctx.Repo.Repository.WikiStorageRepo(), ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
// Get last change information.
lastCommit, err := wikiRepo.GetCommitByPath(pageFilename)
@ -429,7 +429,7 @@ func ListPageRevisions(ctx *context.APIContext) {
}
// get commit count - wiki revisions
commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
commitsCount, _ := gitrepo.FileCommitsCount(ctx, ctx.Repo.Repository.WikiStorageRepo(), ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
page := max(ctx.FormInt("page"), 1)

View File

@ -222,7 +222,7 @@ func FileHistory(ctx *context.Context) {
return
}
commitsCount, err := ctx.Repo.GitRepo.FileCommitsCount(ctx.Repo.RefFullName.ShortName(), ctx.Repo.TreePath)
commitsCount, err := gitrepo.FileCommitsCount(ctx, ctx.Repo.Repository, ctx.Repo.RefFullName.ShortName(), ctx.Repo.TreePath)
if err != nil {
ctx.ServerError("FileCommitsCount", err)
return

View File

@ -5,6 +5,7 @@
package repo
import (
stdCtx "context"
"errors"
"fmt"
"net/http"
@ -39,7 +40,7 @@ const (
)
// calReleaseNumCommitsBehind calculates given release has how many commits behind release target.
func calReleaseNumCommitsBehind(repoCtx *context.Repository, release *repo_model.Release, countCache map[string]int64) error {
func calReleaseNumCommitsBehind(ctx stdCtx.Context, repoCtx *context.Repository, release *repo_model.Release, countCache map[string]int64) error {
target := release.Target
if target == "" {
target = repoCtx.Repository.DefaultBranch
@ -59,7 +60,7 @@ func calReleaseNumCommitsBehind(repoCtx *context.Repository, release *repo_model
return fmt.Errorf("GetBranchCommit(DefaultBranch): %w", err)
}
}
countCache[target], err = commit.CommitsCount()
countCache[target], err = gitrepo.CommitsCountOfCommit(ctx, repoCtx.Repository, commit.ID.String())
if err != nil {
return fmt.Errorf("CommitsCount: %w", err)
}
@ -122,7 +123,7 @@ func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions)
}
if !r.IsDraft {
if err := calReleaseNumCommitsBehind(ctx.Repo, r, countCache); err != nil {
if err := calReleaseNumCommitsBehind(ctx, ctx.Repo, r, countCache); err != nil {
return nil, err
}
}

View File

@ -158,7 +158,7 @@ func TestCalReleaseNumCommitsBehind(t *testing.T) {
countCache := make(map[string]int64)
for _, release := range releases {
err := calReleaseNumCommitsBehind(ctx.Repo, release, countCache)
err := calReleaseNumCommitsBehind(ctx, ctx.Repo, release, countCache)
assert.NoError(t, err)
}

View File

@ -70,7 +70,7 @@ func CommitInfoCache(ctx *context.Context) {
ctx.ServerError("GetBranchCommit", err)
return
}
ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount(ctx)
if err != nil {
ctx.ServerError("GetCommitsCount", err)
return

View File

@ -310,7 +310,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
}
// get commit count - wiki revisions
commitsCount, _ := wikiGitRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
commitsCount, _ := gitrepo.FileCommitsCount(ctx, ctx.Repo.Repository.WikiStorageRepo(), ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
ctx.Data["CommitCount"] = commitsCount
return wikiGitRepo, entry
@ -350,7 +350,7 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry)
}
// get commit count - wiki revisions
commitsCount, _ := wikiGitRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
commitsCount, _ := gitrepo.FileCommitsCount(ctx, ctx.Repo.Repository.WikiStorageRepo(), ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
ctx.Data["CommitCount"] = commitsCount
// get page

View File

@ -202,14 +202,14 @@ func (r *Repository) CanCreateIssueDependencies(ctx context.Context, user *user_
}
// GetCommitsCount returns cached commit count for current view
func (r *Repository) GetCommitsCount() (int64, error) {
func (r *Repository) GetCommitsCount(ctx context.Context) (int64, error) {
if r.Commit == nil {
return 0, nil
}
contextName := r.RefFullName.ShortName()
isRef := r.RefFullName.IsBranch() || r.RefFullName.IsTag()
return cache.GetInt64(r.Repository.GetCommitsCountCacheKey(contextName, isRef), func() (int64, error) {
return r.Commit.CommitsCount()
return gitrepo.CommitsCountOfCommit(ctx, r.Repository, r.Commit.ID.String())
})
}
@ -219,11 +219,10 @@ func (r *Repository) GetCommitGraphsCount(ctx context.Context, hidePRRefs bool,
return cache.GetInt64(cacheKey, func() (int64, error) {
if len(branches) == 0 {
return git.AllCommitsCount(ctx, r.Repository.RepoPath(), hidePRRefs, files...)
return gitrepo.AllCommitsCount(ctx, r.Repository, hidePRRefs, files...)
}
return git.CommitsCount(ctx,
git.CommitsCountOptions{
RepoPath: r.Repository.RepoPath(),
return gitrepo.CommitsCount(ctx, r.Repository,
gitrepo.CommitsCountOptions{
Revision: branches,
RelPath: files,
})
@ -820,7 +819,7 @@ func RepoRefByDefaultBranch() func(*Context) {
ctx.Repo.RefFullName = git.RefNameFromBranch(ctx.Repo.Repository.DefaultBranch)
ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
ctx.Repo.Commit, _ = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.BranchName)
ctx.Repo.CommitsCount, _ = ctx.Repo.GetCommitsCount()
ctx.Repo.CommitsCount, _ = ctx.Repo.GetCommitsCount(ctx)
ctx.Data["RefFullName"] = ctx.Repo.RefFullName
ctx.Data["BranchName"] = ctx.Repo.BranchName
ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
@ -857,7 +856,7 @@ func RepoRefByType(detectRefType git.RefType) func(*Context) {
if err == nil && len(brs) != 0 {
refShortName = brs[0]
} else if len(brs) == 0 {
log.Error("No branches in non-empty repository %s", ctx.Repo.GitRepo.Path)
log.Error("No branches in non-empty repository %s", ctx.Repo.Repository.RelativePath())
} else {
log.Error("GetBranches error: %v", err)
}
@ -969,7 +968,7 @@ func RepoRefByType(detectRefType git.RefType) func(*Context) {
ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch() // only used by the branch selector dropdown: AllowCreateNewRef
ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount(ctx)
if err != nil {
ctx.ServerError("GetCommitsCount", err)
return

View File

@ -11,6 +11,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
@ -190,7 +191,7 @@ func ToCommit(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Rep
// Retrieve files affected by the commit
if opts.Files {
fileStatus, err := git.GetCommitFileStatus(gitRepo.Ctx, repo.RepoPath(), commit.ID.String())
fileStatus, err := gitrepo.GetCommitFileStatus(ctx, repo, commit.ID.String())
if err != nil {
return nil, err
}

View File

@ -303,7 +303,7 @@ func (g *GiteaLocalUploader) CreateReleases(ctx context.Context, releases ...*ba
return fmt.Errorf("GetTagCommit[%v]: %w", rel.TagName, err)
}
rel.Sha1 = commit.ID.String()
rel.NumCommits, err = commit.CommitsCount()
rel.NumCommits, err = gitrepo.CommitsCountOfCommit(ctx, g.repo, commit.ID.String())
if err != nil {
return fmt.Errorf("CommitsCount: %w", err)
}

View File

@ -148,7 +148,7 @@ func createTag(ctx context.Context, gitRepo *git.Repository, rel *repo_model.Rel
}
rel.Sha1 = commit.ID.String()
rel.NumCommits, err = commit.CommitsCount()
rel.NumCommits, err = gitrepo.CommitsCountOfCommit(ctx, rel.Repo, commit.ID.String())
if err != nil {
return false, fmt.Errorf("CommitsCount: %w", err)
}

View File

@ -9,6 +9,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
)
// CacheRef cachhe last commit information of the branch or the tag
@ -19,7 +20,9 @@ func CacheRef(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Rep
}
if gitRepo.LastCommitCache == nil {
commitsCount, err := cache.GetInt64(repo.GetCommitsCountCacheKey(fullRefName.ShortName(), true), commit.CommitsCount)
commitsCount, err := cache.GetInt64(repo.GetCommitsCountCacheKey(fullRefName.ShortName(), true), func() (int64, error) {
return gitrepo.CommitsCountOfCommit(ctx, repo, commit.ID.String())
})
if err != nil {
return err
}

View File

@ -11,7 +11,9 @@ import (
"strings"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
@ -125,7 +127,20 @@ func GetFileContents(ctx context.Context, repo *repo_model.Repository, gitRepo *
return getFileContentsByEntryInternal(ctx, repo, gitRepo, refCommit, entry, opts)
}
func getFileContentsByEntryInternal(_ context.Context, repo *repo_model.Repository, gitRepo *git.Repository, refCommit *utils.RefCommit, entry *git.TreeEntry, opts GetContentsOrListOptions) (*api.ContentsResponse, error) {
func addLastCommitCache(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, cacheKey, fullName, sha string) error {
if gitRepo.LastCommitCache == nil {
commitsCount, err := cache.GetInt64(cacheKey, func() (int64, error) {
return gitrepo.CommitsCountOfCommit(ctx, repo, sha)
})
if err != nil {
return err
}
gitRepo.LastCommitCache = git.NewLastCommitCache(commitsCount, fullName, gitRepo, cache.GetCache())
}
return nil
}
func getFileContentsByEntryInternal(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, refCommit *utils.RefCommit, entry *git.TreeEntry, opts GetContentsOrListOptions) (*api.ContentsResponse, error) {
refType := refCommit.RefName.RefType()
commit := refCommit.Commit
selfURL, err := url.Parse(repo.APIURL() + "/contents/" + util.PathEscapeSegments(opts.TreePath) + "?ref=" + url.QueryEscape(refCommit.InputRef))
@ -147,7 +162,7 @@ func getFileContentsByEntryInternal(_ context.Context, repo *repo_model.Reposito
}
if opts.IncludeCommitMetadata || opts.IncludeCommitMessage {
err = gitRepo.AddLastCommitCache(repo.GetCommitsCountCacheKey(refCommit.InputRef, refType != git.RefTypeCommit), repo.FullName(), refCommit.CommitID)
err = addLastCommitCache(ctx, repo, gitRepo, repo.GetCommitsCountCacheKey(refCommit.InputRef, refType != git.RefTypeCommit), repo.FullName(), refCommit.CommitID)
if err != nil {
return nil, err
}