mirror of
https://github.com/go-gitea/gitea
synced 2025-12-07 13:28:25 +00:00
Merge branch 'main' into Badge
This commit is contained in:
@@ -452,13 +452,10 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
|
||||
|
||||
actions := make([]*Action, 0, opts.PageSize)
|
||||
var count int64
|
||||
opts.SetDefaultValues()
|
||||
|
||||
if opts.Page < 10 { // TODO: why it's 10 but other values? It's an experience value.
|
||||
sess := db.GetEngine(ctx).Where(cond).
|
||||
Select("`action`.*"). // this line will avoid select other joined table's columns
|
||||
Join("INNER", "repository", "`repository`.id = `action`.repo_id")
|
||||
|
||||
opts.SetDefaultValues()
|
||||
sess := db.GetEngine(ctx).Where(cond)
|
||||
sess = db.SetSessionPagination(sess, &opts)
|
||||
|
||||
count, err = sess.Desc("`action`.created_unix").FindAndCount(&actions)
|
||||
@@ -467,11 +464,7 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
|
||||
}
|
||||
} else {
|
||||
// First, only query which IDs are necessary, and only then query all actions to speed up the overall query
|
||||
sess := db.GetEngine(ctx).Where(cond).
|
||||
Select("`action`.id").
|
||||
Join("INNER", "repository", "`repository`.id = `action`.repo_id")
|
||||
|
||||
opts.SetDefaultValues()
|
||||
sess := db.GetEngine(ctx).Where(cond).Select("`action`.id")
|
||||
sess = db.SetSessionPagination(sess, &opts)
|
||||
|
||||
actionIDs := make([]int64, 0, opts.PageSize)
|
||||
@@ -481,8 +474,7 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
|
||||
|
||||
count, err = db.GetEngine(ctx).Where(cond).
|
||||
Table("action").
|
||||
Cols("`action`.id").
|
||||
Join("INNER", "repository", "`repository`.id = `action`.repo_id").Count()
|
||||
Cols("`action`.id").Count()
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("Count: %w", err)
|
||||
}
|
||||
|
||||
@@ -228,6 +228,8 @@ func TestNotifyWatchers(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetFeedsCorrupted(t *testing.T) {
|
||||
// Now we will not check for corrupted data in the feeds
|
||||
// users should run doctor to fix their data
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
unittest.AssertExistsAndLoadBean(t, &activities_model.Action{
|
||||
@@ -241,8 +243,8 @@ func TestGetFeedsCorrupted(t *testing.T) {
|
||||
IncludePrivate: true,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, actions, 0)
|
||||
assert.Equal(t, int64(0), count)
|
||||
assert.Len(t, actions, 1)
|
||||
assert.Equal(t, int64(1), count)
|
||||
}
|
||||
|
||||
func TestConsistencyUpdateAction(t *testing.T) {
|
||||
|
||||
@@ -34,6 +34,7 @@ type ActivityStats struct {
|
||||
OpenedPRAuthorCount int64
|
||||
MergedPRs issues_model.PullRequestList
|
||||
MergedPRAuthorCount int64
|
||||
ActiveIssues issues_model.IssueList
|
||||
OpenedIssues issues_model.IssueList
|
||||
OpenedIssueAuthorCount int64
|
||||
ClosedIssues issues_model.IssueList
|
||||
@@ -172,7 +173,7 @@ func (stats *ActivityStats) MergedPRPerc() int {
|
||||
|
||||
// ActiveIssueCount returns total active issue count
|
||||
func (stats *ActivityStats) ActiveIssueCount() int {
|
||||
return stats.OpenedIssueCount() + stats.ClosedIssueCount()
|
||||
return len(stats.ActiveIssues)
|
||||
}
|
||||
|
||||
// OpenedIssueCount returns open issue count
|
||||
@@ -285,13 +286,21 @@ func (stats *ActivityStats) FillIssues(ctx context.Context, repoID int64, fromTi
|
||||
stats.ClosedIssueAuthorCount = count
|
||||
|
||||
// New issues
|
||||
sess = issuesForActivityStatement(ctx, repoID, fromTime, false, false)
|
||||
sess = newlyCreatedIssues(ctx, repoID, fromTime)
|
||||
sess.OrderBy("issue.created_unix ASC")
|
||||
stats.OpenedIssues = make(issues_model.IssueList, 0)
|
||||
if err = sess.Find(&stats.OpenedIssues); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Active issues
|
||||
sess = activeIssues(ctx, repoID, fromTime)
|
||||
sess.OrderBy("issue.created_unix ASC")
|
||||
stats.ActiveIssues = make(issues_model.IssueList, 0)
|
||||
if err = sess.Find(&stats.ActiveIssues); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Opened issue authors
|
||||
sess = issuesForActivityStatement(ctx, repoID, fromTime, false, false)
|
||||
if _, err = sess.Select("count(distinct issue.poster_id) as `count`").Table("issue").Get(&count); err != nil {
|
||||
@@ -317,6 +326,23 @@ func (stats *ActivityStats) FillUnresolvedIssues(ctx context.Context, repoID int
|
||||
return sess.Find(&stats.UnresolvedIssues)
|
||||
}
|
||||
|
||||
func newlyCreatedIssues(ctx context.Context, repoID int64, fromTime time.Time) *xorm.Session {
|
||||
sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID).
|
||||
And("issue.is_pull = ?", false). // Retain the is_pull check to exclude pull requests
|
||||
And("issue.created_unix >= ?", fromTime.Unix()) // Include all issues created after fromTime
|
||||
|
||||
return sess
|
||||
}
|
||||
|
||||
func activeIssues(ctx context.Context, repoID int64, fromTime time.Time) *xorm.Session {
|
||||
sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID).
|
||||
And("issue.is_pull = ?", false).
|
||||
And("issue.created_unix >= ?", fromTime.Unix()).
|
||||
Or("issue.closed_unix >= ?", fromTime.Unix())
|
||||
|
||||
return sess
|
||||
}
|
||||
|
||||
func issuesForActivityStatement(ctx context.Context, repoID int64, fromTime time.Time, closed, unresolved bool) *xorm.Session {
|
||||
sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID).
|
||||
And("issue.is_closed = ?", closed)
|
||||
|
||||
@@ -179,27 +179,6 @@ func GetMigratingTask(ctx context.Context, repoID int64) (*Task, error) {
|
||||
return &task, nil
|
||||
}
|
||||
|
||||
// GetMigratingTaskByID returns the migrating task by repo's id
|
||||
func GetMigratingTaskByID(ctx context.Context, id, doerID int64) (*Task, *migration.MigrateOptions, error) {
|
||||
task := Task{
|
||||
ID: id,
|
||||
DoerID: doerID,
|
||||
Type: structs.TaskTypeMigrateRepo,
|
||||
}
|
||||
has, err := db.GetEngine(ctx).Get(&task)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
} else if !has {
|
||||
return nil, nil, ErrTaskDoesNotExist{id, 0, task.Type}
|
||||
}
|
||||
|
||||
var opts migration.MigrateOptions
|
||||
if err := json.Unmarshal([]byte(task.PayloadContent), &opts); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &task, &opts, nil
|
||||
}
|
||||
|
||||
// CreateTask creates a task on database
|
||||
func CreateTask(ctx context.Context, task *Task) error {
|
||||
return db.Insert(ctx, task)
|
||||
|
||||
@@ -114,7 +114,7 @@ func readArmoredSign(r io.Reader) (body io.Reader, err error) {
|
||||
return nil, err
|
||||
}
|
||||
if block.Type != openpgp.SignatureType {
|
||||
return nil, fmt.Errorf("expected '" + openpgp.SignatureType + "', got: " + block.Type)
|
||||
return nil, fmt.Errorf("expected '%s', got: %s", openpgp.SignatureType, block.Type)
|
||||
}
|
||||
return block.Body, nil
|
||||
}
|
||||
@@ -139,7 +139,7 @@ func tryGetKeyIDFromSignature(sig *packet.Signature) string {
|
||||
if sig.IssuerKeyId != nil && (*sig.IssuerKeyId) != 0 {
|
||||
return fmt.Sprintf("%016X", *sig.IssuerKeyId)
|
||||
}
|
||||
if sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) > 0 {
|
||||
if len(sig.IssuerFingerprint) > 0 {
|
||||
return fmt.Sprintf("%016X", sig.IssuerFingerprint[12:20])
|
||||
}
|
||||
return ""
|
||||
|
||||
@@ -39,7 +39,7 @@ func (d *postgresSchemaDriver) Open(name string) (driver.Conn, error) {
|
||||
|
||||
// golangci lint is incorrect here - there is no benefit to using driver.ExecerContext here
|
||||
// and in any case pq does not implement it
|
||||
if execer, ok := conn.(driver.Execer); ok { //nolint
|
||||
if execer, ok := conn.(driver.Execer); ok { //nolint:staticcheck
|
||||
_, err := execer.Exec(`SELECT set_config(
|
||||
'search_path',
|
||||
$1 || ',' || current_setting('search_path'),
|
||||
@@ -64,7 +64,7 @@ func (d *postgresSchemaDriver) Open(name string) (driver.Conn, error) {
|
||||
// driver.String.ConvertValue will never return err for string
|
||||
|
||||
// golangci lint is incorrect here - there is no benefit to using stmt.ExecWithContext here
|
||||
_, err = stmt.Exec([]driver.Value{schemaValue}) //nolint
|
||||
_, err = stmt.Exec([]driver.Value{schemaValue}) //nolint:staticcheck
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
return nil, err
|
||||
|
||||
@@ -83,3 +83,22 @@
|
||||
issue_id: 2 # in repo_id 1
|
||||
review_id: 20
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 10
|
||||
type: 22 # review
|
||||
poster_id: 5
|
||||
issue_id: 3 # in repo_id 1
|
||||
content: "reviewed by user5"
|
||||
review_id: 21
|
||||
created_unix: 946684816
|
||||
|
||||
-
|
||||
id: 11
|
||||
type: 27 # review request
|
||||
poster_id: 2
|
||||
issue_id: 3 # in repo_id 1
|
||||
content: "review request for user5"
|
||||
review_id: 22
|
||||
assignee_id: 5
|
||||
created_unix: 946684817
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
[] # empty
|
||||
@@ -26,7 +26,7 @@
|
||||
fork_id: 0
|
||||
is_template: false
|
||||
template_id: 0
|
||||
size: 7597
|
||||
size: 8478
|
||||
is_fsck_enabled: true
|
||||
close_issues_via_commit_in_any_branch: false
|
||||
|
||||
|
||||
@@ -179,3 +179,22 @@
|
||||
content: "Review Comment"
|
||||
updated_unix: 946684810
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 21
|
||||
type: 2
|
||||
reviewer_id: 5
|
||||
issue_id: 3
|
||||
content: "reviewed by user5"
|
||||
commit_id: 4a357436d925b5c974181ff12a994538ddc5a269
|
||||
updated_unix: 946684816
|
||||
created_unix: 946684816
|
||||
|
||||
-
|
||||
id: 22
|
||||
type: 4
|
||||
reviewer_id: 5
|
||||
issue_id: 3
|
||||
content: "review request for user5"
|
||||
updated_unix: 946684817
|
||||
created_unix: 946684817
|
||||
|
||||
@@ -48,12 +48,12 @@ func (issue *Issue) ProjectColumnID(ctx context.Context) int64 {
|
||||
}
|
||||
|
||||
// LoadIssuesFromColumn load issues assigned to this column
|
||||
func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column) (IssueList, error) {
|
||||
issueList, err := Issues(ctx, &IssuesOptions{
|
||||
ProjectColumnID: b.ID,
|
||||
ProjectID: b.ProjectID,
|
||||
SortType: "project-column-sorting",
|
||||
})
|
||||
func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column, opts *IssuesOptions) (IssueList, error) {
|
||||
issueList, err := Issues(ctx, opts.Copy(func(o *IssuesOptions) {
|
||||
o.ProjectColumnID = b.ID
|
||||
o.ProjectID = b.ProjectID
|
||||
o.SortType = "project-column-sorting"
|
||||
}))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -78,10 +78,10 @@ func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column) (IssueLi
|
||||
}
|
||||
|
||||
// LoadIssuesFromColumnList load issues assigned to the columns
|
||||
func LoadIssuesFromColumnList(ctx context.Context, bs project_model.ColumnList) (map[int64]IssueList, error) {
|
||||
func LoadIssuesFromColumnList(ctx context.Context, bs project_model.ColumnList, opts *IssuesOptions) (map[int64]IssueList, error) {
|
||||
issuesMap := make(map[int64]IssueList, len(bs))
|
||||
for i := range bs {
|
||||
il, err := LoadIssuesFromColumn(ctx, bs[i])
|
||||
il, err := LoadIssuesFromColumn(ctx, bs[i], opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -54,6 +54,19 @@ type IssuesOptions struct { //nolint
|
||||
User *user_model.User // issues permission scope
|
||||
}
|
||||
|
||||
// Copy returns a copy of the options.
|
||||
// Be careful, it's not a deep copy, so `IssuesOptions.RepoIDs = {...}` is OK while `IssuesOptions.RepoIDs[0] = ...` is not.
|
||||
func (o *IssuesOptions) Copy(edit ...func(options *IssuesOptions)) *IssuesOptions {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
v := *o
|
||||
for _, e := range edit {
|
||||
e(&v)
|
||||
}
|
||||
return &v
|
||||
}
|
||||
|
||||
// applySorts sort an issues-related session based on the provided
|
||||
// sortType string
|
||||
func applySorts(sess *xorm.Session, sortType string, priorityRepoID int64) {
|
||||
|
||||
@@ -268,6 +268,10 @@ func (pr *PullRequest) LoadAttributes(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pr *PullRequest) IsAgitFlow() bool {
|
||||
return pr.Flow == PullRequestFlowAGit
|
||||
}
|
||||
|
||||
// LoadHeadRepo loads the head repository, pr.HeadRepo will remain nil if it does not exist
|
||||
// and thus ErrRepoNotExist will never be returned
|
||||
func (pr *PullRequest) LoadHeadRepo(ctx context.Context) (err error) {
|
||||
@@ -410,7 +414,7 @@ func (pr *PullRequest) getReviewedByLines(ctx context.Context, writer io.Writer)
|
||||
|
||||
// Note: This doesn't page as we only expect a very limited number of reviews
|
||||
reviews, err := FindLatestReviews(ctx, FindReviewOptions{
|
||||
Type: ReviewTypeApprove,
|
||||
Types: []ReviewType{ReviewTypeApprove},
|
||||
IssueID: pr.IssueID,
|
||||
OfficialOnly: setting.Repository.PullRequest.DefaultMergeMessageOfficialApproversOnly,
|
||||
})
|
||||
|
||||
@@ -26,6 +26,7 @@ type PullRequestsOptions struct {
|
||||
SortType string
|
||||
Labels []int64
|
||||
MilestoneID int64
|
||||
PosterID int64
|
||||
}
|
||||
|
||||
func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullRequestsOptions) *xorm.Session {
|
||||
@@ -46,6 +47,10 @@ func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullR
|
||||
sess.And("issue.milestone_id=?", opts.MilestoneID)
|
||||
}
|
||||
|
||||
if opts.PosterID > 0 {
|
||||
sess.And("issue.poster_id=?", opts.PosterID)
|
||||
}
|
||||
|
||||
return sess
|
||||
}
|
||||
|
||||
|
||||
@@ -247,7 +247,7 @@ func (r *Review) TooltipContent() string {
|
||||
}
|
||||
return "repo.issues.review.official"
|
||||
case ReviewTypeComment:
|
||||
return "repo.issues.review.comment"
|
||||
return "repo.issues.review.commented"
|
||||
case ReviewTypeReject:
|
||||
return "repo.issues.review.rejected"
|
||||
case ReviewTypeRequest:
|
||||
@@ -389,7 +389,7 @@ func GetCurrentReview(ctx context.Context, reviewer *user_model.User, issue *Iss
|
||||
return nil, nil
|
||||
}
|
||||
reviews, err := FindReviews(ctx, FindReviewOptions{
|
||||
Type: ReviewTypePending,
|
||||
Types: []ReviewType{ReviewTypePending},
|
||||
IssueID: issue.ID,
|
||||
ReviewerID: reviewer.ID,
|
||||
})
|
||||
|
||||
@@ -92,7 +92,7 @@ func (reviews ReviewList) LoadIssues(ctx context.Context) error {
|
||||
// FindReviewOptions represent possible filters to find reviews
|
||||
type FindReviewOptions struct {
|
||||
db.ListOptions
|
||||
Type ReviewType
|
||||
Types []ReviewType
|
||||
IssueID int64
|
||||
ReviewerID int64
|
||||
OfficialOnly bool
|
||||
@@ -107,8 +107,8 @@ func (opts *FindReviewOptions) toCond() builder.Cond {
|
||||
if opts.ReviewerID > 0 {
|
||||
cond = cond.And(builder.Eq{"reviewer_id": opts.ReviewerID})
|
||||
}
|
||||
if opts.Type != ReviewTypeUnknown {
|
||||
cond = cond.And(builder.Eq{"type": opts.Type})
|
||||
if len(opts.Types) > 0 {
|
||||
cond = cond.And(builder.In("type", opts.Types))
|
||||
}
|
||||
if opts.OfficialOnly {
|
||||
cond = cond.And(builder.Eq{"official": true})
|
||||
|
||||
@@ -63,7 +63,7 @@ func TestReviewType_Icon(t *testing.T) {
|
||||
func TestFindReviews(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
reviews, err := issues_model.FindReviews(db.DefaultContext, issues_model.FindReviewOptions{
|
||||
Type: issues_model.ReviewTypeApprove,
|
||||
Types: []issues_model.ReviewType{issues_model.ReviewTypeApprove},
|
||||
IssueID: 2,
|
||||
ReviewerID: 1,
|
||||
})
|
||||
@@ -75,7 +75,7 @@ func TestFindReviews(t *testing.T) {
|
||||
func TestFindLatestReviews(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
reviews, err := issues_model.FindLatestReviews(db.DefaultContext, issues_model.FindReviewOptions{
|
||||
Type: issues_model.ReviewTypeApprove,
|
||||
Types: []issues_model.ReviewType{issues_model.ReviewTypeApprove},
|
||||
IssueID: 11,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -500,7 +500,7 @@ var migrations = []Migration{
|
||||
// v259 -> v260
|
||||
NewMigration("Convert scoped access tokens", v1_20.ConvertScopedAccessTokens),
|
||||
|
||||
// Gitea 1.20.0 ends at 260
|
||||
// Gitea 1.20.0 ends at v260
|
||||
|
||||
// v260 -> v261
|
||||
NewMigration("Drop custom_labels column of action_runner table", v1_21.DropCustomLabelsColumnOfActionRunner),
|
||||
@@ -601,6 +601,8 @@ var migrations = []Migration{
|
||||
NewMigration("Add metadata column for comment table", v1_23.AddCommentMetaDataColumn),
|
||||
// v304 -> v305
|
||||
NewMigration("Add index for release sha1", v1_23.AddIndexForReleaseSha1),
|
||||
// v305 -> v306
|
||||
NewMigration("Add Repository Licenses", v1_23.AddRepositoryLicenses),
|
||||
}
|
||||
|
||||
// GetCurrentDBVersion returns the current db version
|
||||
|
||||
@@ -83,7 +83,7 @@ func UnwrapLDAPSourceCfg(x *xorm.Engine) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal %s: %w", source.Cfg, err)
|
||||
}
|
||||
if wrapped.Source != nil && len(wrapped.Source) > 0 {
|
||||
if len(wrapped.Source) > 0 {
|
||||
bs, err := json.Marshal(wrapped.Source)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_23 //nolint
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func AddRepositoryLicenses(x *xorm.Engine) error {
|
||||
type RepoLicense struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"UNIQUE(s) NOT NULL"`
|
||||
CommitID string
|
||||
License string `xorm:"VARCHAR(255) UNIQUE(s) NOT NULL"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX UPDATED"`
|
||||
}
|
||||
|
||||
return x.Sync(new(RepoLicense))
|
||||
}
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
||||
"xorm.io/builder"
|
||||
@@ -112,6 +114,49 @@ func IsUserOrgOwner(ctx context.Context, users user_model.UserList, orgID int64)
|
||||
return results
|
||||
}
|
||||
|
||||
// GetOrgAssignees returns all users that have write access and can be assigned to issues
|
||||
// of the any repository in the organization.
|
||||
func GetOrgAssignees(ctx context.Context, orgID int64) (_ []*user_model.User, err error) {
|
||||
e := db.GetEngine(ctx)
|
||||
userIDs := make([]int64, 0, 10)
|
||||
if err = e.Table("access").
|
||||
Join("INNER", "repository", "`repository`.id = `access`.repo_id").
|
||||
Where("`repository`.owner_id = ? AND `access`.mode >= ?", orgID, perm.AccessModeWrite).
|
||||
Select("user_id").
|
||||
Find(&userIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
additionalUserIDs := make([]int64, 0, 10)
|
||||
if err = e.Table("team_user").
|
||||
Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id").
|
||||
Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
|
||||
Join("INNER", "repository", "`repository`.id = `team_repo`.repo_id").
|
||||
Where("`repository`.owner_id = ? AND (`team_unit`.access_mode >= ? OR (`team_unit`.access_mode = ? AND `team_unit`.`type` = ?))",
|
||||
orgID, perm.AccessModeWrite, perm.AccessModeRead, unit.TypePullRequests).
|
||||
Distinct("`team_user`.uid").
|
||||
Select("`team_user`.uid").
|
||||
Find(&additionalUserIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uniqueUserIDs := make(container.Set[int64])
|
||||
uniqueUserIDs.AddMultiple(userIDs...)
|
||||
uniqueUserIDs.AddMultiple(additionalUserIDs...)
|
||||
|
||||
users := make([]*user_model.User, 0, len(uniqueUserIDs))
|
||||
if len(userIDs) > 0 {
|
||||
if err = e.In("id", uniqueUserIDs.Values()).
|
||||
Where(builder.Eq{"`user`.is_active": true}).
|
||||
OrderBy(user_model.GetOrderByName()).
|
||||
Find(&users); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func loadOrganizationOwners(ctx context.Context, users user_model.UserList, orgID int64) (map[int64]*TeamUser, error) {
|
||||
if len(users) == 0 {
|
||||
return nil, nil
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
)
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(RepoLicense))
|
||||
}
|
||||
|
||||
type RepoLicense struct { //revive:disable-line:exported
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"UNIQUE(s) NOT NULL"`
|
||||
CommitID string
|
||||
License string `xorm:"VARCHAR(255) UNIQUE(s) NOT NULL"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX UPDATED"`
|
||||
}
|
||||
|
||||
// RepoLicenseList defines a list of repo licenses
|
||||
type RepoLicenseList []*RepoLicense //revive:disable-line:exported
|
||||
|
||||
func (rll RepoLicenseList) StringList() []string {
|
||||
var licenses []string
|
||||
for _, rl := range rll {
|
||||
licenses = append(licenses, rl.License)
|
||||
}
|
||||
return licenses
|
||||
}
|
||||
|
||||
// GetRepoLicenses returns the license statistics for a repository
|
||||
func GetRepoLicenses(ctx context.Context, repo *Repository) (RepoLicenseList, error) {
|
||||
licenses := make(RepoLicenseList, 0)
|
||||
if err := db.GetEngine(ctx).Where("`repo_id` = ?", repo.ID).Asc("`license`").Find(&licenses); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return licenses, nil
|
||||
}
|
||||
|
||||
// UpdateRepoLicenses updates the license statistics for repository
|
||||
func UpdateRepoLicenses(ctx context.Context, repo *Repository, commitID string, licenses []string) error {
|
||||
oldLicenses, err := GetRepoLicenses(ctx, repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, license := range licenses {
|
||||
upd := false
|
||||
for _, o := range oldLicenses {
|
||||
// Update already existing license
|
||||
if o.License == license {
|
||||
if _, err := db.GetEngine(ctx).ID(o.ID).Cols("`commit_id`").Update(o); err != nil {
|
||||
return err
|
||||
}
|
||||
upd = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// Insert new license
|
||||
if !upd {
|
||||
if err := db.Insert(ctx, &RepoLicense{
|
||||
RepoID: repo.ID,
|
||||
CommitID: commitID,
|
||||
License: license,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
// Delete old licenses
|
||||
licenseToDelete := make([]int64, 0, len(oldLicenses))
|
||||
for _, o := range oldLicenses {
|
||||
if o.CommitID != commitID {
|
||||
licenseToDelete = append(licenseToDelete, o.ID)
|
||||
}
|
||||
}
|
||||
if len(licenseToDelete) > 0 {
|
||||
if _, err := db.GetEngine(ctx).In("`id`", licenseToDelete).Delete(&RepoLicense{}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyLicense Copy originalRepo license information to destRepo (use for forked repo)
|
||||
func CopyLicense(ctx context.Context, originalRepo, destRepo *Repository) error {
|
||||
repoLicenses, err := GetRepoLicenses(ctx, originalRepo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(repoLicenses) > 0 {
|
||||
newRepoLicenses := make(RepoLicenseList, 0, len(repoLicenses))
|
||||
|
||||
for _, rl := range repoLicenses {
|
||||
newRepoLicense := &RepoLicense{
|
||||
RepoID: destRepo.ID,
|
||||
CommitID: rl.CommitID,
|
||||
License: rl.License,
|
||||
}
|
||||
newRepoLicenses = append(newRepoLicenses, newRepoLicense)
|
||||
}
|
||||
if err := db.Insert(ctx, &newRepoLicenses); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanRepoLicenses will remove all license record of the repo
|
||||
func CleanRepoLicenses(ctx context.Context, repo *Repository) error {
|
||||
return db.DeleteBeans(ctx, &RepoLicense{
|
||||
RepoID: repo.ID,
|
||||
})
|
||||
}
|
||||
@@ -234,6 +234,7 @@ type FindReleasesOptions struct {
|
||||
IsDraft optional.Option[bool]
|
||||
TagNames []string
|
||||
HasSha1 optional.Option[bool] // useful to find draft releases which are created with existing tags
|
||||
NamePattern optional.Option[string]
|
||||
}
|
||||
|
||||
func (opts FindReleasesOptions) ToConds() builder.Cond {
|
||||
@@ -261,6 +262,11 @@ func (opts FindReleasesOptions) ToConds() builder.Cond {
|
||||
cond = cond.And(builder.Eq{"sha1": ""})
|
||||
}
|
||||
}
|
||||
|
||||
if opts.NamePattern.Has() && opts.NamePattern.Value() != "" {
|
||||
cond = cond.And(builder.Like{"lower_tag_name", strings.ToLower(opts.NamePattern.Value())})
|
||||
}
|
||||
|
||||
return cond
|
||||
}
|
||||
|
||||
|
||||
@@ -749,7 +749,7 @@ func GetUserRepositories(ctx context.Context, opts *SearchRepoOptions) (Reposito
|
||||
cond = cond.And(builder.Eq{"is_private": false})
|
||||
}
|
||||
|
||||
if opts.LowerNames != nil && len(opts.LowerNames) > 0 {
|
||||
if len(opts.LowerNames) > 0 {
|
||||
cond = cond.And(builder.In("lower_name", opts.LowerNames))
|
||||
}
|
||||
|
||||
|
||||
+13
-1
@@ -65,7 +65,19 @@ func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Sess
|
||||
builder.Like{"LOWER(full_name)", lowerKeyword},
|
||||
)
|
||||
if opts.SearchByEmail {
|
||||
keywordCond = keywordCond.Or(builder.Like{"LOWER(email)", lowerKeyword})
|
||||
var emailCond builder.Cond
|
||||
emailCond = builder.Like{"LOWER(email)", lowerKeyword}
|
||||
if opts.Actor == nil {
|
||||
emailCond = emailCond.And(builder.Eq{"keep_email_private": false})
|
||||
} else if !opts.Actor.IsAdmin {
|
||||
emailCond = emailCond.And(
|
||||
builder.Or(
|
||||
builder.Eq{"keep_email_private": false},
|
||||
builder.Eq{"id": opts.Actor.ID},
|
||||
),
|
||||
)
|
||||
}
|
||||
keywordCond = keywordCond.Or(emailCond)
|
||||
}
|
||||
|
||||
cond = cond.And(keywordCond)
|
||||
|
||||
@@ -14,4 +14,8 @@ const (
|
||||
UserActivityPubPrivPem = "activitypub.priv_pem"
|
||||
// UserActivityPubPubPem is user's public key
|
||||
UserActivityPubPubPem = "activitypub.pub_pem"
|
||||
// SignupIP is the IP address that the user signed up with
|
||||
SignupIP = "signup.ip"
|
||||
// SignupUserAgent is the user agent that the user signed up with
|
||||
SignupUserAgent = "signup.user_agent"
|
||||
)
|
||||
|
||||
+33
-5
@@ -150,6 +150,14 @@ type User struct {
|
||||
KeepActivityPrivate bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
// Meta defines the meta information of a user, to be stored in the K/V table
|
||||
type Meta struct {
|
||||
// Store the initial registration of the user, to aid in spam prevention
|
||||
// Ensure that one IP isn't creating many accounts (following mediawiki approach)
|
||||
InitialIP string
|
||||
InitialUserAgent string
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(User))
|
||||
}
|
||||
@@ -400,6 +408,10 @@ func (u *User) IsIndividual() bool {
|
||||
return u.Type == UserTypeIndividual
|
||||
}
|
||||
|
||||
func (u *User) IsUser() bool {
|
||||
return u.Type == UserTypeIndividual || u.Type == UserTypeBot
|
||||
}
|
||||
|
||||
// IsBot returns whether or not the user is of type bot
|
||||
func (u *User) IsBot() bool {
|
||||
return u.Type == UserTypeBot
|
||||
@@ -615,17 +627,17 @@ type CreateUserOverwriteOptions struct {
|
||||
}
|
||||
|
||||
// CreateUser creates record of a new user.
|
||||
func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
|
||||
return createUser(ctx, u, false, overwriteDefault...)
|
||||
func CreateUser(ctx context.Context, u *User, meta *Meta, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
|
||||
return createUser(ctx, u, meta, false, overwriteDefault...)
|
||||
}
|
||||
|
||||
// AdminCreateUser is used by admins to manually create users
|
||||
func AdminCreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
|
||||
return createUser(ctx, u, true, overwriteDefault...)
|
||||
func AdminCreateUser(ctx context.Context, u *User, meta *Meta, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
|
||||
return createUser(ctx, u, meta, true, overwriteDefault...)
|
||||
}
|
||||
|
||||
// createUser creates record of a new user.
|
||||
func createUser(ctx context.Context, u *User, createdByAdmin bool, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
|
||||
func createUser(ctx context.Context, u *User, meta *Meta, createdByAdmin bool, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
|
||||
if err = IsUsableUsername(u.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -745,6 +757,22 @@ func createUser(ctx context.Context, u *User, createdByAdmin bool, overwriteDefa
|
||||
return err
|
||||
}
|
||||
|
||||
if setting.RecordUserSignupMetadata {
|
||||
// insert initial IP and UserAgent
|
||||
if err = SetUserSetting(ctx, u.ID, SignupIP, meta.InitialIP); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// trim user agent string to a reasonable length, if necessary
|
||||
userAgent := strings.TrimSpace(meta.InitialUserAgent)
|
||||
if len(userAgent) > 255 {
|
||||
userAgent = userAgent[:255]
|
||||
}
|
||||
if err = SetUserSetting(ctx, u.ID, SignupUserAgent, userAgent); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// insert email address
|
||||
if err := db.Insert(ctx, &EmailAddress{
|
||||
UID: u.ID,
|
||||
|
||||
@@ -227,7 +227,7 @@ func TestCreateUserInvalidEmail(t *testing.T) {
|
||||
MustChangePassword: false,
|
||||
}
|
||||
|
||||
err := user_model.CreateUser(db.DefaultContext, user)
|
||||
err := user_model.CreateUser(db.DefaultContext, user, &user_model.Meta{})
|
||||
assert.Error(t, err)
|
||||
assert.True(t, user_model.IsErrEmailCharIsNotSupported(err))
|
||||
}
|
||||
@@ -241,7 +241,7 @@ func TestCreateUserEmailAlreadyUsed(t *testing.T) {
|
||||
user.Name = "testuser"
|
||||
user.LowerName = strings.ToLower(user.Name)
|
||||
user.ID = 0
|
||||
err := user_model.CreateUser(db.DefaultContext, user)
|
||||
err := user_model.CreateUser(db.DefaultContext, user, &user_model.Meta{})
|
||||
assert.Error(t, err)
|
||||
assert.True(t, user_model.IsErrEmailAlreadyUsed(err))
|
||||
}
|
||||
@@ -258,7 +258,7 @@ func TestCreateUserCustomTimestamps(t *testing.T) {
|
||||
user.ID = 0
|
||||
user.Email = "unique@example.com"
|
||||
user.CreatedUnix = creationTimestamp
|
||||
err := user_model.CreateUser(db.DefaultContext, user)
|
||||
err := user_model.CreateUser(db.DefaultContext, user, &user_model.Meta{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
fetched, err := user_model.GetUserByID(context.Background(), user.ID)
|
||||
@@ -283,7 +283,7 @@ func TestCreateUserWithoutCustomTimestamps(t *testing.T) {
|
||||
user.Email = "unique@example.com"
|
||||
user.CreatedUnix = 0
|
||||
user.UpdatedUnix = 0
|
||||
err := user_model.CreateUser(db.DefaultContext, user)
|
||||
err := user_model.CreateUser(db.DefaultContext, user, &user_model.Meta{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
timestampEnd := time.Now().Unix()
|
||||
|
||||
Reference in New Issue
Block a user