@@ -37,7 +37,7 @@ type Issue struct {
37
37
MilestoneID int64 `xorm:"INDEX"`
38
38
Milestone * Milestone `xorm:"-"`
39
39
Priority int
40
- AssigneeID int64 `xorm:"INDEX "`
40
+ AssigneeID int64 `xorm:"- "`
41
41
Assignee * User `xorm:"-"`
42
42
IsClosed bool `xorm:"INDEX"`
43
43
IsRead bool `xorm:"-"`
@@ -56,6 +56,7 @@ type Issue struct {
56
56
Comments []* Comment `xorm:"-"`
57
57
Reactions ReactionList `xorm:"-"`
58
58
TotalTrackedTime int64 `xorm:"-"`
59
+ Assignees []* User `xorm:"-"`
59
60
}
60
61
61
62
var (
@@ -140,22 +141,6 @@ func (issue *Issue) loadPoster(e Engine) (err error) {
140
141
return
141
142
}
142
143
143
- func (issue * Issue ) loadAssignee (e Engine ) (err error ) {
144
- if issue .Assignee == nil && issue .AssigneeID > 0 {
145
- issue .Assignee , err = getUserByID (e , issue .AssigneeID )
146
- if err != nil {
147
- issue .AssigneeID = - 1
148
- issue .Assignee = NewGhostUser ()
149
- if ! IsErrUserNotExist (err ) {
150
- return fmt .Errorf ("getUserByID.(assignee) [%d]: %v" , issue .AssigneeID , err )
151
- }
152
- err = nil
153
- return
154
- }
155
- }
156
- return
157
- }
158
-
159
144
func (issue * Issue ) loadPullRequest (e Engine ) (err error ) {
160
145
if issue .IsPull && issue .PullRequest == nil {
161
146
issue .PullRequest , err = getPullRequestByIssueID (e , issue .ID )
@@ -231,7 +216,7 @@ func (issue *Issue) loadAttributes(e Engine) (err error) {
231
216
}
232
217
}
233
218
234
- if err = issue .loadAssignee (e ); err != nil {
219
+ if err = issue .loadAssignees (e ); err != nil {
235
220
return
236
221
}
237
222
@@ -343,8 +328,11 @@ func (issue *Issue) APIFormat() *api.Issue {
343
328
if issue .Milestone != nil {
344
329
apiIssue .Milestone = issue .Milestone .APIFormat ()
345
330
}
346
- if issue .Assignee != nil {
347
- apiIssue .Assignee = issue .Assignee .APIFormat ()
331
+ if len (issue .Assignees ) > 0 {
332
+ for _ , assignee := range issue .Assignees {
333
+ apiIssue .Assignees = append (apiIssue .Assignees , assignee .APIFormat ())
334
+ }
335
+ apiIssue .Assignee = issue .Assignees [0 ].APIFormat () // For compatibility, we're keeping the first assignee as `apiIssue.Assignee`
348
336
}
349
337
if issue .IsPull {
350
338
apiIssue .PullRequest = & api.PullRequestMeta {
@@ -605,19 +593,6 @@ func (issue *Issue) ReplaceLabels(labels []*Label, doer *User) (err error) {
605
593
return sess .Commit ()
606
594
}
607
595
608
- // GetAssignee sets the Assignee attribute of this issue.
609
- func (issue * Issue ) GetAssignee () (err error ) {
610
- if issue .AssigneeID == 0 || issue .Assignee != nil {
611
- return nil
612
- }
613
-
614
- issue .Assignee , err = GetUserByID (issue .AssigneeID )
615
- if IsErrUserNotExist (err ) {
616
- return nil
617
- }
618
- return err
619
- }
620
-
621
596
// ReadBy sets issue to be read by given user.
622
597
func (issue * Issue ) ReadBy (userID int64 ) error {
623
598
if err := UpdateIssueUserByRead (userID , issue .ID ); err != nil {
@@ -823,55 +798,6 @@ func (issue *Issue) ChangeContent(doer *User, content string) (err error) {
823
798
return nil
824
799
}
825
800
826
- // ChangeAssignee changes the Assignee field of this issue.
827
- func (issue * Issue ) ChangeAssignee (doer * User , assigneeID int64 ) (err error ) {
828
- var oldAssigneeID = issue .AssigneeID
829
- issue .AssigneeID = assigneeID
830
- if err = UpdateIssueUserByAssignee (issue ); err != nil {
831
- return fmt .Errorf ("UpdateIssueUserByAssignee: %v" , err )
832
- }
833
-
834
- sess := x .NewSession ()
835
- defer sess .Close ()
836
-
837
- if err = issue .loadRepo (sess ); err != nil {
838
- return fmt .Errorf ("loadRepo: %v" , err )
839
- }
840
-
841
- if _ , err = createAssigneeComment (sess , doer , issue .Repo , issue , oldAssigneeID , assigneeID ); err != nil {
842
- return fmt .Errorf ("createAssigneeComment: %v" , err )
843
- }
844
-
845
- issue .Assignee , err = GetUserByID (issue .AssigneeID )
846
- if err != nil && ! IsErrUserNotExist (err ) {
847
- log .Error (4 , "GetUserByID [assignee_id: %v]: %v" , issue .AssigneeID , err )
848
- return nil
849
- }
850
-
851
- // Error not nil here means user does not exist, which is remove assignee.
852
- isRemoveAssignee := err != nil
853
- if issue .IsPull {
854
- issue .PullRequest .Issue = issue
855
- apiPullRequest := & api.PullRequestPayload {
856
- Index : issue .Index ,
857
- PullRequest : issue .PullRequest .APIFormat (),
858
- Repository : issue .Repo .APIFormat (AccessModeNone ),
859
- Sender : doer .APIFormat (),
860
- }
861
- if isRemoveAssignee {
862
- apiPullRequest .Action = api .HookIssueUnassigned
863
- } else {
864
- apiPullRequest .Action = api .HookIssueAssigned
865
- }
866
- if err := PrepareWebhooks (issue .Repo , HookEventPullRequest , apiPullRequest ); err != nil {
867
- log .Error (4 , "PrepareWebhooks [is_pull: %v, remove_assignee: %v]: %v" , issue .IsPull , isRemoveAssignee , err )
868
- return nil
869
- }
870
- }
871
- go HookQueue .Add (issue .RepoID )
872
- return nil
873
- }
874
-
875
801
// GetTasks returns the amount of tasks in the issues content
876
802
func (issue * Issue ) GetTasks () int {
877
803
return len (issueTasksPat .FindAllStringIndex (issue .Content , - 1 ))
@@ -887,6 +813,7 @@ type NewIssueOptions struct {
887
813
Repo * Repository
888
814
Issue * Issue
889
815
LabelIDs []int64
816
+ AssigneeIDs []int64
890
817
Attachments []string // In UUID format.
891
818
IsPull bool
892
819
}
@@ -909,14 +836,32 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
909
836
}
910
837
}
911
838
912
- if assigneeID := opts .Issue .AssigneeID ; assigneeID > 0 {
913
- valid , err := hasAccess (e , assigneeID , opts .Repo , AccessModeWrite )
914
- if err != nil {
915
- return fmt .Errorf ("hasAccess [user_id: %d, repo_id: %d]: %v" , assigneeID , opts .Repo .ID , err )
839
+ // Keep the old assignee id thingy for compatibility reasons
840
+ if opts .Issue .AssigneeID > 0 {
841
+ isAdded := false
842
+ // Check if the user has already been passed to issue.AssigneeIDs, if not, add it
843
+ for _ , aID := range opts .AssigneeIDs {
844
+ if aID == opts .Issue .AssigneeID {
845
+ isAdded = true
846
+ break
847
+ }
916
848
}
917
- if ! valid {
918
- opts .Issue .AssigneeID = 0
919
- opts .Issue .Assignee = nil
849
+
850
+ if ! isAdded {
851
+ opts .AssigneeIDs = append (opts .AssigneeIDs , opts .Issue .AssigneeID )
852
+ }
853
+ }
854
+
855
+ // Check for and validate assignees
856
+ if len (opts .AssigneeIDs ) > 0 {
857
+ for _ , assigneeID := range opts .AssigneeIDs {
858
+ valid , err := hasAccess (e , assigneeID , opts .Repo , AccessModeWrite )
859
+ if err != nil {
860
+ return fmt .Errorf ("hasAccess [user_id: %d, repo_id: %d]: %v" , assigneeID , opts .Repo .ID , err )
861
+ }
862
+ if ! valid {
863
+ return ErrUserDoesNotHaveAccessToRepo {UserID : assigneeID , RepoName : opts .Repo .Name }
864
+ }
920
865
}
921
866
}
922
867
@@ -931,11 +876,10 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
931
876
}
932
877
}
933
878
934
- if opts .Issue .AssigneeID > 0 {
935
- if err = opts .Issue .loadRepo (e ); err != nil {
936
- return err
937
- }
938
- if _ , err = createAssigneeComment (e , doer , opts .Issue .Repo , opts .Issue , - 1 , opts .Issue .AssigneeID ); err != nil {
879
+ // Insert the assignees
880
+ for _ , assigneeID := range opts .AssigneeIDs {
881
+ err = opts .Issue .changeAssignee (e , doer , assigneeID )
882
+ if err != nil {
939
883
return err
940
884
}
941
885
}
@@ -995,7 +939,7 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
995
939
}
996
940
997
941
// NewIssue creates new issue with labels for repository.
998
- func NewIssue (repo * Repository , issue * Issue , labelIDs []int64 , uuids []string ) (err error ) {
942
+ func NewIssue (repo * Repository , issue * Issue , labelIDs []int64 , assigneeIDs [] int64 , uuids []string ) (err error ) {
999
943
sess := x .NewSession ()
1000
944
defer sess .Close ()
1001
945
if err = sess .Begin (); err != nil {
@@ -1007,7 +951,11 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string)
1007
951
Issue : issue ,
1008
952
LabelIDs : labelIDs ,
1009
953
Attachments : uuids ,
954
+ AssigneeIDs : assigneeIDs ,
1010
955
}); err != nil {
956
+ if IsErrUserDoesNotHaveAccessToRepo (err ) {
957
+ return err
958
+ }
1011
959
return fmt .Errorf ("newIssue: %v" , err )
1012
960
}
1013
961
@@ -1150,7 +1098,8 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) error {
1150
1098
}
1151
1099
1152
1100
if opts .AssigneeID > 0 {
1153
- sess .And ("issue.assignee_id=?" , opts .AssigneeID )
1101
+ sess .Join ("INNER" , "issue_assignees" , "issue.id = issue_assignees.issue_id" ).
1102
+ And ("issue_assignees.assignee_id = ?" , opts .AssigneeID )
1154
1103
}
1155
1104
1156
1105
if opts .PosterID > 0 {
@@ -1372,7 +1321,8 @@ func GetIssueStats(opts *IssueStatsOptions) (*IssueStats, error) {
1372
1321
}
1373
1322
1374
1323
if opts .AssigneeID > 0 {
1375
- sess .And ("issue.assignee_id = ?" , opts .AssigneeID )
1324
+ sess .Join ("INNER" , "issue_assignees" , "issue.id = issue_assignees.issue_id" ).
1325
+ And ("issue_assignees.assignee_id = ?" , opts .AssigneeID )
1376
1326
}
1377
1327
1378
1328
if opts .PosterID > 0 {
@@ -1438,13 +1388,15 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
1438
1388
}
1439
1389
case FilterModeAssign :
1440
1390
stats .OpenCount , err = x .Where (cond ).And ("is_closed = ?" , false ).
1441
- And ("assignee_id = ?" , opts .UserID ).
1391
+ Join ("INNER" , "issue_assignees" , "issue.id = issue_assignees.issue_id" ).
1392
+ And ("issue_assignees.assignee_id = ?" , opts .UserID ).
1442
1393
Count (new (Issue ))
1443
1394
if err != nil {
1444
1395
return nil , err
1445
1396
}
1446
1397
stats .ClosedCount , err = x .Where (cond ).And ("is_closed = ?" , true ).
1447
- And ("assignee_id = ?" , opts .UserID ).
1398
+ Join ("INNER" , "issue_assignees" , "issue.id = issue_assignees.issue_id" ).
1399
+ And ("issue_assignees.assignee_id = ?" , opts .UserID ).
1448
1400
Count (new (Issue ))
1449
1401
if err != nil {
1450
1402
return nil , err
@@ -1466,7 +1418,8 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
1466
1418
1467
1419
cond = cond .And (builder.Eq {"issue.is_closed" : opts .IsClosed })
1468
1420
stats .AssignCount , err = x .Where (cond ).
1469
- And ("assignee_id = ?" , opts .UserID ).
1421
+ Join ("INNER" , "issue_assignees" , "issue.id = issue_assignees.issue_id" ).
1422
+ And ("issue_assignees.assignee_id = ?" , opts .UserID ).
1470
1423
Count (new (Issue ))
1471
1424
if err != nil {
1472
1425
return nil , err
@@ -1505,8 +1458,10 @@ func GetRepoIssueStats(repoID, uid int64, filterMode int, isPull bool) (numOpen
1505
1458
1506
1459
switch filterMode {
1507
1460
case FilterModeAssign :
1508
- openCountSession .And ("assignee_id = ?" , uid )
1509
- closedCountSession .And ("assignee_id = ?" , uid )
1461
+ openCountSession .Join ("INNER" , "issue_assignees" , "issue.id = issue_assignees.issue_id" ).
1462
+ And ("issue_assignees.assignee_id = ?" , uid )
1463
+ closedCountSession .Join ("INNER" , "issue_assignees" , "issue.id = issue_assignees.issue_id" ).
1464
+ And ("issue_assignees.assignee_id = ?" , uid )
1510
1465
case FilterModeCreate :
1511
1466
openCountSession .And ("poster_id = ?" , uid )
1512
1467
closedCountSession .And ("poster_id = ?" , uid )
0 commit comments