Skip to content

Commit 73cf0e2

Browse files
authored
Fix milestones too many SQL variables bug (#10880)
* Fix milestones too many SQL variables bug * Fix test * Don't display repositories with no milestone and fix tests * Remove unused code and add some comments
1 parent bf847b9 commit 73cf0e2

File tree

5 files changed

+135
-105
lines changed

5 files changed

+135
-105
lines changed

models/issue_milestone.go

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -525,10 +525,12 @@ func DeleteMilestoneByRepoID(repoID, id int64) error {
525525
return sess.Commit()
526526
}
527527

528-
// CountMilestonesByRepoIDs map from repoIDs to number of milestones matching the options`
529-
func CountMilestonesByRepoIDs(repoIDs []int64, isClosed bool) (map[int64]int64, error) {
528+
// CountMilestones map from repo conditions to number of milestones matching the options`
529+
func CountMilestones(repoCond builder.Cond, isClosed bool) (map[int64]int64, error) {
530530
sess := x.Where("is_closed = ?", isClosed)
531-
sess.In("repo_id", repoIDs)
531+
if repoCond.IsValid() {
532+
sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond))
533+
}
532534

533535
countsSlice := make([]*struct {
534536
RepoID int64
@@ -548,11 +550,21 @@ func CountMilestonesByRepoIDs(repoIDs []int64, isClosed bool) (map[int64]int64,
548550
return countMap, nil
549551
}
550552

551-
// GetMilestonesByRepoIDs returns a list of milestones of given repositories and status.
552-
func GetMilestonesByRepoIDs(repoIDs []int64, page int, isClosed bool, sortType string) (MilestoneList, error) {
553+
// CountMilestonesByRepoIDs map from repoIDs to number of milestones matching the options`
554+
func CountMilestonesByRepoIDs(repoIDs []int64, isClosed bool) (map[int64]int64, error) {
555+
return CountMilestones(
556+
builder.In("repo_id", repoIDs),
557+
isClosed,
558+
)
559+
}
560+
561+
// SearchMilestones search milestones
562+
func SearchMilestones(repoCond builder.Cond, page int, isClosed bool, sortType string) (MilestoneList, error) {
553563
miles := make([]*Milestone, 0, setting.UI.IssuePagingNum)
554564
sess := x.Where("is_closed = ?", isClosed)
555-
sess.In("repo_id", repoIDs)
565+
if repoCond.IsValid() {
566+
sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond))
567+
}
556568
if page > 0 {
557569
sess = sess.Limit(setting.UI.IssuePagingNum, (page-1)*setting.UI.IssuePagingNum)
558570
}
@@ -574,25 +586,45 @@ func GetMilestonesByRepoIDs(repoIDs []int64, page int, isClosed bool, sortType s
574586
return miles, sess.Find(&miles)
575587
}
576588

589+
// GetMilestonesByRepoIDs returns a list of milestones of given repositories and status.
590+
func GetMilestonesByRepoIDs(repoIDs []int64, page int, isClosed bool, sortType string) (MilestoneList, error) {
591+
return SearchMilestones(
592+
builder.In("repo_id", repoIDs),
593+
page,
594+
isClosed,
595+
sortType,
596+
)
597+
}
598+
577599
// MilestonesStats represents milestone statistic information.
578600
type MilestonesStats struct {
579601
OpenCount, ClosedCount int64
580602
}
581603

604+
// Total returns the total counts of milestones
605+
func (m MilestonesStats) Total() int64 {
606+
return m.OpenCount + m.ClosedCount
607+
}
608+
582609
// GetMilestonesStats returns milestone statistic information for dashboard by given conditions.
583-
func GetMilestonesStats(userRepoIDs []int64) (*MilestonesStats, error) {
610+
func GetMilestonesStats(repoCond builder.Cond) (*MilestonesStats, error) {
584611
var err error
585612
stats := &MilestonesStats{}
586613

587-
stats.OpenCount, err = x.Where("is_closed = ?", false).
588-
And(builder.In("repo_id", userRepoIDs)).
589-
Count(new(Milestone))
614+
sess := x.Where("is_closed = ?", false)
615+
if repoCond.IsValid() {
616+
sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond)))
617+
}
618+
stats.OpenCount, err = sess.Count(new(Milestone))
590619
if err != nil {
591620
return nil, err
592621
}
593-
stats.ClosedCount, err = x.Where("is_closed = ?", true).
594-
And(builder.In("repo_id", userRepoIDs)).
595-
Count(new(Milestone))
622+
623+
sess = x.Where("is_closed = ?", true)
624+
if repoCond.IsValid() {
625+
sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond)))
626+
}
627+
stats.ClosedCount, err = sess.Count(new(Milestone))
596628
if err != nil {
597629
return nil, err
598630
}

models/issue_milestone_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
api "code.gitea.io/gitea/modules/structs"
1313
"code.gitea.io/gitea/modules/timeutil"
14+
"xorm.io/builder"
1415

1516
"github.com/stretchr/testify/assert"
1617
)
@@ -370,7 +371,7 @@ func TestGetMilestonesStats(t *testing.T) {
370371
repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
371372
repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository)
372373

373-
milestoneStats, err := GetMilestonesStats([]int64{repo1.ID, repo2.ID})
374+
milestoneStats, err := GetMilestonesStats(builder.In("repo_id", []int64{repo1.ID, repo2.ID}))
374375
assert.NoError(t, err)
375376
assert.EqualValues(t, repo1.NumOpenMilestones+repo2.NumOpenMilestones, milestoneStats.OpenCount)
376377
assert.EqualValues(t, repo1.NumClosedMilestones+repo2.NumClosedMilestones, milestoneStats.ClosedCount)

models/repo_list.go

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ type SearchRepoOptions struct {
163163
TopicOnly bool
164164
// include description in keyword search
165165
IncludeDescription bool
166+
// None -> include has milestones AND has no milestone
167+
// True -> include just has milestones
168+
// False -> include just has no milestone
169+
HasMilestones util.OptionalBool
166170
}
167171

168172
//SearchOrderBy is used to sort the result
@@ -294,14 +298,26 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
294298
if opts.Actor != nil && opts.Actor.IsRestricted {
295299
cond = cond.And(accessibleRepositoryCondition(opts.Actor))
296300
}
301+
302+
switch opts.HasMilestones {
303+
case util.OptionalBoolTrue:
304+
cond = cond.And(builder.Gt{"num_milestones": 0})
305+
case util.OptionalBoolFalse:
306+
cond = cond.And(builder.Eq{"num_milestones": 0}.Or(builder.IsNull{"num_milestones"}))
307+
}
308+
297309
return cond
298310
}
299311

300312
// SearchRepository returns repositories based on search options,
301313
// it returns results in given range and number of total results.
302314
func SearchRepository(opts *SearchRepoOptions) (RepositoryList, int64, error) {
303315
cond := SearchRepositoryCondition(opts)
316+
return SearchRepositoryByCondition(opts, cond, true)
317+
}
304318

319+
// SearchRepositoryByCondition search repositories by condition
320+
func SearchRepositoryByCondition(opts *SearchRepoOptions, cond builder.Cond, loadAttributes bool) (RepositoryList, int64, error) {
305321
if opts.Page <= 0 {
306322
opts.Page = 1
307323
}
@@ -326,16 +342,18 @@ func SearchRepository(opts *SearchRepoOptions) (RepositoryList, int64, error) {
326342
}
327343

328344
repos := make(RepositoryList, 0, opts.PageSize)
329-
if err = sess.
330-
Where(cond).
331-
OrderBy(opts.OrderBy.String()).
332-
Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).
333-
Find(&repos); err != nil {
345+
sess.Where(cond).OrderBy(opts.OrderBy.String())
346+
if opts.PageSize > 0 {
347+
sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
348+
}
349+
if err = sess.Find(&repos); err != nil {
334350
return nil, 0, fmt.Errorf("Repo: %v", err)
335351
}
336352

337-
if err = repos.loadAttributes(sess); err != nil {
338-
return nil, 0, fmt.Errorf("LoadAttributes: %v", err)
353+
if loadAttributes {
354+
if err = repos.loadAttributes(sess); err != nil {
355+
return nil, 0, fmt.Errorf("LoadAttributes: %v", err)
356+
}
339357
}
340358

341359
return repos, count, nil

routers/user/home.go

Lines changed: 61 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import (
2525

2626
"github.com/keybase/go-crypto/openpgp"
2727
"github.com/keybase/go-crypto/openpgp/armor"
28-
"github.com/unknwon/com"
28+
"xorm.io/builder"
2929
)
3030

3131
const (
@@ -173,135 +173,114 @@ func Milestones(ctx *context.Context) {
173173
return
174174
}
175175

176-
sortType := ctx.Query("sort")
177-
page := ctx.QueryInt("page")
176+
var (
177+
repoOpts = models.SearchRepoOptions{
178+
Actor: ctxUser,
179+
OwnerID: ctxUser.ID,
180+
Private: true,
181+
AllPublic: false, // Include also all public repositories of users and public organisations
182+
AllLimited: false, // Include also all public repositories of limited organisations
183+
HasMilestones: util.OptionalBoolTrue, // Just needs display repos has milestones
184+
}
185+
186+
userRepoCond = models.SearchRepositoryCondition(&repoOpts) // all repo condition user could visit
187+
repoCond = userRepoCond
188+
repoIDs []int64
189+
190+
reposQuery = ctx.Query("repos")
191+
isShowClosed = ctx.Query("state") == "closed"
192+
sortType = ctx.Query("sort")
193+
page = ctx.QueryInt("page")
194+
)
195+
178196
if page <= 1 {
179197
page = 1
180198
}
181199

182-
reposQuery := ctx.Query("repos")
183-
isShowClosed := ctx.Query("state") == "closed"
184-
185-
// Get repositories.
186-
var err error
187-
var userRepoIDs []int64
188-
if ctxUser.IsOrganization() {
189-
env, err := ctxUser.AccessibleReposEnv(ctx.User.ID)
190-
if err != nil {
191-
ctx.ServerError("AccessibleReposEnv", err)
192-
return
193-
}
194-
userRepoIDs, err = env.RepoIDs(1, ctxUser.NumRepos)
195-
if err != nil {
196-
ctx.ServerError("env.RepoIDs", err)
197-
return
198-
}
199-
userRepoIDs, err = models.FilterOutRepoIdsWithoutUnitAccess(ctx.User, userRepoIDs, models.UnitTypeIssues, models.UnitTypePullRequests)
200-
if err != nil {
201-
ctx.ServerError("FilterOutRepoIdsWithoutUnitAccess", err)
202-
return
203-
}
204-
} else {
205-
userRepoIDs, err = ctxUser.GetAccessRepoIDs(models.UnitTypeIssues, models.UnitTypePullRequests)
206-
if err != nil {
207-
ctx.ServerError("ctxUser.GetAccessRepoIDs", err)
208-
return
209-
}
210-
}
211-
if len(userRepoIDs) == 0 {
212-
userRepoIDs = []int64{-1}
213-
}
214-
215-
var repoIDs []int64
216200
if len(reposQuery) != 0 {
217201
if issueReposQueryPattern.MatchString(reposQuery) {
218202
// remove "[" and "]" from string
219203
reposQuery = reposQuery[1 : len(reposQuery)-1]
220204
//for each ID (delimiter ",") add to int to repoIDs
221-
reposSet := false
205+
222206
for _, rID := range strings.Split(reposQuery, ",") {
223207
// Ensure nonempty string entries
224208
if rID != "" && rID != "0" {
225-
reposSet = true
226209
rIDint64, err := strconv.ParseInt(rID, 10, 64)
227210
// If the repo id specified by query is not parseable or not accessible by user, just ignore it.
228-
if err == nil && com.IsSliceContainsInt64(userRepoIDs, rIDint64) {
211+
if err == nil {
229212
repoIDs = append(repoIDs, rIDint64)
230213
}
231214
}
232215
}
233-
if reposSet && len(repoIDs) == 0 {
234-
// force an empty result
235-
repoIDs = []int64{-1}
216+
if len(repoIDs) > 0 {
217+
// Don't just let repoCond = builder.In("id", repoIDs) because user may has no permission on repoIDs
218+
// But the original repoCond has a limitation
219+
repoCond = repoCond.And(builder.In("id", repoIDs))
236220
}
237221
} else {
238222
log.Warn("issueReposQueryPattern not match with query")
239223
}
240224
}
241225

242-
if len(repoIDs) == 0 {
243-
repoIDs = userRepoIDs
244-
}
245-
246-
counts, err := models.CountMilestonesByRepoIDs(userRepoIDs, isShowClosed)
226+
counts, err := models.CountMilestones(userRepoCond, isShowClosed)
247227
if err != nil {
248228
ctx.ServerError("CountMilestonesByRepoIDs", err)
249229
return
250230
}
251231

252-
milestones, err := models.GetMilestonesByRepoIDs(repoIDs, page, isShowClosed, sortType)
232+
milestones, err := models.SearchMilestones(repoCond, page, isShowClosed, sortType)
253233
if err != nil {
254234
ctx.ServerError("GetMilestonesByRepoIDs", err)
255235
return
256236
}
257237

258-
showReposMap := make(map[int64]*models.Repository, len(counts))
259-
for rID := range counts {
260-
if rID == -1 {
261-
break
262-
}
263-
repo, err := models.GetRepositoryByID(rID)
264-
if err != nil {
265-
if models.IsErrRepoNotExist(err) {
266-
ctx.NotFound("GetRepositoryByID", err)
267-
return
268-
} else if err != nil {
269-
ctx.ServerError("GetRepositoryByID", fmt.Errorf("[%d]%v", rID, err))
270-
return
271-
}
272-
}
273-
showReposMap[rID] = repo
274-
}
275-
276-
showRepos := models.RepositoryListOfMap(showReposMap)
277-
sort.Sort(showRepos)
278-
if err = showRepos.LoadAttributes(); err != nil {
279-
ctx.ServerError("LoadAttributes", err)
238+
showRepos, _, err := models.SearchRepositoryByCondition(&repoOpts, userRepoCond, false)
239+
if err != nil {
240+
ctx.ServerError("SearchRepositoryByCondition", err)
280241
return
281242
}
243+
sort.Sort(showRepos)
244+
245+
for i := 0; i < len(milestones); {
246+
for _, repo := range showRepos {
247+
if milestones[i].RepoID == repo.ID {
248+
milestones[i].Repo = repo
249+
break
250+
}
251+
}
252+
if milestones[i].Repo == nil {
253+
log.Warn("Cannot find milestone %d 's repository %d", milestones[i].ID, milestones[i].RepoID)
254+
milestones = append(milestones[:i], milestones[i+1:]...)
255+
continue
256+
}
282257

283-
for _, m := range milestones {
284-
m.Repo = showReposMap[m.RepoID]
285-
m.RenderedContent = string(markdown.Render([]byte(m.Content), m.Repo.Link(), m.Repo.ComposeMetas()))
286-
if m.Repo.IsTimetrackerEnabled() {
287-
err := m.LoadTotalTrackedTime()
258+
milestones[i].RenderedContent = string(markdown.Render([]byte(milestones[i].Content), milestones[i].Repo.Link(), milestones[i].Repo.ComposeMetas()))
259+
if milestones[i].Repo.IsTimetrackerEnabled() {
260+
err := milestones[i].LoadTotalTrackedTime()
288261
if err != nil {
289262
ctx.ServerError("LoadTotalTrackedTime", err)
290263
return
291264
}
292265
}
266+
i++
293267
}
294268

295-
milestoneStats, err := models.GetMilestonesStats(repoIDs)
269+
milestoneStats, err := models.GetMilestonesStats(repoCond)
296270
if err != nil {
297271
ctx.ServerError("GetMilestoneStats", err)
298272
return
299273
}
300274

301-
totalMilestoneStats, err := models.GetMilestonesStats(userRepoIDs)
302-
if err != nil {
303-
ctx.ServerError("GetMilestoneStats", err)
304-
return
275+
var totalMilestoneStats *models.MilestonesStats
276+
if len(repoIDs) == 0 {
277+
totalMilestoneStats = milestoneStats
278+
} else {
279+
totalMilestoneStats, err = models.GetMilestonesStats(userRepoCond)
280+
if err != nil {
281+
ctx.ServerError("GetMilestoneStats", err)
282+
return
283+
}
305284
}
306285

307286
var pagerCount int
@@ -320,7 +299,7 @@ func Milestones(ctx *context.Context) {
320299
ctx.Data["Counts"] = counts
321300
ctx.Data["MilestoneStats"] = milestoneStats
322301
ctx.Data["SortType"] = sortType
323-
if len(repoIDs) != len(userRepoIDs) {
302+
if milestoneStats.Total() != totalMilestoneStats.Total() {
324303
ctx.Data["RepoIDs"] = repoIDs
325304
}
326305
ctx.Data["IsShowClosed"] = isShowClosed

0 commit comments

Comments
 (0)