Some refactors for issues stats (#24793)
This PR - [x] Move some functions from `issues.go` to `issue_stats.go` and `issue_label.go` - [x] Remove duplicated issue options `UserIssueStatsOption` to keep only one `IssuesOptions`
This commit is contained in:
@@ -8,10 +8,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
project_model "code.gitea.io/gitea/models/project"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
@@ -212,17 +210,6 @@ func (issue *Issue) GetPullRequest() (pr *PullRequest, err error) {
|
||||
return pr, err
|
||||
}
|
||||
|
||||
// LoadLabels loads labels
|
||||
func (issue *Issue) LoadLabels(ctx context.Context) (err error) {
|
||||
if issue.Labels == nil && issue.ID != 0 {
|
||||
issue.Labels, err = GetLabelsByIssueID(ctx, issue.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getLabelsByIssueID [%d]: %w", issue.ID, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadPoster loads poster
|
||||
func (issue *Issue) LoadPoster(ctx context.Context) (err error) {
|
||||
if issue.Poster == nil && issue.PosterID != 0 {
|
||||
@@ -459,175 +446,6 @@ func (issue *Issue) IsPoster(uid int64) bool {
|
||||
return issue.OriginalAuthorID == 0 && issue.PosterID == uid
|
||||
}
|
||||
|
||||
func (issue *Issue) getLabels(ctx context.Context) (err error) {
|
||||
if len(issue.Labels) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
issue.Labels, err = GetLabelsByIssueID(ctx, issue.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getLabelsByIssueID: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func clearIssueLabels(ctx context.Context, issue *Issue, doer *user_model.User) (err error) {
|
||||
if err = issue.getLabels(ctx); err != nil {
|
||||
return fmt.Errorf("getLabels: %w", err)
|
||||
}
|
||||
|
||||
for i := range issue.Labels {
|
||||
if err = deleteIssueLabel(ctx, issue, issue.Labels[i], doer); err != nil {
|
||||
return fmt.Errorf("removeLabel: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearIssueLabels removes all issue labels as the given user.
|
||||
// Triggers appropriate WebHooks, if any.
|
||||
func ClearIssueLabels(issue *Issue, doer *user_model.User) (err error) {
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
if err := issue.LoadRepo(ctx); err != nil {
|
||||
return err
|
||||
} else if err = issue.LoadPullRequest(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
perm, err := access_model.GetUserRepoPermission(ctx, issue.Repo, doer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !perm.CanWriteIssuesOrPulls(issue.IsPull) {
|
||||
return ErrRepoLabelNotExist{}
|
||||
}
|
||||
|
||||
if err = clearIssueLabels(ctx, issue, doer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = committer.Commit(); err != nil {
|
||||
return fmt.Errorf("Commit: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type labelSorter []*Label
|
||||
|
||||
func (ts labelSorter) Len() int {
|
||||
return len([]*Label(ts))
|
||||
}
|
||||
|
||||
func (ts labelSorter) Less(i, j int) bool {
|
||||
return []*Label(ts)[i].ID < []*Label(ts)[j].ID
|
||||
}
|
||||
|
||||
func (ts labelSorter) Swap(i, j int) {
|
||||
[]*Label(ts)[i], []*Label(ts)[j] = []*Label(ts)[j], []*Label(ts)[i]
|
||||
}
|
||||
|
||||
// Ensure only one label of a given scope exists, with labels at the end of the
|
||||
// array getting preference over earlier ones.
|
||||
func RemoveDuplicateExclusiveLabels(labels []*Label) []*Label {
|
||||
validLabels := make([]*Label, 0, len(labels))
|
||||
|
||||
for i, label := range labels {
|
||||
scope := label.ExclusiveScope()
|
||||
if scope != "" {
|
||||
foundOther := false
|
||||
for _, otherLabel := range labels[i+1:] {
|
||||
if otherLabel.ExclusiveScope() == scope {
|
||||
foundOther = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if foundOther {
|
||||
continue
|
||||
}
|
||||
}
|
||||
validLabels = append(validLabels, label)
|
||||
}
|
||||
|
||||
return validLabels
|
||||
}
|
||||
|
||||
// ReplaceIssueLabels removes all current labels and add new labels to the issue.
|
||||
// Triggers appropriate WebHooks, if any.
|
||||
func ReplaceIssueLabels(issue *Issue, labels []*Label, doer *user_model.User) (err error) {
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
if err = issue.LoadRepo(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = issue.LoadLabels(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
labels = RemoveDuplicateExclusiveLabels(labels)
|
||||
|
||||
sort.Sort(labelSorter(labels))
|
||||
sort.Sort(labelSorter(issue.Labels))
|
||||
|
||||
var toAdd, toRemove []*Label
|
||||
|
||||
addIndex, removeIndex := 0, 0
|
||||
for addIndex < len(labels) && removeIndex < len(issue.Labels) {
|
||||
addLabel := labels[addIndex]
|
||||
removeLabel := issue.Labels[removeIndex]
|
||||
if addLabel.ID == removeLabel.ID {
|
||||
// Silently drop invalid labels
|
||||
if removeLabel.RepoID != issue.RepoID && removeLabel.OrgID != issue.Repo.OwnerID {
|
||||
toRemove = append(toRemove, removeLabel)
|
||||
}
|
||||
|
||||
addIndex++
|
||||
removeIndex++
|
||||
} else if addLabel.ID < removeLabel.ID {
|
||||
// Only add if the label is valid
|
||||
if addLabel.RepoID == issue.RepoID || addLabel.OrgID == issue.Repo.OwnerID {
|
||||
toAdd = append(toAdd, addLabel)
|
||||
}
|
||||
addIndex++
|
||||
} else {
|
||||
toRemove = append(toRemove, removeLabel)
|
||||
removeIndex++
|
||||
}
|
||||
}
|
||||
toAdd = append(toAdd, labels[addIndex:]...)
|
||||
toRemove = append(toRemove, issue.Labels[removeIndex:]...)
|
||||
|
||||
if len(toAdd) > 0 {
|
||||
if err = newIssueLabels(ctx, issue, toAdd, doer); err != nil {
|
||||
return fmt.Errorf("addLabels: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, l := range toRemove {
|
||||
if err = deleteIssueLabel(ctx, issue, l, doer); err != nil {
|
||||
return fmt.Errorf("removeLabel: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
issue.Labels = nil
|
||||
if err = issue.LoadLabels(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
// GetTasks returns the amount of tasks in the issues content
|
||||
func (issue *Issue) GetTasks() int {
|
||||
return len(issueTasksPat.FindAllStringIndex(issue.Content, -1))
|
||||
@@ -862,16 +680,6 @@ func (issue *Issue) GetExternalName() string { return issue.OriginalAuthor }
|
||||
// GetExternalID ExternalUserRemappable interface
|
||||
func (issue *Issue) GetExternalID() int64 { return issue.OriginalAuthorID }
|
||||
|
||||
// CountOrphanedIssues count issues without a repo
|
||||
func CountOrphanedIssues(ctx context.Context) (int64, error) {
|
||||
return db.GetEngine(ctx).
|
||||
Table("issue").
|
||||
Join("LEFT", "repository", "issue.repo_id=repository.id").
|
||||
Where(builder.IsNull{"repository.id"}).
|
||||
Select("COUNT(`issue`.`id`)").
|
||||
Count()
|
||||
}
|
||||
|
||||
// HasOriginalAuthor returns if an issue was migrated and has an original author.
|
||||
func (issue *Issue) HasOriginalAuthor() bool {
|
||||
return issue.OriginalAuthor != "" && issue.OriginalAuthorID != 0
|
||||
|
490
models/issues/issue_label.go
Normal file
490
models/issues/issue_label.go
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
383
models/issues/issue_stats.go
Normal file
383
models/issues/issue_stats.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,7 @@ import (
|
||||
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/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/builder"
|
||||
@@ -204,14 +205,16 @@ func TestIssues(t *testing.T) {
|
||||
func TestGetUserIssueStats(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
for _, test := range []struct {
|
||||
Opts issues_model.UserIssueStatsOptions
|
||||
FilterMode int
|
||||
Opts issues_model.IssuesOptions
|
||||
ExpectedIssueStats issues_model.IssueStats
|
||||
}{
|
||||
{
|
||||
issues_model.UserIssueStatsOptions{
|
||||
UserID: 1,
|
||||
RepoIDs: []int64{1},
|
||||
FilterMode: issues_model.FilterModeAll,
|
||||
issues_model.FilterModeAll,
|
||||
issues_model.IssuesOptions{
|
||||
User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}),
|
||||
RepoIDs: []int64{1},
|
||||
IsPull: util.OptionalBoolFalse,
|
||||
},
|
||||
issues_model.IssueStats{
|
||||
YourRepositoriesCount: 1, // 6
|
||||
@@ -222,11 +225,12 @@ func TestGetUserIssueStats(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
issues_model.UserIssueStatsOptions{
|
||||
UserID: 1,
|
||||
RepoIDs: []int64{1},
|
||||
FilterMode: issues_model.FilterModeAll,
|
||||
IsClosed: true,
|
||||
issues_model.FilterModeAll,
|
||||
issues_model.IssuesOptions{
|
||||
User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}),
|
||||
RepoIDs: []int64{1},
|
||||
IsPull: util.OptionalBoolFalse,
|
||||
IsClosed: util.OptionalBoolTrue,
|
||||
},
|
||||
issues_model.IssueStats{
|
||||
YourRepositoriesCount: 1, // 6
|
||||
@@ -237,9 +241,10 @@ func TestGetUserIssueStats(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
issues_model.UserIssueStatsOptions{
|
||||
UserID: 1,
|
||||
FilterMode: issues_model.FilterModeAssign,
|
||||
issues_model.FilterModeAssign,
|
||||
issues_model.IssuesOptions{
|
||||
User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}),
|
||||
IsPull: util.OptionalBoolFalse,
|
||||
},
|
||||
issues_model.IssueStats{
|
||||
YourRepositoriesCount: 1, // 6
|
||||
@@ -250,9 +255,10 @@ func TestGetUserIssueStats(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
issues_model.UserIssueStatsOptions{
|
||||
UserID: 1,
|
||||
FilterMode: issues_model.FilterModeCreate,
|
||||
issues_model.FilterModeCreate,
|
||||
issues_model.IssuesOptions{
|
||||
User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}),
|
||||
IsPull: util.OptionalBoolFalse,
|
||||
},
|
||||
issues_model.IssueStats{
|
||||
YourRepositoriesCount: 1, // 6
|
||||
@@ -263,9 +269,10 @@ func TestGetUserIssueStats(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
issues_model.UserIssueStatsOptions{
|
||||
UserID: 1,
|
||||
FilterMode: issues_model.FilterModeMention,
|
||||
issues_model.FilterModeMention,
|
||||
issues_model.IssuesOptions{
|
||||
User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}),
|
||||
IsPull: util.OptionalBoolFalse,
|
||||
},
|
||||
issues_model.IssueStats{
|
||||
YourRepositoriesCount: 1, // 6
|
||||
@@ -277,10 +284,11 @@ func TestGetUserIssueStats(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
issues_model.UserIssueStatsOptions{
|
||||
UserID: 1,
|
||||
FilterMode: issues_model.FilterModeCreate,
|
||||
IssueIDs: []int64{1},
|
||||
issues_model.FilterModeCreate,
|
||||
issues_model.IssuesOptions{
|
||||
User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}),
|
||||
IssueIDs: []int64{1},
|
||||
IsPull: util.OptionalBoolFalse,
|
||||
},
|
||||
issues_model.IssueStats{
|
||||
YourRepositoriesCount: 1, // 1
|
||||
@@ -291,11 +299,12 @@ func TestGetUserIssueStats(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
issues_model.UserIssueStatsOptions{
|
||||
UserID: 2,
|
||||
Org: unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}),
|
||||
Team: unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 7}),
|
||||
FilterMode: issues_model.FilterModeAll,
|
||||
issues_model.FilterModeAll,
|
||||
issues_model.IssuesOptions{
|
||||
User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}),
|
||||
Org: unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}),
|
||||
Team: unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 7}),
|
||||
IsPull: util.OptionalBoolFalse,
|
||||
},
|
||||
issues_model.IssueStats{
|
||||
YourRepositoriesCount: 2,
|
||||
@@ -306,7 +315,7 @@ func TestGetUserIssueStats(t *testing.T) {
|
||||
},
|
||||
} {
|
||||
t.Run(fmt.Sprintf("%#v", test.Opts), func(t *testing.T) {
|
||||
stats, err := issues_model.GetUserIssueStats(test.Opts)
|
||||
stats, err := issues_model.GetUserIssueStats(test.FilterMode, test.Opts)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
@@ -495,7 +504,7 @@ func TestCorrectIssueStats(t *testing.T) {
|
||||
// Now we will call the GetIssueStats with these IDs and if working,
|
||||
// get the correct stats back.
|
||||
issueStats, err := issues_model.GetIssueStats(&issues_model.IssuesOptions{
|
||||
RepoID: 1,
|
||||
RepoIDs: []int64{1},
|
||||
IssueIDs: ids,
|
||||
})
|
||||
|
||||
|
@@ -81,7 +81,7 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
|
||||
}
|
||||
|
||||
// Update issue count of labels
|
||||
if err := issue.getLabels(ctx); err != nil {
|
||||
if err := issue.LoadLabels(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for idx := range issue.Labels {
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -302,7 +302,7 @@ func populateIssueIndexer(ctx context.Context) {
|
||||
// UpdateRepoIndexer add/update all issues of the repositories
|
||||
func UpdateRepoIndexer(ctx context.Context, repo *repo_model.Repository) {
|
||||
is, err := issues_model.Issues(ctx, &issues_model.IssuesOptions{
|
||||
RepoID: repo.ID,
|
||||
RepoIDs: []int64{repo.ID},
|
||||
IsClosed: util.OptionalBoolNone,
|
||||
IsPull: util.OptionalBoolNone,
|
||||
})
|
||||
|
@@ -470,7 +470,7 @@ func ListIssues(ctx *context.APIContext) {
|
||||
if len(keyword) == 0 || len(issueIDs) > 0 || len(labelIDs) > 0 {
|
||||
issuesOpt := &issues_model.IssuesOptions{
|
||||
ListOptions: listOptions,
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
RepoIDs: []int64{ctx.Repo.Repository.ID},
|
||||
IsClosed: isClosed,
|
||||
IssueIDs: issueIDs,
|
||||
LabelIDs: labelIDs,
|
||||
|
@@ -207,7 +207,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
|
||||
issueStats = &issues_model.IssueStats{}
|
||||
} else {
|
||||
issueStats, err = issues_model.GetIssueStats(&issues_model.IssuesOptions{
|
||||
RepoID: repo.ID,
|
||||
RepoIDs: []int64{repo.ID},
|
||||
LabelIDs: labelIDs,
|
||||
MilestoneIDs: []int64{milestoneID},
|
||||
ProjectID: projectID,
|
||||
@@ -258,7 +258,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
|
||||
Page: pager.Paginater.Current(),
|
||||
PageSize: setting.UI.IssuePagingNum,
|
||||
},
|
||||
RepoID: repo.ID,
|
||||
RepoIDs: []int64{repo.ID},
|
||||
AssigneeID: assigneeID,
|
||||
PosterID: posterID,
|
||||
MentionedID: mentionedID,
|
||||
@@ -2652,7 +2652,7 @@ func ListIssues(ctx *context.Context) {
|
||||
if len(keyword) == 0 || len(issueIDs) > 0 || len(labelIDs) > 0 {
|
||||
issuesOpt := &issues_model.IssuesOptions{
|
||||
ListOptions: listOptions,
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
RepoIDs: []int64{ctx.Repo.Repository.ID},
|
||||
IsClosed: isClosed,
|
||||
IssueIDs: issueIDs,
|
||||
LabelIDs: labelIDs,
|
||||
|
@@ -521,10 +521,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
||||
|
||||
// Parse ctx.FormString("repos") and remember matched repo IDs for later.
|
||||
// Gets set when clicking filters on the issues overview page.
|
||||
repoIDs := getRepoIDs(ctx.FormString("repos"))
|
||||
if len(repoIDs) > 0 {
|
||||
opts.RepoCond = builder.In("issue.repo_id", repoIDs)
|
||||
}
|
||||
opts.RepoIDs = getRepoIDs(ctx.FormString("repos"))
|
||||
|
||||
// ------------------------------
|
||||
// Get issues as defined by opts.
|
||||
@@ -580,11 +577,10 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
||||
// -------------------------------
|
||||
var issueStats *issues_model.IssueStats
|
||||
if !forceEmpty {
|
||||
statsOpts := issues_model.UserIssueStatsOptions{
|
||||
UserID: ctx.Doer.ID,
|
||||
FilterMode: filterMode,
|
||||
IsPull: isPullList,
|
||||
IsClosed: isShowClosed,
|
||||
statsOpts := issues_model.IssuesOptions{
|
||||
User: ctx.Doer,
|
||||
IsPull: util.OptionalBoolOf(isPullList),
|
||||
IsClosed: util.OptionalBoolOf(isShowClosed),
|
||||
IssueIDs: issueIDsFromSearch,
|
||||
IsArchived: util.OptionalBoolFalse,
|
||||
LabelIDs: opts.LabelIDs,
|
||||
@@ -593,7 +589,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
||||
RepoCond: opts.RepoCond,
|
||||
}
|
||||
|
||||
issueStats, err = issues_model.GetUserIssueStats(statsOpts)
|
||||
issueStats, err = issues_model.GetUserIssueStats(filterMode, statsOpts)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserIssueStats Shown", err)
|
||||
return
|
||||
@@ -609,9 +605,9 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
||||
} else {
|
||||
shownIssues = int(issueStats.ClosedCount)
|
||||
}
|
||||
if len(repoIDs) != 0 {
|
||||
if len(opts.RepoIDs) != 0 {
|
||||
shownIssues = 0
|
||||
for _, repoID := range repoIDs {
|
||||
for _, repoID := range opts.RepoIDs {
|
||||
shownIssues += int(issueCountByRepo[repoID])
|
||||
}
|
||||
}
|
||||
@@ -622,8 +618,8 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
||||
}
|
||||
ctx.Data["TotalIssueCount"] = allIssueCount
|
||||
|
||||
if len(repoIDs) == 1 {
|
||||
repo := showReposMap[repoIDs[0]]
|
||||
if len(opts.RepoIDs) == 1 {
|
||||
repo := showReposMap[opts.RepoIDs[0]]
|
||||
if repo != nil {
|
||||
ctx.Data["SingleRepoLink"] = repo.Link()
|
||||
}
|
||||
@@ -665,7 +661,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
||||
ctx.Data["IssueStats"] = issueStats
|
||||
ctx.Data["ViewType"] = viewType
|
||||
ctx.Data["SortType"] = sortType
|
||||
ctx.Data["RepoIDs"] = repoIDs
|
||||
ctx.Data["RepoIDs"] = opts.RepoIDs
|
||||
ctx.Data["IsShowClosed"] = isShowClosed
|
||||
ctx.Data["SelectLabels"] = selectedLabels
|
||||
|
||||
@@ -676,7 +672,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
||||
}
|
||||
|
||||
// Convert []int64 to string
|
||||
reposParam, _ := json.Marshal(repoIDs)
|
||||
reposParam, _ := json.Marshal(opts.RepoIDs)
|
||||
|
||||
ctx.Data["ReposParam"] = string(reposParam)
|
||||
|
||||
|
@@ -104,7 +104,7 @@ func TestGiteaUploadRepo(t *testing.T) {
|
||||
assert.Len(t, releases, 1)
|
||||
|
||||
issues, err := issues_model.Issues(db.DefaultContext, &issues_model.IssuesOptions{
|
||||
RepoID: repo.ID,
|
||||
RepoIDs: []int64{repo.ID},
|
||||
IsPull: util.OptionalBoolFalse,
|
||||
SortType: "oldest",
|
||||
})
|
||||
|
Reference in New Issue
Block a user