Move some functions to service layer (#26969)
parent
b8ad558c93
commit
e3ed67859a
@ -0,0 +1,315 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package issues
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// MilestoneList is a list of milestones offering additional functionality
|
||||
type MilestoneList []*Milestone
|
||||
|
||||
func (milestones MilestoneList) getMilestoneIDs() []int64 {
|
||||
ids := make([]int64, 0, len(milestones))
|
||||
for _, ms := range milestones {
|
||||
ids = append(ids, ms.ID)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// GetMilestonesOption contain options to get milestones
|
||||
type GetMilestonesOption struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
State api.StateType
|
||||
Name string
|
||||
SortType string
|
||||
}
|
||||
|
||||
func (opts GetMilestonesOption) toCond() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.RepoID != 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
}
|
||||
|
||||
switch opts.State {
|
||||
case api.StateClosed:
|
||||
cond = cond.And(builder.Eq{"is_closed": true})
|
||||
case api.StateAll:
|
||||
break
|
||||
// api.StateOpen:
|
||||
default:
|
||||
cond = cond.And(builder.Eq{"is_closed": false})
|
||||
}
|
||||
|
||||
if len(opts.Name) != 0 {
|
||||
cond = cond.And(db.BuildCaseInsensitiveLike("name", opts.Name))
|
||||
}
|
||||
|
||||
return cond
|
||||
}
|
||||
|
||||
// GetMilestones returns milestones filtered by GetMilestonesOption's
|
||||
func GetMilestones(opts GetMilestonesOption) (MilestoneList, int64, error) {
|
||||
sess := db.GetEngine(db.DefaultContext).Where(opts.toCond())
|
||||
|
||||
if opts.Page != 0 {
|
||||
sess = db.SetSessionPagination(sess, &opts)
|
||||
}
|
||||
|
||||
switch opts.SortType {
|
||||
case "furthestduedate":
|
||||
sess.Desc("deadline_unix")
|
||||
case "leastcomplete":
|
||||
sess.Asc("completeness")
|
||||
case "mostcomplete":
|
||||
sess.Desc("completeness")
|
||||
case "leastissues":
|
||||
sess.Asc("num_issues")
|
||||
case "mostissues":
|
||||
sess.Desc("num_issues")
|
||||
case "id":
|
||||
sess.Asc("id")
|
||||
default:
|
||||
sess.Asc("deadline_unix").Asc("id")
|
||||
}
|
||||
|
||||
miles := make([]*Milestone, 0, opts.PageSize)
|
||||
total, err := sess.FindAndCount(&miles)
|
||||
return miles, total, err
|
||||
}
|
||||
|
||||
// GetMilestoneIDsByNames returns a list of milestone ids by given names.
|
||||
// It doesn't filter them by repo, so it could return milestones belonging to different repos.
|
||||
// It's used for filtering issues via indexer, otherwise it would be useless.
|
||||
// Since it could return milestones with the same name, so the length of returned ids could be more than the length of names.
|
||||
func GetMilestoneIDsByNames(ctx context.Context, names []string) ([]int64, error) {
|
||||
var ids []int64
|
||||
return ids, db.GetEngine(ctx).Table("milestone").
|
||||
Where(db.BuildCaseInsensitiveIn("name", names)).
|
||||
Cols("id").
|
||||
Find(&ids)
|
||||
}
|
||||
|
||||
// SearchMilestones search milestones
|
||||
func SearchMilestones(repoCond builder.Cond, page int, isClosed bool, sortType, keyword string) (MilestoneList, error) {
|
||||
miles := make([]*Milestone, 0, setting.UI.IssuePagingNum)
|
||||
sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", isClosed)
|
||||
if len(keyword) > 0 {
|
||||
sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)})
|
||||
}
|
||||
if repoCond.IsValid() {
|
||||
sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond))
|
||||
}
|
||||
if page > 0 {
|
||||
sess = sess.Limit(setting.UI.IssuePagingNum, (page-1)*setting.UI.IssuePagingNum)
|
||||
}
|
||||
|
||||
switch sortType {
|
||||
case "furthestduedate":
|
||||
sess.Desc("deadline_unix")
|
||||
case "leastcomplete":
|
||||
sess.Asc("completeness")
|
||||
case "mostcomplete":
|
||||
sess.Desc("completeness")
|
||||
case "leastissues":
|
||||
sess.Asc("num_issues")
|
||||
case "mostissues":
|
||||
sess.Desc("num_issues")
|
||||
default:
|
||||
sess.Asc("deadline_unix")
|
||||
}
|
||||
return miles, sess.Find(&miles)
|
||||
}
|
||||
|
||||
// GetMilestonesByRepoIDs returns a list of milestones of given repositories and status.
|
||||
func GetMilestonesByRepoIDs(repoIDs []int64, page int, isClosed bool, sortType string) (MilestoneList, error) {
|
||||
return SearchMilestones(
|
||||
builder.In("repo_id", repoIDs),
|
||||
page,
|
||||
isClosed,
|
||||
sortType,
|
||||
"",
|
||||
)
|
||||
}
|
||||
|
||||
func (milestones MilestoneList) loadTotalTrackedTimes(ctx context.Context) error {
|
||||
type totalTimesByMilestone struct {
|
||||
MilestoneID int64
|
||||
Time int64
|
||||
}
|
||||
if len(milestones) == 0 {
|
||||
return nil
|
||||
}
|
||||
trackedTimes := make(map[int64]int64, len(milestones))
|
||||
|
||||
// Get total tracked time by milestone_id
|
||||
rows, err := db.GetEngine(ctx).Table("issue").
|
||||
Join("INNER", "milestone", "issue.milestone_id = milestone.id").
|
||||
Join("LEFT", "tracked_time", "tracked_time.issue_id = issue.id").
|
||||
Where("tracked_time.deleted = ?", false).
|
||||
Select("milestone_id, sum(time) as time").
|
||||
In("milestone_id", milestones.getMilestoneIDs()).
|
||||
GroupBy("milestone_id").
|
||||
Rows(new(totalTimesByMilestone))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var totalTime totalTimesByMilestone
|
||||
err = rows.Scan(&totalTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
trackedTimes[totalTime.MilestoneID] = totalTime.Time
|
||||
}
|
||||
|
||||
for _, milestone := range milestones {
|
||||
milestone.TotalTrackedTime = trackedTimes[milestone.ID]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadTotalTrackedTimes loads for every milestone in the list the TotalTrackedTime by a batch request
|
||||
func (milestones MilestoneList) LoadTotalTrackedTimes() error {
|
||||
return milestones.loadTotalTrackedTimes(db.DefaultContext)
|
||||
}
|
||||
|
||||
// CountMilestones returns number of milestones in given repository with other options
|
||||
func CountMilestones(ctx context.Context, opts GetMilestonesOption) (int64, error) {
|
||||
return db.GetEngine(ctx).
|
||||
Where(opts.toCond()).
|
||||
Count(new(Milestone))
|
||||
}
|
||||
|
||||
// CountMilestonesByRepoCond map from repo conditions to number of milestones matching the options`
|
||||
func CountMilestonesByRepoCond(repoCond builder.Cond, isClosed bool) (map[int64]int64, error) {
|
||||
sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", isClosed)
|
||||
if repoCond.IsValid() {
|
||||
sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond))
|
||||
}
|
||||
|
||||
countsSlice := make([]*struct {
|
||||
RepoID int64
|
||||
Count int64
|
||||
}, 0, 10)
|
||||
if err := sess.GroupBy("repo_id").
|
||||
Select("repo_id AS repo_id, COUNT(*) AS count").
|
||||
Table("milestone").
|
||||
Find(&countsSlice); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
countMap := make(map[int64]int64, len(countsSlice))
|
||||
for _, c := range countsSlice {
|
||||
countMap[c.RepoID] = c.Count
|
||||
}
|
||||
return countMap, nil
|
||||
}
|
||||
|
||||
// CountMilestonesByRepoCondAndKw map from repo conditions and the keyword of milestones' name to number of milestones matching the options`
|
||||
func CountMilestonesByRepoCondAndKw(repoCond builder.Cond, keyword string, isClosed bool) (map[int64]int64, error) {
|
||||
sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", isClosed)
|
||||
if len(keyword) > 0 {
|
||||
sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)})
|
||||
}
|
||||
if repoCond.IsValid() {
|
||||
sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond))
|
||||
}
|
||||
|
||||
countsSlice := make([]*struct {
|
||||
RepoID int64
|
||||
Count int64
|
||||
}, 0, 10)
|
||||
if err := sess.GroupBy("repo_id").
|
||||
Select("repo_id AS repo_id, COUNT(*) AS count").
|
||||
Table("milestone").
|
||||
Find(&countsSlice); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
countMap := make(map[int64]int64, len(countsSlice))
|
||||
for _, c := range countsSlice {
|
||||
countMap[c.RepoID] = c.Count
|
||||
}
|
||||
return countMap, nil
|
||||
}
|
||||
|
||||
// MilestonesStats represents milestone statistic information.
|
||||
type MilestonesStats struct {
|
||||
OpenCount, ClosedCount int64
|
||||
}
|
||||
|
||||
// Total returns the total counts of milestones
|
||||
func (m MilestonesStats) Total() int64 {
|
||||
return m.OpenCount + m.ClosedCount
|
||||
}
|
||||
|
||||
// GetMilestonesStatsByRepoCond returns milestone statistic information for dashboard by given conditions.
|
||||
func GetMilestonesStatsByRepoCond(repoCond builder.Cond) (*MilestonesStats, error) {
|
||||
var err error
|
||||
stats := &MilestonesStats{}
|
||||
|
||||
sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", false)
|
||||
if repoCond.IsValid() {
|
||||
sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond)))
|
||||
}
|
||||
stats.OpenCount, err = sess.Count(new(Milestone))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sess = db.GetEngine(db.DefaultContext).Where("is_closed = ?", true)
|
||||
if repoCond.IsValid() {
|
||||
sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond)))
|
||||
}
|
||||
stats.ClosedCount, err = sess.Count(new(Milestone))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// GetMilestonesStatsByRepoCondAndKw returns milestone statistic information for dashboard by given repo conditions and name keyword.
|
||||
func GetMilestonesStatsByRepoCondAndKw(repoCond builder.Cond, keyword string) (*MilestonesStats, error) {
|
||||
var err error
|
||||
stats := &MilestonesStats{}
|
||||
|
||||
sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", false)
|
||||
if len(keyword) > 0 {
|
||||
sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)})
|
||||
}
|
||||
if repoCond.IsValid() {
|
||||
sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond)))
|
||||
}
|
||||
stats.OpenCount, err = sess.Count(new(Milestone))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sess = db.GetEngine(db.DefaultContext).Where("is_closed = ?", true)
|
||||
if len(keyword) > 0 {
|
||||
sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)})
|
||||
}
|
||||
if repoCond.IsValid() {
|
||||
sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond)))
|
||||
}
|
||||
stats.ClosedCount, err = sess.Count(new(Milestone))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
@ -1,196 +0,0 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
)
|
||||
|
||||
// InsertMilestones creates milestones of repository.
|
||||
func InsertMilestones(ms ...*issues_model.Milestone) (err error) {
|
||||
if len(ms) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
sess := db.GetEngine(ctx)
|
||||
|
||||
// to return the id, so we should not use batch insert
|
||||
for _, m := range ms {
|
||||
if _, err = sess.NoAutoTime().Insert(m); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = db.Exec(ctx, "UPDATE `repository` SET num_milestones = num_milestones + ? WHERE id = ?", len(ms), ms[0].RepoID); err != nil {
|
||||
return err
|
||||
}
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
// InsertIssues insert issues to database
|
||||
func InsertIssues(issues ...*issues_model.Issue) error {
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
for _, issue := range issues {
|
||||
if err := insertIssue(ctx, issue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
func insertIssue(ctx context.Context, issue *issues_model.Issue) error {
|
||||
sess := db.GetEngine(ctx)
|
||||
if _, err := sess.NoAutoTime().Insert(issue); err != nil {
|
||||
return err
|
||||
}
|
||||
issueLabels := make([]issues_model.IssueLabel, 0, len(issue.Labels))
|
||||
for _, label := range issue.Labels {
|
||||
issueLabels = append(issueLabels, issues_model.IssueLabel{
|
||||
IssueID: issue.ID,
|
||||
LabelID: label.ID,
|
||||
})
|
||||
}
|
||||
if len(issueLabels) > 0 {
|
||||
if _, err := sess.Insert(issueLabels); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, reaction := range issue.Reactions {
|
||||
reaction.IssueID = issue.ID
|
||||
}
|
||||
|
||||
if len(issue.Reactions) > 0 {
|
||||
if _, err := sess.Insert(issue.Reactions); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertIssueComments inserts many comments of issues.
|
||||
func InsertIssueComments(comments []*issues_model.Comment) error {
|
||||
if len(comments) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
issueIDs := make(container.Set[int64])
|
||||
for _, comment := range comments {
|
||||
issueIDs.Add(comment.IssueID)
|
||||
}
|
||||
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
for _, comment := range comments {
|
||||
if _, err := db.GetEngine(ctx).NoAutoTime().Insert(comment); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, reaction := range comment.Reactions {
|
||||
reaction.IssueID = comment.IssueID
|
||||
reaction.CommentID = comment.ID
|
||||
}
|
||||
if len(comment.Reactions) > 0 {
|
||||
if err := db.Insert(ctx, comment.Reactions); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for issueID := range issueIDs {
|
||||
if _, err := db.Exec(ctx, "UPDATE issue set num_comments = (SELECT count(*) FROM comment WHERE issue_id = ? AND `type`=?) WHERE id = ?",
|
||||
issueID, issues_model.CommentTypeComment, issueID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
// InsertPullRequests inserted pull requests
|
||||
func InsertPullRequests(ctx context.Context, prs ...*issues_model.PullRequest) error {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
sess := db.GetEngine(ctx)
|
||||
for _, pr := range prs {
|
||||
if err := insertIssue(ctx, pr.Issue); err != nil {
|
||||
return err
|
||||
}
|
||||
pr.IssueID = pr.Issue.ID
|
||||
if _, err := sess.NoAutoTime().Insert(pr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
// InsertReleases migrates release
|
||||
func InsertReleases(rels ...*repo_model.Release) error {
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
sess := db.GetEngine(ctx)
|
||||
|
||||
for _, rel := range rels {
|
||||
if _, err := sess.NoAutoTime().Insert(rel); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(rel.Attachments) > 0 {
|
||||
for i := range rel.Attachments {
|
||||
rel.Attachments[i].ReleaseID = rel.ID
|
||||
}
|
||||
|
||||
if _, err := sess.NoAutoTime().Insert(rel.Attachments); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
// UpdateMigrationsByType updates all migrated repositories' posterid from gitServiceType to replace originalAuthorID to posterID
|
||||
func UpdateMigrationsByType(tp structs.GitServiceType, externalUserID string, userID int64) error {
|
||||
if err := issues_model.UpdateIssuesMigrationsByType(tp, externalUserID, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := issues_model.UpdateCommentsMigrationsByType(tp, externalUserID, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := repo_model.UpdateReleasesMigrationsByType(tp, externalUserID, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := issues_model.UpdateReactionsMigrationsByType(tp, externalUserID, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
return issues_model.UpdateReviewsMigrationsByType(tp, externalUserID, userID)
|
||||
}
|
@ -1,145 +0,0 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
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"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMigrate_InsertMilestones(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
reponame := "repo1"
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame})
|
||||
name := "milestonetest1"
|
||||
ms := &issues_model.Milestone{
|
||||
RepoID: repo.ID,
|
||||
Name: name,
|
||||
}
|
||||
err := InsertMilestones(ms)
|
||||
assert.NoError(t, err)
|
||||
unittest.AssertExistsAndLoadBean(t, ms)
|
||||
repoModified := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID})
|
||||
assert.EqualValues(t, repo.NumMilestones+1, repoModified.NumMilestones)
|
||||
|
||||
unittest.CheckConsistencyFor(t, &issues_model.Milestone{})
|
||||
}
|
||||
|
||||
func assertCreateIssues(t *testing.T, isPull bool) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
reponame := "repo1"
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame})
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
|
||||
milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1})
|
||||
assert.EqualValues(t, milestone.ID, 1)
|
||||
reaction := &issues_model.Reaction{
|
||||
Type: "heart",
|
||||
UserID: owner.ID,
|
||||
}
|
||||
|
||||
title := "issuetitle1"
|
||||
is := &issues_model.Issue{
|
||||
RepoID: repo.ID,
|
||||
MilestoneID: milestone.ID,
|
||||
Repo: repo,
|
||||
Title: title,
|
||||
Content: "issuecontent1",
|
||||
IsPull: isPull,
|
||||
PosterID: owner.ID,
|
||||
Poster: owner,
|
||||
IsClosed: true,
|
||||
Labels: []*issues_model.Label{label},
|
||||
Reactions: []*issues_model.Reaction{reaction},
|
||||
}
|
||||
err := InsertIssues(is)
|
||||
assert.NoError(t, err)
|
||||
|
||||
i := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{Title: title})
|
||||
unittest.AssertExistsAndLoadBean(t, &issues_model.Reaction{Type: "heart", UserID: owner.ID, IssueID: i.ID})
|
||||
}
|
||||
|
||||
func TestMigrate_CreateIssuesIsPullFalse(t *testing.T) {
|
||||
assertCreateIssues(t, false)
|
||||
}
|
||||
|
||||
func TestMigrate_CreateIssuesIsPullTrue(t *testing.T) {
|
||||
assertCreateIssues(t, true)
|
||||
}
|
||||
|
||||
func TestMigrate_InsertIssueComments(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
|
||||
_ = issue.LoadRepo(db.DefaultContext)
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID})
|
||||
reaction := &issues_model.Reaction{
|
||||
Type: "heart",
|
||||
UserID: owner.ID,
|
||||
}
|
||||
|
||||
comment := &issues_model.Comment{
|
||||
PosterID: owner.ID,
|
||||
Poster: owner,
|
||||
IssueID: issue.ID,
|
||||
Issue: issue,
|
||||
Reactions: []*issues_model.Reaction{reaction},
|
||||
}
|
||||
|
||||
err := InsertIssueComments([]*issues_model.Comment{comment})
|
||||
assert.NoError(t, err)
|
||||
|
||||
issueModified := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
|
||||
assert.EqualValues(t, issue.NumComments+1, issueModified.NumComments)
|
||||
|
||||
unittest.CheckConsistencyFor(t, &issues_model.Issue{})
|
||||
}
|
||||
|
||||
func TestMigrate_InsertPullRequests(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
reponame := "repo1"
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame})
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
|
||||
i := &issues_model.Issue{
|
||||
RepoID: repo.ID,
|
||||
Repo: repo,
|
||||
Title: "title1",
|
||||
Content: "issuecontent1",
|
||||
IsPull: true,
|
||||
PosterID: owner.ID,
|
||||
Poster: owner,
|
||||
}
|
||||
|
||||
p := &issues_model.PullRequest{
|
||||
Issue: i,
|
||||
}
|
||||
|
||||
err := InsertPullRequests(db.DefaultContext, p)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_ = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{IssueID: i.ID})
|
||||
|
||||
unittest.CheckConsistencyFor(t, &issues_model.Issue{}, &issues_model.PullRequest{})
|
||||
}
|
||||
|
||||
func TestMigrate_InsertReleases(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
a := &repo_model.Attachment{
|
||||
UUID: "a0eebc91-9c0c-4ef7-bb6e-6bb9bd380a12",
|
||||
}
|
||||
r := &repo_model.Release{
|
||||
Attachments: []*repo_model.Attachment{a},
|
||||
}
|
||||
|
||||
err := InsertReleases(r)
|
||||
assert.NoError(t, err)
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMigrate_InsertReleases(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
a := &Attachment{
|
||||
UUID: "a0eebc91-9c0c-4ef7-bb6e-6bb9bd380a12",
|
||||
}
|
||||
r := &Release{
|
||||
Attachments: []*Attachment{a},
|
||||
}
|
||||
|
||||
err := InsertReleases(r)
|
||||
assert.NoError(t, err)
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2020 The Gitea Authors.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// DeleteCollaboration removes collaboration relation between the user and repository.
|
||||
func DeleteCollaboration(repo *repo_model.Repository, uid int64) (err error) {
|
||||
collaboration := &repo_model.Collaboration{
|
||||
RepoID: repo.ID,
|
||||
UserID: uid,
|
||||
}
|
||||
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
if has, err := db.GetEngine(ctx).Delete(collaboration); err != nil || has == 0 {
|
||||
return err
|
||||
} else if err = access_model.RecalculateAccesses(ctx, repo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = repo_model.WatchRepo(ctx, uid, repo.ID, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = reconsiderWatches(ctx, repo, uid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Unassign a user from any issue (s)he has been assigned to in the repository
|
||||
if err := reconsiderRepoIssuesAssignee(ctx, repo, uid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
func reconsiderRepoIssuesAssignee(ctx context.Context, repo *repo_model.Repository, uid int64) error {
|
||||
user, err := user_model.GetUserByID(ctx, uid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if canAssigned, err := access_model.CanBeAssigned(ctx, user, repo, true); err != nil || canAssigned {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := db.GetEngine(ctx).Where(builder.Eq{"assignee_id": uid}).
|
||||
In("issue_id", builder.Select("id").From("issue").Where(builder.Eq{"repo_id": repo.ID})).
|
||||
Delete(&issues_model.IssueAssignees{}); err != nil {
|
||||
return fmt.Errorf("Could not delete assignee[%d] %w", uid, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func reconsiderWatches(ctx context.Context, repo *repo_model.Repository, uid int64) error {
|
||||
if has, err := access_model.HasAccess(ctx, uid, repo); err != nil || has {
|
||||
return err
|
||||
}
|
||||
if err := repo_model.WatchRepo(ctx, uid, repo.ID, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove all IssueWatches a user has subscribed to in the repository
|
||||
return issues_model.RemoveIssueWatchersByRepoID(ctx, uid, repo.ID)
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2020 The Gitea Authors.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repository
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
)
|
||||
|
||||
// DeleteCollaboration removes collaboration relation between the user and repository.
|
||||
func DeleteCollaboration(repo *repo_model.Repository, uid int64) (err error) {
|
||||
collaboration := &repo_model.Collaboration{
|
||||
RepoID: repo.ID,
|
||||
UserID: uid,
|
||||
}
|
||||
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
if has, err := db.GetEngine(ctx).Delete(collaboration); err != nil || has == 0 {
|
||||
return err
|
||||
} else if err = access_model.RecalculateAccesses(ctx, repo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = repo_model.WatchRepo(ctx, uid, repo.ID, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = models.ReconsiderWatches(ctx, repo, uid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Unassign a user from any issue (s)he has been assigned to in the repository
|
||||
if err := models.ReconsiderRepoIssuesAssignee(ctx, repo, uid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return committer.Commit()
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package models
|
||||
package repository
|
||||
|
||||
import (
|
||||
"testing"
|
Loading…
Reference in new issue