mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	Fix issues/pr list broken when there are many repositories (#8409)
* fix issues/pr list broken when there are many repositories * remove unused codes * fix counting error on issues/prs * keep the old logic * fix panic * fix tests
This commit is contained in:
		| @@ -1306,18 +1306,19 @@ func GetIssuesByIDs(issueIDs []int64) ([]*Issue, error) { | |||||||
|  |  | ||||||
| // IssuesOptions represents options of an issue. | // IssuesOptions represents options of an issue. | ||||||
| type IssuesOptions struct { | type IssuesOptions struct { | ||||||
| 	RepoIDs     []int64 // include all repos if empty | 	RepoIDs      []int64 // include all repos if empty | ||||||
| 	AssigneeID  int64 | 	RepoSubQuery *builder.Builder | ||||||
| 	PosterID    int64 | 	AssigneeID   int64 | ||||||
| 	MentionedID int64 | 	PosterID     int64 | ||||||
| 	MilestoneID int64 | 	MentionedID  int64 | ||||||
| 	Page        int | 	MilestoneID  int64 | ||||||
| 	PageSize    int | 	Page         int | ||||||
| 	IsClosed    util.OptionalBool | 	PageSize     int | ||||||
| 	IsPull      util.OptionalBool | 	IsClosed     util.OptionalBool | ||||||
| 	LabelIDs    []int64 | 	IsPull       util.OptionalBool | ||||||
| 	SortType    string | 	LabelIDs     []int64 | ||||||
| 	IssueIDs    []int64 | 	SortType     string | ||||||
|  | 	IssueIDs     []int64 | ||||||
| } | } | ||||||
|  |  | ||||||
| // sortIssuesSession sort an issues-related session based on the provided | // sortIssuesSession sort an issues-related session based on the provided | ||||||
| @@ -1360,7 +1361,9 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) { | |||||||
| 		sess.In("issue.id", opts.IssueIDs) | 		sess.In("issue.id", opts.IssueIDs) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if len(opts.RepoIDs) > 0 { | 	if opts.RepoSubQuery != nil { | ||||||
|  | 		sess.In("issue.repo_id", opts.RepoSubQuery) | ||||||
|  | 	} else if len(opts.RepoIDs) > 0 { | ||||||
| 		// In case repository IDs are provided but actually no repository has issue. | 		// In case repository IDs are provided but actually no repository has issue. | ||||||
| 		sess.In("issue.repo_id", opts.RepoIDs) | 		sess.In("issue.repo_id", opts.RepoIDs) | ||||||
| 	} | 	} | ||||||
| @@ -1627,12 +1630,12 @@ func GetIssueStats(opts *IssueStatsOptions) (*IssueStats, error) { | |||||||
|  |  | ||||||
| // UserIssueStatsOptions contains parameters accepted by GetUserIssueStats. | // UserIssueStatsOptions contains parameters accepted by GetUserIssueStats. | ||||||
| type UserIssueStatsOptions struct { | type UserIssueStatsOptions struct { | ||||||
| 	UserID      int64 | 	UserID       int64 | ||||||
| 	RepoID      int64 | 	RepoID       int64 | ||||||
| 	UserRepoIDs []int64 | 	RepoSubQuery *builder.Builder | ||||||
| 	FilterMode  int | 	FilterMode   int | ||||||
| 	IsPull      bool | 	IsPull       bool | ||||||
| 	IsClosed    bool | 	IsClosed     bool | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetUserIssueStats returns issue statistic information for dashboard by given conditions. | // GetUserIssueStats returns issue statistic information for dashboard by given conditions. | ||||||
| @@ -1646,16 +1649,23 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) { | |||||||
| 		cond = cond.And(builder.Eq{"issue.repo_id": opts.RepoID}) | 		cond = cond.And(builder.Eq{"issue.repo_id": opts.RepoID}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	var repoCond = builder.NewCond() | ||||||
|  | 	if opts.RepoSubQuery != nil { | ||||||
|  | 		repoCond = builder.In("issue.repo_id", opts.RepoSubQuery) | ||||||
|  | 	} else { | ||||||
|  | 		repoCond = builder.Expr("0=1") | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	switch opts.FilterMode { | 	switch opts.FilterMode { | ||||||
| 	case FilterModeAll: | 	case FilterModeAll: | ||||||
| 		stats.OpenCount, err = x.Where(cond).And("is_closed = ?", false). | 		stats.OpenCount, err = x.Where(cond).And("is_closed = ?", false). | ||||||
| 			And(builder.In("issue.repo_id", opts.UserRepoIDs)). | 			And(repoCond). | ||||||
| 			Count(new(Issue)) | 			Count(new(Issue)) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 		stats.ClosedCount, err = x.Where(cond).And("is_closed = ?", true). | 		stats.ClosedCount, err = x.Where(cond).And("is_closed = ?", true). | ||||||
| 			And(builder.In("issue.repo_id", opts.UserRepoIDs)). | 			And(repoCond). | ||||||
| 			Count(new(Issue)) | 			Count(new(Issue)) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| @@ -1730,7 +1740,7 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	stats.YourRepositoriesCount, err = x.Where(cond). | 	stats.YourRepositoriesCount, err = x.Where(cond). | ||||||
| 		And(builder.In("issue.repo_id", opts.UserRepoIDs)). | 		And(repoCond). | ||||||
| 		Count(new(Issue)) | 		Count(new(Issue)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import ( | |||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	"xorm.io/builder" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestIssue_ReplaceLabels(t *testing.T) { | func TestIssue_ReplaceLabels(t *testing.T) { | ||||||
| @@ -266,10 +267,12 @@ func TestGetUserIssueStats(t *testing.T) { | |||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			UserIssueStatsOptions{ | 			UserIssueStatsOptions{ | ||||||
| 				UserID:      2, | 				UserID: 2, | ||||||
| 				UserRepoIDs: []int64{1, 2}, | 				RepoSubQuery: builder.Select("repository.id"). | ||||||
| 				FilterMode:  FilterModeAll, | 					From("repository"). | ||||||
| 				IsClosed:    true, | 					Where(builder.In("repository.id", []int64{1, 2})), | ||||||
|  | 				FilterMode: FilterModeAll, | ||||||
|  | 				IsClosed:   true, | ||||||
| 			}, | 			}, | ||||||
| 			IssueStats{ | 			IssueStats{ | ||||||
| 				YourRepositoriesCount: 2, | 				YourRepositoriesCount: 2, | ||||||
|   | |||||||
| @@ -615,50 +615,35 @@ func (u *User) GetRepositories(page, pageSize int) (err error) { | |||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetRepositoryIDs returns repositories IDs where user owned and has unittypes | // UnitRepositoriesSubQuery returns repositories query builder according units | ||||||
| func (u *User) GetRepositoryIDs(units ...UnitType) ([]int64, error) { | func (u *User) UnitRepositoriesSubQuery(units ...UnitType) *builder.Builder { | ||||||
| 	var ids []int64 | 	b := builder.Select("repository.id").From("repository") | ||||||
|  |  | ||||||
| 	sess := x.Table("repository").Cols("repository.id") |  | ||||||
|  |  | ||||||
| 	if len(units) > 0 { | 	if len(units) > 0 { | ||||||
| 		sess = sess.Join("INNER", "repo_unit", "repository.id = repo_unit.repo_id") | 		b.Join("INNER", "repo_unit", builder.Expr("repository.id = repo_unit.repo_id"). | ||||||
| 		sess = sess.In("repo_unit.type", units) | 			And(builder.In("repo_unit.type", units)), | ||||||
|  | 		) | ||||||
| 	} | 	} | ||||||
|  | 	return b.Where(builder.Eq{"repository.owner_id": u.ID}) | ||||||
| 	return ids, sess.Where("owner_id = ?", u.ID).Find(&ids) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetOrgRepositoryIDs returns repositories IDs where user's team owned and has unittypes | // OrgUnitRepositoriesSubQuery returns repositories query builder according orgnizations and units | ||||||
| func (u *User) GetOrgRepositoryIDs(units ...UnitType) ([]int64, error) { | func (u *User) OrgUnitRepositoriesSubQuery(userID int64, units ...UnitType) *builder.Builder { | ||||||
| 	var ids []int64 | 	b := builder. | ||||||
|  | 		Select("team_repo.repo_id"). | ||||||
| 	sess := x.Table("repository"). | 		From("team_repo"). | ||||||
| 		Cols("repository.id"). | 		Join("INNER", "team_user", builder.Eq{"team_user.uid": userID}.And( | ||||||
| 		Join("INNER", "team_user", "repository.owner_id = team_user.org_id"). | 			builder.Expr("team_user.team_id = team_repo.team_id"), | ||||||
| 		Join("INNER", "team_repo", "repository.is_private != ? OR (team_user.team_id = team_repo.team_id AND repository.id = team_repo.repo_id)", true) | 		)) | ||||||
|  |  | ||||||
| 	if len(units) > 0 { | 	if len(units) > 0 { | ||||||
| 		sess = sess.Join("INNER", "team_unit", "team_unit.team_id = team_user.team_id") | 		b.Join("INNER", "team_unit", builder.Eq{"team_unit.org_id": u.ID}.And( | ||||||
| 		sess = sess.In("team_unit.type", units) | 			builder.Expr("team_unit.team_id = team_repo.team_id").And( | ||||||
|  | 				builder.In("`type`", units), | ||||||
|  | 			), | ||||||
|  | 		)) | ||||||
| 	} | 	} | ||||||
|  | 	return b.Where(builder.Eq{"team_repo.org_id": u.ID}). | ||||||
| 	return ids, sess. | 		GroupBy("team_repo.repo_id") | ||||||
| 		Where("team_user.uid = ?", u.ID). |  | ||||||
| 		GroupBy("repository.id").Find(&ids) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // GetAccessRepoIDs returns all repositories IDs where user's or user is a team member organizations |  | ||||||
| func (u *User) GetAccessRepoIDs(units ...UnitType) ([]int64, error) { |  | ||||||
| 	ids, err := u.GetRepositoryIDs(units...) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	ids2, err := u.GetOrgRepositoryIDs(units...) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return append(ids, ids2...), nil |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetMirrorRepositories returns mirror repositories that user owns, including private repositories. | // GetMirrorRepositories returns mirror repositories that user owns, including private repositories. | ||||||
|   | |||||||
| @@ -275,28 +275,6 @@ func BenchmarkHashPassword(b *testing.B) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestGetOrgRepositoryIDs(t *testing.T) { |  | ||||||
| 	assert.NoError(t, PrepareTestDatabase()) |  | ||||||
| 	user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) |  | ||||||
| 	user4 := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) |  | ||||||
| 	user5 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) |  | ||||||
|  |  | ||||||
| 	accessibleRepos, err := user2.GetOrgRepositoryIDs() |  | ||||||
| 	assert.NoError(t, err) |  | ||||||
| 	// User 2's team has access to private repos 3, 5, repo 32 is a public repo of the organization |  | ||||||
| 	assert.Equal(t, []int64{3, 5, 23, 24, 32}, accessibleRepos) |  | ||||||
|  |  | ||||||
| 	accessibleRepos, err = user4.GetOrgRepositoryIDs() |  | ||||||
| 	assert.NoError(t, err) |  | ||||||
| 	// User 4's team has access to private repo 3, repo 32 is a public repo of the organization |  | ||||||
| 	assert.Equal(t, []int64{3, 32}, accessibleRepos) |  | ||||||
|  |  | ||||||
| 	accessibleRepos, err = user5.GetOrgRepositoryIDs() |  | ||||||
| 	assert.NoError(t, err) |  | ||||||
| 	// User 5's team has no access to any repo |  | ||||||
| 	assert.Len(t, accessibleRepos, 0) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestNewGitSig(t *testing.T) { | func TestNewGitSig(t *testing.T) { | ||||||
| 	users := make([]*User, 0, 20) | 	users := make([]*User, 0, 20) | ||||||
| 	sess := x.NewSession() | 	sess := x.NewSession() | ||||||
|   | |||||||
| @@ -14,13 +14,11 @@ import ( | |||||||
| 	"code.gitea.io/gitea/models" | 	"code.gitea.io/gitea/models" | ||||||
| 	"code.gitea.io/gitea/modules/base" | 	"code.gitea.io/gitea/modules/base" | ||||||
| 	"code.gitea.io/gitea/modules/context" | 	"code.gitea.io/gitea/modules/context" | ||||||
| 	"code.gitea.io/gitea/modules/log" |  | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/util" | 	"code.gitea.io/gitea/modules/util" | ||||||
|  |  | ||||||
| 	"github.com/keybase/go-crypto/openpgp" | 	"github.com/keybase/go-crypto/openpgp" | ||||||
| 	"github.com/keybase/go-crypto/openpgp/armor" | 	"github.com/keybase/go-crypto/openpgp/armor" | ||||||
| 	"github.com/unknwon/com" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| @@ -152,6 +150,24 @@ func Dashboard(ctx *context.Context) { | |||||||
| // Issues render the user issues page | // Issues render the user issues page | ||||||
| func Issues(ctx *context.Context) { | func Issues(ctx *context.Context) { | ||||||
| 	isPullList := ctx.Params(":type") == "pulls" | 	isPullList := ctx.Params(":type") == "pulls" | ||||||
|  | 	repoID := ctx.QueryInt64("repo") | ||||||
|  | 	if repoID > 0 { | ||||||
|  | 		repo, err := models.GetRepositoryByID(repoID) | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.ServerError("GetRepositoryByID", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		perm, err := models.GetUserRepoPermission(repo, ctx.User) | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.ServerError("GetUserRepoPermission", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		if !perm.CanReadIssuesOrPulls(isPullList) { | ||||||
|  | 			ctx.NotFound("Repository does not exist or you have no permission", nil) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if isPullList { | 	if isPullList { | ||||||
| 		ctx.Data["Title"] = ctx.Tr("pull_requests") | 		ctx.Data["Title"] = ctx.Tr("pull_requests") | ||||||
| 		ctx.Data["PageIsPulls"] = true | 		ctx.Data["PageIsPulls"] = true | ||||||
| @@ -194,58 +210,32 @@ func Issues(ctx *context.Context) { | |||||||
| 		page = 1 | 		page = 1 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	repoID := ctx.QueryInt64("repo") | 	var ( | ||||||
| 	isShowClosed := ctx.Query("state") == "closed" | 		isShowClosed = ctx.Query("state") == "closed" | ||||||
|  | 		err          error | ||||||
|  | 		opts         = &models.IssuesOptions{ | ||||||
|  | 			IsClosed: util.OptionalBoolOf(isShowClosed), | ||||||
|  | 			IsPull:   util.OptionalBoolOf(isPullList), | ||||||
|  | 			SortType: sortType, | ||||||
|  | 		} | ||||||
|  | 	) | ||||||
|  |  | ||||||
| 	// Get repositories. | 	// Get repositories. | ||||||
| 	var err error | 	if repoID > 0 { | ||||||
| 	var userRepoIDs []int64 | 		opts.RepoIDs = []int64{repoID} | ||||||
| 	if ctxUser.IsOrganization() { |  | ||||||
| 		env, err := ctxUser.AccessibleReposEnv(ctx.User.ID) |  | ||||||
| 		if err != nil { |  | ||||||
| 			ctx.ServerError("AccessibleReposEnv", err) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		userRepoIDs, err = env.RepoIDs(1, ctxUser.NumRepos) |  | ||||||
| 		if err != nil { |  | ||||||
| 			ctx.ServerError("env.RepoIDs", err) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	} else { | 	} else { | ||||||
| 		unitType := models.UnitTypeIssues | 		unitType := models.UnitTypeIssues | ||||||
| 		if isPullList { | 		if isPullList { | ||||||
| 			unitType = models.UnitTypePullRequests | 			unitType = models.UnitTypePullRequests | ||||||
| 		} | 		} | ||||||
| 		userRepoIDs, err = ctxUser.GetAccessRepoIDs(unitType) | 		if ctxUser.IsOrganization() { | ||||||
| 		if err != nil { | 			opts.RepoSubQuery = ctxUser.OrgUnitRepositoriesSubQuery(ctx.User.ID, unitType) | ||||||
| 			ctx.ServerError("ctxUser.GetAccessRepoIDs", err) | 		} else { | ||||||
| 			return | 			opts.RepoSubQuery = ctxUser.UnitRepositoriesSubQuery(unitType) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if len(userRepoIDs) == 0 { |  | ||||||
| 		userRepoIDs = []int64{-1} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	opts := &models.IssuesOptions{ |  | ||||||
| 		IsClosed: util.OptionalBoolOf(isShowClosed), |  | ||||||
| 		IsPull:   util.OptionalBoolOf(isPullList), |  | ||||||
| 		SortType: sortType, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if repoID > 0 { |  | ||||||
| 		opts.RepoIDs = []int64{repoID} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	switch filterMode { | 	switch filterMode { | ||||||
| 	case models.FilterModeAll: |  | ||||||
| 		if repoID > 0 { |  | ||||||
| 			if !com.IsSliceContainsInt64(userRepoIDs, repoID) { |  | ||||||
| 				// force an empty result |  | ||||||
| 				opts.RepoIDs = []int64{-1} |  | ||||||
| 			} |  | ||||||
| 		} else { |  | ||||||
| 			opts.RepoIDs = userRepoIDs |  | ||||||
| 		} |  | ||||||
| 	case models.FilterModeAssign: | 	case models.FilterModeAssign: | ||||||
| 		opts.AssigneeID = ctxUser.ID | 		opts.AssigneeID = ctxUser.ID | ||||||
| 	case models.FilterModeCreate: | 	case models.FilterModeCreate: | ||||||
| @@ -254,14 +244,6 @@ func Issues(ctx *context.Context) { | |||||||
| 		opts.MentionedID = ctxUser.ID | 		opts.MentionedID = ctxUser.ID | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	counts, err := models.CountIssuesByRepo(opts) |  | ||||||
| 	if err != nil { |  | ||||||
| 		ctx.ServerError("CountIssuesByRepo", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	opts.Page = page |  | ||||||
| 	opts.PageSize = setting.UI.IssuePagingNum |  | ||||||
| 	var labelIDs []int64 | 	var labelIDs []int64 | ||||||
| 	selectLabels := ctx.Query("labels") | 	selectLabels := ctx.Query("labels") | ||||||
| 	if len(selectLabels) > 0 && selectLabels != "0" { | 	if len(selectLabels) > 0 && selectLabels != "0" { | ||||||
| @@ -273,6 +255,15 @@ func Issues(ctx *context.Context) { | |||||||
| 	} | 	} | ||||||
| 	opts.LabelIDs = labelIDs | 	opts.LabelIDs = labelIDs | ||||||
|  |  | ||||||
|  | 	counts, err := models.CountIssuesByRepo(opts) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.ServerError("CountIssuesByRepo", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	opts.Page = page | ||||||
|  | 	opts.PageSize = setting.UI.IssuePagingNum | ||||||
|  |  | ||||||
| 	issues, err := models.Issues(opts) | 	issues, err := models.Issues(opts) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.ServerError("Issues", err) | 		ctx.ServerError("Issues", err) | ||||||
| @@ -289,41 +280,6 @@ func Issues(ctx *context.Context) { | |||||||
| 		showReposMap[repoID] = repo | 		showReposMap[repoID] = repo | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if repoID > 0 { |  | ||||||
| 		if _, ok := showReposMap[repoID]; !ok { |  | ||||||
| 			repo, err := models.GetRepositoryByID(repoID) |  | ||||||
| 			if models.IsErrRepoNotExist(err) { |  | ||||||
| 				ctx.NotFound("GetRepositoryByID", err) |  | ||||||
| 				return |  | ||||||
| 			} else if err != nil { |  | ||||||
| 				ctx.ServerError("GetRepositoryByID", fmt.Errorf("[%d]%v", repoID, err)) |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 			showReposMap[repoID] = repo |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		repo := showReposMap[repoID] |  | ||||||
|  |  | ||||||
| 		// Check if user has access to given repository. |  | ||||||
| 		perm, err := models.GetUserRepoPermission(repo, ctxUser) |  | ||||||
| 		if err != nil { |  | ||||||
| 			ctx.ServerError("GetUserRepoPermission", fmt.Errorf("[%d]%v", repoID, err)) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		if !perm.CanRead(models.UnitTypeIssues) { |  | ||||||
| 			if log.IsTrace() { |  | ||||||
| 				log.Trace("Permission Denied: User %-v cannot read %-v of repo %-v\n"+ |  | ||||||
| 					"User in repo has Permissions: %-+v", |  | ||||||
| 					ctxUser, |  | ||||||
| 					models.UnitTypeIssues, |  | ||||||
| 					repo, |  | ||||||
| 					perm) |  | ||||||
| 			} |  | ||||||
| 			ctx.Status(404) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	showRepos := models.RepositoryListOfMap(showReposMap) | 	showRepos := models.RepositoryListOfMap(showReposMap) | ||||||
| 	sort.Sort(showRepos) | 	sort.Sort(showRepos) | ||||||
| 	if err = showRepos.LoadAttributes(); err != nil { | 	if err = showRepos.LoadAttributes(); err != nil { | ||||||
| @@ -341,12 +297,12 @@ func Issues(ctx *context.Context) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	issueStats, err := models.GetUserIssueStats(models.UserIssueStatsOptions{ | 	issueStats, err := models.GetUserIssueStats(models.UserIssueStatsOptions{ | ||||||
| 		UserID:      ctxUser.ID, | 		UserID:       ctxUser.ID, | ||||||
| 		RepoID:      repoID, | 		RepoID:       repoID, | ||||||
| 		UserRepoIDs: userRepoIDs, | 		RepoSubQuery: opts.RepoSubQuery, | ||||||
| 		FilterMode:  filterMode, | 		FilterMode:   filterMode, | ||||||
| 		IsPull:      isPullList, | 		IsPull:       isPullList, | ||||||
| 		IsClosed:    isShowClosed, | 		IsClosed:     isShowClosed, | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.ServerError("GetUserIssueStats", err) | 		ctx.ServerError("GetUserIssueStats", err) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user