2014-03-22 17:50:50 +00:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2018-11-28 11:26:14 +00:00
// Copyright 2018 The Gitea Authors. All rights reserved.
2014-03-22 17:50:50 +00:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package repo
2014-07-26 06:28:04 +00:00
import (
2017-01-25 02:43:02 +00:00
"bytes"
2022-04-28 11:48:48 +00:00
stdCtx "context"
2014-07-26 06:28:04 +00:00
"errors"
"fmt"
2021-09-22 05:38:34 +00:00
"io"
2022-01-21 17:59:26 +00:00
"math/big"
2018-07-17 21:23:58 +00:00
"net/http"
2021-11-16 18:18:25 +00:00
"net/url"
2020-09-11 14:48:39 +00:00
"path"
2017-03-15 01:10:35 +00:00
"strconv"
2014-07-26 06:28:04 +00:00
"strings"
2022-04-07 18:59:56 +00:00
"time"
2014-07-26 06:28:04 +00:00
2016-11-10 16:24:48 +00:00
"code.gitea.io/gitea/models"
2021-09-24 11:32:56 +00:00
"code.gitea.io/gitea/models/db"
2022-03-31 09:20:39 +00:00
issues_model "code.gitea.io/gitea/models/issues"
2022-03-29 06:29:02 +00:00
"code.gitea.io/gitea/models/organization"
2022-03-29 14:16:31 +00:00
project_model "code.gitea.io/gitea/models/project"
2021-11-19 13:39:57 +00:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-09 19:57:58 +00:00
"code.gitea.io/gitea/models/unit"
2021-11-24 09:49:20 +00:00
user_model "code.gitea.io/gitea/models/user"
2016-11-10 16:24:48 +00:00
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
2020-10-17 04:23:08 +00:00
"code.gitea.io/gitea/modules/convert"
2019-03-27 09:33:00 +00:00
"code.gitea.io/gitea/modules/git"
2019-02-21 00:54:05 +00:00
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
2016-11-10 16:24:48 +00:00
"code.gitea.io/gitea/modules/log"
2019-12-07 04:21:18 +00:00
"code.gitea.io/gitea/modules/markup"
2017-09-21 05:20:14 +00:00
"code.gitea.io/gitea/modules/markup/markdown"
2016-11-10 16:24:48 +00:00
"code.gitea.io/gitea/modules/setting"
2019-06-06 00:37:45 +00:00
api "code.gitea.io/gitea/modules/structs"
2022-04-01 08:47:50 +00:00
"code.gitea.io/gitea/modules/templates/vars"
2022-04-07 18:59:56 +00:00
"code.gitea.io/gitea/modules/timeutil"
2020-10-05 05:49:33 +00:00
"code.gitea.io/gitea/modules/upload"
2017-01-25 02:43:02 +00:00
"code.gitea.io/gitea/modules/util"
2021-01-26 15:36:53 +00:00
"code.gitea.io/gitea/modules/web"
2021-12-10 08:14:24 +00:00
asymkey_service "code.gitea.io/gitea/services/asymkey"
2019-09-24 17:39:50 +00:00
comment_service "code.gitea.io/gitea/services/comments"
2021-04-06 19:44:05 +00:00
"code.gitea.io/gitea/services/forms"
2019-09-30 13:50:44 +00:00
issue_service "code.gitea.io/gitea/services/issue"
2019-12-07 02:44:10 +00:00
pull_service "code.gitea.io/gitea/services/pull"
2014-07-26 06:28:04 +00:00
)
const (
2019-10-15 12:19:32 +00:00
tplAttachment base . TplName = "repo/issue/view_content/attachments"
2020-09-11 14:48:39 +00:00
tplIssues base . TplName = "repo/issue/list"
tplIssueNew base . TplName = "repo/issue/new"
tplIssueChoose base . TplName = "repo/issue/choose"
tplIssueView base . TplName = "repo/issue/view"
2014-07-26 06:28:04 +00:00
2017-12-03 23:14:26 +00:00
tplReactions base . TplName = "repo/issue/view_content/reactions"
2020-09-11 14:48:39 +00:00
issueTemplateKey = "IssueTemplate"
issueTemplateTitleKey = "IssueTemplateTitle"
2014-07-26 06:28:04 +00:00
)
2022-01-20 17:46:10 +00:00
// IssueTemplateCandidates issue templates
var IssueTemplateCandidates = [ ] string {
"ISSUE_TEMPLATE.md" ,
"issue_template.md" ,
".gitea/ISSUE_TEMPLATE.md" ,
".gitea/issue_template.md" ,
".github/ISSUE_TEMPLATE.md" ,
".github/issue_template.md" ,
}
2014-07-26 06:28:04 +00:00
2019-02-18 20:55:04 +00:00
// MustAllowUserComment checks to make sure if an issue is locked.
// If locked and user has permissions to write to the repository,
// then the comment is allowed, else it is blocked
func MustAllowUserComment ( ctx * context . Context ) {
issue := GetActionIssue ( ctx )
if ctx . Written ( ) {
return
}
2022-03-22 07:03:22 +00:00
if issue . IsLocked && ! ctx . Repo . CanWriteIssuesOrPulls ( issue . IsPull ) && ! ctx . Doer . IsAdmin {
2019-02-18 20:55:04 +00:00
ctx . Flash . Error ( ctx . Tr ( "repo.issues.comment_on_locked" ) )
ctx . Redirect ( issue . HTMLURL ( ) )
return
}
}
2016-11-24 07:04:31 +00:00
// MustEnableIssues check if repository enable internal issues
2016-03-11 16:56:52 +00:00
func MustEnableIssues ( ctx * context . Context ) {
2021-11-09 19:57:58 +00:00
if ! ctx . Repo . CanRead ( unit . TypeIssues ) &&
! ctx . Repo . CanRead ( unit . TypeExternalTracker ) {
2018-01-10 21:34:17 +00:00
ctx . NotFound ( "MustEnableIssues" , nil )
2016-03-07 04:57:46 +00:00
return
2015-12-05 02:30:33 +00:00
}
2016-11-04 08:06:54 +00:00
2021-11-09 19:57:58 +00:00
unit , err := ctx . Repo . Repository . GetUnit ( unit . TypeExternalTracker )
2017-02-04 15:53:46 +00:00
if err == nil {
ctx . Redirect ( unit . ExternalTrackerConfig ( ) . ExternalTrackerURL )
2016-11-04 08:06:54 +00:00
return
}
2015-12-05 02:30:33 +00:00
}
2018-11-28 11:26:14 +00:00
// MustAllowPulls check if repository enable pull requests and user have right to do that
2016-03-11 16:56:52 +00:00
func MustAllowPulls ( ctx * context . Context ) {
2021-11-09 19:57:58 +00:00
if ! ctx . Repo . Repository . CanEnablePulls ( ) || ! ctx . Repo . CanRead ( unit . TypePullRequests ) {
2018-01-10 21:34:17 +00:00
ctx . NotFound ( "MustAllowPulls" , nil )
2016-03-07 04:57:46 +00:00
return
2015-12-05 02:30:33 +00:00
}
2015-12-20 03:07:06 +00:00
2016-03-07 04:57:46 +00:00
// User can send pull request if owns a forked repository.
2022-03-22 07:03:22 +00:00
if ctx . IsSigned && repo_model . HasForkedRepo ( ctx . Doer . ID , ctx . Repo . Repository . ID ) {
2016-03-07 04:57:46 +00:00
ctx . Repo . PullRequest . Allowed = true
2022-03-22 07:03:22 +00:00
ctx . Repo . PullRequest . HeadInfoSubURL = url . PathEscape ( ctx . Doer . Name ) + ":" + util . PathEscapeSegments ( ctx . Repo . BranchName )
2016-03-07 04:57:46 +00:00
}
2015-12-05 02:30:33 +00:00
}
2020-08-17 03:07:38 +00:00
func issues ( ctx * context . Context , milestoneID , projectID int64 , isPullOption util . OptionalBool ) {
2018-11-29 01:46:30 +00:00
var err error
2021-08-11 00:31:13 +00:00
viewType := ctx . FormString ( "type" )
sortType := ctx . FormString ( "sort" )
2021-01-17 16:34:19 +00:00
types := [ ] string { "all" , "your_repositories" , "assigned" , "created_by" , "mentioned" , "review_requested" }
2020-12-25 09:59:32 +00:00
if ! util . IsStringInSlice ( viewType , types , true ) {
2014-07-26 06:28:04 +00:00
viewType = "all"
}
2015-08-15 04:07:08 +00:00
var (
2021-07-29 01:42:15 +00:00
assigneeID = ctx . FormInt64 ( "assignee" )
2021-01-17 16:34:19 +00:00
posterID int64
mentionedID int64
reviewRequestedID int64
forceEmpty bool
2015-08-15 04:07:08 +00:00
)
2014-07-26 06:28:04 +00:00
2017-06-15 03:09:03 +00:00
if ctx . IsSigned {
switch viewType {
case "created_by" :
2022-03-22 07:03:22 +00:00
posterID = ctx . Doer . ID
2017-06-15 03:09:03 +00:00
case "mentioned" :
2022-03-22 07:03:22 +00:00
mentionedID = ctx . Doer . ID
2020-11-19 21:39:55 +00:00
case "assigned" :
2022-03-22 07:03:22 +00:00
assigneeID = ctx . Doer . ID
2021-01-17 16:34:19 +00:00
case "review_requested" :
2022-03-22 07:03:22 +00:00
reviewRequestedID = ctx . Doer . ID
2017-06-15 03:09:03 +00:00
}
}
2015-07-24 18:52:25 +00:00
repo := ctx . Repo . Repository
2019-01-23 04:10:38 +00:00
var labelIDs [ ] int64
2021-08-11 00:31:13 +00:00
selectLabels := ctx . FormString ( "labels" )
2019-01-23 04:10:38 +00:00
if len ( selectLabels ) > 0 && selectLabels != "0" {
labelIDs , err = base . StringsToInt64s ( strings . Split ( selectLabels , "," ) )
if err != nil {
ctx . ServerError ( "StringsToInt64s" , err )
return
}
}
2016-12-24 10:33:21 +00:00
2021-08-11 00:31:13 +00:00
keyword := strings . Trim ( ctx . FormString ( "q" ) , " " )
2017-01-25 02:43:02 +00:00
if bytes . Contains ( [ ] byte ( keyword ) , [ ] byte { 0x00 } ) {
keyword = ""
}
var issueIDs [ ] int64
if len ( keyword ) > 0 {
2022-01-27 08:30:51 +00:00
issueIDs , err = issue_indexer . SearchIssuesByKeyword ( ctx , [ ] int64 { repo . ID } , keyword )
2019-02-19 14:39:39 +00:00
if err != nil {
2022-01-27 08:30:51 +00:00
if issue_indexer . IsAvailable ( ) {
ctx . ServerError ( "issueIndexer.Search" , err )
return
}
ctx . Data [ "IssueIndexerUnavailable" ] = true
2019-02-19 14:39:39 +00:00
}
2017-01-25 02:43:02 +00:00
if len ( issueIDs ) == 0 {
forceEmpty = true
}
}
2016-12-24 10:33:21 +00:00
var issueStats * models . IssueStats
if forceEmpty {
issueStats = & models . IssueStats { }
} else {
2017-01-25 02:43:02 +00:00
issueStats , err = models . GetIssueStats ( & models . IssueStatsOptions {
2021-01-17 16:34:19 +00:00
RepoID : repo . ID ,
Labels : selectLabels ,
MilestoneID : milestoneID ,
AssigneeID : assigneeID ,
MentionedID : mentionedID ,
PosterID : posterID ,
ReviewRequestedID : reviewRequestedID ,
IsPull : isPullOption ,
IssueIDs : issueIDs ,
2016-12-24 10:33:21 +00:00
} )
2017-01-25 02:43:02 +00:00
if err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "GetIssueStats" , err )
2017-01-25 02:43:02 +00:00
return
}
2016-12-24 10:33:21 +00:00
}
2020-07-09 21:13:06 +00:00
2021-08-11 00:31:13 +00:00
isShowClosed := ctx . FormString ( "state" ) == "closed"
2020-07-09 21:13:06 +00:00
// if open issues are zero and close don't, use closed as default
2021-08-11 00:31:13 +00:00
if len ( ctx . FormString ( "state" ) ) == 0 && issueStats . OpenCount == 0 && issueStats . ClosedCount != 0 {
2020-07-09 21:13:06 +00:00
isShowClosed = true
}
2021-07-29 01:42:15 +00:00
page := ctx . FormInt ( "page" )
2015-07-24 08:42:47 +00:00
if page <= 1 {
page = 1
}
2015-07-27 19:14:37 +00:00
var total int
if ! isShowClosed {
total = int ( issueStats . OpenCount )
} else {
total = int ( issueStats . ClosedCount )
2015-07-24 08:42:47 +00:00
}
2019-04-20 04:15:19 +00:00
pager := context . NewPagination ( total , setting . UI . IssuePagingNum , page , 5 )
2014-07-26 06:28:04 +00:00
2020-04-30 04:15:39 +00:00
var mileIDs [ ] int64
if milestoneID > 0 {
mileIDs = [ ] int64 { milestoneID }
}
2016-12-24 10:33:21 +00:00
var issues [ ] * models . Issue
if forceEmpty {
issues = [ ] * models . Issue { }
} else {
issues , err = models . Issues ( & models . IssuesOptions {
2021-09-24 11:32:56 +00:00
ListOptions : db . ListOptions {
2020-01-24 19:00:29 +00:00
Page : pager . Paginater . Current ( ) ,
PageSize : setting . UI . IssuePagingNum ,
} ,
2022-04-25 14:06:24 +00:00
RepoID : repo . ID ,
2021-01-17 16:34:19 +00:00
AssigneeID : assigneeID ,
PosterID : posterID ,
MentionedID : mentionedID ,
ReviewRequestedID : reviewRequestedID ,
MilestoneIDs : mileIDs ,
ProjectID : projectID ,
IsClosed : util . OptionalBoolOf ( isShowClosed ) ,
IsPull : isPullOption ,
LabelIDs : labelIDs ,
SortType : sortType ,
IssueIDs : issueIDs ,
2016-12-24 10:33:21 +00:00
} )
if err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "Issues" , err )
2016-12-24 10:33:21 +00:00
return
}
2014-07-26 06:28:04 +00:00
}
2022-01-20 17:46:10 +00:00
issueList := models . IssueList ( issues )
2021-04-15 17:34:43 +00:00
approvalCounts , err := issueList . GetApprovalCounts ( )
2020-03-06 03:44:06 +00:00
if err != nil {
ctx . ServerError ( "ApprovalCounts" , err )
return
}
2014-07-26 06:28:04 +00:00
// Get posters.
for i := range issues {
2017-02-03 07:22:39 +00:00
// Check read status
2015-07-24 18:52:25 +00:00
if ! ctx . IsSigned {
issues [ i ] . IsRead = true
2022-03-22 07:03:22 +00:00
} else if err = issues [ i ] . GetIsRead ( ctx . Doer . ID ) ; err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "GetIsRead" , err )
2017-02-03 07:22:39 +00:00
return
2014-07-26 06:28:04 +00:00
}
2021-04-15 17:34:43 +00:00
}
2019-04-02 19:54:29 +00:00
2022-04-26 22:40:01 +00:00
commitStatuses , lastStatus , err := pull_service . GetIssuesAllCommitStatus ( ctx , issues )
2021-04-15 17:34:43 +00:00
if err != nil {
2022-04-26 22:40:01 +00:00
ctx . ServerError ( "GetIssuesAllCommitStatus" , err )
2021-04-15 17:34:43 +00:00
return
2014-07-26 06:28:04 +00:00
}
2019-04-02 19:54:29 +00:00
2015-08-05 12:23:08 +00:00
ctx . Data [ "Issues" ] = issues
2022-04-26 22:40:01 +00:00
ctx . Data [ "CommitLastStatus" ] = lastStatus
ctx . Data [ "CommitStatuses" ] = commitStatuses
2015-08-05 12:23:08 +00:00
2018-11-29 01:46:30 +00:00
// Get assignees.
2021-12-10 01:27:50 +00:00
ctx . Data [ "Assignees" ] , err = models . GetRepoAssignees ( repo )
2015-08-05 12:23:08 +00:00
if err != nil {
2018-11-29 01:46:30 +00:00
ctx . ServerError ( "GetAssignees" , err )
2015-08-05 12:23:08 +00:00
return
}
2015-08-15 03:24:41 +00:00
2020-12-21 15:39:28 +00:00
handleTeamMentions ( ctx )
if ctx . Written ( ) {
return
}
2021-09-24 11:32:56 +00:00
labels , err := models . GetLabelsByRepoID ( repo . ID , "" , db . ListOptions { } )
2015-08-15 03:24:41 +00:00
if err != nil {
2018-11-29 01:46:30 +00:00
ctx . ServerError ( "GetLabelsByRepoID" , err )
2015-08-15 03:24:41 +00:00
return
}
Add Organization Wide Labels (#10814)
* Add organization wide labels
Implement organization wide labels similar to organization wide
webhooks. This lets you create individual labels for organizations that can be used
for all repos under that organization (so being able to reuse the same
label across multiple repos).
This makes it possible for small organizations with many repos to use
labels effectively.
Fixes #7406
* Add migration
* remove comments
* fix tests
* Update options/locale/locale_en-US.ini
Removed unused translation string
* show org labels in issue search label filter
* Use more clear var name
* rename migration after merge from master
* comment typo
* update migration again after rebase with master
* check for orgID <=0 per guillep2k review
* fmt
* Apply suggestions from code review
Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com>
* remove unused code
* Make sure RepoID is 0 when searching orgID per code review
* more changes/code review requests
* More descriptive translation var per code review
* func description/delete comment when issue label deleted instead of hiding it
* remove comment
* only use issues in that repo when calculating number of open issues for org label on repo label page
* Add integration test for IssuesSearch API with labels
* remove unused function
* Update models/issue_label.go
Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com>
* Use subquery in GetLabelIDsInReposByNames
* Fix tests to use correct orgID
* fix more tests
* IssuesSearch api now uses new BuildLabelNamesIssueIDsCondition. Add a few more tests as well
* update comment for clarity
* Revert previous code change now that we can use the new BuildLabelNamesIssueIDsCondition
* Don't sort repos by date in IssuesSearch API
After much debugging I've found a strange issue where in some cases MySQL will return a different result than other enigines if a query is sorted by a null collumn. For example with our integration test data where we don't set updated_unix in repository fixtures:
SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 45
Returns different results for MySQL than other engines. However, the similar query:
SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 30
Returns the same results.
This causes integration tests to fail on MySQL in certain cases but would never show up in a real installation. Since this API call always returns issues based on the optionally provided repo_priority_id or the issueID itself, there is no change to results by changing the repo sorting method used to get ids earlier in the function.
* linter is back!
* code review
* remove now unused option
* Fix newline at end of files
* more unused code
* update to master
* check for matching ids before query
* Update models/issue_label.go
Co-Authored-By: 6543 <6543@obermui.de>
* Update models/issue_label.go
* update comments
* Update routers/org/setting.go
Co-authored-by: Lauris BH <lauris@nix.lv>
Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com>
Co-authored-by: 6543 <6543@obermui.de>
2020-04-01 04:14:46 +00:00
if repo . Owner . IsOrganization ( ) {
2021-09-24 11:32:56 +00:00
orgLabels , err := models . GetLabelsByOrgID ( repo . Owner . ID , ctx . FormString ( "sort" ) , db . ListOptions { } )
Add Organization Wide Labels (#10814)
* Add organization wide labels
Implement organization wide labels similar to organization wide
webhooks. This lets you create individual labels for organizations that can be used
for all repos under that organization (so being able to reuse the same
label across multiple repos).
This makes it possible for small organizations with many repos to use
labels effectively.
Fixes #7406
* Add migration
* remove comments
* fix tests
* Update options/locale/locale_en-US.ini
Removed unused translation string
* show org labels in issue search label filter
* Use more clear var name
* rename migration after merge from master
* comment typo
* update migration again after rebase with master
* check for orgID <=0 per guillep2k review
* fmt
* Apply suggestions from code review
Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com>
* remove unused code
* Make sure RepoID is 0 when searching orgID per code review
* more changes/code review requests
* More descriptive translation var per code review
* func description/delete comment when issue label deleted instead of hiding it
* remove comment
* only use issues in that repo when calculating number of open issues for org label on repo label page
* Add integration test for IssuesSearch API with labels
* remove unused function
* Update models/issue_label.go
Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com>
* Use subquery in GetLabelIDsInReposByNames
* Fix tests to use correct orgID
* fix more tests
* IssuesSearch api now uses new BuildLabelNamesIssueIDsCondition. Add a few more tests as well
* update comment for clarity
* Revert previous code change now that we can use the new BuildLabelNamesIssueIDsCondition
* Don't sort repos by date in IssuesSearch API
After much debugging I've found a strange issue where in some cases MySQL will return a different result than other enigines if a query is sorted by a null collumn. For example with our integration test data where we don't set updated_unix in repository fixtures:
SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 45
Returns different results for MySQL than other engines. However, the similar query:
SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 30
Returns the same results.
This causes integration tests to fail on MySQL in certain cases but would never show up in a real installation. Since this API call always returns issues based on the optionally provided repo_priority_id or the issueID itself, there is no change to results by changing the repo sorting method used to get ids earlier in the function.
* linter is back!
* code review
* remove now unused option
* Fix newline at end of files
* more unused code
* update to master
* check for matching ids before query
* Update models/issue_label.go
Co-Authored-By: 6543 <6543@obermui.de>
* Update models/issue_label.go
* update comments
* Update routers/org/setting.go
Co-authored-by: Lauris BH <lauris@nix.lv>
Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com>
Co-authored-by: 6543 <6543@obermui.de>
2020-04-01 04:14:46 +00:00
if err != nil {
ctx . ServerError ( "GetLabelsByOrgID" , err )
return
}
ctx . Data [ "OrgLabels" ] = orgLabels
labels = append ( labels , orgLabels ... )
}
2019-01-23 04:10:38 +00:00
for _ , l := range labels {
l . LoadSelectedLabelsAfterClick ( labelIDs )
}
2018-11-29 01:46:30 +00:00
ctx . Data [ "Labels" ] = labels
2019-01-23 04:10:38 +00:00
ctx . Data [ "NumLabels" ] = len ( labels )
2014-07-26 06:28:04 +00:00
2021-07-29 01:42:15 +00:00
if ctx . FormInt64 ( "assignee" ) == 0 {
2016-07-17 01:25:30 +00:00
assigneeID = 0 // Reset ID to prevent unexpected selection of assignee.
}
2022-01-20 17:46:10 +00:00
ctx . Data [ "IssueRefEndNames" ] , ctx . Data [ "IssueRefURLs" ] = issue_service . GetRefEndNamesAndURLs ( issues , ctx . Repo . RepoLink )
2020-05-14 22:55:43 +00:00
2020-03-06 03:44:06 +00:00
ctx . Data [ "ApprovalCounts" ] = func ( issueID int64 , typ string ) int64 {
counts , ok := approvalCounts [ issueID ]
if ! ok || len ( counts ) == 0 {
return 0
}
reviewTyp := models . ReviewTypeApprove
if typ == "reject" {
reviewTyp = models . ReviewTypeReject
2020-04-06 16:33:34 +00:00
} else if typ == "waiting" {
reviewTyp = models . ReviewTypeRequest
2020-03-06 03:44:06 +00:00
}
for _ , count := range counts {
if count . Type == reviewTyp {
return count . Count
}
}
return 0
}
2021-10-07 22:00:02 +00:00
if ctx . Repo . CanWriteIssuesOrPulls ( ctx . Params ( ":type" ) == "pulls" ) {
2022-03-29 14:16:31 +00:00
projects , _ , err := project_model . GetProjects ( project_model . SearchOptions {
2021-10-07 22:00:02 +00:00
RepoID : repo . ID ,
2022-03-29 14:16:31 +00:00
Type : project_model . TypeRepository ,
2021-10-07 22:00:02 +00:00
IsClosed : util . OptionalBoolOf ( isShowClosed ) ,
} )
if err != nil {
ctx . ServerError ( "GetProjects" , err )
return
}
ctx . Data [ "Projects" ] = projects
}
2014-07-26 06:28:04 +00:00
ctx . Data [ "IssueStats" ] = issueStats
2019-12-28 14:43:46 +00:00
ctx . Data [ "SelLabelIDs" ] = labelIDs
ctx . Data [ "SelectLabels" ] = selectLabels
2014-07-26 06:28:04 +00:00
ctx . Data [ "ViewType" ] = viewType
2015-08-15 04:07:08 +00:00
ctx . Data [ "SortType" ] = sortType
2015-08-05 12:23:08 +00:00
ctx . Data [ "MilestoneID" ] = milestoneID
2015-08-15 03:24:41 +00:00
ctx . Data [ "AssigneeID" ] = assigneeID
2014-07-26 06:28:04 +00:00
ctx . Data [ "IsShowClosed" ] = isShowClosed
2017-01-25 02:43:02 +00:00
ctx . Data [ "Keyword" ] = keyword
2014-07-26 06:28:04 +00:00
if isShowClosed {
ctx . Data [ "State" ] = "closed"
} else {
2015-07-24 18:52:25 +00:00
ctx . Data [ "State" ] = "open"
2014-07-26 06:28:04 +00:00
}
2019-04-20 04:15:19 +00:00
pager . AddParam ( ctx , "q" , "Keyword" )
pager . AddParam ( ctx , "type" , "ViewType" )
pager . AddParam ( ctx , "sort" , "SortType" )
pager . AddParam ( ctx , "state" , "State" )
pager . AddParam ( ctx , "labels" , "SelectLabels" )
pager . AddParam ( ctx , "milestone" , "MilestoneID" )
pager . AddParam ( ctx , "assignee" , "AssigneeID" )
ctx . Data [ "Page" ] = pager
2018-11-29 01:46:30 +00:00
}
// Issues render issues page
func Issues ( ctx * context . Context ) {
isPullList := ctx . Params ( ":type" ) == "pulls"
if isPullList {
MustAllowPulls ( ctx )
if ctx . Written ( ) {
return
}
ctx . Data [ "Title" ] = ctx . Tr ( "repo.pulls" )
ctx . Data [ "PageIsPullList" ] = true
} else {
MustEnableIssues ( ctx )
if ctx . Written ( ) {
return
}
ctx . Data [ "Title" ] = ctx . Tr ( "repo.issues" )
ctx . Data [ "PageIsIssueList" ] = true
2020-09-11 14:48:39 +00:00
ctx . Data [ "NewIssueChooseTemplate" ] = len ( ctx . IssueTemplatesFromDefaultBranch ( ) ) > 0
2018-11-29 01:46:30 +00:00
}
2021-07-29 01:42:15 +00:00
issues ( ctx , ctx . FormInt64 ( "milestone" ) , ctx . FormInt64 ( "project" ) , util . OptionalBoolOf ( isPullList ) )
2021-05-09 02:33:49 +00:00
if ctx . Written ( ) {
return
}
2018-11-29 01:46:30 +00:00
var err error
2020-07-28 11:30:40 +00:00
// Get milestones
2022-04-08 09:11:15 +00:00
ctx . Data [ "Milestones" ] , _ , err = issues_model . GetMilestones ( issues_model . GetMilestonesOption {
2020-07-28 11:30:40 +00:00
RepoID : ctx . Repo . Repository . ID ,
2021-08-11 00:31:13 +00:00
State : api . StateType ( ctx . FormString ( "state" ) ) ,
2020-07-28 11:30:40 +00:00
} )
2018-11-29 01:46:30 +00:00
if err != nil {
ctx . ServerError ( "GetAllRepoMilestones" , err )
return
}
2015-07-24 18:52:25 +00:00
2020-01-16 14:18:30 +00:00
ctx . Data [ "CanWriteIssuesOrPulls" ] = ctx . Repo . CanWriteIssuesOrPulls ( isPullList )
2019-02-19 23:09:47 +00:00
2021-04-05 15:30:52 +00:00
ctx . HTML ( http . StatusOK , tplIssues )
2014-07-26 06:28:04 +00:00
}
2016-11-24 07:04:31 +00:00
// RetrieveRepoMilestonesAndAssignees find all the milestones and assignees of a repository
2021-12-10 01:27:50 +00:00
func RetrieveRepoMilestonesAndAssignees ( ctx * context . Context , repo * repo_model . Repository ) {
2015-09-01 23:07:02 +00:00
var err error
2022-04-08 09:11:15 +00:00
ctx . Data [ "OpenMilestones" ] , _ , err = issues_model . GetMilestones ( issues_model . GetMilestonesOption {
2020-07-28 11:30:40 +00:00
RepoID : repo . ID ,
State : api . StateOpen ,
} )
2015-09-01 23:07:02 +00:00
if err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "GetMilestones" , err )
2015-09-01 23:07:02 +00:00
return
}
2022-04-08 09:11:15 +00:00
ctx . Data [ "ClosedMilestones" ] , _ , err = issues_model . GetMilestones ( issues_model . GetMilestonesOption {
2020-07-28 11:30:40 +00:00
RepoID : repo . ID ,
State : api . StateClosed ,
} )
2015-09-01 23:07:02 +00:00
if err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "GetMilestones" , err )
2015-09-01 23:07:02 +00:00
return
}
2021-12-10 01:27:50 +00:00
ctx . Data [ "Assignees" ] , err = models . GetRepoAssignees ( repo )
2015-09-01 23:07:02 +00:00
if err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "GetAssignees" , err )
2015-09-01 23:07:02 +00:00
return
}
2020-12-21 15:39:28 +00:00
handleTeamMentions ( ctx )
2015-09-01 23:07:02 +00:00
}
2021-12-10 01:27:50 +00:00
func retrieveProjects ( ctx * context . Context , repo * repo_model . Repository ) {
2020-08-17 03:07:38 +00:00
var err error
2022-03-29 14:16:31 +00:00
ctx . Data [ "OpenProjects" ] , _ , err = project_model . GetProjects ( project_model . SearchOptions {
2020-08-17 03:07:38 +00:00
RepoID : repo . ID ,
Page : - 1 ,
IsClosed : util . OptionalBoolFalse ,
2022-03-29 14:16:31 +00:00
Type : project_model . TypeRepository ,
2020-08-17 03:07:38 +00:00
} )
if err != nil {
ctx . ServerError ( "GetProjects" , err )
return
}
2022-03-29 14:16:31 +00:00
ctx . Data [ "ClosedProjects" ] , _ , err = project_model . GetProjects ( project_model . SearchOptions {
2020-08-17 03:07:38 +00:00
RepoID : repo . ID ,
Page : - 1 ,
IsClosed : util . OptionalBoolTrue ,
2022-03-29 14:16:31 +00:00
Type : project_model . TypeRepository ,
2020-08-17 03:07:38 +00:00
} )
if err != nil {
ctx . ServerError ( "GetProjects" , err )
return
}
}
2020-10-12 19:55:13 +00:00
// repoReviewerSelection items to bee shown
type repoReviewerSelection struct {
IsTeam bool
2022-03-29 06:29:02 +00:00
Team * organization . Team
2021-11-24 09:49:20 +00:00
User * user_model . User
2020-10-12 19:55:13 +00:00
Review * models . Review
CanChange bool
Checked bool
ItemID int64
}
2020-04-06 16:33:34 +00:00
// RetrieveRepoReviewers find all reviewers of a repository
2021-12-10 01:27:50 +00:00
func RetrieveRepoReviewers ( ctx * context . Context , repo * repo_model . Repository , issue * models . Issue , canChooseReviewer bool ) {
2020-10-12 19:55:13 +00:00
ctx . Data [ "CanChooseReviewer" ] = canChooseReviewer
2020-10-14 12:11:11 +00:00
originalAuthorReviews , err := models . GetReviewersFromOriginalAuthorsByIssueID ( issue . ID )
if err != nil {
ctx . ServerError ( "GetReviewersFromOriginalAuthorsByIssueID" , err )
return
}
ctx . Data [ "OriginalReviews" ] = originalAuthorReviews
2020-10-12 19:55:13 +00:00
reviews , err := models . GetReviewersByIssueID ( issue . ID )
2020-04-06 16:33:34 +00:00
if err != nil {
2020-10-12 19:55:13 +00:00
ctx . ServerError ( "GetReviewersByIssueID" , err )
return
}
if len ( reviews ) == 0 && ! canChooseReviewer {
2020-04-06 16:33:34 +00:00
return
}
2020-10-12 19:55:13 +00:00
var (
pullReviews [ ] * repoReviewerSelection
reviewersResult [ ] * repoReviewerSelection
teamReviewersResult [ ] * repoReviewerSelection
2022-03-29 06:29:02 +00:00
teamReviewers [ ] * organization . Team
2021-11-24 09:49:20 +00:00
reviewers [ ] * user_model . User
2020-10-12 19:55:13 +00:00
)
if canChooseReviewer {
posterID := issue . PosterID
if issue . OriginalAuthorID > 0 {
posterID = 0
}
2022-03-22 07:03:22 +00:00
reviewers , err = models . GetReviewers ( repo , ctx . Doer . ID , posterID )
2020-10-12 19:55:13 +00:00
if err != nil {
ctx . ServerError ( "GetReviewers" , err )
return
}
2021-12-10 01:27:50 +00:00
teamReviewers , err = models . GetReviewerTeams ( repo )
2020-10-12 19:55:13 +00:00
if err != nil {
ctx . ServerError ( "GetReviewerTeams" , err )
return
}
if len ( reviewers ) > 0 {
reviewersResult = make ( [ ] * repoReviewerSelection , 0 , len ( reviewers ) )
}
if len ( teamReviewers ) > 0 {
teamReviewersResult = make ( [ ] * repoReviewerSelection , 0 , len ( teamReviewers ) )
}
}
pullReviews = make ( [ ] * repoReviewerSelection , 0 , len ( reviews ) )
for _ , review := range reviews {
tmp := & repoReviewerSelection {
Checked : review . Type == models . ReviewTypeRequest ,
Review : review ,
ItemID : review . ReviewerID ,
}
if review . ReviewerTeamID > 0 {
tmp . IsTeam = true
tmp . ItemID = - review . ReviewerTeamID
}
if ctx . Repo . IsAdmin ( ) {
// Admin can dismiss or re-request any review requests
tmp . CanChange = true
2022-03-22 07:03:22 +00:00
} else if ctx . Doer != nil && ctx . Doer . ID == review . ReviewerID && review . Type == models . ReviewTypeRequest {
2020-10-12 19:55:13 +00:00
// A user can refuse review requests
tmp . CanChange = true
2022-03-22 07:03:22 +00:00
} else if ( canChooseReviewer || ( ctx . Doer != nil && ctx . Doer . ID == issue . PosterID ) ) && review . Type != models . ReviewTypeRequest &&
ctx . Doer . ID != review . ReviewerID {
2020-10-12 19:55:13 +00:00
// The poster of the PR, a manager, or official reviewers can re-request review from other reviewers
tmp . CanChange = true
}
pullReviews = append ( pullReviews , tmp )
if canChooseReviewer {
if tmp . IsTeam {
teamReviewersResult = append ( teamReviewersResult , tmp )
} else {
reviewersResult = append ( reviewersResult , tmp )
}
}
}
if len ( pullReviews ) > 0 {
// Drop all non-existing users and teams from the reviews
currentPullReviewers := make ( [ ] * repoReviewerSelection , 0 , len ( pullReviews ) )
for _ , item := range pullReviews {
if item . Review . ReviewerID > 0 {
if err = item . Review . LoadReviewer ( ) ; err != nil {
2021-11-24 09:49:20 +00:00
if user_model . IsErrUserNotExist ( err ) {
2020-10-12 19:55:13 +00:00
continue
}
ctx . ServerError ( "LoadReviewer" , err )
return
}
item . User = item . Review . Reviewer
} else if item . Review . ReviewerTeamID > 0 {
if err = item . Review . LoadReviewerTeam ( ) ; err != nil {
2022-03-29 06:29:02 +00:00
if organization . IsErrTeamNotExist ( err ) {
2020-10-12 19:55:13 +00:00
continue
}
ctx . ServerError ( "LoadReviewerTeam" , err )
return
}
item . Team = item . Review . ReviewerTeam
} else {
continue
}
currentPullReviewers = append ( currentPullReviewers , item )
}
ctx . Data [ "PullReviewers" ] = currentPullReviewers
}
if canChooseReviewer && reviewersResult != nil {
preadded := len ( reviewersResult )
for _ , reviewer := range reviewers {
found := false
reviewAddLoop :
for _ , tmp := range reviewersResult [ : preadded ] {
if tmp . ItemID == reviewer . ID {
tmp . User = reviewer
found = true
break reviewAddLoop
}
}
if found {
continue
}
reviewersResult = append ( reviewersResult , & repoReviewerSelection {
IsTeam : false ,
CanChange : true ,
User : reviewer ,
ItemID : reviewer . ID ,
} )
}
ctx . Data [ "Reviewers" ] = reviewersResult
}
if canChooseReviewer && teamReviewersResult != nil {
preadded := len ( teamReviewersResult )
for _ , team := range teamReviewers {
found := false
teamReviewAddLoop :
for _ , tmp := range teamReviewersResult [ : preadded ] {
if tmp . ItemID == - team . ID {
tmp . Team = team
found = true
break teamReviewAddLoop
}
}
if found {
continue
}
teamReviewersResult = append ( teamReviewersResult , & repoReviewerSelection {
IsTeam : true ,
CanChange : true ,
Team : team ,
ItemID : - team . ID ,
} )
}
ctx . Data [ "TeamReviewers" ] = teamReviewersResult
}
2020-04-06 16:33:34 +00:00
}
2016-11-24 07:04:31 +00:00
// RetrieveRepoMetas find all the meta information of a repository
2021-12-10 01:27:50 +00:00
func RetrieveRepoMetas ( ctx * context . Context , repo * repo_model . Repository , isPull bool ) [ ] * models . Label {
2020-01-20 12:00:32 +00:00
if ! ctx . Repo . CanWriteIssuesOrPulls ( isPull ) {
2015-08-31 07:24:28 +00:00
return nil
}
2021-09-24 11:32:56 +00:00
labels , err := models . GetLabelsByRepoID ( repo . ID , "" , db . ListOptions { } )
2015-08-31 07:24:28 +00:00
if err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "GetLabelsByRepoID" , err )
2015-08-31 07:24:28 +00:00
return nil
}
ctx . Data [ "Labels" ] = labels
Add Organization Wide Labels (#10814)
* Add organization wide labels
Implement organization wide labels similar to organization wide
webhooks. This lets you create individual labels for organizations that can be used
for all repos under that organization (so being able to reuse the same
label across multiple repos).
This makes it possible for small organizations with many repos to use
labels effectively.
Fixes #7406
* Add migration
* remove comments
* fix tests
* Update options/locale/locale_en-US.ini
Removed unused translation string
* show org labels in issue search label filter
* Use more clear var name
* rename migration after merge from master
* comment typo
* update migration again after rebase with master
* check for orgID <=0 per guillep2k review
* fmt
* Apply suggestions from code review
Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com>
* remove unused code
* Make sure RepoID is 0 when searching orgID per code review
* more changes/code review requests
* More descriptive translation var per code review
* func description/delete comment when issue label deleted instead of hiding it
* remove comment
* only use issues in that repo when calculating number of open issues for org label on repo label page
* Add integration test for IssuesSearch API with labels
* remove unused function
* Update models/issue_label.go
Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com>
* Use subquery in GetLabelIDsInReposByNames
* Fix tests to use correct orgID
* fix more tests
* IssuesSearch api now uses new BuildLabelNamesIssueIDsCondition. Add a few more tests as well
* update comment for clarity
* Revert previous code change now that we can use the new BuildLabelNamesIssueIDsCondition
* Don't sort repos by date in IssuesSearch API
After much debugging I've found a strange issue where in some cases MySQL will return a different result than other enigines if a query is sorted by a null collumn. For example with our integration test data where we don't set updated_unix in repository fixtures:
SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 45
Returns different results for MySQL than other engines. However, the similar query:
SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 30
Returns the same results.
This causes integration tests to fail on MySQL in certain cases but would never show up in a real installation. Since this API call always returns issues based on the optionally provided repo_priority_id or the issueID itself, there is no change to results by changing the repo sorting method used to get ids earlier in the function.
* linter is back!
* code review
* remove now unused option
* Fix newline at end of files
* more unused code
* update to master
* check for matching ids before query
* Update models/issue_label.go
Co-Authored-By: 6543 <6543@obermui.de>
* Update models/issue_label.go
* update comments
* Update routers/org/setting.go
Co-authored-by: Lauris BH <lauris@nix.lv>
Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com>
Co-authored-by: 6543 <6543@obermui.de>
2020-04-01 04:14:46 +00:00
if repo . Owner . IsOrganization ( ) {
2021-09-24 11:32:56 +00:00
orgLabels , err := models . GetLabelsByOrgID ( repo . Owner . ID , ctx . FormString ( "sort" ) , db . ListOptions { } )
Add Organization Wide Labels (#10814)
* Add organization wide labels
Implement organization wide labels similar to organization wide
webhooks. This lets you create individual labels for organizations that can be used
for all repos under that organization (so being able to reuse the same
label across multiple repos).
This makes it possible for small organizations with many repos to use
labels effectively.
Fixes #7406
* Add migration
* remove comments
* fix tests
* Update options/locale/locale_en-US.ini
Removed unused translation string
* show org labels in issue search label filter
* Use more clear var name
* rename migration after merge from master
* comment typo
* update migration again after rebase with master
* check for orgID <=0 per guillep2k review
* fmt
* Apply suggestions from code review
Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com>
* remove unused code
* Make sure RepoID is 0 when searching orgID per code review
* more changes/code review requests
* More descriptive translation var per code review
* func description/delete comment when issue label deleted instead of hiding it
* remove comment
* only use issues in that repo when calculating number of open issues for org label on repo label page
* Add integration test for IssuesSearch API with labels
* remove unused function
* Update models/issue_label.go
Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com>
* Use subquery in GetLabelIDsInReposByNames
* Fix tests to use correct orgID
* fix more tests
* IssuesSearch api now uses new BuildLabelNamesIssueIDsCondition. Add a few more tests as well
* update comment for clarity
* Revert previous code change now that we can use the new BuildLabelNamesIssueIDsCondition
* Don't sort repos by date in IssuesSearch API
After much debugging I've found a strange issue where in some cases MySQL will return a different result than other enigines if a query is sorted by a null collumn. For example with our integration test data where we don't set updated_unix in repository fixtures:
SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 45
Returns different results for MySQL than other engines. However, the similar query:
SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 30
Returns the same results.
This causes integration tests to fail on MySQL in certain cases but would never show up in a real installation. Since this API call always returns issues based on the optionally provided repo_priority_id or the issueID itself, there is no change to results by changing the repo sorting method used to get ids earlier in the function.
* linter is back!
* code review
* remove now unused option
* Fix newline at end of files
* more unused code
* update to master
* check for matching ids before query
* Update models/issue_label.go
Co-Authored-By: 6543 <6543@obermui.de>
* Update models/issue_label.go
* update comments
* Update routers/org/setting.go
Co-authored-by: Lauris BH <lauris@nix.lv>
Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com>
Co-authored-by: 6543 <6543@obermui.de>
2020-04-01 04:14:46 +00:00
if err != nil {
return nil
}
ctx . Data [ "OrgLabels" ] = orgLabels
labels = append ( labels , orgLabels ... )
}
2015-08-31 07:24:28 +00:00
2015-09-01 23:07:02 +00:00
RetrieveRepoMilestonesAndAssignees ( ctx , repo )
if ctx . Written ( ) {
2015-08-31 07:24:28 +00:00
return nil
}
2020-08-17 03:07:38 +00:00
retrieveProjects ( ctx , repo )
if ctx . Written ( ) {
return nil
}
2021-12-08 19:08:16 +00:00
brs , _ , err := ctx . Repo . GitRepo . GetBranchNames ( 0 , 0 )
2017-08-24 12:30:27 +00:00
if err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "GetBranches" , err )
2017-08-24 12:30:27 +00:00
return nil
}
ctx . Data [ "Branches" ] = brs
2018-07-17 21:23:58 +00:00
// Contains true if the user can create issue dependencies
2022-03-22 07:03:22 +00:00
ctx . Data [ "CanCreateIssueDependencies" ] = ctx . Repo . CanCreateIssueDependencies ( ctx . Doer , isPull )
2018-07-17 21:23:58 +00:00
2015-08-31 07:24:28 +00:00
return labels
}
2016-03-11 16:56:52 +00:00
func getFileContentFromDefaultBranch ( ctx * context . Context , filename string ) ( string , bool ) {
2016-02-17 22:21:31 +00:00
var bytes [ ] byte
if ctx . Repo . Commit == nil {
var err error
ctx . Repo . Commit , err = ctx . Repo . GitRepo . GetBranchCommit ( ctx . Repo . Repository . DefaultBranch )
if err != nil {
return "" , false
}
}
entry , err := ctx . Repo . Commit . GetTreeEntryByPath ( filename )
if err != nil {
return "" , false
}
2017-11-29 01:50:39 +00:00
if entry . Blob ( ) . Size ( ) >= setting . UI . MaxDisplayFileSize {
return "" , false
}
2019-04-19 12:17:27 +00:00
r , err := entry . Blob ( ) . DataAsync ( )
2016-02-17 22:21:31 +00:00
if err != nil {
return "" , false
}
2019-04-19 12:17:27 +00:00
defer r . Close ( )
2021-09-22 05:38:34 +00:00
bytes , err = io . ReadAll ( r )
2016-02-17 22:21:31 +00:00
if err != nil {
return "" , false
}
return string ( bytes ) , true
}
2021-12-20 04:41:31 +00:00
func setTemplateIfExists ( ctx * context . Context , ctxDataKey string , possibleDirs , possibleFiles [ ] string ) {
2020-09-11 14:48:39 +00:00
templateCandidates := make ( [ ] string , 0 , len ( possibleFiles ) )
2021-08-11 00:31:13 +00:00
if ctx . FormString ( "template" ) != "" {
2020-09-11 14:48:39 +00:00
for _ , dirName := range possibleDirs {
2021-08-11 00:31:13 +00:00
templateCandidates = append ( templateCandidates , path . Join ( dirName , ctx . FormString ( "template" ) ) )
2020-09-11 14:48:39 +00:00
}
}
templateCandidates = append ( templateCandidates , possibleFiles ... ) // Append files to the end because they should be fallback
for _ , filename := range templateCandidates {
templateContent , found := getFileContentFromDefaultBranch ( ctx , filename )
2016-02-17 22:21:31 +00:00
if found {
2020-09-11 14:48:39 +00:00
var meta api . IssueTemplate
templateBody , err := markdown . ExtractMetadata ( templateContent , & meta )
if err != nil {
log . Debug ( "could not extract metadata from %s [%s]: %v" , filename , ctx . Repo . Repository . FullName ( ) , err )
ctx . Data [ ctxDataKey ] = templateContent
return
}
ctx . Data [ issueTemplateTitleKey ] = meta . Title
ctx . Data [ ctxDataKey ] = templateBody
labelIDs := make ( [ ] string , 0 , len ( meta . Labels ) )
2021-09-24 11:32:56 +00:00
if repoLabels , err := models . GetLabelsByRepoID ( ctx . Repo . Repository . ID , "" , db . ListOptions { } ) ; err == nil {
2021-02-10 17:18:22 +00:00
ctx . Data [ "Labels" ] = repoLabels
if ctx . Repo . Owner . IsOrganization ( ) {
2021-09-24 11:32:56 +00:00
if orgLabels , err := models . GetLabelsByOrgID ( ctx . Repo . Owner . ID , ctx . FormString ( "sort" ) , db . ListOptions { } ) ; err == nil {
2021-02-10 17:18:22 +00:00
ctx . Data [ "OrgLabels" ] = orgLabels
repoLabels = append ( repoLabels , orgLabels ... )
}
}
2020-09-11 14:48:39 +00:00
for _ , metaLabel := range meta . Labels {
for _ , repoLabel := range repoLabels {
if strings . EqualFold ( repoLabel . Name , metaLabel ) {
repoLabel . IsChecked = true
2021-11-16 18:18:25 +00:00
labelIDs = append ( labelIDs , strconv . FormatInt ( repoLabel . ID , 10 ) )
2020-09-11 14:48:39 +00:00
break
}
}
}
}
ctx . Data [ "HasSelectedLabel" ] = len ( labelIDs ) > 0
ctx . Data [ "label_ids" ] = strings . Join ( labelIDs , "," )
2021-12-17 21:29:09 +00:00
ctx . Data [ "Reference" ] = meta . Ref
ctx . Data [ "RefEndName" ] = git . RefEndName ( meta . Ref )
2016-02-17 22:21:31 +00:00
return
}
}
}
2019-01-21 11:45:32 +00:00
// NewIssue render creating issue page
2016-03-11 16:56:52 +00:00
func NewIssue ( ctx * context . Context ) {
2015-08-09 07:23:02 +00:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.issues.new" )
ctx . Data [ "PageIsIssueList" ] = true
2020-09-11 14:48:39 +00:00
ctx . Data [ "NewIssueChooseTemplate" ] = len ( ctx . IssueTemplatesFromDefaultBranch ( ) ) > 0
2016-08-25 04:35:03 +00:00
ctx . Data [ "RequireHighlightJS" ] = true
2017-12-11 06:03:04 +00:00
ctx . Data [ "RequireTribute" ] = true
2018-08-13 19:04:39 +00:00
ctx . Data [ "PullRequestWorkInProgressPrefixes" ] = setting . Repository . PullRequest . WorkInProgressPrefixes
2021-08-11 00:31:13 +00:00
title := ctx . FormString ( "title" )
2020-09-11 14:48:39 +00:00
ctx . Data [ "TitleQuery" ] = title
2021-08-11 00:31:13 +00:00
body := ctx . FormString ( "body" )
2019-01-28 15:23:59 +00:00
ctx . Data [ "BodyQuery" ] = body
2021-03-18 02:02:38 +00:00
2021-11-09 19:57:58 +00:00
ctx . Data [ "IsProjectsEnabled" ] = ctx . Repo . CanRead ( unit . TypeProjects )
2020-10-05 05:49:33 +00:00
ctx . Data [ "IsAttachmentEnabled" ] = setting . Attachment . Enabled
upload . AddUploadContext ( ctx , "comment" )
2018-11-29 01:46:30 +00:00
2021-07-29 01:42:15 +00:00
milestoneID := ctx . FormInt64 ( "milestone" )
2019-06-10 14:16:02 +00:00
if milestoneID > 0 {
2022-04-08 09:11:15 +00:00
milestone , err := issues_model . GetMilestoneByRepoID ( ctx , ctx . Repo . Repository . ID , milestoneID )
2019-06-10 14:16:02 +00:00
if err != nil {
log . Error ( "GetMilestoneByID: %d: %v" , milestoneID , err )
} else {
ctx . Data [ "milestone_id" ] = milestoneID
ctx . Data [ "Milestone" ] = milestone
}
2018-11-29 01:46:30 +00:00
}
2021-07-29 01:42:15 +00:00
projectID := ctx . FormInt64 ( "project" )
2020-08-17 03:07:38 +00:00
if projectID > 0 {
2022-03-29 14:16:31 +00:00
project , err := project_model . GetProjectByID ( projectID )
2020-08-17 03:07:38 +00:00
if err != nil {
log . Error ( "GetProjectByID: %d: %v" , projectID , err )
} else if project . RepoID != ctx . Repo . Repository . ID {
log . Error ( "GetProjectByID: %d: %v" , projectID , fmt . Errorf ( "project[%d] not in repo [%d]" , project . ID , ctx . Repo . Repository . ID ) )
} else {
ctx . Data [ "project_id" ] = projectID
ctx . Data [ "Project" ] = project
}
2021-10-05 19:21:52 +00:00
if len ( ctx . Req . URL . Query ( ) . Get ( "project" ) ) > 0 {
ctx . Data [ "redirect_after_creation" ] = "project"
}
2020-08-17 03:07:38 +00:00
}
2020-01-19 06:43:38 +00:00
RetrieveRepoMetas ( ctx , ctx . Repo . Repository , false )
2020-09-11 14:48:39 +00:00
setTemplateIfExists ( ctx , issueTemplateKey , context . IssueTemplateDirCandidates , IssueTemplateCandidates )
2015-08-31 07:24:28 +00:00
if ctx . Written ( ) {
return
2015-08-10 10:57:57 +00:00
}
2015-08-09 07:23:02 +00:00
2021-11-09 19:57:58 +00:00
ctx . Data [ "HasIssuesOrPullsWritePermission" ] = ctx . Repo . CanWrite ( unit . TypeIssues )
2020-04-04 05:39:48 +00:00
2021-04-05 15:30:52 +00:00
ctx . HTML ( http . StatusOK , tplIssueNew )
2014-07-26 06:28:04 +00:00
}
2020-09-11 14:48:39 +00:00
// NewIssueChooseTemplate render creating issue from template page
func NewIssueChooseTemplate ( ctx * context . Context ) {
ctx . Data [ "Title" ] = ctx . Tr ( "repo.issues.new" )
ctx . Data [ "PageIsIssueList" ] = true
issueTemplates := ctx . IssueTemplatesFromDefaultBranch ( )
ctx . Data [ "IssueTemplates" ] = issueTemplates
2022-03-22 19:54:24 +00:00
if len ( issueTemplates ) == 0 {
// The "issues/new" and "issues/new/choose" share the same query parameters "project" and "milestone", if no template here, just redirect to the "issues/new" page with these parameters.
ctx . Redirect ( fmt . Sprintf ( "%s/issues/new?%s" , ctx . Repo . Repository . HTMLURL ( ) , ctx . Req . URL . RawQuery ) , http . StatusSeeOther )
return
}
ctx . Data [ "milestone" ] = ctx . FormInt64 ( "milestone" )
ctx . Data [ "project" ] = ctx . FormInt64 ( "project" )
2021-04-05 15:30:52 +00:00
ctx . HTML ( http . StatusOK , tplIssueChoose )
2020-09-11 14:48:39 +00:00
}
2022-03-09 00:38:11 +00:00
// DeleteIssue deletes an issue
func DeleteIssue ( ctx * context . Context ) {
issue := GetActionIssue ( ctx )
if ctx . Written ( ) {
return
}
2022-03-22 07:03:22 +00:00
if err := issue_service . DeleteIssue ( ctx . Doer , ctx . Repo . GitRepo , issue ) ; err != nil {
2022-03-09 00:38:11 +00:00
ctx . ServerError ( "DeleteIssueByID" , err )
return
}
ctx . Redirect ( fmt . Sprintf ( "%s/issues" , ctx . Repo . Repository . HTMLURL ( ) ) , http . StatusSeeOther )
}
2021-07-08 11:38:13 +00:00
// ValidateRepoMetas check and returns repository's meta information
2021-04-06 19:44:05 +00:00
func ValidateRepoMetas ( ctx * context . Context , form forms . CreateIssueForm , isPull bool ) ( [ ] int64 , [ ] int64 , int64 , int64 ) {
2015-09-01 23:07:02 +00:00
var (
repo = ctx . Repo . Repository
err error
)
2020-01-19 06:43:38 +00:00
labels := RetrieveRepoMetas ( ctx , ctx . Repo . Repository , isPull )
2015-09-01 23:07:02 +00:00
if ctx . Written ( ) {
2020-08-17 03:07:38 +00:00
return nil , nil , 0 , 0
2015-09-01 23:07:02 +00:00
}
2017-02-01 02:36:08 +00:00
var labelIDs [ ] int64
2015-09-01 23:07:02 +00:00
hasSelected := false
2017-02-01 02:36:08 +00:00
// Check labels.
if len ( form . LabelIDs ) > 0 {
labelIDs , err = base . StringsToInt64s ( strings . Split ( form . LabelIDs , "," ) )
if err != nil {
2020-08-17 03:07:38 +00:00
return nil , nil , 0 , 0
2017-02-01 02:36:08 +00:00
}
labelIDMark := base . Int64sToMap ( labelIDs )
for i := range labels {
if labelIDMark [ labels [ i ] . ID ] {
labels [ i ] . IsChecked = true
hasSelected = true
}
2015-09-01 23:07:02 +00:00
}
}
2017-02-01 02:36:08 +00:00
ctx . Data [ "Labels" ] = labels
2015-09-01 23:07:02 +00:00
ctx . Data [ "HasSelectedLabel" ] = hasSelected
ctx . Data [ "label_ids" ] = form . LabelIDs
// Check milestone.
milestoneID := form . MilestoneID
if milestoneID > 0 {
2022-04-08 09:11:15 +00:00
milestone , err := issues_model . GetMilestoneByRepoID ( ctx , ctx . Repo . Repository . ID , milestoneID )
2015-09-01 23:07:02 +00:00
if err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "GetMilestoneByID" , err )
2020-08-17 03:07:38 +00:00
return nil , nil , 0 , 0
2015-09-01 23:07:02 +00:00
}
2021-12-10 01:27:50 +00:00
if milestone . RepoID != repo . ID {
ctx . ServerError ( "GetMilestoneByID" , err )
return nil , nil , 0 , 0
}
ctx . Data [ "Milestone" ] = milestone
2015-09-01 23:07:02 +00:00
ctx . Data [ "milestone_id" ] = milestoneID
}
2020-08-17 03:07:38 +00:00
if form . ProjectID > 0 {
2022-03-29 14:16:31 +00:00
p , err := project_model . GetProjectByID ( form . ProjectID )
2020-08-17 03:07:38 +00:00
if err != nil {
ctx . ServerError ( "GetProjectByID" , err )
return nil , nil , 0 , 0
}
if p . RepoID != ctx . Repo . Repository . ID {
ctx . NotFound ( "" , nil )
return nil , nil , 0 , 0
}
ctx . Data [ "Project" ] = p
ctx . Data [ "project_id" ] = form . ProjectID
}
2018-05-09 16:29:04 +00:00
// Check assignees
var assigneeIDs [ ] int64
if len ( form . AssigneeIDs ) > 0 {
assigneeIDs , err = base . StringsToInt64s ( strings . Split ( form . AssigneeIDs , "," ) )
2015-09-01 23:07:02 +00:00
if err != nil {
2020-08-17 03:07:38 +00:00
return nil , nil , 0 , 0
2018-05-09 16:29:04 +00:00
}
2019-10-25 14:46:37 +00:00
// Check if the passed assignees actually exists and is assignable
2018-05-09 16:29:04 +00:00
for _ , aID := range assigneeIDs {
2021-11-24 09:49:20 +00:00
assignee , err := user_model . GetUserByID ( aID )
2018-11-28 11:26:14 +00:00
if err != nil {
ctx . ServerError ( "GetUserByID" , err )
2020-08-17 03:07:38 +00:00
return nil , nil , 0 , 0
2018-11-28 11:26:14 +00:00
}
2019-10-25 14:46:37 +00:00
valid , err := models . CanBeAssigned ( assignee , repo , isPull )
2018-05-09 16:29:04 +00:00
if err != nil {
2020-08-17 03:07:38 +00:00
ctx . ServerError ( "CanBeAssigned" , err )
return nil , nil , 0 , 0
2018-11-28 11:26:14 +00:00
}
2020-08-17 03:07:38 +00:00
2019-10-25 14:46:37 +00:00
if ! valid {
ctx . ServerError ( "canBeAssigned" , models . ErrUserDoesNotHaveAccessToRepo { UserID : aID , RepoName : repo . Name } )
2020-08-17 03:07:38 +00:00
return nil , nil , 0 , 0
2018-05-09 16:29:04 +00:00
}
2015-09-01 23:07:02 +00:00
}
}
2018-05-09 16:29:04 +00:00
// Keep the old assignee id thingy for compatibility reasons
if form . AssigneeID > 0 {
assigneeIDs = append ( assigneeIDs , form . AssigneeID )
}
2020-08-17 03:07:38 +00:00
return labelIDs , assigneeIDs , milestoneID , form . ProjectID
2015-09-01 23:07:02 +00:00
}
2016-11-24 07:04:31 +00:00
// NewIssuePost response for creating new issue
2021-01-26 15:36:53 +00:00
func NewIssuePost ( ctx * context . Context ) {
2021-04-06 19:44:05 +00:00
form := web . GetForm ( ctx ) . ( * forms . CreateIssueForm )
2015-08-09 07:23:02 +00:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.issues.new" )
ctx . Data [ "PageIsIssueList" ] = true
2020-09-11 14:48:39 +00:00
ctx . Data [ "NewIssueChooseTemplate" ] = len ( ctx . IssueTemplatesFromDefaultBranch ( ) ) > 0
2016-08-11 12:48:08 +00:00
ctx . Data [ "RequireHighlightJS" ] = true
2018-08-13 19:04:39 +00:00
ctx . Data [ "PullRequestWorkInProgressPrefixes" ] = setting . Repository . PullRequest . WorkInProgressPrefixes
2020-10-05 05:49:33 +00:00
ctx . Data [ "IsAttachmentEnabled" ] = setting . Attachment . Enabled
upload . AddUploadContext ( ctx , "comment" )
2014-07-26 06:28:04 +00:00
2015-08-10 08:52:08 +00:00
var (
2015-08-10 10:57:57 +00:00
repo = ctx . Repo . Repository
2015-08-11 15:24:40 +00:00
attachments [ ] string
2015-08-10 08:52:08 +00:00
)
2015-08-31 07:24:28 +00:00
2021-01-26 15:36:53 +00:00
labelIDs , assigneeIDs , milestoneID , projectID := ValidateRepoMetas ( ctx , * form , false )
2015-09-01 23:07:02 +00:00
if ctx . Written ( ) {
return
2015-08-10 08:52:08 +00:00
}
2020-08-18 04:23:45 +00:00
if setting . Attachment . Enabled {
2016-08-11 12:48:08 +00:00
attachments = form . Files
2015-08-11 15:24:40 +00:00
}
2014-07-26 06:28:04 +00:00
if ctx . HasError ( ) {
2021-04-05 15:30:52 +00:00
ctx . HTML ( http . StatusOK , tplIssueNew )
2014-07-26 06:28:04 +00:00
return
}
2019-01-21 11:45:32 +00:00
if util . IsEmptyString ( form . Title ) {
ctx . RenderWithErr ( ctx . Tr ( "repo.issues.new.title_empty" ) , tplIssueNew , form )
return
}
2014-07-26 06:28:04 +00:00
issue := & models . Issue {
2016-03-14 03:20:22 +00:00
RepoID : repo . ID ,
2021-11-16 18:18:25 +00:00
Repo : repo ,
2016-08-14 10:32:24 +00:00
Title : form . Title ,
2022-03-22 07:03:22 +00:00
PosterID : ctx . Doer . ID ,
Poster : ctx . Doer ,
2015-08-10 10:57:57 +00:00
MilestoneID : milestoneID ,
2015-08-10 13:47:23 +00:00
Content : form . Content ,
2017-08-24 12:30:27 +00:00
Ref : form . Ref ,
2014-07-26 06:28:04 +00:00
}
Add Organization Wide Labels (#10814)
* Add organization wide labels
Implement organization wide labels similar to organization wide
webhooks. This lets you create individual labels for organizations that can be used
for all repos under that organization (so being able to reuse the same
label across multiple repos).
This makes it possible for small organizations with many repos to use
labels effectively.
Fixes #7406
* Add migration
* remove comments
* fix tests
* Update options/locale/locale_en-US.ini
Removed unused translation string
* show org labels in issue search label filter
* Use more clear var name
* rename migration after merge from master
* comment typo
* update migration again after rebase with master
* check for orgID <=0 per guillep2k review
* fmt
* Apply suggestions from code review
Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com>
* remove unused code
* Make sure RepoID is 0 when searching orgID per code review
* more changes/code review requests
* More descriptive translation var per code review
* func description/delete comment when issue label deleted instead of hiding it
* remove comment
* only use issues in that repo when calculating number of open issues for org label on repo label page
* Add integration test for IssuesSearch API with labels
* remove unused function
* Update models/issue_label.go
Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com>
* Use subquery in GetLabelIDsInReposByNames
* Fix tests to use correct orgID
* fix more tests
* IssuesSearch api now uses new BuildLabelNamesIssueIDsCondition. Add a few more tests as well
* update comment for clarity
* Revert previous code change now that we can use the new BuildLabelNamesIssueIDsCondition
* Don't sort repos by date in IssuesSearch API
After much debugging I've found a strange issue where in some cases MySQL will return a different result than other enigines if a query is sorted by a null collumn. For example with our integration test data where we don't set updated_unix in repository fixtures:
SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 45
Returns different results for MySQL than other engines. However, the similar query:
SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 30
Returns the same results.
This causes integration tests to fail on MySQL in certain cases but would never show up in a real installation. Since this API call always returns issues based on the optionally provided repo_priority_id or the issueID itself, there is no change to results by changing the repo sorting method used to get ids earlier in the function.
* linter is back!
* code review
* remove now unused option
* Fix newline at end of files
* more unused code
* update to master
* check for matching ids before query
* Update models/issue_label.go
Co-Authored-By: 6543 <6543@obermui.de>
* Update models/issue_label.go
* update comments
* Update routers/org/setting.go
Co-authored-by: Lauris BH <lauris@nix.lv>
Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com>
Co-authored-by: 6543 <6543@obermui.de>
2020-04-01 04:14:46 +00:00
2019-10-28 16:45:43 +00:00
if err := issue_service . NewIssue ( repo , issue , labelIDs , attachments , assigneeIDs ) ; err != nil {
2018-05-09 16:29:04 +00:00
if models . IsErrUserDoesNotHaveAccessToRepo ( err ) {
2021-04-05 15:30:52 +00:00
ctx . Error ( http . StatusBadRequest , "UserDoesNotHaveAccessToRepo" , err . Error ( ) )
2018-05-09 16:29:04 +00:00
return
}
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "NewIssue" , err )
2014-07-26 06:28:04 +00:00
return
2015-08-10 15:31:59 +00:00
}
2020-08-17 03:07:38 +00:00
if projectID > 0 {
2022-03-22 07:03:22 +00:00
if err := models . ChangeProjectAssign ( issue , ctx . Doer , projectID ) ; err != nil {
2020-08-17 03:07:38 +00:00
ctx . ServerError ( "ChangeProjectAssign" , err )
return
}
}
2015-09-01 23:07:02 +00:00
log . Trace ( "Issue created: %d/%d" , repo . ID , issue . ID )
2021-10-05 19:21:52 +00:00
if ctx . FormString ( "redirect_after_creation" ) == "project" {
2021-11-16 18:18:25 +00:00
ctx . Redirect ( ctx . Repo . RepoLink + "/projects/" + strconv . FormatInt ( form . ProjectID , 10 ) )
2021-10-05 19:21:52 +00:00
} else {
2021-11-16 18:18:25 +00:00
ctx . Redirect ( issue . Link ( ) )
2021-10-05 19:21:52 +00:00
}
2014-07-26 06:28:04 +00:00
}
2022-01-10 09:32:37 +00:00
// roleDescriptor returns the Role Descriptor for a comment in/with the given repo, poster and issue
2022-04-28 11:48:48 +00:00
func roleDescriptor ( ctx stdCtx . Context , repo * repo_model . Repository , poster * user_model . User , issue * models . Issue ) ( models . RoleDescriptor , error ) {
perm , err := models . GetUserRepoPermission ( ctx , repo , poster )
2018-11-28 11:26:14 +00:00
if err != nil {
2021-11-11 06:29:30 +00:00
return models . RoleDescriptorNone , err
2017-12-21 07:43:26 +00:00
}
2020-11-28 15:52:29 +00:00
2021-11-11 06:29:30 +00:00
// By default the poster has no roles on the comment.
roleDescriptor := models . RoleDescriptorNone
2020-11-28 15:52:29 +00:00
2021-11-11 06:29:30 +00:00
// Check if the poster is owner of the repo.
if perm . IsOwner ( ) {
// If the poster isn't a admin, enable the owner role.
if ! poster . IsAdmin {
roleDescriptor = roleDescriptor . WithRole ( models . RoleDescriptorOwner )
} else {
2020-11-28 15:52:29 +00:00
2021-11-11 06:29:30 +00:00
// Otherwise check if poster is the real repo admin.
ok , err := models . IsUserRealRepoAdmin ( repo , poster )
if err != nil {
return models . RoleDescriptorNone , err
}
if ok {
roleDescriptor = roleDescriptor . WithRole ( models . RoleDescriptorOwner )
}
2020-11-28 15:52:29 +00:00
}
2021-11-11 06:29:30 +00:00
}
2020-11-28 15:52:29 +00:00
2021-11-11 06:29:30 +00:00
// Is the poster can write issues or pulls to the repo, enable the Writer role.
// Only enable this if the poster doesn't have the owner role already.
if ! roleDescriptor . HasRole ( "Owner" ) && perm . CanWriteIssuesOrPulls ( issue . IsPull ) {
roleDescriptor = roleDescriptor . WithRole ( models . RoleDescriptorWriter )
2020-11-28 15:52:29 +00:00
}
2021-11-11 06:29:30 +00:00
// If the poster is the actual poster of the issue, enable Poster role.
if issue . IsPoster ( poster . ID ) {
roleDescriptor = roleDescriptor . WithRole ( models . RoleDescriptorPoster )
2017-12-21 07:43:26 +00:00
}
2018-11-28 11:26:14 +00:00
2021-11-11 06:29:30 +00:00
return roleDescriptor , nil
2017-12-21 07:43:26 +00:00
}
2019-12-16 06:20:25 +00:00
func getBranchData ( ctx * context . Context , issue * models . Issue ) {
ctx . Data [ "BaseBranch" ] = nil
ctx . Data [ "HeadBranch" ] = nil
ctx . Data [ "HeadUserName" ] = nil
ctx . Data [ "BaseName" ] = ctx . Repo . Repository . OwnerName
if issue . IsPull {
pull := issue . PullRequest
ctx . Data [ "BaseBranch" ] = pull . BaseBranch
ctx . Data [ "HeadBranch" ] = pull . HeadBranch
ctx . Data [ "HeadUserName" ] = pull . MustHeadUserName ( )
}
}
2016-11-24 07:04:31 +00:00
// ViewIssue render issue view page
2016-03-11 16:56:52 +00:00
func ViewIssue ( ctx * context . Context ) {
2019-12-14 00:53:32 +00:00
if ctx . Params ( ":type" ) == "issues" {
// If issue was requested we check if repo has external tracker and redirect
2021-11-09 19:57:58 +00:00
extIssueUnit , err := ctx . Repo . Repository . GetUnit ( unit . TypeExternalTracker )
2019-12-14 00:53:32 +00:00
if err == nil && extIssueUnit != nil {
if extIssueUnit . ExternalTrackerConfig ( ) . ExternalTrackerStyle == markup . IssueNameStyleNumeric || extIssueUnit . ExternalTrackerConfig ( ) . ExternalTrackerStyle == "" {
metas := ctx . Repo . Repository . ComposeMetas ( )
metas [ "index" ] = ctx . Params ( ":index" )
2022-04-01 08:47:50 +00:00
res , err := vars . Expand ( extIssueUnit . ExternalTrackerConfig ( ) . ExternalTrackerFormat , metas )
if err != nil {
log . Error ( "unable to expand template vars for issue url. issue: %s, err: %v" , metas [ "index" ] , err )
ctx . ServerError ( "Expand" , err )
return
}
ctx . Redirect ( res )
2019-12-14 00:53:32 +00:00
return
}
2021-12-10 01:27:50 +00:00
} else if err != nil && ! repo_model . IsErrUnitTypeNotExist ( err ) {
2019-12-14 00:53:32 +00:00
ctx . ServerError ( "GetUnit" , err )
2019-12-07 04:21:18 +00:00
return
}
}
2015-08-12 09:04:23 +00:00
issue , err := models . GetIssueByIndex ( ctx . Repo . Repository . ID , ctx . ParamsInt64 ( ":index" ) )
2014-07-26 06:28:04 +00:00
if err != nil {
2015-08-12 09:04:23 +00:00
if models . IsErrIssueNotExist ( err ) {
2018-01-10 21:34:17 +00:00
ctx . NotFound ( "GetIssueByIndex" , err )
2014-07-26 06:28:04 +00:00
} else {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "GetIssueByIndex" , err )
2014-07-26 06:28:04 +00:00
}
return
}
2021-11-16 18:18:25 +00:00
if issue . Repo == nil {
issue . Repo = ctx . Repo . Repository
}
2017-03-29 23:31:47 +00:00
2015-09-01 23:07:02 +00:00
// Make sure type and URL matches.
if ctx . Params ( ":type" ) == "issues" && issue . IsPull {
2021-11-16 18:18:25 +00:00
ctx . Redirect ( issue . Link ( ) )
2015-09-01 23:07:02 +00:00
return
} else if ctx . Params ( ":type" ) == "pulls" && ! issue . IsPull {
2021-11-16 18:18:25 +00:00
ctx . Redirect ( issue . Link ( ) )
2015-09-01 23:07:02 +00:00
return
}
2015-09-02 08:08:05 +00:00
if issue . IsPull {
2016-03-07 04:57:46 +00:00
MustAllowPulls ( ctx )
if ctx . Written ( ) {
return
}
ctx . Data [ "PageIsPullList" ] = true
2015-09-02 08:08:05 +00:00
ctx . Data [ "PageIsPullConversation" ] = true
} else {
2015-12-05 02:30:33 +00:00
MustEnableIssues ( ctx )
if ctx . Written ( ) {
return
}
2015-09-02 08:08:05 +00:00
ctx . Data [ "PageIsIssueList" ] = true
2020-09-11 14:48:39 +00:00
ctx . Data [ "NewIssueChooseTemplate" ] = len ( ctx . IssueTemplatesFromDefaultBranch ( ) ) > 0
2015-09-02 08:08:05 +00:00
}
2021-11-09 19:57:58 +00:00
if issue . IsPull && ! ctx . Repo . CanRead ( unit . TypeIssues ) {
2020-01-19 06:43:38 +00:00
ctx . Data [ "IssueType" ] = "pulls"
2021-11-09 19:57:58 +00:00
} else if ! issue . IsPull && ! ctx . Repo . CanRead ( unit . TypePullRequests ) {
2020-01-19 06:43:38 +00:00
ctx . Data [ "IssueType" ] = "issues"
} else {
ctx . Data [ "IssueType" ] = "all"
}
2018-11-28 11:26:14 +00:00
ctx . Data [ "RequireHighlightJS" ] = true
ctx . Data [ "RequireTribute" ] = true
2021-11-09 19:57:58 +00:00
ctx . Data [ "IsProjectsEnabled" ] = ctx . Repo . CanRead ( unit . TypeProjects )
2020-10-05 05:49:33 +00:00
ctx . Data [ "IsAttachmentEnabled" ] = setting . Attachment . Enabled
upload . AddUploadContext ( ctx , "comment" )
2018-11-28 11:26:14 +00:00
2019-09-20 05:45:38 +00:00
if err = issue . LoadAttributes ( ) ; err != nil {
ctx . ServerError ( "LoadAttributes" , err )
return
}
if err = filterXRefComments ( ctx , issue ) ; err != nil {
ctx . ServerError ( "filterXRefComments" , err )
2018-12-13 15:55:43 +00:00
return
}
2018-11-28 11:26:14 +00:00
ctx . Data [ "Title" ] = fmt . Sprintf ( "#%d - %s" , issue . Index , issue . Title )
2020-04-21 13:48:53 +00:00
iw := new ( models . IssueWatch )
2022-03-22 07:03:22 +00:00
if ctx . Doer != nil {
iw . UserID = ctx . Doer . ID
2020-04-21 13:48:53 +00:00
iw . IssueID = issue . ID
2022-03-22 07:03:22 +00:00
iw . IsWatching , err = models . CheckIssueWatch ( ctx . Doer , issue )
2018-11-28 11:26:14 +00:00
if err != nil {
2021-01-14 20:27:22 +00:00
ctx . ServerError ( "CheckIssueWatch" , err )
2018-11-28 11:26:14 +00:00
return
}
}
ctx . Data [ "IssueWatch" ] = iw
2021-04-19 22:25:08 +00:00
issue . RenderedContent , err = markdown . RenderString ( & markup . RenderContext {
URLPrefix : ctx . Repo . RepoLink ,
Metas : ctx . Repo . Repository . ComposeMetas ( ) ,
2021-06-20 22:39:12 +00:00
GitRepo : ctx . Repo . GitRepo ,
2021-08-28 20:15:56 +00:00
Ctx : ctx ,
2021-04-19 22:25:08 +00:00
} , issue . Content )
if err != nil {
ctx . ServerError ( "RenderString" , err )
return
}
2014-07-26 06:28:04 +00:00
2015-08-14 16:42:43 +00:00
repo := ctx . Repo . Repository
2015-09-01 23:07:02 +00:00
// Get more information if it's a pull request.
if issue . IsPull {
2016-08-16 17:19:09 +00:00
if issue . PullRequest . HasMerged {
ctx . Data [ "DisableStatusChange" ] = issue . PullRequest . HasMerged
2015-09-02 13:26:56 +00:00
PrepareMergedViewPullInfo ( ctx , issue )
} else {
PrepareViewPullInfo ( ctx , issue )
2019-04-20 20:50:34 +00:00
ctx . Data [ "DisableStatusChange" ] = ctx . Data [ "IsPullRequestBroken" ] == true && issue . IsClosed
2015-09-02 13:26:56 +00:00
}
2015-09-02 08:08:05 +00:00
if ctx . Written ( ) {
2015-09-01 23:07:02 +00:00
return
}
}
2015-08-12 09:04:23 +00:00
// Metas.
2015-08-14 16:42:43 +00:00
// Check labels.
labelIDMark := make ( map [ int64 ] bool )
for i := range issue . Labels {
labelIDMark [ issue . Labels [ i ] . ID ] = true
}
2021-09-24 11:32:56 +00:00
labels , err := models . GetLabelsByRepoID ( repo . ID , "" , db . ListOptions { } )
2015-08-14 16:42:43 +00:00
if err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "GetLabelsByRepoID" , err )
2015-08-14 16:42:43 +00:00
return
}
Add Organization Wide Labels (#10814)
* Add organization wide labels
Implement organization wide labels similar to organization wide
webhooks. This lets you create individual labels for organizations that can be used
for all repos under that organization (so being able to reuse the same
label across multiple repos).
This makes it possible for small organizations with many repos to use
labels effectively.
Fixes #7406
* Add migration
* remove comments
* fix tests
* Update options/locale/locale_en-US.ini
Removed unused translation string
* show org labels in issue search label filter
* Use more clear var name
* rename migration after merge from master
* comment typo
* update migration again after rebase with master
* check for orgID <=0 per guillep2k review
* fmt
* Apply suggestions from code review
Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com>
* remove unused code
* Make sure RepoID is 0 when searching orgID per code review
* more changes/code review requests
* More descriptive translation var per code review
* func description/delete comment when issue label deleted instead of hiding it
* remove comment
* only use issues in that repo when calculating number of open issues for org label on repo label page
* Add integration test for IssuesSearch API with labels
* remove unused function
* Update models/issue_label.go
Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com>
* Use subquery in GetLabelIDsInReposByNames
* Fix tests to use correct orgID
* fix more tests
* IssuesSearch api now uses new BuildLabelNamesIssueIDsCondition. Add a few more tests as well
* update comment for clarity
* Revert previous code change now that we can use the new BuildLabelNamesIssueIDsCondition
* Don't sort repos by date in IssuesSearch API
After much debugging I've found a strange issue where in some cases MySQL will return a different result than other enigines if a query is sorted by a null collumn. For example with our integration test data where we don't set updated_unix in repository fixtures:
SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 45
Returns different results for MySQL than other engines. However, the similar query:
SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 30
Returns the same results.
This causes integration tests to fail on MySQL in certain cases but would never show up in a real installation. Since this API call always returns issues based on the optionally provided repo_priority_id or the issueID itself, there is no change to results by changing the repo sorting method used to get ids earlier in the function.
* linter is back!
* code review
* remove now unused option
* Fix newline at end of files
* more unused code
* update to master
* check for matching ids before query
* Update models/issue_label.go
Co-Authored-By: 6543 <6543@obermui.de>
* Update models/issue_label.go
* update comments
* Update routers/org/setting.go
Co-authored-by: Lauris BH <lauris@nix.lv>
Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com>
Co-authored-by: 6543 <6543@obermui.de>
2020-04-01 04:14:46 +00:00
ctx . Data [ "Labels" ] = labels
if repo . Owner . IsOrganization ( ) {
2021-09-24 11:32:56 +00:00
orgLabels , err := models . GetLabelsByOrgID ( repo . Owner . ID , ctx . FormString ( "sort" ) , db . ListOptions { } )
Add Organization Wide Labels (#10814)
* Add organization wide labels
Implement organization wide labels similar to organization wide
webhooks. This lets you create individual labels for organizations that can be used
for all repos under that organization (so being able to reuse the same
label across multiple repos).
This makes it possible for small organizations with many repos to use
labels effectively.
Fixes #7406
* Add migration
* remove comments
* fix tests
* Update options/locale/locale_en-US.ini
Removed unused translation string
* show org labels in issue search label filter
* Use more clear var name
* rename migration after merge from master
* comment typo
* update migration again after rebase with master
* check for orgID <=0 per guillep2k review
* fmt
* Apply suggestions from code review
Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com>
* remove unused code
* Make sure RepoID is 0 when searching orgID per code review
* more changes/code review requests
* More descriptive translation var per code review
* func description/delete comment when issue label deleted instead of hiding it
* remove comment
* only use issues in that repo when calculating number of open issues for org label on repo label page
* Add integration test for IssuesSearch API with labels
* remove unused function
* Update models/issue_label.go
Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com>
* Use subquery in GetLabelIDsInReposByNames
* Fix tests to use correct orgID
* fix more tests
* IssuesSearch api now uses new BuildLabelNamesIssueIDsCondition. Add a few more tests as well
* update comment for clarity
* Revert previous code change now that we can use the new BuildLabelNamesIssueIDsCondition
* Don't sort repos by date in IssuesSearch API
After much debugging I've found a strange issue where in some cases MySQL will return a different result than other enigines if a query is sorted by a null collumn. For example with our integration test data where we don't set updated_unix in repository fixtures:
SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 45
Returns different results for MySQL than other engines. However, the similar query:
SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 30
Returns the same results.
This causes integration tests to fail on MySQL in certain cases but would never show up in a real installation. Since this API call always returns issues based on the optionally provided repo_priority_id or the issueID itself, there is no change to results by changing the repo sorting method used to get ids earlier in the function.
* linter is back!
* code review
* remove now unused option
* Fix newline at end of files
* more unused code
* update to master
* check for matching ids before query
* Update models/issue_label.go
Co-Authored-By: 6543 <6543@obermui.de>
* Update models/issue_label.go
* update comments
* Update routers/org/setting.go
Co-authored-by: Lauris BH <lauris@nix.lv>
Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com>
Co-authored-by: 6543 <6543@obermui.de>
2020-04-01 04:14:46 +00:00
if err != nil {
ctx . ServerError ( "GetLabelsByOrgID" , err )
return
}
ctx . Data [ "OrgLabels" ] = orgLabels
labels = append ( labels , orgLabels ... )
}
2015-08-14 16:42:43 +00:00
hasSelected := false
for i := range labels {
if labelIDMark [ labels [ i ] . ID ] {
labels [ i ] . IsChecked = true
hasSelected = true
}
}
ctx . Data [ "HasSelectedLabel" ] = hasSelected
// Check milestone and assignee.
2018-11-28 11:26:14 +00:00
if ctx . Repo . CanWriteIssuesOrPulls ( issue . IsPull ) {
2015-09-01 23:07:02 +00:00
RetrieveRepoMilestonesAndAssignees ( ctx , repo )
2020-08-17 03:07:38 +00:00
retrieveProjects ( ctx , repo )
2015-09-01 23:07:02 +00:00
if ctx . Written ( ) {
2015-08-14 16:42:43 +00:00
return
}
}
2014-07-26 06:28:04 +00:00
2020-04-06 16:33:34 +00:00
if issue . IsPull {
2021-11-09 19:57:58 +00:00
canChooseReviewer := ctx . Repo . CanWrite ( unit . TypePullRequests )
2022-03-22 07:03:22 +00:00
if ! canChooseReviewer && ctx . Doer != nil && ctx . IsSigned {
canChooseReviewer , err = models . IsOfficialReviewer ( issue , ctx . Doer )
2020-04-06 16:33:34 +00:00
if err != nil {
ctx . ServerError ( "IsOfficialReviewer" , err )
return
}
}
2020-10-12 19:55:13 +00:00
RetrieveRepoReviewers ( ctx , repo , issue , canChooseReviewer )
2020-04-06 16:33:34 +00:00
if ctx . Written ( ) {
return
}
}
2015-08-12 09:04:23 +00:00
if ctx . IsSigned {
2015-08-12 10:44:09 +00:00
// Update issue-user.
2022-05-03 19:46:28 +00:00
if err = issue . ReadBy ( ctx , ctx . Doer . ID ) ; err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "ReadBy" , err )
2015-08-12 10:44:09 +00:00
return
}
2015-08-12 09:04:23 +00:00
}
2015-08-13 18:43:40 +00:00
var (
2021-11-11 06:29:30 +00:00
role models . RoleDescriptor
2016-01-19 13:04:24 +00:00
ok bool
2021-11-11 06:29:30 +00:00
marked = make ( map [ int64 ] models . RoleDescriptor )
2016-01-19 13:04:24 +00:00
comment * models . Comment
2021-11-24 09:49:20 +00:00
participants = make ( [ ] * user_model . User , 1 , 10 )
2015-08-13 18:43:40 +00:00
)
2017-09-12 06:48:13 +00:00
if ctx . Repo . Repository . IsTimetrackerEnabled ( ) {
if ctx . IsSigned {
// Deal with the stopwatch
2022-03-22 07:03:22 +00:00
ctx . Data [ "IsStopwatchRunning" ] = models . StopwatchExists ( ctx . Doer . ID , issue . ID )
2017-09-12 06:48:13 +00:00
if ! ctx . Data [ "IsStopwatchRunning" ] . ( bool ) {
var exists bool
var sw * models . Stopwatch
2022-03-22 07:03:22 +00:00
if exists , sw , err = models . HasUserStopwatch ( ctx . Doer . ID ) ; err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "HasUserStopwatch" , err )
2017-09-12 06:48:13 +00:00
return
}
ctx . Data [ "HasUserStopwatch" ] = exists
if exists {
// Add warning if the user has already a stopwatch
var otherIssue * models . Issue
if otherIssue , err = models . GetIssueByID ( sw . IssueID ) ; err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "GetIssueByID" , err )
2017-09-12 06:48:13 +00:00
return
}
2022-04-08 09:11:15 +00:00
if err = otherIssue . LoadRepo ( ctx ) ; err != nil {
2018-12-13 15:55:43 +00:00
ctx . ServerError ( "LoadRepo" , err )
return
}
2017-09-12 06:48:13 +00:00
// Add link to the issue of the already running stopwatch
ctx . Data [ "OtherStopwatchURL" ] = otherIssue . HTMLURL ( )
}
}
2022-03-22 07:03:22 +00:00
ctx . Data [ "CanUseTimetracker" ] = ctx . Repo . CanUseTimetracker ( issue , ctx . Doer )
2017-09-12 06:48:13 +00:00
} else {
ctx . Data [ "CanUseTimetracker" ] = false
}
2021-08-12 12:43:08 +00:00
if ctx . Data [ "WorkingUsers" ] , err = models . TotalTimes ( & models . FindTrackedTimesOptions { IssueID : issue . ID } ) ; err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "TotalTimes" , err )
2017-09-12 06:48:13 +00:00
return
}
}
2016-02-02 01:55:12 +00:00
2018-07-17 21:23:58 +00:00
// Check if the user can use the dependencies
2022-03-22 07:03:22 +00:00
ctx . Data [ "CanCreateIssueDependencies" ] = ctx . Repo . CanCreateIssueDependencies ( ctx . Doer , issue . IsPull )
2018-07-17 21:23:58 +00:00
Allow cross-repository dependencies on issues (#7901)
* in progress changes for #7405, added ability to add cross-repo dependencies
* removed unused repolink var
* fixed query that was breaking ci tests; fixed check in issue dependency add so that the id of the issue and dependency is checked rather than the indexes
* reverted removal of string in local files becasue these are done via crowdin, not updated manually
* removed 'Select("issue.*")' from getBlockedByDependencies and getBlockingDependencies based on comments in PR review
* changed getBlockedByDependencies and getBlockingDependencies to use a more xorm-like query, also updated the sidebar as a result
* simplified the getBlockingDependencies and getBlockedByDependencies methods; changed the sidebar to show the dependencies in a different format where you can see the name of the repository
* made some changes to the issue view in the dependencies (issue name on top, repo full name on separate line). Change view of issue in the dependency search results (also showing the full repo name on separate line)
* replace call to FindUserAccessibleRepoIDs with SearchRepositoryByName. The former was hardcoded to use isPrivate = false on the repo search, but this code needed it to be true. The SearchRepositoryByName method is used more in the code including on the user's dashboard
* some more tweaks to the layout of the issues when showing dependencies and in the search box when you add new dependencies
* added Name to the RepositoryMeta struct
* updated swagger doc
* fixed total count for link header on SearchIssues
* fixed indentation
* fixed aligment of remove icon on dependencies in issue sidebar
* removed unnecessary nil check (unnecessary because issue.loadRepo is called prior to this block)
* reverting .css change, somehow missed or forgot that less is used
* updated less file and generated css; updated sidebar template with styles to line up delete and issue index
* added ordering to the blocked by/depends on queries
* fixed sorting in issue dependency search and the depends on/blocks views to show issues from the current repo first, then by created date descending; added a "all cross repository dependencies" setting to allow this feature to be turned off, if turned off, the issue dependency search will work the way it did before (restricted to the current repository)
* re-applied my swagger changes after merge
* fixed split string condition in issue search
* changed ALLOW_CROSS_REPOSITORY_DEPENDENCIES description to sound more global than just the issue dependency search; returning 400 in the cross repo issue search api method if not enabled; fixed bug where the issue count did not respect the state parameter
* when adding a dependency to an issue, added a check to make sure the issue and dependency are in the same repo if cross repo dependencies is not enabled
* updated sortIssuesSession call in PullRequests, another commit moved this method from pull.go to pull_list.go so I had to re-apply my change here
* fixed incorrect setting of user id parameter in search repos call
2019-10-31 05:06:10 +00:00
// check if dependencies can be created across repositories
ctx . Data [ "AllowCrossRepositoryDependencies" ] = setting . Service . AllowCrossRepositoryDependencies
2022-04-28 11:48:48 +00:00
if issue . ShowRole , err = roleDescriptor ( ctx , repo , issue . Poster , issue ) ; err != nil {
2021-11-11 06:29:30 +00:00
ctx . ServerError ( "roleDescriptor" , err )
2020-09-10 18:09:14 +00:00
return
}
2021-11-11 06:29:30 +00:00
marked [ issue . PosterID ] = issue . ShowRole
2020-09-10 18:09:14 +00:00
2016-02-02 01:55:12 +00:00
// Render comments and and fetch participants.
participants [ 0 ] = issue . Poster
2015-08-13 18:43:40 +00:00
for _ , comment = range issue . Comments {
2019-05-06 12:09:31 +00:00
comment . Issue = issue
2018-12-13 15:55:43 +00:00
if err := comment . LoadPoster ( ) ; err != nil {
ctx . ServerError ( "LoadPoster" , err )
return
}
2022-01-18 17:28:38 +00:00
if comment . Type == models . CommentTypeComment || comment . Type == models . CommentTypeReview {
2018-12-13 15:55:43 +00:00
if err := comment . LoadAttachments ( ) ; err != nil {
ctx . ServerError ( "LoadAttachments" , err )
return
}
2021-04-19 22:25:08 +00:00
comment . RenderedContent , err = markdown . RenderString ( & markup . RenderContext {
URLPrefix : ctx . Repo . RepoLink ,
Metas : ctx . Repo . Repository . ComposeMetas ( ) ,
2021-06-20 22:39:12 +00:00
GitRepo : ctx . Repo . GitRepo ,
2021-08-28 20:15:56 +00:00
Ctx : ctx ,
2021-04-19 22:25:08 +00:00
} , comment . Content )
if err != nil {
ctx . ServerError ( "RenderString" , err )
return
}
2015-08-13 18:43:40 +00:00
// Check tag.
2021-11-11 06:29:30 +00:00
role , ok = marked [ comment . PosterID ]
2015-08-13 18:43:40 +00:00
if ok {
2021-11-11 06:29:30 +00:00
comment . ShowRole = role
2015-08-13 18:43:40 +00:00
continue
}
2022-04-28 11:48:48 +00:00
comment . ShowRole , err = roleDescriptor ( ctx , repo , comment . Poster , issue )
2017-12-21 07:43:26 +00:00
if err != nil {
2021-11-11 06:29:30 +00:00
ctx . ServerError ( "roleDescriptor" , err )
2017-12-21 07:43:26 +00:00
return
2015-08-13 18:43:40 +00:00
}
2021-11-11 06:29:30 +00:00
marked [ comment . PosterID ] = comment . ShowRole
2019-09-07 14:53:35 +00:00
participants = addParticipant ( comment . Poster , participants )
2017-01-30 12:46:45 +00:00
} else if comment . Type == models . CommentTypeLabel {
if err = comment . LoadLabel ( ) ; err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "LoadLabel" , err )
2017-01-30 12:46:45 +00:00
return
}
2017-02-01 02:36:08 +00:00
} else if comment . Type == models . CommentTypeMilestone {
if err = comment . LoadMilestone ( ) ; err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "LoadMilestone" , err )
2017-02-01 02:36:08 +00:00
return
}
2022-04-08 09:11:15 +00:00
ghostMilestone := & issues_model . Milestone {
2017-06-17 04:51:28 +00:00
ID : - 1 ,
Name : ctx . Tr ( "repo.issues.deleted_milestone" ) ,
}
if comment . OldMilestoneID > 0 && comment . OldMilestone == nil {
comment . OldMilestone = ghostMilestone
}
if comment . MilestoneID > 0 && comment . Milestone == nil {
comment . Milestone = ghostMilestone
}
2020-08-17 03:07:38 +00:00
} else if comment . Type == models . CommentTypeProject {
if err = comment . LoadProject ( ) ; err != nil {
ctx . ServerError ( "LoadProject" , err )
return
}
2022-03-29 14:16:31 +00:00
ghostProject := & project_model . Project {
2020-08-17 03:07:38 +00:00
ID : - 1 ,
Title : ctx . Tr ( "repo.issues.deleted_project" ) ,
}
if comment . OldProjectID > 0 && comment . OldProject == nil {
comment . OldProject = ghostProject
}
if comment . ProjectID > 0 && comment . Project == nil {
comment . Project = ghostProject
}
2020-04-06 16:33:34 +00:00
} else if comment . Type == models . CommentTypeAssignees || comment . Type == models . CommentTypeReviewRequest {
2020-10-12 19:55:13 +00:00
if err = comment . LoadAssigneeUserAndTeam ( ) ; err != nil {
ctx . ServerError ( "LoadAssigneeUserAndTeam" , err )
2017-02-03 15:09:10 +00:00
return
}
2018-07-17 21:23:58 +00:00
} else if comment . Type == models . CommentTypeRemoveDependency || comment . Type == models . CommentTypeAddDependency {
if err = comment . LoadDepIssueDetails ( ) ; err != nil {
2020-09-04 01:36:56 +00:00
if ! models . IsErrIssueNotExist ( err ) {
ctx . ServerError ( "LoadDepIssueDetails" , err )
return
}
2018-07-17 21:23:58 +00:00
}
2021-02-11 17:32:25 +00:00
} else if comment . Type == models . CommentTypeCode || comment . Type == models . CommentTypeReview || comment . Type == models . CommentTypeDismissReview {
2021-04-19 22:25:08 +00:00
comment . RenderedContent , err = markdown . RenderString ( & markup . RenderContext {
URLPrefix : ctx . Repo . RepoLink ,
Metas : ctx . Repo . Repository . ComposeMetas ( ) ,
2021-06-20 22:39:12 +00:00
GitRepo : ctx . Repo . GitRepo ,
2021-08-28 20:15:56 +00:00
Ctx : ctx ,
2021-04-19 22:25:08 +00:00
} , comment . Content )
if err != nil {
ctx . ServerError ( "RenderString" , err )
return
}
2018-08-06 04:43:22 +00:00
if err = comment . LoadReview ( ) ; err != nil && ! models . IsErrReviewNotExist ( err ) {
ctx . ServerError ( "LoadReview" , err )
return
}
2019-09-07 14:53:35 +00:00
participants = addParticipant ( comment . Poster , participants )
2018-08-06 04:43:22 +00:00
if comment . Review == nil {
continue
}
2022-01-19 23:26:57 +00:00
if err = comment . Review . LoadAttributes ( ctx ) ; err != nil {
2021-11-24 09:49:20 +00:00
if ! user_model . IsErrUserNotExist ( err ) {
2019-05-06 12:09:31 +00:00
ctx . ServerError ( "Review.LoadAttributes" , err )
return
}
2021-11-24 09:49:20 +00:00
comment . Review . Reviewer = user_model . NewGhostUser ( )
2018-08-06 04:43:22 +00:00
}
2022-01-19 23:26:57 +00:00
if err = comment . Review . LoadCodeComments ( ctx ) ; err != nil {
2018-08-06 04:43:22 +00:00
ctx . ServerError ( "Review.LoadCodeComments" , err )
return
}
2021-01-17 17:29:10 +00:00
for _ , codeComments := range comment . Review . CodeComments {
for _ , lineComments := range codeComments {
for _ , c := range lineComments {
// Check tag.
2021-11-11 06:29:30 +00:00
role , ok = marked [ c . PosterID ]
2021-01-17 17:29:10 +00:00
if ok {
2021-11-11 06:29:30 +00:00
c . ShowRole = role
2021-01-17 17:29:10 +00:00
continue
}
2020-04-18 13:50:25 +00:00
2022-04-28 11:48:48 +00:00
c . ShowRole , err = roleDescriptor ( ctx , repo , c . Poster , issue )
2021-01-17 17:29:10 +00:00
if err != nil {
2021-11-11 06:29:30 +00:00
ctx . ServerError ( "roleDescriptor" , err )
2021-01-17 17:29:10 +00:00
return
}
2021-11-11 06:29:30 +00:00
marked [ c . PosterID ] = c . ShowRole
2021-01-17 17:29:10 +00:00
participants = addParticipant ( c . Poster , participants )
}
}
}
2020-04-18 13:50:25 +00:00
if err = comment . LoadResolveDoer ( ) ; err != nil {
ctx . ServerError ( "LoadResolveDoer" , err )
return
}
2022-01-21 17:59:26 +00:00
} else if comment . Type == models . CommentTypePullRequestPush {
2020-05-20 12:47:24 +00:00
participants = addParticipant ( comment . Poster , participants )
2022-01-19 23:26:57 +00:00
if err = comment . LoadPushCommits ( ctx ) ; err != nil {
2020-05-20 12:47:24 +00:00
ctx . ServerError ( "LoadPushCommits" , err )
return
}
2021-02-19 10:52:11 +00:00
} else if comment . Type == models . CommentTypeAddTimeManual ||
comment . Type == models . CommentTypeStopTracking {
// drop error since times could be pruned from DB..
_ = comment . LoadTime ( )
2015-08-13 08:07:11 +00:00
}
}
2014-07-26 06:28:04 +00:00
2020-10-25 21:49:48 +00:00
// Combine multiple label assignments into a single comment
combineLabelComments ( issue )
2019-12-16 06:20:25 +00:00
getBranchData ( ctx , issue )
2016-12-25 15:27:25 +00:00
if issue . IsPull {
pull := issue . PullRequest
2018-12-11 23:49:33 +00:00
pull . Issue = issue
2016-12-25 16:19:25 +00:00
canDelete := false
2020-01-11 07:29:34 +00:00
ctx . Data [ "AllowMerge" ] = false
2016-12-25 16:19:25 +00:00
2017-06-21 01:00:03 +00:00
if ctx . IsSigned {
2022-04-28 11:48:48 +00:00
if err := pull . LoadHeadRepoCtx ( ctx ) ; err != nil {
2020-03-02 22:31:55 +00:00
log . Error ( "LoadHeadRepo: %v" , err )
2022-04-28 15:45:33 +00:00
} else if pull . HeadRepo != nil {
2022-04-28 11:48:48 +00:00
perm , err := models . GetUserRepoPermission ( ctx , pull . HeadRepo , ctx . Doer )
2018-11-28 11:26:14 +00:00
if err != nil {
ctx . ServerError ( "GetUserRepoPermission" , err )
return
}
2021-11-09 19:57:58 +00:00
if perm . CanWrite ( unit . TypeCode ) {
2018-11-28 11:26:14 +00:00
// Check if branch is not protected
2022-04-28 15:45:33 +00:00
if pull . HeadBranch != pull . HeadRepo . DefaultBranch {
if protected , err := models . IsProtectedBranch ( pull . HeadRepo . ID , pull . HeadBranch ) ; err != nil {
log . Error ( "IsProtectedBranch: %v" , err )
} else if ! protected {
canDelete = true
ctx . Data [ "DeleteBranchLink" ] = issue . Link ( ) + "/cleanup"
}
2018-11-28 11:26:14 +00:00
}
2022-04-28 15:45:33 +00:00
ctx . Data [ "CanWriteToHeadRepo" ] = true
2017-06-21 01:00:03 +00:00
}
2016-12-25 16:19:25 +00:00
}
2020-01-11 07:29:34 +00:00
2022-04-28 11:48:48 +00:00
if err := pull . LoadBaseRepoCtx ( ctx ) ; err != nil {
2020-03-02 22:31:55 +00:00
log . Error ( "LoadBaseRepo: %v" , err )
2020-01-11 07:29:34 +00:00
}
2022-04-28 11:48:48 +00:00
perm , err := models . GetUserRepoPermission ( ctx , pull . BaseRepo , ctx . Doer )
2020-01-11 07:29:34 +00:00
if err != nil {
ctx . ServerError ( "GetUserRepoPermission" , err )
return
}
2022-05-03 19:46:28 +00:00
ctx . Data [ "AllowMerge" ] , err = pull_service . IsUserAllowedToMerge ( ctx , pull , perm , ctx . Doer )
2020-01-11 07:29:34 +00:00
if err != nil {
ctx . ServerError ( "IsUserAllowedToMerge" , err )
return
}
2020-04-18 13:50:25 +00:00
2022-03-22 07:03:22 +00:00
if ctx . Data [ "CanMarkConversation" ] , err = models . CanMarkConversation ( issue , ctx . Doer ) ; err != nil {
2020-04-18 13:50:25 +00:00
ctx . ServerError ( "CanMarkConversation" , err )
return
}
2016-12-25 16:19:25 +00:00
}
2016-12-25 15:27:25 +00:00
2021-11-09 19:57:58 +00:00
prUnit , err := repo . GetUnit ( unit . TypePullRequests )
2018-01-05 18:56:50 +00:00
if err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "GetUnit" , err )
2018-01-05 18:56:50 +00:00
return
}
prConfig := prUnit . PullRequestsConfig ( )
// Check correct values and select default
2021-12-10 01:27:50 +00:00
if ms , ok := ctx . Data [ "MergeStyle" ] . ( repo_model . MergeStyle ) ; ! ok ||
2018-01-05 18:56:50 +00:00
! prConfig . IsMergeStyleAllowed ( ms ) {
2021-03-27 14:55:40 +00:00
defaultMergeStyle := prConfig . GetDefaultMergeStyle ( )
if prConfig . IsMergeStyleAllowed ( defaultMergeStyle ) && ! ok {
ctx . Data [ "MergeStyle" ] = defaultMergeStyle
} else if prConfig . AllowMerge {
2021-12-10 01:27:50 +00:00
ctx . Data [ "MergeStyle" ] = repo_model . MergeStyleMerge
2018-01-05 18:56:50 +00:00
} else if prConfig . AllowRebase {
2021-12-10 01:27:50 +00:00
ctx . Data [ "MergeStyle" ] = repo_model . MergeStyleRebase
2018-12-27 10:27:08 +00:00
} else if prConfig . AllowRebaseMerge {
2021-12-10 01:27:50 +00:00
ctx . Data [ "MergeStyle" ] = repo_model . MergeStyleRebaseMerge
2018-01-05 18:56:50 +00:00
} else if prConfig . AllowSquash {
2021-12-10 01:27:50 +00:00
ctx . Data [ "MergeStyle" ] = repo_model . MergeStyleSquash
2021-03-04 03:41:23 +00:00
} else if prConfig . AllowManualMerge {
2021-12-10 01:27:50 +00:00
ctx . Data [ "MergeStyle" ] = repo_model . MergeStyleManuallyMerged
2018-01-05 18:56:50 +00:00
} else {
ctx . Data [ "MergeStyle" ] = ""
}
}
2018-12-11 11:28:37 +00:00
if err = pull . LoadProtectedBranch ( ) ; err != nil {
ctx . ServerError ( "LoadProtectedBranch" , err )
return
}
2021-10-17 16:58:36 +00:00
ctx . Data [ "ShowMergeInstructions" ] = true
2018-12-11 11:28:37 +00:00
if pull . ProtectedBranch != nil {
2021-11-09 13:14:12 +00:00
var showMergeInstructions bool
2022-03-22 07:03:22 +00:00
if ctx . Doer != nil {
showMergeInstructions = pull . ProtectedBranch . CanUserPush ( ctx . Doer . ID )
2021-11-09 13:14:12 +00:00
}
2022-05-03 19:46:28 +00:00
ctx . Data [ "IsBlockedByApprovals" ] = ! pull . ProtectedBranch . HasEnoughApprovals ( ctx , pull )
ctx . Data [ "IsBlockedByRejection" ] = pull . ProtectedBranch . MergeBlockedByRejectedReview ( ctx , pull )
ctx . Data [ "IsBlockedByOfficialReviewRequests" ] = pull . ProtectedBranch . MergeBlockedByOfficialReviewRequests ( ctx , pull )
2020-04-17 01:00:36 +00:00
ctx . Data [ "IsBlockedByOutdatedBranch" ] = pull . ProtectedBranch . MergeBlockedByOutdatedBranch ( pull )
2022-05-03 19:46:28 +00:00
ctx . Data [ "GrantedApprovals" ] = pull . ProtectedBranch . GetGrantedApprovalsCount ( ctx , pull )
2020-01-15 08:32:57 +00:00
ctx . Data [ "RequireSigned" ] = pull . ProtectedBranch . RequireSignedCommits
2020-10-13 18:50:57 +00:00
ctx . Data [ "ChangedProtectedFiles" ] = pull . ChangedProtectedFiles
ctx . Data [ "IsBlockedByChangedProtectedFiles" ] = len ( pull . ChangedProtectedFiles ) != 0
ctx . Data [ "ChangedProtectedFilesNum" ] = len ( pull . ChangedProtectedFiles )
2021-11-09 13:14:12 +00:00
ctx . Data [ "ShowMergeInstructions" ] = showMergeInstructions
2020-01-15 08:32:57 +00:00
}
ctx . Data [ "WillSign" ] = false
2022-03-22 07:03:22 +00:00
if ctx . Doer != nil {
sign , key , _ , err := asymkey_service . SignMerge ( ctx , pull , ctx . Doer , pull . BaseRepo . RepoPath ( ) , pull . BaseBranch , pull . GetGitRefName ( ) )
2020-01-15 08:32:57 +00:00
ctx . Data [ "WillSign" ] = sign
ctx . Data [ "SigningKey" ] = key
if err != nil {
2021-12-10 08:14:24 +00:00
if asymkey_service . IsErrWontSign ( err ) {
ctx . Data [ "WontSignReason" ] = err . ( * asymkey_service . ErrWontSign ) . Reason
2020-01-15 08:32:57 +00:00
} else {
ctx . Data [ "WontSignReason" ] = "error"
log . Error ( "Error whilst checking if could sign pr %d in repo %s. Error: %v" , pull . ID , pull . BaseRepo . FullName ( ) , err )
}
}
2020-08-23 21:59:41 +00:00
} else {
ctx . Data [ "WontSignReason" ] = "not_signed_in"
2018-12-11 11:28:37 +00:00
}
2022-01-03 19:45:58 +00:00
isPullBranchDeletable := canDelete &&
2020-01-07 17:06:14 +00:00
pull . HeadRepo != nil &&
2021-11-30 20:06:32 +00:00
git . IsBranchExist ( ctx , pull . HeadRepo . RepoPath ( ) , pull . HeadBranch ) &&
2020-01-07 17:06:14 +00:00
( ! pull . HasMerged || ctx . Data [ "HeadBranchCommitID" ] == ctx . Data [ "PullHeadCommitID" ] )
2021-03-04 03:41:23 +00:00
2022-01-03 19:45:58 +00:00
if isPullBranchDeletable && pull . HasMerged {
2022-05-03 19:46:28 +00:00
exist , err := models . HasUnmergedPullRequestsByHeadInfo ( ctx , pull . HeadRepoID , pull . HeadBranch )
2022-01-03 19:45:58 +00:00
if err != nil {
ctx . ServerError ( "HasUnmergedPullRequestsByHeadInfo" , err )
return
}
isPullBranchDeletable = ! exist
}
ctx . Data [ "IsPullBranchDeletable" ] = isPullBranchDeletable
2021-03-04 03:41:23 +00:00
stillCanManualMerge := func ( ) bool {
if pull . HasMerged || issue . IsClosed || ! ctx . IsSigned {
return false
}
if pull . CanAutoMerge ( ) || pull . IsWorkInProgress ( ) || pull . IsChecking ( ) {
return false
}
2022-03-22 07:03:22 +00:00
if ( ctx . Doer . IsAdmin || ctx . Repo . IsAdmin ( ) ) && prConfig . AllowManualMerge {
2021-03-04 03:41:23 +00:00
return true
}
return false
}
ctx . Data [ "StillCanManualMerge" ] = stillCanManualMerge ( )
2016-12-25 15:27:25 +00:00
}
2018-07-17 21:23:58 +00:00
// Get Dependencies
ctx . Data [ "BlockedByDependencies" ] , err = issue . BlockedByDependencies ( )
2019-06-12 19:41:28 +00:00
if err != nil {
ctx . ServerError ( "BlockedByDependencies" , err )
return
}
2018-07-17 21:23:58 +00:00
ctx . Data [ "BlockingDependencies" ] , err = issue . BlockingDependencies ( )
2019-06-12 19:41:28 +00:00
if err != nil {
ctx . ServerError ( "BlockingDependencies" , err )
return
}
2018-07-17 21:23:58 +00:00
2016-01-19 13:04:24 +00:00
ctx . Data [ "Participants" ] = participants
2016-02-02 01:55:12 +00:00
ctx . Data [ "NumParticipants" ] = len ( participants )
2014-07-26 06:28:04 +00:00
ctx . Data [ "Issue" ] = issue
2021-12-17 21:29:09 +00:00
ctx . Data [ "Reference" ] = issue . Ref
2021-11-16 18:18:25 +00:00
ctx . Data [ "SignInLink" ] = setting . AppSubURL + "/user/login?redirect_to=" + url . QueryEscape ( ctx . Data [ "Link" ] . ( string ) )
2022-03-22 07:03:22 +00:00
ctx . Data [ "IsIssuePoster" ] = ctx . IsSigned && issue . IsPoster ( ctx . Doer . ID )
2020-04-04 05:39:48 +00:00
ctx . Data [ "HasIssuesOrPullsWritePermission" ] = ctx . Repo . CanWriteIssuesOrPulls ( issue . IsPull )
2021-11-09 19:57:58 +00:00
ctx . Data [ "HasProjectsWritePermission" ] = ctx . Repo . CanWrite ( unit . TypeProjects )
2022-03-22 07:03:22 +00:00
ctx . Data [ "IsRepoAdmin" ] = ctx . IsSigned && ( ctx . Repo . IsAdmin ( ) || ctx . Doer . IsAdmin )
2019-02-18 20:55:04 +00:00
ctx . Data [ "LockReasons" ] = setting . Repository . Issue . LockReasons
2020-05-14 22:55:43 +00:00
ctx . Data [ "RefEndName" ] = git . RefEndName ( issue . Ref )
2022-01-21 17:59:26 +00:00
var hiddenCommentTypes * big . Int
if ctx . IsSigned {
2022-03-22 07:03:22 +00:00
val , err := user_model . GetUserSetting ( ctx . Doer . ID , user_model . SettingsKeyHiddenCommentTypes )
2022-01-21 17:59:26 +00:00
if err != nil {
ctx . ServerError ( "GetUserSetting" , err )
return
}
hiddenCommentTypes , _ = new ( big . Int ) . SetString ( val , 10 ) // we can safely ignore the failed conversion here
}
ctx . Data [ "ShouldShowCommentType" ] = func ( commentType models . CommentType ) bool {
return hiddenCommentTypes == nil || hiddenCommentTypes . Bit ( int ( commentType ) ) == 0
}
2021-04-05 15:30:52 +00:00
ctx . HTML ( http . StatusOK , tplIssueView )
2014-07-26 06:28:04 +00:00
}
2017-09-12 06:48:13 +00:00
// GetActionIssue will return the issue which is used in the context.
func GetActionIssue ( ctx * context . Context ) * models . Issue {
2015-08-19 15:14:57 +00:00
issue , err := models . GetIssueByIndex ( ctx . Repo . Repository . ID , ctx . ParamsInt64 ( ":index" ) )
2014-07-26 06:28:04 +00:00
if err != nil {
2017-10-16 07:55:43 +00:00
ctx . NotFoundOrServerError ( "GetIssueByIndex" , models . IsErrIssueNotExist , err )
return nil
}
2018-12-13 15:55:43 +00:00
issue . Repo = ctx . Repo . Repository
2017-12-03 23:14:26 +00:00
checkIssueRights ( ctx , issue )
if ctx . Written ( ) {
2017-10-16 07:55:43 +00:00
return nil
}
if err = issue . LoadAttributes ( ) ; err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "LoadAttributes" , nil )
2015-08-19 15:14:57 +00:00
return nil
}
return issue
}
2017-12-03 23:14:26 +00:00
func checkIssueRights ( ctx * context . Context , issue * models . Issue ) {
2021-11-09 19:57:58 +00:00
if issue . IsPull && ! ctx . Repo . CanRead ( unit . TypePullRequests ) ||
! issue . IsPull && ! ctx . Repo . CanRead ( unit . TypeIssues ) {
2018-01-10 21:34:17 +00:00
ctx . NotFound ( "IssueOrPullRequestUnitNotAllowed" , nil )
2017-12-03 23:14:26 +00:00
}
}
2017-03-15 01:10:35 +00:00
func getActionIssues ( ctx * context . Context ) [ ] * models . Issue {
2021-08-11 00:31:13 +00:00
commaSeparatedIssueIDs := ctx . FormString ( "issue_ids" )
2017-03-15 01:10:35 +00:00
if len ( commaSeparatedIssueIDs ) == 0 {
return nil
}
issueIDs := make ( [ ] int64 , 0 , 10 )
for _ , stringIssueID := range strings . Split ( commaSeparatedIssueIDs , "," ) {
issueID , err := strconv . ParseInt ( stringIssueID , 10 , 64 )
if err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "ParseInt" , err )
2017-03-15 01:10:35 +00:00
return nil
}
issueIDs = append ( issueIDs , issueID )
}
issues , err := models . GetIssuesByIDs ( issueIDs )
if err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "GetIssuesByIDs" , err )
2017-03-15 01:10:35 +00:00
return nil
}
2017-10-16 07:55:43 +00:00
// Check access rights for all issues
2021-11-09 19:57:58 +00:00
issueUnitEnabled := ctx . Repo . CanRead ( unit . TypeIssues )
prUnitEnabled := ctx . Repo . CanRead ( unit . TypePullRequests )
2017-10-16 07:55:43 +00:00
for _ , issue := range issues {
if issue . IsPull && ! prUnitEnabled || ! issue . IsPull && ! issueUnitEnabled {
2018-01-10 21:34:17 +00:00
ctx . NotFound ( "IssueOrPullRequestUnitNotAllowed" , nil )
2017-10-16 07:55:43 +00:00
return nil
}
if err = issue . LoadAttributes ( ) ; err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "LoadAttributes" , err )
2017-10-16 07:55:43 +00:00
return nil
}
}
2017-03-15 01:10:35 +00:00
return issues
}
2022-04-07 18:59:56 +00:00
// GetIssueInfo get an issue of a repository
func GetIssueInfo ( ctx * context . Context ) {
issue , err := models . GetIssueWithAttrsByIndex ( ctx . Repo . Repository . ID , ctx . ParamsInt64 ( ":index" ) )
if err != nil {
if models . IsErrIssueNotExist ( err ) {
ctx . Error ( http . StatusNotFound )
} else {
ctx . Error ( http . StatusInternalServerError , "GetIssueByIndex" , err . Error ( ) )
}
return
}
ctx . JSON ( http . StatusOK , convert . ToAPIIssue ( issue ) )
}
2016-11-24 07:04:31 +00:00
// UpdateIssueTitle change issue's title
2016-03-11 16:56:52 +00:00
func UpdateIssueTitle ( ctx * context . Context ) {
2017-09-12 06:48:13 +00:00
issue := GetActionIssue ( ctx )
2015-08-19 15:14:57 +00:00
if ctx . Written ( ) {
2014-07-26 06:28:04 +00:00
return
}
2022-03-22 07:03:22 +00:00
if ! ctx . IsSigned || ( ! issue . IsPoster ( ctx . Doer . ID ) && ! ctx . Repo . CanWriteIssuesOrPulls ( issue . IsPull ) ) {
2021-04-05 15:30:52 +00:00
ctx . Error ( http . StatusForbidden )
2014-07-26 06:28:04 +00:00
return
}
2021-07-29 01:42:15 +00:00
title := ctx . FormTrim ( "title" )
2016-08-14 10:32:24 +00:00
if len ( title ) == 0 {
2021-04-05 15:30:52 +00:00
ctx . Error ( http . StatusNoContent )
2015-08-19 15:14:57 +00:00
return
2014-07-26 06:28:04 +00:00
}
2015-08-19 15:14:57 +00:00
2022-03-22 07:03:22 +00:00
if err := issue_service . ChangeTitle ( issue , ctx . Doer , title ) ; err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "ChangeTitle" , err )
2014-07-26 06:28:04 +00:00
return
}
2021-04-05 15:30:52 +00:00
ctx . JSON ( http . StatusOK , map [ string ] interface { } {
2016-08-14 10:32:24 +00:00
"title" : issue . Title ,
2014-07-26 06:28:04 +00:00
} )
}
2020-09-08 16:29:51 +00:00
// UpdateIssueRef change issue's ref (branch)
func UpdateIssueRef ( ctx * context . Context ) {
issue := GetActionIssue ( ctx )
if ctx . Written ( ) {
return
}
2022-03-22 07:03:22 +00:00
if ! ctx . IsSigned || ( ! issue . IsPoster ( ctx . Doer . ID ) && ! ctx . Repo . CanWriteIssuesOrPulls ( issue . IsPull ) ) || issue . IsPull {
2021-04-05 15:30:52 +00:00
ctx . Error ( http . StatusForbidden )
2020-09-08 16:29:51 +00:00
return
}
2021-07-29 01:42:15 +00:00
ref := ctx . FormTrim ( "ref" )
2020-09-08 16:29:51 +00:00
2022-03-22 07:03:22 +00:00
if err := issue_service . ChangeIssueRef ( issue , ctx . Doer , ref ) ; err != nil {
2020-09-08 16:29:51 +00:00
ctx . ServerError ( "ChangeRef" , err )
return
}
2021-04-05 15:30:52 +00:00
ctx . JSON ( http . StatusOK , map [ string ] interface { } {
2020-09-08 16:29:51 +00:00
"ref" : ref ,
} )
}
2016-11-24 07:04:31 +00:00
// UpdateIssueContent change issue's content
2016-03-11 16:56:52 +00:00
func UpdateIssueContent ( ctx * context . Context ) {
2017-09-12 06:48:13 +00:00
issue := GetActionIssue ( ctx )
2015-08-19 20:31:28 +00:00
if ctx . Written ( ) {
return
}
2022-03-22 07:03:22 +00:00
if ! ctx . IsSigned || ( ctx . Doer . ID != issue . PosterID && ! ctx . Repo . CanWriteIssuesOrPulls ( issue . IsPull ) ) {
2021-04-05 15:30:52 +00:00
ctx . Error ( http . StatusForbidden )
2015-08-19 20:31:28 +00:00
return
}
2022-03-22 07:03:22 +00:00
if err := issue_service . ChangeContent ( issue , ctx . Doer , ctx . Req . FormValue ( "content" ) ) ; err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "ChangeContent" , err )
2015-08-19 20:31:28 +00:00
return
}
2021-08-21 13:04:47 +00:00
// when update the request doesn't intend to update attachments (eg: change checkbox state), ignore attachment updates
if ! ctx . FormBool ( "ignore_attachments" ) {
if err := updateAttachments ( issue , ctx . FormStrings ( "files[]" ) ) ; err != nil {
ctx . ServerError ( "UpdateAttachments" , err )
return
}
2021-04-19 22:25:08 +00:00
}
content , err := markdown . RenderString ( & markup . RenderContext {
2021-11-16 18:18:25 +00:00
URLPrefix : ctx . FormString ( "context" ) , // FIXME: <- IS THIS SAFE ?
2021-04-19 22:25:08 +00:00
Metas : ctx . Repo . Repository . ComposeMetas ( ) ,
2021-06-20 22:39:12 +00:00
GitRepo : ctx . Repo . GitRepo ,
2021-08-28 20:15:56 +00:00
Ctx : ctx ,
2021-04-19 22:25:08 +00:00
} , issue . Content )
if err != nil {
ctx . ServerError ( "RenderString" , err )
return
2019-10-15 12:19:32 +00:00
}
2021-04-05 15:30:52 +00:00
ctx . JSON ( http . StatusOK , map [ string ] interface { } {
2021-04-19 22:25:08 +00:00
"content" : content ,
2020-12-13 19:12:27 +00:00
"attachments" : attachmentsHTML ( ctx , issue . Attachments , issue . Content ) ,
2015-08-19 20:31:28 +00:00
} )
}
2022-04-07 18:59:56 +00:00
// UpdateIssueDeadline updates an issue deadline
func UpdateIssueDeadline ( ctx * context . Context ) {
form := web . GetForm ( ctx ) . ( * api . EditDeadlineOption )
issue , err := models . GetIssueByIndex ( ctx . Repo . Repository . ID , ctx . ParamsInt64 ( ":index" ) )
if err != nil {
if models . IsErrIssueNotExist ( err ) {
ctx . NotFound ( "GetIssueByIndex" , err )
} else {
ctx . Error ( http . StatusInternalServerError , "GetIssueByIndex" , err . Error ( ) )
}
return
}
if ! ctx . Repo . CanWriteIssuesOrPulls ( issue . IsPull ) {
ctx . Error ( http . StatusForbidden , "" , "Not repo writer" )
return
}
var deadlineUnix timeutil . TimeStamp
var deadline time . Time
if form . Deadline != nil && ! form . Deadline . IsZero ( ) {
deadline = time . Date ( form . Deadline . Year ( ) , form . Deadline . Month ( ) , form . Deadline . Day ( ) ,
23 , 59 , 59 , 0 , time . Local )
deadlineUnix = timeutil . TimeStamp ( deadline . Unix ( ) )
}
if err := models . UpdateIssueDeadline ( issue , deadlineUnix , ctx . Doer ) ; err != nil {
ctx . Error ( http . StatusInternalServerError , "UpdateIssueDeadline" , err . Error ( ) )
return
}
ctx . JSON ( http . StatusCreated , api . IssueDeadline { Deadline : & deadline } )
}
2016-11-24 07:04:31 +00:00
// UpdateIssueMilestone change issue's milestone
2016-03-11 16:56:52 +00:00
func UpdateIssueMilestone ( ctx * context . Context ) {
2017-03-15 01:10:35 +00:00
issues := getActionIssues ( ctx )
2015-08-14 16:42:43 +00:00
if ctx . Written ( ) {
2014-07-26 06:28:04 +00:00
return
}
2021-07-29 01:42:15 +00:00
milestoneID := ctx . FormInt64 ( "id" )
2017-03-15 01:10:35 +00:00
for _ , issue := range issues {
oldMilestoneID := issue . MilestoneID
if oldMilestoneID == milestoneID {
continue
}
issue . MilestoneID = milestoneID
2022-03-22 07:03:22 +00:00
if err := issue_service . ChangeMilestoneAssign ( issue , ctx . Doer , oldMilestoneID ) ; err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "ChangeMilestoneAssign" , err )
2017-03-15 01:10:35 +00:00
return
}
2014-07-26 06:28:04 +00:00
}
2021-04-05 15:30:52 +00:00
ctx . JSON ( http . StatusOK , map [ string ] interface { } {
2014-07-26 06:28:04 +00:00
"ok" : true ,
} )
}
2019-10-25 14:46:37 +00:00
// UpdateIssueAssignee change issue's or pull's assignee
2016-03-11 16:56:52 +00:00
func UpdateIssueAssignee ( ctx * context . Context ) {
2017-03-15 01:10:35 +00:00
issues := getActionIssues ( ctx )
2015-08-14 16:42:43 +00:00
if ctx . Written ( ) {
2014-07-26 06:28:04 +00:00
return
}
2021-07-29 01:42:15 +00:00
assigneeID := ctx . FormInt64 ( "id" )
2021-08-11 00:31:13 +00:00
action := ctx . FormString ( "action" )
2018-05-09 16:29:04 +00:00
2017-03-15 01:10:35 +00:00
for _ , issue := range issues {
2018-05-09 16:29:04 +00:00
switch action {
case "clear" :
2022-03-22 07:03:22 +00:00
if err := issue_service . DeleteNotPassedAssignee ( issue , ctx . Doer , [ ] * user_model . User { } ) ; err != nil {
2018-05-09 16:29:04 +00:00
ctx . ServerError ( "ClearAssignees" , err )
return
}
default :
2021-11-24 09:49:20 +00:00
assignee , err := user_model . GetUserByID ( assigneeID )
2019-10-25 14:46:37 +00:00
if err != nil {
ctx . ServerError ( "GetUserByID" , err )
return
}
valid , err := models . CanBeAssigned ( assignee , issue . Repo , issue . IsPull )
if err != nil {
ctx . ServerError ( "canBeAssigned" , err )
2018-05-09 16:29:04 +00:00
return
}
2019-10-25 14:46:37 +00:00
if ! valid {
ctx . ServerError ( "canBeAssigned" , models . ErrUserDoesNotHaveAccessToRepo { UserID : assigneeID , RepoName : issue . Repo . Name } )
return
}
2022-03-22 07:03:22 +00:00
_ , _ , err = issue_service . ToggleAssignee ( issue , ctx . Doer , assigneeID )
2019-10-25 14:46:37 +00:00
if err != nil {
ctx . ServerError ( "ToggleAssignee" , err )
return
}
2017-03-15 01:10:35 +00:00
}
2014-07-26 06:28:04 +00:00
}
2021-04-05 15:30:52 +00:00
ctx . JSON ( http . StatusOK , map [ string ] interface { } {
2017-03-15 01:10:35 +00:00
"ok" : true ,
} )
}
2014-07-26 06:28:04 +00:00
2020-10-12 19:55:13 +00:00
// UpdatePullReviewRequest add or remove review request
func UpdatePullReviewRequest ( ctx * context . Context ) {
2020-04-06 16:33:34 +00:00
issues := getActionIssues ( ctx )
if ctx . Written ( ) {
return
}
2021-07-29 01:42:15 +00:00
reviewID := ctx . FormInt64 ( "id" )
2021-08-11 00:31:13 +00:00
action := ctx . FormString ( "action" )
2020-04-06 16:33:34 +00:00
2020-09-02 16:55:13 +00:00
// TODO: Not support 'clear' now
if action != "attach" && action != "detach" {
2022-03-23 04:54:07 +00:00
ctx . Status ( http . StatusForbidden )
2020-04-06 16:33:34 +00:00
return
}
for _ , issue := range issues {
2022-04-08 09:11:15 +00:00
if err := issue . LoadRepo ( ctx ) ; err != nil {
2020-10-12 19:55:13 +00:00
ctx . ServerError ( "issue.LoadRepo" , err )
return
}
if ! issue . IsPull {
log . Warn (
"UpdatePullReviewRequest: refusing to add review request for non-PR issue %-v#%d" ,
issue . Repo , issue . Index ,
)
2022-03-23 04:54:07 +00:00
ctx . Status ( http . StatusForbidden )
2020-10-12 19:55:13 +00:00
return
}
if reviewID < 0 {
// negative reviewIDs represent team requests
2022-03-22 15:22:54 +00:00
if err := issue . Repo . GetOwner ( ctx ) ; err != nil {
2020-10-12 19:55:13 +00:00
ctx . ServerError ( "issue.Repo.GetOwner" , err )
return
}
if ! issue . Repo . Owner . IsOrganization ( ) {
log . Warn (
"UpdatePullReviewRequest: refusing to add team review request for %s#%d owned by non organization UID[%d]" ,
issue . Repo . FullName ( ) , issue . Index , issue . Repo . ID ,
)
2022-03-23 04:54:07 +00:00
ctx . Status ( http . StatusForbidden )
2020-10-12 19:55:13 +00:00
return
}
2020-04-06 16:33:34 +00:00
2022-03-29 06:29:02 +00:00
team , err := organization . GetTeamByID ( - reviewID )
2020-04-06 16:33:34 +00:00
if err != nil {
2022-03-29 06:29:02 +00:00
ctx . ServerError ( "GetTeamByID" , err )
2020-04-06 16:33:34 +00:00
return
}
2020-10-12 19:55:13 +00:00
if team . OrgID != issue . Repo . OwnerID {
log . Warn (
"UpdatePullReviewRequest: refusing to add team review request for UID[%d] team %s to %s#%d owned by UID[%d]" ,
team . OrgID , team . Name , issue . Repo . FullName ( ) , issue . Index , issue . Repo . ID )
2022-03-23 04:54:07 +00:00
ctx . Status ( http . StatusForbidden )
2020-10-12 19:55:13 +00:00
return
}
2022-04-28 11:48:48 +00:00
err = issue_service . IsValidTeamReviewRequest ( ctx , team , ctx . Doer , action == "attach" , issue )
2020-04-06 16:33:34 +00:00
if err != nil {
2020-10-12 19:55:13 +00:00
if models . IsErrNotValidReviewRequest ( err ) {
log . Warn (
"UpdatePullReviewRequest: refusing to add invalid team review request for UID[%d] team %s to %s#%d owned by UID[%d]: Error: %v" ,
team . OrgID , team . Name , issue . Repo . FullName ( ) , issue . Index , issue . Repo . ID ,
err ,
)
2022-03-23 04:54:07 +00:00
ctx . Status ( http . StatusForbidden )
2020-10-12 19:55:13 +00:00
return
}
2020-10-20 18:18:25 +00:00
ctx . ServerError ( "IsValidTeamReviewRequest" , err )
2020-04-06 16:33:34 +00:00
return
}
2022-03-22 07:03:22 +00:00
_ , err = issue_service . TeamReviewRequest ( issue , ctx . Doer , team , action == "attach" )
2020-04-06 16:33:34 +00:00
if err != nil {
2020-10-12 19:55:13 +00:00
ctx . ServerError ( "TeamReviewRequest" , err )
2020-04-06 16:33:34 +00:00
return
}
2020-10-12 19:55:13 +00:00
continue
}
2021-11-24 09:49:20 +00:00
reviewer , err := user_model . GetUserByID ( reviewID )
2020-10-12 19:55:13 +00:00
if err != nil {
2021-11-24 09:49:20 +00:00
if user_model . IsErrUserNotExist ( err ) {
2020-10-12 19:55:13 +00:00
log . Warn (
"UpdatePullReviewRequest: requested reviewer [%d] for %-v to %-v#%d is not exist: Error: %v" ,
reviewID , issue . Repo , issue . Index ,
err ,
)
2022-03-23 04:54:07 +00:00
ctx . Status ( http . StatusForbidden )
2020-10-12 19:55:13 +00:00
return
}
ctx . ServerError ( "GetUserByID" , err )
return
}
2022-04-28 11:48:48 +00:00
err = issue_service . IsValidReviewRequest ( ctx , reviewer , ctx . Doer , action == "attach" , issue , nil )
2020-10-12 19:55:13 +00:00
if err != nil {
if models . IsErrNotValidReviewRequest ( err ) {
log . Warn (
"UpdatePullReviewRequest: refusing to add invalid review request for %-v to %-v#%d: Error: %v" ,
reviewer , issue . Repo , issue . Index ,
err ,
)
2022-03-23 04:54:07 +00:00
ctx . Status ( http . StatusForbidden )
2020-10-12 19:55:13 +00:00
return
}
ctx . ServerError ( "isValidReviewRequest" , err )
return
}
2022-03-22 07:03:22 +00:00
_ , err = issue_service . ReviewRequest ( issue , ctx . Doer , reviewer , action == "attach" )
2020-10-12 19:55:13 +00:00
if err != nil {
ctx . ServerError ( "ReviewRequest" , err )
2020-09-02 16:55:13 +00:00
return
2020-04-06 16:33:34 +00:00
}
}
2021-04-05 15:30:52 +00:00
ctx . JSON ( http . StatusOK , map [ string ] interface { } {
2020-04-06 16:33:34 +00:00
"ok" : true ,
} )
}
2022-04-07 18:59:56 +00:00
// SearchIssues searches for issues across the repositories that the user has access to
func SearchIssues ( ctx * context . Context ) {
before , since , err := context . GetQueryBeforeSince ( ctx )
if err != nil {
ctx . Error ( http . StatusUnprocessableEntity , err . Error ( ) )
return
}
var isClosed util . OptionalBool
switch ctx . FormString ( "state" ) {
case "closed" :
isClosed = util . OptionalBoolTrue
case "all" :
isClosed = util . OptionalBoolNone
default :
isClosed = util . OptionalBoolFalse
}
// find repos user can access (for issue search)
opts := & models . SearchRepoOptions {
Private : false ,
AllPublic : true ,
TopicOnly : false ,
Collaborate : util . OptionalBoolNone ,
// This needs to be a column that is not nil in fixtures or
// MySQL will return different results when sorting by null in some cases
OrderBy : db . SearchOrderByAlphabetically ,
Actor : ctx . Doer ,
}
if ctx . IsSigned {
opts . Private = true
opts . AllLimited = true
}
if ctx . FormString ( "owner" ) != "" {
owner , err := user_model . GetUserByName ( ctx . FormString ( "owner" ) )
if err != nil {
if user_model . IsErrUserNotExist ( err ) {
ctx . Error ( http . StatusBadRequest , "Owner not found" , err . Error ( ) )
} else {
ctx . Error ( http . StatusInternalServerError , "GetUserByName" , err . Error ( ) )
}
return
}
opts . OwnerID = owner . ID
opts . AllLimited = false
opts . AllPublic = false
opts . Collaborate = util . OptionalBoolFalse
}
if ctx . FormString ( "team" ) != "" {
if ctx . FormString ( "owner" ) == "" {
ctx . Error ( http . StatusBadRequest , "" , "Owner organisation is required for filtering on team" )
return
}
team , err := organization . GetTeam ( opts . OwnerID , ctx . FormString ( "team" ) )
if err != nil {
if organization . IsErrTeamNotExist ( err ) {
ctx . Error ( http . StatusBadRequest , "Team not found" , err . Error ( ) )
} else {
ctx . Error ( http . StatusInternalServerError , "GetUserByName" , err . Error ( ) )
}
return
}
opts . TeamID = team . ID
}
2022-04-25 14:06:24 +00:00
repoCond := models . SearchRepositoryCondition ( opts )
2022-04-07 18:59:56 +00:00
repoIDs , _ , err := models . SearchRepositoryIDs ( opts )
if err != nil {
ctx . Error ( http . StatusInternalServerError , "SearchRepositoryByName" , err . Error ( ) )
return
}
var issues [ ] * models . Issue
var filteredCount int64
keyword := ctx . FormTrim ( "q" )
if strings . IndexByte ( keyword , 0 ) >= 0 {
keyword = ""
}
var issueIDs [ ] int64
if len ( keyword ) > 0 && len ( repoIDs ) > 0 {
if issueIDs , err = issue_indexer . SearchIssuesByKeyword ( ctx , repoIDs , keyword ) ; err != nil {
ctx . Error ( http . StatusInternalServerError , "SearchIssuesByKeyword" , err . Error ( ) )
return
}
}
var isPull util . OptionalBool
switch ctx . FormString ( "type" ) {
case "pulls" :
isPull = util . OptionalBoolTrue
case "issues" :
isPull = util . OptionalBoolFalse
default :
isPull = util . OptionalBoolNone
}
labels := ctx . FormTrim ( "labels" )
var includedLabelNames [ ] string
if len ( labels ) > 0 {
includedLabelNames = strings . Split ( labels , "," )
}
milestones := ctx . FormTrim ( "milestones" )
var includedMilestones [ ] string
if len ( milestones ) > 0 {
includedMilestones = strings . Split ( milestones , "," )
}
// this api is also used in UI,
// so the default limit is set to fit UI needs
limit := ctx . FormInt ( "limit" )
if limit == 0 {
limit = setting . UI . IssuePagingNum
} else if limit > setting . API . MaxResponseItems {
limit = setting . API . MaxResponseItems
}
// Only fetch the issues if we either don't have a keyword or the search returned issues
// This would otherwise return all issues if no issues were found by the search.
if len ( keyword ) == 0 || len ( issueIDs ) > 0 || len ( includedLabelNames ) > 0 || len ( includedMilestones ) > 0 {
issuesOpt := & models . IssuesOptions {
ListOptions : db . ListOptions {
Page : ctx . FormInt ( "page" ) ,
PageSize : limit ,
} ,
2022-04-25 14:06:24 +00:00
RepoCond : repoCond ,
2022-04-07 18:59:56 +00:00
IsClosed : isClosed ,
IssueIDs : issueIDs ,
IncludedLabelNames : includedLabelNames ,
IncludeMilestones : includedMilestones ,
SortType : "priorityrepo" ,
PriorityRepoID : ctx . FormInt64 ( "priority_repo_id" ) ,
IsPull : isPull ,
UpdatedBeforeUnix : before ,
UpdatedAfterUnix : since ,
}
ctxUserID := int64 ( 0 )
if ctx . IsSigned {
ctxUserID = ctx . Doer . ID
}
// Filter for: Created by User, Assigned to User, Mentioning User, Review of User Requested
if ctx . FormBool ( "created" ) {
issuesOpt . PosterID = ctxUserID
}
if ctx . FormBool ( "assigned" ) {
issuesOpt . AssigneeID = ctxUserID
}
if ctx . FormBool ( "mentioned" ) {
issuesOpt . MentionedID = ctxUserID
}
if ctx . FormBool ( "review_requested" ) {
issuesOpt . ReviewRequestedID = ctxUserID
}
if issues , err = models . Issues ( issuesOpt ) ; err != nil {
ctx . Error ( http . StatusInternalServerError , "Issues" , err . Error ( ) )
return
}
issuesOpt . ListOptions = db . ListOptions {
Page : - 1 ,
}
if filteredCount , err = models . CountIssues ( issuesOpt ) ; err != nil {
ctx . Error ( http . StatusInternalServerError , "CountIssues" , err . Error ( ) )
return
}
}
ctx . SetTotalCountHeader ( filteredCount )
ctx . JSON ( http . StatusOK , convert . ToAPIIssueList ( issues ) )
}
func getUserIDForFilter ( ctx * context . Context , queryName string ) int64 {
userName := ctx . FormString ( queryName )
if len ( userName ) == 0 {
return 0
}
user , err := user_model . GetUserByName ( userName )
if user_model . IsErrUserNotExist ( err ) {
ctx . NotFound ( "" , err )
return 0
}
if err != nil {
ctx . Error ( http . StatusInternalServerError , err . Error ( ) )
return 0
}
return user . ID
}
// ListIssues list the issues of a repository
func ListIssues ( ctx * context . Context ) {
before , since , err := context . GetQueryBeforeSince ( ctx )
if err != nil {
ctx . Error ( http . StatusUnprocessableEntity , err . Error ( ) )
return
}
var isClosed util . OptionalBool
switch ctx . FormString ( "state" ) {
case "closed" :
isClosed = util . OptionalBoolTrue
case "all" :
isClosed = util . OptionalBoolNone
default :
isClosed = util . OptionalBoolFalse
}
var issues [ ] * models . Issue
var filteredCount int64
keyword := ctx . FormTrim ( "q" )
if strings . IndexByte ( keyword , 0 ) >= 0 {
keyword = ""
}
var issueIDs [ ] int64
var labelIDs [ ] int64
if len ( keyword ) > 0 {
issueIDs , err = issue_indexer . SearchIssuesByKeyword ( ctx , [ ] int64 { ctx . Repo . Repository . ID } , keyword )
if err != nil {
ctx . Error ( http . StatusInternalServerError , err . Error ( ) )
return
}
}
if splitted := strings . Split ( ctx . FormString ( "labels" ) , "," ) ; len ( splitted ) > 0 {
labelIDs , err = models . GetLabelIDsInRepoByNames ( ctx . Repo . Repository . ID , splitted )
if err != nil {
ctx . Error ( http . StatusInternalServerError , err . Error ( ) )
return
}
}
var mileIDs [ ] int64
if part := strings . Split ( ctx . FormString ( "milestones" ) , "," ) ; len ( part ) > 0 {
for i := range part {
// uses names and fall back to ids
// non existent milestones are discarded
2022-04-08 09:11:15 +00:00
mile , err := issues_model . GetMilestoneByRepoIDANDName ( ctx . Repo . Repository . ID , part [ i ] )
2022-04-07 18:59:56 +00:00
if err == nil {
mileIDs = append ( mileIDs , mile . ID )
continue
}
2022-04-08 09:11:15 +00:00
if ! issues_model . IsErrMilestoneNotExist ( err ) {
2022-04-07 18:59:56 +00:00
ctx . Error ( http . StatusInternalServerError , err . Error ( ) )
return
}
id , err := strconv . ParseInt ( part [ i ] , 10 , 64 )
if err != nil {
continue
}
2022-04-08 09:11:15 +00:00
mile , err = issues_model . GetMilestoneByRepoID ( ctx , ctx . Repo . Repository . ID , id )
2022-04-07 18:59:56 +00:00
if err == nil {
mileIDs = append ( mileIDs , mile . ID )
continue
}
2022-04-08 09:11:15 +00:00
if issues_model . IsErrMilestoneNotExist ( err ) {
2022-04-07 18:59:56 +00:00
continue
}
ctx . Error ( http . StatusInternalServerError , err . Error ( ) )
}
}
listOptions := db . ListOptions {
Page : ctx . FormInt ( "page" ) ,
PageSize : convert . ToCorrectPageSize ( ctx . FormInt ( "limit" ) ) ,
}
var isPull util . OptionalBool
switch ctx . FormString ( "type" ) {
case "pulls" :
isPull = util . OptionalBoolTrue
case "issues" :
isPull = util . OptionalBoolFalse
default :
isPull = util . OptionalBoolNone
}
// FIXME: we should be more efficient here
createdByID := getUserIDForFilter ( ctx , "created_by" )
if ctx . Written ( ) {
return
}
assignedByID := getUserIDForFilter ( ctx , "assigned_by" )
if ctx . Written ( ) {
return
}
mentionedByID := getUserIDForFilter ( ctx , "mentioned_by" )
if ctx . Written ( ) {
return
}
// Only fetch the issues if we either don't have a keyword or the search returned issues
// This would otherwise return all issues if no issues were found by the search.
if len ( keyword ) == 0 || len ( issueIDs ) > 0 || len ( labelIDs ) > 0 {
issuesOpt := & models . IssuesOptions {
ListOptions : listOptions ,
2022-04-25 14:06:24 +00:00
RepoID : ctx . Repo . Repository . ID ,
2022-04-07 18:59:56 +00:00
IsClosed : isClosed ,
IssueIDs : issueIDs ,
LabelIDs : labelIDs ,
MilestoneIDs : mileIDs ,
IsPull : isPull ,
UpdatedBeforeUnix : before ,
UpdatedAfterUnix : since ,
PosterID : createdByID ,
AssigneeID : assignedByID ,
MentionedID : mentionedByID ,
}
if issues , err = models . Issues ( issuesOpt ) ; err != nil {
ctx . Error ( http . StatusInternalServerError , err . Error ( ) )
return
}
issuesOpt . ListOptions = db . ListOptions {
Page : - 1 ,
}
if filteredCount , err = models . CountIssues ( issuesOpt ) ; err != nil {
ctx . Error ( http . StatusInternalServerError , err . Error ( ) )
return
}
}
ctx . SetTotalCountHeader ( filteredCount )
ctx . JSON ( http . StatusOK , convert . ToAPIIssueList ( issues ) )
}
2017-03-15 01:10:35 +00:00
// UpdateIssueStatus change issue's status
func UpdateIssueStatus ( ctx * context . Context ) {
issues := getActionIssues ( ctx )
if ctx . Written ( ) {
2014-07-26 06:28:04 +00:00
return
}
2017-03-15 01:10:35 +00:00
var isClosed bool
2021-08-11 00:31:13 +00:00
switch action := ctx . FormString ( "action" ) ; action {
2017-03-15 01:10:35 +00:00
case "open" :
isClosed = false
case "close" :
isClosed = true
default :
log . Warn ( "Unrecognized action: %s" , action )
}
if _ , err := models . IssueList ( issues ) . LoadRepositories ( ) ; err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "LoadRepositories" , err )
2017-03-15 01:10:35 +00:00
return
}
for _ , issue := range issues {
2018-10-18 11:23:05 +00:00
if issue . IsClosed != isClosed {
2022-03-22 07:03:22 +00:00
if err := issue_service . ChangeStatus ( issue , ctx . Doer , isClosed ) ; err != nil {
2018-10-18 11:23:05 +00:00
if models . IsErrDependenciesLeft ( err ) {
ctx . JSON ( http . StatusPreconditionFailed , map [ string ] interface { } {
"error" : "cannot close this issue because it still has open dependencies" ,
} )
return
}
ctx . ServerError ( "ChangeStatus" , err )
2018-07-17 21:23:58 +00:00
return
}
2017-03-15 01:10:35 +00:00
}
}
2021-04-05 15:30:52 +00:00
ctx . JSON ( http . StatusOK , map [ string ] interface { } {
2014-07-26 06:28:04 +00:00
"ok" : true ,
} )
}
2016-11-24 07:04:31 +00:00
// NewComment create a comment for issue
2021-01-26 15:36:53 +00:00
func NewComment ( ctx * context . Context ) {
2021-04-06 19:44:05 +00:00
form := web . GetForm ( ctx ) . ( * forms . CreateCommentForm )
2017-10-16 07:55:43 +00:00
issue := GetActionIssue ( ctx )
if ctx . Written ( ) {
2014-07-26 06:28:04 +00:00
return
}
2022-03-22 07:03:22 +00:00
if ! ctx . IsSigned || ( ctx . Doer . ID != issue . PosterID && ! ctx . Repo . CanReadIssuesOrPulls ( issue . IsPull ) ) {
2019-04-22 20:40:51 +00:00
if log . IsTrace ( ) {
if ctx . IsSigned {
issueType := "issues"
if issue . IsPull {
issueType = "pulls"
}
log . Trace ( "Permission Denied: User %-v not the Poster (ID: %d) and cannot read %s in Repo %-v.\n" +
"User in Repo has Permissions: %-+v" ,
2022-03-22 07:03:22 +00:00
ctx . Doer ,
2019-04-22 20:40:51 +00:00
log . NewColoredIDValue ( issue . PosterID ) ,
issueType ,
ctx . Repo . Repository ,
ctx . Repo . Permission )
} else {
log . Trace ( "Permission Denied: Not logged in" )
}
}
2021-04-05 15:30:52 +00:00
ctx . Error ( http . StatusForbidden )
2020-01-20 12:00:32 +00:00
return
2019-02-18 20:55:04 +00:00
}
2022-03-22 07:03:22 +00:00
if issue . IsLocked && ! ctx . Repo . CanWriteIssuesOrPulls ( issue . IsPull ) && ! ctx . Doer . IsAdmin {
2019-02-18 20:55:04 +00:00
ctx . Flash . Error ( ctx . Tr ( "repo.issues.comment_on_locked" ) )
2022-03-23 04:54:07 +00:00
ctx . Redirect ( issue . HTMLURL ( ) )
2018-11-28 11:26:14 +00:00
return
}
2015-08-13 08:07:11 +00:00
var attachments [ ] string
2020-08-18 04:23:45 +00:00
if setting . Attachment . Enabled {
2016-08-11 12:48:08 +00:00
attachments = form . Files
2014-07-26 06:28:04 +00:00
}
2015-08-13 08:07:11 +00:00
if ctx . HasError ( ) {
ctx . Flash . Error ( ctx . Data [ "ErrorMsg" ] . ( string ) )
2019-12-14 00:53:32 +00:00
ctx . Redirect ( issue . HTMLURL ( ) )
2015-08-13 08:07:11 +00:00
return
2014-07-26 06:28:04 +00:00
}
2015-10-18 23:30:39 +00:00
var comment * models . Comment
2015-09-13 15:26:25 +00:00
defer func ( ) {
2015-10-31 22:59:07 +00:00
// Check if issue admin/poster changes the status of issue.
2022-03-22 07:03:22 +00:00
if ( ctx . Repo . CanWriteIssuesOrPulls ( issue . IsPull ) || ( ctx . IsSigned && issue . IsPoster ( ctx . Doer . ID ) ) ) &&
2015-09-13 15:26:25 +00:00
( form . Status == "reopen" || form . Status == "close" ) &&
2016-08-16 17:19:09 +00:00
! ( issue . IsPull && issue . PullRequest . HasMerged ) {
2015-10-18 23:30:39 +00:00
2015-10-25 07:10:22 +00:00
// Duplication and conflict check should apply to reopen pull request.
2015-10-18 23:30:39 +00:00
var pr * models . PullRequest
2015-10-23 14:31:13 +00:00
if form . Status == "reopen" && issue . IsPull {
2015-10-18 23:30:39 +00:00
pull := issue . PullRequest
2019-06-12 19:41:28 +00:00
var err error
2021-07-28 09:42:56 +00:00
pr , err = models . GetUnmergedPullRequest ( pull . HeadRepoID , pull . BaseRepoID , pull . HeadBranch , pull . BaseBranch , pull . Flow )
2015-10-18 23:30:39 +00:00
if err != nil {
if ! models . IsErrPullRequestNotExist ( err ) {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "GetUnmergedPullRequest" , err )
2015-10-18 23:30:39 +00:00
return
}
}
2015-10-25 07:10:22 +00:00
// Regenerate patch and test conflict.
if pr == nil {
2021-07-28 09:42:56 +00:00
issue . PullRequest . HeadCommitID = ""
2019-12-07 02:44:10 +00:00
pull_service . AddToTaskQueue ( issue . PullRequest )
2015-10-25 07:10:22 +00:00
}
2015-10-18 23:30:39 +00:00
}
if pr != nil {
ctx . Flash . Info ( ctx . Tr ( "repo.pulls.open_unmerged_pull_exists" , pr . Index ) )
2015-09-13 15:26:25 +00:00
} else {
2018-10-18 11:23:05 +00:00
isClosed := form . Status == "close"
2022-03-22 07:03:22 +00:00
if err := issue_service . ChangeStatus ( issue , ctx . Doer , isClosed ) ; err != nil {
2019-04-02 07:48:31 +00:00
log . Error ( "ChangeStatus: %v" , err )
2018-07-17 21:23:58 +00:00
if models . IsErrDependenciesLeft ( err ) {
if issue . IsPull {
ctx . Flash . Error ( ctx . Tr ( "repo.issues.dependency.pr_close_blocked" ) )
2022-03-23 04:54:07 +00:00
ctx . Redirect ( fmt . Sprintf ( "%s/pulls/%d" , ctx . Repo . RepoLink , issue . Index ) )
2018-07-17 21:23:58 +00:00
} else {
ctx . Flash . Error ( ctx . Tr ( "repo.issues.dependency.issue_close_blocked" ) )
2022-03-23 04:54:07 +00:00
ctx . Redirect ( fmt . Sprintf ( "%s/issues/%d" , ctx . Repo . RepoLink , issue . Index ) )
2018-07-17 21:23:58 +00:00
}
return
}
2015-10-18 23:30:39 +00:00
} else {
2022-03-22 07:03:22 +00:00
if err := stopTimerIfAvailable ( ctx . Doer , issue ) ; err != nil {
2019-02-05 11:38:11 +00:00
ctx . ServerError ( "CreateOrStopIssueStopwatch" , err )
return
}
2016-02-22 17:40:00 +00:00
log . Trace ( "Issue [%d] status changed to closed: %v" , issue . ID , issue . IsClosed )
2015-10-18 23:30:39 +00:00
}
2015-09-13 15:26:25 +00:00
}
}
2015-10-18 23:30:39 +00:00
// Redirect to comment hashtag if there is any actual content.
typeName := "issues"
if issue . IsPull {
typeName = "pulls"
}
if comment != nil {
ctx . Redirect ( fmt . Sprintf ( "%s/%s/%d#%s" , ctx . Repo . RepoLink , typeName , issue . Index , comment . HashTag ( ) ) )
} else {
ctx . Redirect ( fmt . Sprintf ( "%s/%s/%d" , ctx . Repo . RepoLink , typeName , issue . Index ) )
}
2015-09-13 15:26:25 +00:00
} ( )
2015-08-13 08:07:11 +00:00
// Fix #321: Allow empty comments, as long as we have attachments.
if len ( form . Content ) == 0 && len ( attachments ) == 0 {
return
2014-07-26 06:28:04 +00:00
}
2022-03-22 07:03:22 +00:00
comment , err := comment_service . CreateIssueComment ( ctx . Doer , ctx . Repo . Repository , issue , form . Content , attachments )
2015-08-13 08:07:11 +00:00
if err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "CreateIssueComment" , err )
2014-07-26 06:28:04 +00:00
return
}
2015-08-13 08:07:11 +00:00
log . Trace ( "Comment created: %d/%d/%d" , ctx . Repo . Repository . ID , issue . ID , comment . ID )
2014-07-26 06:28:04 +00:00
}
2016-11-24 07:04:31 +00:00
// UpdateCommentContent change comment of issue's content
2016-03-11 16:56:52 +00:00
func UpdateCommentContent ( ctx * context . Context ) {
2015-08-19 20:31:28 +00:00
comment , err := models . GetCommentByID ( ctx . ParamsInt64 ( ":id" ) )
if err != nil {
2016-08-30 09:08:38 +00:00
ctx . NotFoundOrServerError ( "GetCommentByID" , models . IsErrCommentNotExist , err )
2015-08-19 20:31:28 +00:00
return
}
2018-11-28 11:26:14 +00:00
if err := comment . LoadIssue ( ) ; err != nil {
ctx . NotFoundOrServerError ( "LoadIssue" , models . IsErrIssueNotExist , err )
return
}
2022-03-22 07:03:22 +00:00
if ! ctx . IsSigned || ( ctx . Doer . ID != comment . PosterID && ! ctx . Repo . CanWriteIssuesOrPulls ( comment . Issue . IsPull ) ) {
2021-04-05 15:30:52 +00:00
ctx . Error ( http . StatusForbidden )
2015-08-19 20:31:28 +00:00
return
2022-01-18 17:28:38 +00:00
}
if comment . Type != models . CommentTypeComment && comment . Type != models . CommentTypeReview && comment . Type != models . CommentTypeCode {
2021-04-05 15:30:52 +00:00
ctx . Error ( http . StatusNoContent )
2015-08-19 20:31:28 +00:00
return
}
2018-05-16 14:01:55 +00:00
oldContent := comment . Content
2021-08-11 00:31:13 +00:00
comment . Content = ctx . FormString ( "content" )
2015-08-19 20:31:28 +00:00
if len ( comment . Content ) == 0 {
2021-04-05 15:30:52 +00:00
ctx . JSON ( http . StatusOK , map [ string ] interface { } {
2015-08-19 20:31:28 +00:00
"content" : "" ,
} )
return
}
2022-03-22 07:03:22 +00:00
if err = comment_service . UpdateComment ( comment , ctx . Doer , oldContent ) ; err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "UpdateComment" , err )
2015-08-19 20:31:28 +00:00
return
}
2022-01-18 17:28:38 +00:00
if err := comment . LoadAttachments ( ) ; err != nil {
ctx . ServerError ( "LoadAttachments" , err )
return
2021-08-20 19:26:19 +00:00
}
2021-08-21 13:04:47 +00:00
// when the update request doesn't intend to update attachments (eg: change checkbox state), ignore attachment updates
if ! ctx . FormBool ( "ignore_attachments" ) {
if err := updateAttachments ( comment , ctx . FormStrings ( "files[]" ) ) ; err != nil {
ctx . ServerError ( "UpdateAttachments" , err )
return
}
2021-04-19 22:25:08 +00:00
}
content , err := markdown . RenderString ( & markup . RenderContext {
2021-11-16 18:18:25 +00:00
URLPrefix : ctx . FormString ( "context" ) , // FIXME: <- IS THIS SAFE ?
2021-04-19 22:25:08 +00:00
Metas : ctx . Repo . Repository . ComposeMetas ( ) ,
2021-06-20 22:39:12 +00:00
GitRepo : ctx . Repo . GitRepo ,
2021-08-28 20:15:56 +00:00
Ctx : ctx ,
2021-04-19 22:25:08 +00:00
} , comment . Content )
if err != nil {
ctx . ServerError ( "RenderString" , err )
return
2019-10-15 12:19:32 +00:00
}
2021-04-05 15:30:52 +00:00
ctx . JSON ( http . StatusOK , map [ string ] interface { } {
2021-04-19 22:25:08 +00:00
"content" : content ,
2020-12-13 19:12:27 +00:00
"attachments" : attachmentsHTML ( ctx , comment . Attachments , comment . Content ) ,
2015-08-19 20:31:28 +00:00
} )
}
2016-11-24 07:04:31 +00:00
// DeleteComment delete comment of issue
2016-07-25 18:48:17 +00:00
func DeleteComment ( ctx * context . Context ) {
comment , err := models . GetCommentByID ( ctx . ParamsInt64 ( ":id" ) )
if err != nil {
2016-08-30 09:08:38 +00:00
ctx . NotFoundOrServerError ( "GetCommentByID" , models . IsErrCommentNotExist , err )
2016-07-25 18:48:17 +00:00
return
}
2018-11-28 11:26:14 +00:00
if err := comment . LoadIssue ( ) ; err != nil {
ctx . NotFoundOrServerError ( "LoadIssue" , models . IsErrIssueNotExist , err )
return
}
2022-03-22 07:03:22 +00:00
if ! ctx . IsSigned || ( ctx . Doer . ID != comment . PosterID && ! ctx . Repo . CanWriteIssuesOrPulls ( comment . Issue . IsPull ) ) {
2021-04-05 15:30:52 +00:00
ctx . Error ( http . StatusForbidden )
2016-07-25 18:48:17 +00:00
return
2018-08-06 04:43:22 +00:00
} else if comment . Type != models . CommentTypeComment && comment . Type != models . CommentTypeCode {
2021-04-05 15:30:52 +00:00
ctx . Error ( http . StatusNoContent )
2016-07-25 18:48:17 +00:00
return
}
2022-03-22 07:03:22 +00:00
if err = comment_service . DeleteComment ( ctx . Doer , comment ) ; err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "DeleteCommentByID" , err )
2016-07-25 18:48:17 +00:00
return
}
2022-03-23 04:54:07 +00:00
ctx . Status ( http . StatusOK )
2016-07-25 18:48:17 +00:00
}
2017-12-03 23:14:26 +00:00
// ChangeIssueReaction create a reaction for issue
2021-01-26 15:36:53 +00:00
func ChangeIssueReaction ( ctx * context . Context ) {
2021-04-06 19:44:05 +00:00
form := web . GetForm ( ctx ) . ( * forms . ReactionForm )
2017-12-03 23:14:26 +00:00
issue := GetActionIssue ( ctx )
if ctx . Written ( ) {
return
}
2022-03-22 07:03:22 +00:00
if ! ctx . IsSigned || ( ctx . Doer . ID != issue . PosterID && ! ctx . Repo . CanReadIssuesOrPulls ( issue . IsPull ) ) {
2019-04-22 20:40:51 +00:00
if log . IsTrace ( ) {
if ctx . IsSigned {
issueType := "issues"
if issue . IsPull {
issueType = "pulls"
}
log . Trace ( "Permission Denied: User %-v not the Poster (ID: %d) and cannot read %s in Repo %-v.\n" +
"User in Repo has Permissions: %-+v" ,
2022-03-22 07:03:22 +00:00
ctx . Doer ,
2019-04-22 20:40:51 +00:00
log . NewColoredIDValue ( issue . PosterID ) ,
issueType ,
ctx . Repo . Repository ,
ctx . Repo . Permission )
} else {
log . Trace ( "Permission Denied: Not logged in" )
}
}
2021-04-05 15:30:52 +00:00
ctx . Error ( http . StatusForbidden )
2018-11-28 11:26:14 +00:00
return
}
2017-12-03 23:14:26 +00:00
if ctx . HasError ( ) {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "ChangeIssueReaction" , errors . New ( ctx . GetErrMsg ( ) ) )
2017-12-03 23:14:26 +00:00
return
}
switch ctx . Params ( ":action" ) {
case "react" :
2022-03-31 09:20:39 +00:00
reaction , err := issues_model . CreateIssueReaction ( ctx . Doer . ID , issue . ID , form . Content )
2017-12-03 23:14:26 +00:00
if err != nil {
2022-03-31 09:20:39 +00:00
if issues_model . IsErrForbiddenIssueReaction ( err ) {
2019-12-07 22:04:19 +00:00
ctx . ServerError ( "ChangeIssueReaction" , err )
return
}
2017-12-03 23:14:26 +00:00
log . Info ( "CreateIssueReaction: %s" , err )
break
}
// Reload new reactions
issue . Reactions = nil
if err = issue . LoadAttributes ( ) ; err != nil {
log . Info ( "issue.LoadAttributes: %s" , err )
break
}
log . Trace ( "Reaction for issue created: %d/%d/%d" , ctx . Repo . Repository . ID , issue . ID , reaction . ID )
case "unreact" :
2022-03-31 09:20:39 +00:00
if err := issues_model . DeleteIssueReaction ( ctx . Doer . ID , issue . ID , form . Content ) ; err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "DeleteIssueReaction" , err )
2017-12-03 23:14:26 +00:00
return
}
// Reload new reactions
issue . Reactions = nil
if err := issue . LoadAttributes ( ) ; err != nil {
log . Info ( "issue.LoadAttributes: %s" , err )
break
}
log . Trace ( "Reaction for issue removed: %d/%d" , ctx . Repo . Repository . ID , issue . ID )
default :
2018-01-10 21:34:17 +00:00
ctx . NotFound ( fmt . Sprintf ( "Unknown action %s" , ctx . Params ( ":action" ) ) , nil )
2017-12-03 23:14:26 +00:00
return
}
if len ( issue . Reactions ) == 0 {
2021-04-05 15:30:52 +00:00
ctx . JSON ( http . StatusOK , map [ string ] interface { } {
2017-12-03 23:14:26 +00:00
"empty" : true ,
"html" : "" ,
} )
return
}
2021-12-15 06:59:57 +00:00
html , err := ctx . RenderToString ( tplReactions , map [ string ] interface { } {
2017-12-03 23:14:26 +00:00
"ctx" : ctx . Data ,
"ActionURL" : fmt . Sprintf ( "%s/issues/%d/reactions" , ctx . Repo . RepoLink , issue . Index ) ,
"Reactions" : issue . Reactions . GroupByType ( ) ,
} )
if err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "ChangeIssueReaction.HTMLString" , err )
2017-12-03 23:14:26 +00:00
return
}
2021-04-05 15:30:52 +00:00
ctx . JSON ( http . StatusOK , map [ string ] interface { } {
2017-12-03 23:14:26 +00:00
"html" : html ,
} )
}
// ChangeCommentReaction create a reaction for comment
2021-01-26 15:36:53 +00:00
func ChangeCommentReaction ( ctx * context . Context ) {
2021-04-06 19:44:05 +00:00
form := web . GetForm ( ctx ) . ( * forms . ReactionForm )
2017-12-03 23:14:26 +00:00
comment , err := models . GetCommentByID ( ctx . ParamsInt64 ( ":id" ) )
if err != nil {
ctx . NotFoundOrServerError ( "GetCommentByID" , models . IsErrCommentNotExist , err )
return
}
2018-11-28 11:26:14 +00:00
if err := comment . LoadIssue ( ) ; err != nil {
ctx . NotFoundOrServerError ( "LoadIssue" , models . IsErrIssueNotExist , err )
2017-12-03 23:14:26 +00:00
return
}
2022-03-22 07:03:22 +00:00
if ! ctx . IsSigned || ( ctx . Doer . ID != comment . PosterID && ! ctx . Repo . CanReadIssuesOrPulls ( comment . Issue . IsPull ) ) {
2019-04-22 20:40:51 +00:00
if log . IsTrace ( ) {
if ctx . IsSigned {
issueType := "issues"
if comment . Issue . IsPull {
issueType = "pulls"
}
log . Trace ( "Permission Denied: User %-v not the Poster (ID: %d) and cannot read %s in Repo %-v.\n" +
"User in Repo has Permissions: %-+v" ,
2022-03-22 07:03:22 +00:00
ctx . Doer ,
2019-04-22 20:40:51 +00:00
log . NewColoredIDValue ( comment . Issue . PosterID ) ,
issueType ,
ctx . Repo . Repository ,
ctx . Repo . Permission )
} else {
log . Trace ( "Permission Denied: Not logged in" )
}
}
2021-04-05 15:30:52 +00:00
ctx . Error ( http . StatusForbidden )
2018-11-28 11:26:14 +00:00
return
2022-01-18 17:28:38 +00:00
}
if comment . Type != models . CommentTypeComment && comment . Type != models . CommentTypeCode && comment . Type != models . CommentTypeReview {
2021-04-05 15:30:52 +00:00
ctx . Error ( http . StatusNoContent )
2017-12-03 23:14:26 +00:00
return
}
switch ctx . Params ( ":action" ) {
case "react" :
2022-03-31 09:20:39 +00:00
reaction , err := issues_model . CreateCommentReaction ( ctx . Doer . ID , comment . Issue . ID , comment . ID , form . Content )
2017-12-03 23:14:26 +00:00
if err != nil {
2022-03-31 09:20:39 +00:00
if issues_model . IsErrForbiddenIssueReaction ( err ) {
2019-12-07 22:04:19 +00:00
ctx . ServerError ( "ChangeIssueReaction" , err )
return
}
2017-12-03 23:14:26 +00:00
log . Info ( "CreateCommentReaction: %s" , err )
break
}
// Reload new reactions
comment . Reactions = nil
2020-01-15 11:14:07 +00:00
if err = comment . LoadReactions ( ctx . Repo . Repository ) ; err != nil {
2017-12-03 23:14:26 +00:00
log . Info ( "comment.LoadReactions: %s" , err )
break
}
2018-11-28 11:26:14 +00:00
log . Trace ( "Reaction for comment created: %d/%d/%d/%d" , ctx . Repo . Repository . ID , comment . Issue . ID , comment . ID , reaction . ID )
2017-12-03 23:14:26 +00:00
case "unreact" :
2022-03-31 09:20:39 +00:00
if err := issues_model . DeleteCommentReaction ( ctx . Doer . ID , comment . Issue . ID , comment . ID , form . Content ) ; err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "DeleteCommentReaction" , err )
2017-12-03 23:14:26 +00:00
return
}
// Reload new reactions
comment . Reactions = nil
2020-01-15 11:14:07 +00:00
if err = comment . LoadReactions ( ctx . Repo . Repository ) ; err != nil {
2017-12-03 23:14:26 +00:00
log . Info ( "comment.LoadReactions: %s" , err )
break
}
2018-11-28 11:26:14 +00:00
log . Trace ( "Reaction for comment removed: %d/%d/%d" , ctx . Repo . Repository . ID , comment . Issue . ID , comment . ID )
2017-12-03 23:14:26 +00:00
default :
2018-01-10 21:34:17 +00:00
ctx . NotFound ( fmt . Sprintf ( "Unknown action %s" , ctx . Params ( ":action" ) ) , nil )
2017-12-03 23:14:26 +00:00
return
}
if len ( comment . Reactions ) == 0 {
2021-04-05 15:30:52 +00:00
ctx . JSON ( http . StatusOK , map [ string ] interface { } {
2017-12-03 23:14:26 +00:00
"empty" : true ,
"html" : "" ,
} )
return
}
2021-12-15 06:59:57 +00:00
html , err := ctx . RenderToString ( tplReactions , map [ string ] interface { } {
2017-12-03 23:14:26 +00:00
"ctx" : ctx . Data ,
"ActionURL" : fmt . Sprintf ( "%s/comments/%d/reactions" , ctx . Repo . RepoLink , comment . ID ) ,
"Reactions" : comment . Reactions . GroupByType ( ) ,
} )
if err != nil {
2018-01-10 21:34:17 +00:00
ctx . ServerError ( "ChangeCommentReaction.HTMLString" , err )
2017-12-03 23:14:26 +00:00
return
}
2021-04-05 15:30:52 +00:00
ctx . JSON ( http . StatusOK , map [ string ] interface { } {
2017-12-03 23:14:26 +00:00
"html" : html ,
} )
}
2019-09-07 14:53:35 +00:00
2021-11-24 09:49:20 +00:00
func addParticipant ( poster * user_model . User , participants [ ] * user_model . User ) [ ] * user_model . User {
2019-09-07 14:53:35 +00:00
for _ , part := range participants {
if poster . ID == part . ID {
return participants
}
}
return append ( participants , poster )
}
2019-09-20 05:45:38 +00:00
func filterXRefComments ( ctx * context . Context , issue * models . Issue ) error {
// Remove comments that the user has no permissions to see
for i := 0 ; i < len ( issue . Comments ) ; {
c := issue . Comments [ i ]
if models . CommentTypeIsRef ( c . Type ) && c . RefRepoID != issue . RepoID && c . RefRepoID != 0 {
var err error
// Set RefRepo for description in template
2021-12-10 01:27:50 +00:00
c . RefRepo , err = repo_model . GetRepositoryByID ( c . RefRepoID )
2019-09-20 05:45:38 +00:00
if err != nil {
return err
}
2022-04-28 11:48:48 +00:00
perm , err := models . GetUserRepoPermission ( ctx , c . RefRepo , ctx . Doer )
2019-09-20 05:45:38 +00:00
if err != nil {
return err
}
if ! perm . CanReadIssuesOrPulls ( c . RefIsPull ) {
issue . Comments = append ( issue . Comments [ : i ] , issue . Comments [ i + 1 : ] ... )
continue
}
}
i ++
}
return nil
}
2019-10-15 12:19:32 +00:00
// GetIssueAttachments returns attachments for the issue
func GetIssueAttachments ( ctx * context . Context ) {
issue := GetActionIssue ( ctx )
2022-01-20 17:46:10 +00:00
attachments := make ( [ ] * api . Attachment , len ( issue . Attachments ) )
2019-10-15 12:19:32 +00:00
for i := 0 ; i < len ( issue . Attachments ) ; i ++ {
2020-10-17 04:23:08 +00:00
attachments [ i ] = convert . ToReleaseAttachment ( issue . Attachments [ i ] )
2019-10-15 12:19:32 +00:00
}
2021-04-05 15:30:52 +00:00
ctx . JSON ( http . StatusOK , attachments )
2019-10-15 12:19:32 +00:00
}
// GetCommentAttachments returns attachments for the comment
func GetCommentAttachments ( ctx * context . Context ) {
comment , err := models . GetCommentByID ( ctx . ParamsInt64 ( ":id" ) )
if err != nil {
ctx . NotFoundOrServerError ( "GetCommentByID" , models . IsErrCommentNotExist , err )
return
}
2022-01-20 17:46:10 +00:00
attachments := make ( [ ] * api . Attachment , 0 )
2019-10-15 12:19:32 +00:00
if comment . Type == models . CommentTypeComment {
if err := comment . LoadAttachments ( ) ; err != nil {
ctx . ServerError ( "LoadAttachments" , err )
return
}
for i := 0 ; i < len ( comment . Attachments ) ; i ++ {
2020-10-17 04:23:08 +00:00
attachments = append ( attachments , convert . ToReleaseAttachment ( comment . Attachments [ i ] ) )
2019-10-15 12:19:32 +00:00
}
}
2021-04-05 15:30:52 +00:00
ctx . JSON ( http . StatusOK , attachments )
2019-10-15 12:19:32 +00:00
}
func updateAttachments ( item interface { } , files [ ] string ) error {
2021-11-19 13:39:57 +00:00
var attachments [ ] * repo_model . Attachment
2019-10-15 12:19:32 +00:00
switch content := item . ( type ) {
case * models . Issue :
attachments = content . Attachments
case * models . Comment :
attachments = content . Attachments
default :
2022-02-26 12:15:32 +00:00
return fmt . Errorf ( "unknown Type: %T" , content )
2019-10-15 12:19:32 +00:00
}
for i := 0 ; i < len ( attachments ) ; i ++ {
if util . IsStringInSlice ( attachments [ i ] . UUID , files ) {
continue
}
2021-11-19 13:39:57 +00:00
if err := repo_model . DeleteAttachment ( attachments [ i ] , true ) ; err != nil {
2019-10-15 12:19:32 +00:00
return err
}
}
var err error
if len ( files ) > 0 {
switch content := item . ( type ) {
case * models . Issue :
2022-03-29 14:57:33 +00:00
err = models . UpdateIssueAttachments ( content . ID , files )
2019-10-15 12:19:32 +00:00
case * models . Comment :
err = content . UpdateAttachments ( files )
default :
2022-02-26 12:15:32 +00:00
return fmt . Errorf ( "unknown Type: %T" , content )
2019-10-15 12:19:32 +00:00
}
if err != nil {
return err
}
}
switch content := item . ( type ) {
case * models . Issue :
2021-11-19 13:39:57 +00:00
content . Attachments , err = repo_model . GetAttachmentsByIssueID ( content . ID )
2019-10-15 12:19:32 +00:00
case * models . Comment :
2021-11-19 13:39:57 +00:00
content . Attachments , err = repo_model . GetAttachmentsByCommentID ( content . ID )
2019-10-15 12:19:32 +00:00
default :
2022-02-26 12:15:32 +00:00
return fmt . Errorf ( "unknown Type: %T" , content )
2019-10-15 12:19:32 +00:00
}
return err
}
2021-11-19 13:39:57 +00:00
func attachmentsHTML ( ctx * context . Context , attachments [ ] * repo_model . Attachment , content string ) string {
2021-12-15 06:59:57 +00:00
attachHTML , err := ctx . RenderToString ( tplAttachment , map [ string ] interface { } {
2019-10-15 12:19:32 +00:00
"ctx" : ctx . Data ,
"Attachments" : attachments ,
2020-12-13 19:12:27 +00:00
"Content" : content ,
2019-10-15 12:19:32 +00:00
} )
if err != nil {
ctx . ServerError ( "attachmentsHTML.HTMLString" , err )
return ""
}
return attachHTML
}
2020-10-25 21:49:48 +00:00
2021-03-05 15:17:32 +00:00
// combineLabelComments combine the nearby label comments as one.
2020-10-25 21:49:48 +00:00
func combineLabelComments ( issue * models . Issue ) {
2021-03-05 15:17:32 +00:00
var prev , cur * models . Comment
2020-11-20 22:29:09 +00:00
for i := 0 ; i < len ( issue . Comments ) ; i ++ {
2021-03-05 15:17:32 +00:00
cur = issue . Comments [ i ]
2020-11-20 22:29:09 +00:00
if i > 0 {
2020-10-25 21:49:48 +00:00
prev = issue . Comments [ i - 1 ]
}
2020-11-20 22:29:09 +00:00
if i == 0 || cur . Type != models . CommentTypeLabel ||
( prev != nil && prev . PosterID != cur . PosterID ) ||
( prev != nil && cur . CreatedUnix - prev . CreatedUnix >= 60 ) {
2021-02-10 02:50:44 +00:00
if cur . Type == models . CommentTypeLabel && cur . Label != nil {
2020-11-20 22:29:09 +00:00
if cur . Content != "1" {
cur . RemovedLabels = append ( cur . RemovedLabels , cur . Label )
2020-10-25 21:49:48 +00:00
} else {
2020-11-20 22:29:09 +00:00
cur . AddedLabels = append ( cur . AddedLabels , cur . Label )
2020-10-25 21:49:48 +00:00
}
}
2020-11-20 22:29:09 +00:00
continue
2020-10-25 21:49:48 +00:00
}
2020-11-20 22:29:09 +00:00
2021-03-05 15:17:32 +00:00
if cur . Label != nil { // now cur MUST be label comment
if prev . Type == models . CommentTypeLabel { // we can combine them only prev is a label comment
if cur . Content != "1" {
2021-11-04 14:51:30 +00:00
// remove labels from the AddedLabels list if the label that was removed is already
// in this list, and if it's not in this list, add the label to RemovedLabels
addedAndRemoved := false
for i , label := range prev . AddedLabels {
if cur . Label . ID == label . ID {
prev . AddedLabels = append ( prev . AddedLabels [ : i ] , prev . AddedLabels [ i + 1 : ] ... )
addedAndRemoved = true
break
}
}
if ! addedAndRemoved {
prev . RemovedLabels = append ( prev . RemovedLabels , cur . Label )
}
2021-03-05 15:17:32 +00:00
} else {
2021-11-04 14:51:30 +00:00
// remove labels from the RemovedLabels list if the label that was added is already
// in this list, and if it's not in this list, add the label to AddedLabels
removedAndAdded := false
for i , label := range prev . RemovedLabels {
if cur . Label . ID == label . ID {
prev . RemovedLabels = append ( prev . RemovedLabels [ : i ] , prev . RemovedLabels [ i + 1 : ] ... )
removedAndAdded = true
break
}
}
if ! removedAndAdded {
prev . AddedLabels = append ( prev . AddedLabels , cur . Label )
}
2021-03-05 15:17:32 +00:00
}
prev . CreatedUnix = cur . CreatedUnix
// remove the current comment since it has been combined to prev comment
issue . Comments = append ( issue . Comments [ : i ] , issue . Comments [ i + 1 : ] ... )
i --
} else { // if prev is not a label comment, start a new group
if cur . Content != "1" {
cur . RemovedLabels = append ( cur . RemovedLabels , cur . Label )
} else {
cur . AddedLabels = append ( cur . AddedLabels , cur . Label )
}
2021-02-10 02:50:44 +00:00
}
2020-11-20 22:29:09 +00:00
}
2020-10-25 21:49:48 +00:00
}
}
2020-12-21 15:39:28 +00:00
// get all teams that current user can mention
func handleTeamMentions ( ctx * context . Context ) {
2022-03-22 07:03:22 +00:00
if ctx . Doer == nil || ! ctx . Repo . Owner . IsOrganization ( ) {
2020-12-21 15:39:28 +00:00
return
}
2021-11-19 11:41:40 +00:00
var isAdmin bool
2020-12-21 15:39:28 +00:00
var err error
2022-03-29 06:29:02 +00:00
var teams [ ] * organization . Team
org := organization . OrgFromUser ( ctx . Repo . Owner )
2020-12-21 15:39:28 +00:00
// Admin has super access.
2022-03-22 07:03:22 +00:00
if ctx . Doer . IsAdmin {
2020-12-21 15:39:28 +00:00
isAdmin = true
} else {
2022-03-22 07:03:22 +00:00
isAdmin , err = org . IsOwnedBy ( ctx . Doer . ID )
2020-12-21 15:39:28 +00:00
if err != nil {
ctx . ServerError ( "IsOwnedBy" , err )
return
}
}
if isAdmin {
2021-11-19 11:41:40 +00:00
teams , err = org . LoadTeams ( )
if err != nil {
2021-08-12 12:43:08 +00:00
ctx . ServerError ( "LoadTeams" , err )
2020-12-21 15:39:28 +00:00
return
}
} else {
2022-03-22 07:03:22 +00:00
teams , err = org . GetUserTeams ( ctx . Doer . ID )
2020-12-21 15:39:28 +00:00
if err != nil {
ctx . ServerError ( "GetUserTeams" , err )
return
}
}
2021-11-19 11:41:40 +00:00
ctx . Data [ "MentionableTeams" ] = teams
2020-12-21 15:39:28 +00:00
ctx . Data [ "MentionableTeamsOrg" ] = ctx . Repo . Owner . Name
Avatar refactor, move avatar code from `models` to `models.avatars`, remove duplicated code (#17123)
Why this refactor
The goal is to move most files from `models` package to `models.xxx` package. Many models depend on avatar model, so just move this first.
And the existing logic is not clear, there are too many function like `AvatarLink`, `RelAvatarLink`, `SizedRelAvatarLink`, `SizedAvatarLink`, `MakeFinalAvatarURL`, `HashedAvatarLink`, etc. This refactor make everything clear:
* user.AvatarLink()
* user.AvatarLinkWithSize(size)
* avatars.GenerateEmailAvatarFastLink(email, size)
* avatars.GenerateEmailAvatarFinalLink(email, size)
And many duplicated code are deleted in route handler, the handler and the model share the same avatar logic now.
2021-10-05 23:25:46 +00:00
ctx . Data [ "MentionableTeamsOrgAvatar" ] = ctx . Repo . Owner . AvatarLink ( )
2020-12-21 15:39:28 +00:00
}