feat: vendor gitea 1.16.2
This commit is contained in:
@@ -247,7 +247,7 @@ func DeleteBranches(ctx context.Context, repoID, doerID int64, branchIDs []int64
|
||||
return err
|
||||
}
|
||||
for _, branch := range branches {
|
||||
if err := AddDeletedBranch(ctx, repoID, branch.Name, doerID); err != nil {
|
||||
if err := MarkBranchAsDeleted(ctx, repoID, branch.Name, doerID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -268,8 +268,8 @@ func UpdateBranch(ctx context.Context, repoID, pusherID int64, branchName string
|
||||
})
|
||||
}
|
||||
|
||||
// AddDeletedBranch adds a deleted branch to the database
|
||||
func AddDeletedBranch(ctx context.Context, repoID int64, branchName string, deletedByID int64) error {
|
||||
// MarkBranchAsDeleted marks branch as deleted
|
||||
func MarkBranchAsDeleted(ctx context.Context, repoID int64, branchName string, deletedByID int64) error {
|
||||
branch, err := GetBranch(ctx, repoID, branchName)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -397,10 +397,16 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
|
||||
|
||||
if protectedBranch != nil {
|
||||
// there is a protect rule for this branch
|
||||
protectedBranch.RuleName = to
|
||||
if _, err = sess.ID(protectedBranch.ID).Cols("branch_name").Update(protectedBranch); err != nil {
|
||||
existingRule, err := GetProtectedBranchRuleByName(ctx, repo.ID, to)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existingRule == nil || existingRule.ID == protectedBranch.ID {
|
||||
protectedBranch.RuleName = to
|
||||
if _, err = sess.ID(protectedBranch.ID).Cols("branch_name").Update(protectedBranch); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// some glob protect rules may match this branch
|
||||
protected, err := IsBranchProtected(ctx, repo.ID, from)
|
||||
@@ -444,7 +450,7 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
|
||||
type FindRecentlyPushedNewBranchesOptions struct {
|
||||
Repo *repo_model.Repository
|
||||
BaseRepo *repo_model.Repository
|
||||
CommitAfterUnix int64
|
||||
PushedAfterUnix int64
|
||||
MaxCount int
|
||||
}
|
||||
|
||||
@@ -454,11 +460,11 @@ type RecentlyPushedNewBranch struct {
|
||||
BranchDisplayName string
|
||||
BranchLink string
|
||||
BranchCompareURL string
|
||||
CommitTime timeutil.TimeStamp
|
||||
PushedTime timeutil.TimeStamp
|
||||
}
|
||||
|
||||
// FindRecentlyPushedNewBranches return at most 2 new branches pushed by the user in 2 hours which has no opened PRs created
|
||||
// if opts.CommitAfterUnix is 0, we will find the branches that were committed to in the last 2 hours
|
||||
// if opts.PushedAfterUnix is 0, we will find the branches that were pushed in the last 2 hours
|
||||
// if opts.ListOptions is not set, we will only display top 2 latest branches.
|
||||
// Protected branches will be skipped since they are unlikely to be used to create new PRs.
|
||||
func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, opts FindRecentlyPushedNewBranchesOptions) ([]*RecentlyPushedNewBranch, error) {
|
||||
@@ -486,16 +492,29 @@ func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, o
|
||||
}
|
||||
repoIDs := builder.Select("id").From("repository").Where(repoCond)
|
||||
|
||||
if opts.CommitAfterUnix == 0 {
|
||||
opts.CommitAfterUnix = time.Now().Add(-time.Hour * 2).Unix()
|
||||
if opts.PushedAfterUnix == 0 {
|
||||
opts.PushedAfterUnix = time.Now().Add(-time.Hour * 2).Unix()
|
||||
}
|
||||
|
||||
baseBranch, err := GetBranch(ctx, opts.BaseRepo.ID, opts.BaseRepo.DefaultBranch)
|
||||
var ignoredCommitIDs []string
|
||||
baseDefaultBranch, err := GetBranch(ctx, opts.BaseRepo.ID, opts.BaseRepo.DefaultBranch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
log.Warn("GetBranch:DefaultBranch: %v", err)
|
||||
} else {
|
||||
ignoredCommitIDs = append(ignoredCommitIDs, baseDefaultBranch.CommitID)
|
||||
}
|
||||
|
||||
// find all related branches, these branches may already created PRs, we will check later
|
||||
baseDefaultTargetBranchName := opts.BaseRepo.MustGetUnit(ctx, unit.TypePullRequests).PullRequestsConfig().DefaultTargetBranch
|
||||
if baseDefaultTargetBranchName != "" && baseDefaultTargetBranchName != opts.BaseRepo.DefaultBranch {
|
||||
baseDefaultTargetBranch, err := GetBranch(ctx, opts.BaseRepo.ID, baseDefaultTargetBranchName)
|
||||
if err != nil {
|
||||
log.Warn("GetBranch:DefaultTargetBranch: %v", err)
|
||||
} else {
|
||||
ignoredCommitIDs = append(ignoredCommitIDs, baseDefaultTargetBranch.CommitID)
|
||||
}
|
||||
}
|
||||
|
||||
// find all related branches, these branches may already have PRs, we will check later
|
||||
var branches []*Branch
|
||||
if err := db.GetEngine(ctx).
|
||||
Where(builder.And(
|
||||
@@ -503,10 +522,10 @@ func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, o
|
||||
"pusher_id": doer.ID,
|
||||
"is_deleted": false,
|
||||
},
|
||||
builder.Gte{"commit_time": opts.CommitAfterUnix},
|
||||
builder.Gte{"updated_unix": opts.PushedAfterUnix},
|
||||
builder.In("repo_id", repoIDs),
|
||||
// newly created branch have no changes, so skip them
|
||||
builder.Neq{"commit_id": baseBranch.CommitID},
|
||||
builder.NotIn("commit_id", ignoredCommitIDs),
|
||||
)).
|
||||
OrderBy(db.SearchOrderByRecentUpdated.String()).
|
||||
Find(&branches); err != nil {
|
||||
@@ -514,10 +533,8 @@ func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, o
|
||||
}
|
||||
|
||||
newBranches := make([]*RecentlyPushedNewBranch, 0, len(branches))
|
||||
if opts.MaxCount == 0 {
|
||||
// by default we display 2 recently pushed new branch
|
||||
opts.MaxCount = 2
|
||||
}
|
||||
opts.MaxCount = util.IfZero(opts.MaxCount, 2) // by default, we display 2 recently pushed new branch
|
||||
baseTargetBranchName := opts.BaseRepo.GetPullRequestTargetBranch(ctx)
|
||||
for _, branch := range branches {
|
||||
// whether the branch is protected
|
||||
protected, err := IsBranchProtected(ctx, branch.RepoID, branch.Name)
|
||||
@@ -555,8 +572,8 @@ func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, o
|
||||
BranchDisplayName: branchDisplayName,
|
||||
BranchName: branch.Name,
|
||||
BranchLink: fmt.Sprintf("%s/src/branch/%s", branch.Repo.Link(), util.PathEscapeSegments(branch.Name)),
|
||||
BranchCompareURL: branch.Repo.ComposeBranchCompareURL(opts.BaseRepo, branch.Name),
|
||||
CommitTime: branch.CommitTime,
|
||||
BranchCompareURL: branch.Repo.ComposeBranchCompareURL(opts.BaseRepo, baseTargetBranchName, branch.Name),
|
||||
PushedTime: branch.UpdatedUnix,
|
||||
})
|
||||
}
|
||||
if len(newBranches) == opts.MaxCount {
|
||||
@@ -566,3 +583,12 @@ func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, o
|
||||
|
||||
return newBranches, nil
|
||||
}
|
||||
|
||||
// CountBranches returns the number of branches in the repository
|
||||
func CountBranches(ctx context.Context, repoID int64, includeDeleted bool) (int64, error) {
|
||||
sess := db.GetEngine(ctx).Where("repo_id=?", repoID)
|
||||
if !includeDeleted {
|
||||
sess.And("is_deleted=?", false)
|
||||
}
|
||||
return sess.Count(new(Branch))
|
||||
}
|
||||
|
||||
@@ -6,14 +6,17 @@ package git_test
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -25,8 +28,8 @@ func TestAddDeletedBranch(t *testing.T) {
|
||||
firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1})
|
||||
|
||||
assert.True(t, firstBranch.IsDeleted)
|
||||
assert.NoError(t, git_model.AddDeletedBranch(t.Context(), repo.ID, firstBranch.Name, firstBranch.DeletedByID))
|
||||
assert.NoError(t, git_model.AddDeletedBranch(t.Context(), repo.ID, "branch2", int64(1)))
|
||||
assert.NoError(t, git_model.MarkBranchAsDeleted(t.Context(), repo.ID, firstBranch.Name, firstBranch.DeletedByID))
|
||||
assert.NoError(t, git_model.MarkBranchAsDeleted(t.Context(), repo.ID, "branch2", int64(1)))
|
||||
|
||||
secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: repo.ID, Name: "branch2"})
|
||||
assert.True(t, secondBranch.IsDeleted)
|
||||
@@ -63,6 +66,36 @@ func TestGetDeletedBranch(t *testing.T) {
|
||||
assert.NotNil(t, getDeletedBranch(t, firstBranch))
|
||||
}
|
||||
|
||||
func TestFindRecentlyPushedNewBranchesUsesPushTime(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10})
|
||||
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 12})
|
||||
branch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: repo.ID, Name: "outdated-new-branch"})
|
||||
|
||||
commitUnix := time.Now().Add(-3 * time.Hour).Unix()
|
||||
pushUnix := time.Now().Add(-30 * time.Minute).Unix()
|
||||
_, err := db.GetEngine(t.Context()).Exec(
|
||||
"UPDATE branch SET commit_time = ?, updated_unix = ? WHERE id = ?",
|
||||
commitUnix,
|
||||
pushUnix,
|
||||
branch.ID,
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
|
||||
branches, err := git_model.FindRecentlyPushedNewBranches(t.Context(), doer, git_model.FindRecentlyPushedNewBranchesOptions{
|
||||
Repo: repo,
|
||||
BaseRepo: repo,
|
||||
PushedAfterUnix: time.Now().Add(-time.Hour).Unix(),
|
||||
MaxCount: 1,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, branches, 1) {
|
||||
assert.Equal(t, branch.Name, branches[0].BranchName)
|
||||
assert.Equal(t, timeutil.TimeStamp(pushUnix), branches[0].PushedTime)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeletedBranchLoadUser(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
@@ -114,7 +147,7 @@ func TestFindRenamedBranch(t *testing.T) {
|
||||
assert.True(t, exist)
|
||||
assert.Equal(t, "master", branch.To)
|
||||
|
||||
_, exist, err = git_model.FindRenamedBranch(t.Context(), 1, "unknow")
|
||||
_, exist, err = git_model.FindRenamedBranch(t.Context(), 1, "unknown")
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, exist)
|
||||
}
|
||||
@@ -159,6 +192,53 @@ func TestRenameBranch(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestRenameBranchProtectedRuleConflict(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
master := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: repo1.ID, Name: "master"})
|
||||
|
||||
devBranch := &git_model.Branch{
|
||||
RepoID: repo1.ID,
|
||||
Name: "dev",
|
||||
CommitID: master.CommitID,
|
||||
CommitMessage: master.CommitMessage,
|
||||
CommitTime: master.CommitTime,
|
||||
PusherID: master.PusherID,
|
||||
}
|
||||
assert.NoError(t, db.Insert(t.Context(), devBranch))
|
||||
|
||||
pbDev := git_model.ProtectedBranch{
|
||||
RepoID: repo1.ID,
|
||||
RuleName: "dev",
|
||||
CanPush: true,
|
||||
}
|
||||
assert.NoError(t, git_model.UpdateProtectBranch(t.Context(), repo1, &pbDev, git_model.WhitelistOptions{}))
|
||||
|
||||
pbMain := git_model.ProtectedBranch{
|
||||
RepoID: repo1.ID,
|
||||
RuleName: "main",
|
||||
CanPush: true,
|
||||
}
|
||||
assert.NoError(t, git_model.UpdateProtectBranch(t.Context(), repo1, &pbMain, git_model.WhitelistOptions{}))
|
||||
|
||||
assert.NoError(t, git_model.RenameBranch(t.Context(), repo1, "dev", "main", func(ctx context.Context, isDefault bool) error {
|
||||
return nil
|
||||
}))
|
||||
|
||||
unittest.AssertNotExistsBean(t, &git_model.Branch{RepoID: repo1.ID, Name: "dev"})
|
||||
unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: repo1.ID, Name: "main"})
|
||||
|
||||
protectedDev, err := git_model.GetProtectedBranchRuleByName(t.Context(), repo1.ID, "dev")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, protectedDev)
|
||||
assert.Equal(t, "dev", protectedDev.RuleName)
|
||||
|
||||
protectedMainByID, err := git_model.GetProtectedBranchRuleByID(t.Context(), repo1.ID, pbMain.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, protectedMainByID)
|
||||
assert.Equal(t, "main", protectedMainByID.RuleName)
|
||||
}
|
||||
|
||||
func TestOnlyGetDeletedBranchOnCorrectRepo(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
@@ -183,3 +263,25 @@ func TestOnlyGetDeletedBranchOnCorrectRepo(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, deletedBranch)
|
||||
}
|
||||
|
||||
func TestCountBranches(t *testing.T) {
|
||||
// 1. Setup - Exactly like TestAddDeletedBranch
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
|
||||
// 2. Execution - Using t.Context() to match the rest of the file
|
||||
initialCount, err := git_model.CountBranches(t.Context(), repo.ID, false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 3. Database Action - Using t.Context()
|
||||
err = db.Insert(t.Context(), &git_model.Branch{
|
||||
RepoID: repo.ID,
|
||||
Name: "test-branch-for-counting",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 4. Verification
|
||||
newCount, err := git_model.CountBranches(t.Context(), repo.ID, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, initialCount+1, newCount)
|
||||
}
|
||||
|
||||
@@ -30,17 +30,21 @@ import (
|
||||
|
||||
// CommitStatus holds a single Status of a single Commit
|
||||
type CommitStatus struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Index int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
|
||||
RepoID int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
|
||||
Repo *repo_model.Repository `xorm:"-"`
|
||||
State commitstatus.CommitStatusState `xorm:"VARCHAR(7) NOT NULL"`
|
||||
SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"`
|
||||
TargetURL string `xorm:"TEXT"`
|
||||
Description string `xorm:"TEXT"`
|
||||
ContextHash string `xorm:"VARCHAR(64) index"`
|
||||
Context string `xorm:"TEXT"`
|
||||
Creator *user_model.User `xorm:"-"`
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Index int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
|
||||
RepoID int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
|
||||
Repo *repo_model.Repository `xorm:"-"`
|
||||
State commitstatus.CommitStatusState `xorm:"VARCHAR(7) NOT NULL"`
|
||||
SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"`
|
||||
|
||||
// TargetURL points to the commit status page reported by a CI system
|
||||
// If Gitea Actions is used, it is a relative link like "{RepoLink}/actions/runs/{RunID}/jobs{JobID}"
|
||||
TargetURL string `xorm:"TEXT"`
|
||||
|
||||
Description string `xorm:"TEXT"`
|
||||
ContextHash string `xorm:"VARCHAR(64) index"`
|
||||
Context string `xorm:"TEXT"`
|
||||
Creator *user_model.User `xorm:"-"`
|
||||
CreatorID int64
|
||||
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
@@ -211,21 +215,45 @@ func (status *CommitStatus) LocaleString(lang translation.Locale) string {
|
||||
|
||||
// HideActionsURL set `TargetURL` to an empty string if the status comes from Gitea Actions
|
||||
func (status *CommitStatus) HideActionsURL(ctx context.Context) {
|
||||
if _, ok := status.cutTargetURLGiteaActionsPrefix(ctx); ok {
|
||||
status.TargetURL = ""
|
||||
}
|
||||
}
|
||||
|
||||
func (status *CommitStatus) cutTargetURLGiteaActionsPrefix(ctx context.Context) (string, bool) {
|
||||
if status.RepoID == 0 {
|
||||
return
|
||||
return "", false
|
||||
}
|
||||
|
||||
if status.Repo == nil {
|
||||
if err := status.loadRepository(ctx); err != nil {
|
||||
log.Error("loadRepository: %v", err)
|
||||
return
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
prefix := status.Repo.Link() + "/actions"
|
||||
if strings.HasPrefix(status.TargetURL, prefix) {
|
||||
status.TargetURL = ""
|
||||
return strings.CutPrefix(status.TargetURL, prefix)
|
||||
}
|
||||
|
||||
// ParseGiteaActionsTargetURL parses the commit status target URL as Gitea Actions link
|
||||
func (status *CommitStatus) ParseGiteaActionsTargetURL(ctx context.Context) (runID, jobID int64, ok bool) {
|
||||
s, ok := status.cutTargetURLGiteaActionsPrefix(ctx)
|
||||
if !ok {
|
||||
return 0, 0, false
|
||||
}
|
||||
|
||||
parts := strings.Split(s, "/") // expect: /runs/{runID}/jobs/{jobID}
|
||||
if len(parts) < 5 || parts[1] != "runs" || parts[3] != "jobs" {
|
||||
return 0, 0, false
|
||||
}
|
||||
|
||||
runID, err1 := strconv.ParseInt(parts[2], 10, 64)
|
||||
jobID, err2 := strconv.ParseInt(parts[4], 10, 64)
|
||||
if err1 != nil || err2 != nil {
|
||||
return 0, 0, false
|
||||
}
|
||||
return runID, jobID, true
|
||||
}
|
||||
|
||||
// CalcCommitStatus returns commit status state via some status, the commit statues should order by id desc
|
||||
|
||||
@@ -139,7 +139,7 @@ func Test_CalcCommitStatus(t *testing.T) {
|
||||
},
|
||||
},
|
||||
expected: &git_model.CommitStatus{
|
||||
State: commitstatus.CommitStatusPending,
|
||||
State: commitstatus.CommitStatusFailure,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -243,7 +243,7 @@ func TestCommitStatusesHideActionsURL(t *testing.T) {
|
||||
statuses := []*git_model.CommitStatus{
|
||||
{
|
||||
RepoID: repo.ID,
|
||||
TargetURL: fmt.Sprintf("%s/jobs/%d", run.Link(), run.Index),
|
||||
TargetURL: fmt.Sprintf("%s/jobs/%d", run.Link(), run.ID),
|
||||
},
|
||||
{
|
||||
RepoID: repo.ID,
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
@@ -42,30 +41,6 @@ func (err ErrLFSLockNotExist) Unwrap() error {
|
||||
return util.ErrNotExist
|
||||
}
|
||||
|
||||
// ErrLFSUnauthorizedAction represents a "LFSUnauthorizedAction" kind of error.
|
||||
type ErrLFSUnauthorizedAction struct {
|
||||
RepoID int64
|
||||
UserName string
|
||||
Mode perm.AccessMode
|
||||
}
|
||||
|
||||
// IsErrLFSUnauthorizedAction checks if an error is a ErrLFSUnauthorizedAction.
|
||||
func IsErrLFSUnauthorizedAction(err error) bool {
|
||||
_, ok := err.(ErrLFSUnauthorizedAction)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrLFSUnauthorizedAction) Error() string {
|
||||
if err.Mode == perm.AccessModeWrite {
|
||||
return fmt.Sprintf("User %s doesn't have write access for lfs lock [rid: %d]", err.UserName, err.RepoID)
|
||||
}
|
||||
return fmt.Sprintf("User %s doesn't have read access for lfs lock [rid: %d]", err.UserName, err.RepoID)
|
||||
}
|
||||
|
||||
func (err ErrLFSUnauthorizedAction) Unwrap() error {
|
||||
return util.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// ErrLFSLockAlreadyExist represents a "LFSLockAlreadyExist" kind of error.
|
||||
type ErrLFSLockAlreadyExist struct {
|
||||
RepoID int64
|
||||
@@ -93,12 +68,6 @@ type ErrLFSFileLocked struct {
|
||||
UserName string
|
||||
}
|
||||
|
||||
// IsErrLFSFileLocked checks if an error is a ErrLFSFileLocked.
|
||||
func IsErrLFSFileLocked(err error) bool {
|
||||
_, ok := err.(ErrLFSFileLocked)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrLFSFileLocked) Error() string {
|
||||
return fmt.Sprintf("File is lfs locked [repo: %d, locked by: %s, path: %s]", err.RepoID, err.UserName, err.Path)
|
||||
}
|
||||
@@ -343,15 +312,12 @@ func IterateRepositoryIDsWithLFSMetaObjects(ctx context.Context, f func(ctx cont
|
||||
|
||||
// IterateLFSMetaObjectsForRepoOptions provides options for IterateLFSMetaObjectsForRepo
|
||||
type IterateLFSMetaObjectsForRepoOptions struct {
|
||||
OlderThan timeutil.TimeStamp
|
||||
UpdatedLessRecentlyThan timeutil.TimeStamp
|
||||
OrderByUpdated bool
|
||||
LoopFunctionAlwaysUpdates bool
|
||||
OlderThan timeutil.TimeStamp
|
||||
UpdatedLessRecentlyThan timeutil.TimeStamp
|
||||
}
|
||||
|
||||
// IterateLFSMetaObjectsForRepo provides a iterator for LFSMetaObjects per Repo
|
||||
func IterateLFSMetaObjectsForRepo(ctx context.Context, repoID int64, f func(context.Context, *LFSMetaObject, int64) error, opts *IterateLFSMetaObjectsForRepoOptions) error {
|
||||
var start int
|
||||
batchSize := setting.Database.IterateBufferSize
|
||||
engine := db.GetEngine(ctx)
|
||||
type CountLFSMetaObject struct {
|
||||
@@ -359,7 +325,7 @@ func IterateLFSMetaObjectsForRepo(ctx context.Context, repoID int64, f func(cont
|
||||
LFSMetaObject `xorm:"extends"`
|
||||
}
|
||||
|
||||
id := int64(0)
|
||||
lastID := int64(0)
|
||||
|
||||
for {
|
||||
beans := make([]*CountLFSMetaObject, 0, batchSize)
|
||||
@@ -372,29 +338,23 @@ func IterateLFSMetaObjectsForRepo(ctx context.Context, repoID int64, f func(cont
|
||||
if !opts.UpdatedLessRecentlyThan.IsZero() {
|
||||
sess.And("`lfs_meta_object`.updated_unix < ?", opts.UpdatedLessRecentlyThan)
|
||||
}
|
||||
sess.GroupBy("`lfs_meta_object`.id")
|
||||
if opts.OrderByUpdated {
|
||||
sess.OrderBy("`lfs_meta_object`.updated_unix ASC")
|
||||
} else {
|
||||
sess.And("`lfs_meta_object`.id > ?", id)
|
||||
sess.OrderBy("`lfs_meta_object`.id ASC")
|
||||
}
|
||||
if err := sess.Limit(batchSize, start).Find(&beans); err != nil {
|
||||
sess.GroupBy("`lfs_meta_object`.id").
|
||||
And("`lfs_meta_object`.id > ?", lastID).
|
||||
OrderBy("`lfs_meta_object`.id ASC")
|
||||
|
||||
if err := sess.Limit(batchSize).Find(&beans); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(beans) == 0 {
|
||||
return nil
|
||||
}
|
||||
if !opts.LoopFunctionAlwaysUpdates {
|
||||
start += len(beans)
|
||||
}
|
||||
|
||||
for _, bean := range beans {
|
||||
if err := f(ctx, &bean.LFSMetaObject, bean.Count); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
id = beans[len(beans)-1].ID
|
||||
lastID = beans[len(beans)-1].ID
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,10 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
@@ -71,10 +68,6 @@ func (l *LFSLock) LoadOwner(ctx context.Context) error {
|
||||
// CreateLFSLock creates a new lock.
|
||||
func CreateLFSLock(ctx context.Context, repo *repo_model.Repository, lock *LFSLock) (*LFSLock, error) {
|
||||
return db.WithTx2(ctx, func(ctx context.Context) (*LFSLock, error) {
|
||||
if err := CheckLFSAccessForRepo(ctx, lock.OwnerID, repo, perm.AccessModeWrite); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lock.Path = util.PathJoinRel(lock.Path)
|
||||
lock.RepoID = repo.ID
|
||||
|
||||
@@ -137,7 +130,7 @@ func GetLFSLockByRepoID(ctx context.Context, repoID int64, page, pageSize int) (
|
||||
// GetTreePathLock returns LSF lock for the treePath
|
||||
func GetTreePathLock(ctx context.Context, repoID int64, treePath string) (*LFSLock, error) {
|
||||
if !setting.LFS.StartServer {
|
||||
return nil, nil
|
||||
return nil, nil //nolint:nilnil // return nil when LFS is not started
|
||||
}
|
||||
|
||||
locks, err := GetLFSLockByRepoID(ctx, repoID, 0, 0)
|
||||
@@ -149,7 +142,7 @@ func GetTreePathLock(ctx context.Context, repoID int64, treePath string) (*LFSLo
|
||||
return lock, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
return nil, nil //nolint:nilnil // return nil to indicate that the object does not exist
|
||||
}
|
||||
|
||||
// CountLFSLockByRepoID returns a count of all LFSLocks associated with a repository.
|
||||
@@ -165,10 +158,6 @@ func DeleteLFSLockByID(ctx context.Context, id int64, repo *repo_model.Repositor
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := CheckLFSAccessForRepo(ctx, u.ID, repo, perm.AccessModeWrite); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !force && u.ID != lock.OwnerID {
|
||||
return nil, errors.New("user doesn't own lock and force flag is not set")
|
||||
}
|
||||
@@ -180,22 +169,3 @@ func DeleteLFSLockByID(ctx context.Context, id int64, repo *repo_model.Repositor
|
||||
return lock, nil
|
||||
})
|
||||
}
|
||||
|
||||
// CheckLFSAccessForRepo check needed access mode base on action
|
||||
func CheckLFSAccessForRepo(ctx context.Context, ownerID int64, repo *repo_model.Repository, mode perm.AccessMode) error {
|
||||
if ownerID == 0 {
|
||||
return ErrLFSUnauthorizedAction{repo.ID, "undefined", mode}
|
||||
}
|
||||
u, err := user_model.GetUserByID(ctx, ownerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
perm, err := access_model.GetUserRepoPermission(ctx, repo, u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !perm.CanAccess(mode, unit.TypeCode) {
|
||||
return ErrLFSUnauthorizedAction{repo.ID, u.DisplayName(), mode}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/lfs"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIterateLFSMetaObjectsForRepoUpdatesDoNotSkip(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
ctx := t.Context()
|
||||
repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, "user2", "repo1")
|
||||
assert.NoError(t, err)
|
||||
|
||||
defer test.MockVariableValue(&setting.Database.IterateBufferSize, 1)()
|
||||
|
||||
created := make([]*git_model.LFSMetaObject, 0, 3)
|
||||
for i := range 3 {
|
||||
content := []byte("gitea-lfs-" + strconv.Itoa(i))
|
||||
pointer, err := lfs.GeneratePointer(bytes.NewReader(content))
|
||||
assert.NoError(t, err)
|
||||
|
||||
meta, err := git_model.NewLFSMetaObject(ctx, repo.ID, pointer)
|
||||
assert.NoError(t, err)
|
||||
created = append(created, meta)
|
||||
}
|
||||
|
||||
iterated := make([]int64, 0, len(created))
|
||||
cutoff := time.Now().Add(24 * time.Hour)
|
||||
iterErr := git_model.IterateLFSMetaObjectsForRepo(ctx, repo.ID, func(ctx context.Context, meta *git_model.LFSMetaObject, count int64) error {
|
||||
iterated = append(iterated, meta.ID)
|
||||
_, err := db.GetEngine(ctx).ID(meta.ID).Cols("updated_unix").Update(&git_model.LFSMetaObject{
|
||||
UpdatedUnix: timeutil.TimeStamp(time.Now().Unix()),
|
||||
})
|
||||
return err
|
||||
}, &git_model.IterateLFSMetaObjectsForRepoOptions{
|
||||
OlderThan: timeutil.TimeStamp(cutoff.Unix()),
|
||||
UpdatedLessRecentlyThan: timeutil.TimeStamp(cutoff.Unix()),
|
||||
})
|
||||
assert.NoError(t, iterErr)
|
||||
|
||||
expected := []int64{created[0].ID, created[1].ID, created[2].ID}
|
||||
assert.Equal(t, expected, iterated)
|
||||
}
|
||||
@@ -318,7 +318,7 @@ func GetProtectedBranchRuleByName(ctx context.Context, repoID int64, ruleName st
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !exist {
|
||||
return nil, nil
|
||||
return nil, nil //nolint:nilnil // return nil to indicate that the object does not exist
|
||||
}
|
||||
return rel, nil
|
||||
}
|
||||
@@ -329,7 +329,7 @@ func GetProtectedBranchRuleByID(ctx context.Context, repoID, ruleID int64) (*Pro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !exist {
|
||||
return nil, nil
|
||||
return nil, nil //nolint:nilnil // return nil to indicate that the object does not exist
|
||||
}
|
||||
return rel, nil
|
||||
}
|
||||
@@ -495,9 +495,9 @@ func updateUserWhitelist(ctx context.Context, repo *repo_model.Repository, curre
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetUserByID [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err)
|
||||
}
|
||||
perm, err := access_model.GetUserRepoPermission(ctx, repo, user)
|
||||
perm, err := access_model.GetIndividualUserRepoPermission(ctx, repo, user)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetUserRepoPermission [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err)
|
||||
return nil, fmt.Errorf("GetIndividualUserRepoPermission [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err)
|
||||
}
|
||||
|
||||
if !perm.CanWrite(unit.TypeCode) {
|
||||
|
||||
@@ -104,7 +104,7 @@ func GetProtectedTagByID(ctx context.Context, id int64) (*ProtectedTag, error) {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, nil
|
||||
return nil, nil //nolint:nilnil // return nil to indicate that the object does not exist
|
||||
}
|
||||
return tag, nil
|
||||
}
|
||||
@@ -117,7 +117,7 @@ func GetProtectedTagByNamePattern(ctx context.Context, repoID int64, pattern str
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, nil
|
||||
return nil, nil //nolint:nilnil // return nil to indicate that the object does not exist
|
||||
}
|
||||
return tag, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user