0
# Issues and Merge Requests
1
2
Issue tracking, merge request workflows, code review processes, and collaborative development features. Covers comprehensive issue management, MR approvals, discussions, state transitions, and collaborative development workflows.
3
4
## Capabilities
5
6
### Issue Management
7
8
```python { .api }
9
class ProjectIssue(
10
SubscribableMixin,
11
TodoMixin,
12
TimeTrackingMixin,
13
SaveMixin,
14
ObjectDeleteMixin,
15
RESTObject
16
):
17
"""
18
Project issue object with comprehensive workflow management.
19
20
Key Attributes:
21
- id: Issue ID
22
- iid: Internal issue ID (project-specific)
23
- title: Issue title
24
- description: Issue description/body
25
- state: Issue state ("opened", "closed")
26
- labels: List of label names
27
- milestone: Milestone information
28
- assignees: List of assigned users
29
- author: Issue author information
30
- created_at: Creation timestamp
31
- updated_at: Last update timestamp
32
- closed_at: Close timestamp
33
- due_date: Due date
34
- weight: Issue weight/points
35
- user_notes_count: Number of comments
36
- merge_requests_count: Number of related MRs
37
- upvotes: Number of upvotes
38
- downvotes: Number of downvotes
39
- confidential: Confidential issue flag
40
- web_url: Issue web URL
41
"""
42
43
def save(self) -> None:
44
"""Save issue changes."""
45
46
def delete(self) -> None:
47
"""Delete issue permanently."""
48
49
def subscribe(self) -> dict:
50
"""Subscribe to issue notifications."""
51
52
def unsubscribe(self) -> dict:
53
"""Unsubscribe from issue notifications."""
54
55
def todo(self) -> dict:
56
"""Create todo item for issue."""
57
58
def move(self, to_project_id: int) -> dict:
59
"""
60
Move issue to another project.
61
62
Parameters:
63
- to_project_id: Target project ID
64
65
Returns:
66
Dictionary with move result
67
"""
68
69
def clone(self, to_project_id: int, with_notes: bool = False) -> dict:
70
"""
71
Clone issue to another project.
72
73
Parameters:
74
- to_project_id: Target project ID
75
- with_notes: Include notes in clone
76
77
Returns:
78
Dictionary with clone result
79
"""
80
81
class ProjectIssueManager(CRUDMixin[ProjectIssue]):
82
"""Manager for project issues with comprehensive filtering."""
83
84
def list(
85
self,
86
state: str | None = None,
87
labels: str | list[str] | None = None,
88
milestone: str | None = None,
89
scope: str | None = None,
90
author_id: int | None = None,
91
author_username: str | None = None,
92
assignee_id: int | None = None,
93
assignee_username: str | None = None,
94
my_reaction_emoji: str | None = None,
95
order_by: str | None = None,
96
sort: str | None = None,
97
search: str | None = None,
98
created_after: str | None = None,
99
created_before: str | None = None,
100
updated_after: str | None = None,
101
updated_before: str | None = None,
102
confidential: bool | None = None,
103
not_labels: str | list[str] | None = None,
104
not_milestone: str | None = None,
105
not_author_id: int | None = None,
106
not_assignee_id: int | None = None,
107
not_my_reaction_emoji: str | None = None,
108
issue_type: str | None = None,
109
weight: int | None = None,
110
**kwargs
111
) -> list[ProjectIssue]:
112
"""List project issues with extensive filtering."""
113
114
def create(self, data: dict, **kwargs) -> ProjectIssue:
115
"""
116
Create new issue.
117
118
Required fields:
119
- title: Issue title
120
121
Optional fields:
122
- description: Issue description
123
- confidential: Make issue confidential
124
- assignee_ids: List of assignee user IDs
125
- milestone_id: Milestone ID
126
- labels: Comma-separated label names
127
- created_at: Creation date (admin only)
128
- due_date: Due date (ISO format)
129
- merge_request_to_resolve_discussions_of: MR ID to resolve discussions
130
- discussion_to_resolve: Discussion ID to resolve
131
- weight: Issue weight
132
- epic_id: Epic ID (GitLab Premium)
133
- issue_type: Issue type ("issue", "incident", "test_case", "requirement", "task")
134
"""
135
```
136
137
### Merge Request Management
138
139
```python { .api }
140
class ProjectMergeRequest(
141
SubscribableMixin,
142
TodoMixin,
143
TimeTrackingMixin,
144
SaveMixin,
145
ObjectDeleteMixin,
146
RESTObject
147
):
148
"""
149
Project merge request object with comprehensive workflow management.
150
151
Key Attributes:
152
- id: MR ID
153
- iid: Internal MR ID (project-specific)
154
- title: MR title
155
- description: MR description/body
156
- state: MR state ("opened", "closed", "merged")
157
- source_branch: Source branch name
158
- target_branch: Target branch name
159
- source_project_id: Source project ID
160
- target_project_id: Target project ID
161
- labels: List of label names
162
- milestone: Milestone information
163
- assignees: List of assigned users
164
- reviewers: List of reviewer users
165
- author: MR author information
166
- merge_user: User who merged the MR
167
- merged_at: Merge timestamp
168
- created_at: Creation timestamp
169
- updated_at: Last update timestamp
170
- closed_at: Close timestamp
171
- merge_commit_sha: Merge commit SHA
172
- squash_commit_sha: Squash commit SHA
173
- user_notes_count: Number of comments
174
- upvotes: Number of upvotes
175
- downvotes: Number of downvotes
176
- work_in_progress: WIP status
177
- draft: Draft status
178
- merge_when_pipeline_succeeds: Auto-merge setting
179
- merge_status: Merge status
180
- sha: Source branch HEAD SHA
181
- merge_commit_sha: Merge commit SHA
182
- squash: Squash commits on merge
183
- web_url: MR web URL
184
- changes_count: Number of changed files
185
- conflicts: Merge conflicts information
186
- diverged_commits_count: Diverged commits count
187
- rebase_in_progress: Rebase status
188
- approvals_before_merge: Required approvals
189
- reference: MR reference string
190
- references: Related references
191
- task_completion_status: Task completion info
192
- has_conflicts: Conflicts flag
193
- blocking_discussions_resolved: Discussions resolved flag
194
"""
195
196
def save(self) -> None:
197
"""Save MR changes."""
198
199
def delete(self) -> None:
200
"""Delete MR permanently."""
201
202
def subscribe(self) -> dict:
203
"""Subscribe to MR notifications."""
204
205
def unsubscribe(self) -> dict:
206
"""Unsubscribe from MR notifications."""
207
208
def merge(
209
self,
210
merge_commit_message: str | None = None,
211
squash_commit_message: str | None = None,
212
squash: bool | None = None,
213
should_remove_source_branch: bool | None = None,
214
merge_when_pipeline_succeeds: bool | None = None,
215
sha: str | None = None,
216
**kwargs
217
) -> dict:
218
"""
219
Merge the merge request.
220
221
Parameters:
222
- merge_commit_message: Custom merge commit message
223
- squash_commit_message: Custom squash commit message
224
- squash: Squash commits on merge
225
- should_remove_source_branch: Remove source branch after merge
226
- merge_when_pipeline_succeeds: Merge when pipeline succeeds
227
- sha: Source branch HEAD SHA for merge validation
228
229
Returns:
230
Dictionary with merge result
231
232
Raises:
233
GitlabMRForbiddenError: If merge is not allowed
234
"""
235
236
def cancel_merge_when_pipeline_succeeds(self) -> dict:
237
"""Cancel auto-merge when pipeline succeeds."""
238
239
def rebase(self, skip_ci: bool = False) -> dict:
240
"""
241
Rebase merge request.
242
243
Parameters:
244
- skip_ci: Skip CI pipeline for rebase commit
245
246
Returns:
247
Dictionary with rebase result
248
249
Raises:
250
GitlabMRRebaseError: If rebase fails
251
"""
252
253
def approve(self, sha: str | None = None) -> dict:
254
"""
255
Approve merge request.
256
257
Parameters:
258
- sha: Source branch HEAD SHA
259
260
Returns:
261
Dictionary with approval result
262
263
Raises:
264
GitlabMRApprovalError: If approval fails
265
"""
266
267
def unapprove(self) -> dict:
268
"""
269
Remove approval from merge request.
270
271
Returns:
272
Dictionary with unapproval result
273
274
Raises:
275
GitlabMRApprovalError: If unapproval fails
276
"""
277
278
def reset_approvals(self) -> dict:
279
"""
280
Reset all approvals for merge request.
281
282
Returns:
283
Dictionary with reset result
284
285
Raises:
286
GitlabMRResetApprovalError: If reset fails
287
"""
288
289
class ProjectMergeRequestManager(CRUDMixin[ProjectMergeRequest]):
290
"""Manager for project merge requests with comprehensive filtering."""
291
292
def list(
293
self,
294
state: str | None = None,
295
order_by: str | None = None,
296
sort: str | None = None,
297
milestone: str | None = None,
298
view: str | None = None,
299
labels: str | list[str] | None = None,
300
with_labels_details: bool | None = None,
301
with_merge_status_recheck: bool | None = None,
302
created_after: str | None = None,
303
created_before: str | None = None,
304
updated_after: str | None = None,
305
updated_before: str | None = None,
306
scope: str | None = None,
307
author_id: int | None = None,
308
author_username: str | None = None,
309
assignee_id: int | None = None,
310
assignee_username: str | None = None,
311
reviewer_id: int | None = None,
312
reviewer_username: str | None = None,
313
my_reaction_emoji: str | None = None,
314
source_branch: str | None = None,
315
target_branch: str | None = None,
316
search: str | None = None,
317
wip: str | None = None,
318
not_labels: str | list[str] | None = None,
319
not_milestone: str | None = None,
320
not_author_id: int | None = None,
321
not_assignee_id: int | None = None,
322
not_reviewer_id: int | None = None,
323
not_my_reaction_emoji: str | None = None,
324
deployed_before: str | None = None,
325
deployed_after: str | None = None,
326
environment: str | None = None,
327
**kwargs
328
) -> list[ProjectMergeRequest]:
329
"""List project merge requests with extensive filtering."""
330
331
def create(self, data: dict, **kwargs) -> ProjectMergeRequest:
332
"""
333
Create new merge request.
334
335
Required fields:
336
- source_branch: Source branch name
337
- target_branch: Target branch name
338
- title: MR title
339
340
Optional fields:
341
- description: MR description
342
- assignee_ids: List of assignee user IDs
343
- reviewer_ids: List of reviewer user IDs
344
- milestone_id: Milestone ID
345
- labels: Comma-separated label names
346
- target_project_id: Target project ID (for cross-project MRs)
347
- remove_source_branch: Remove source branch when merged
348
- squash: Squash commits when merging
349
- allow_collaboration: Allow commits from maintainers
350
- allow_maintainer_to_push: Allow maintainer to push (deprecated)
351
"""
352
```
353
354
### Issue and MR Resource Managers
355
356
```python { .api }
357
# Both issues and merge requests have similar nested resource managers:
358
359
# Notes/Comments
360
@property
361
def notes(self) -> ProjectIssueNoteManager | ProjectMergeRequestNoteManager:
362
"""Access issue/MR comments and discussions."""
363
364
# Award Emojis
365
@property
366
def award_emojis(self) -> ProjectIssueAwardEmojiManager | ProjectMergeRequestAwardEmojiManager:
367
"""Access emoji reactions."""
368
369
# Resource Links
370
@property
371
def links(self) -> ProjectIssueLinkManager:
372
"""Access issue links (issues only)."""
373
374
# Time Tracking
375
def time_stats(self) -> dict:
376
"""Get time tracking statistics."""
377
378
def add_spent_time(self, duration: str) -> dict:
379
"""
380
Add spent time.
381
382
Parameters:
383
- duration: Time duration (e.g., "1h 30m", "2d")
384
"""
385
386
def reset_spent_time(self) -> dict:
387
"""Reset all spent time."""
388
389
def reset_time_estimate(self) -> dict:
390
"""Reset time estimate."""
391
```
392
393
### Merge Request Approvals
394
395
```python { .api }
396
class ProjectMergeRequestApproval(RESTObject):
397
"""
398
MR approval information.
399
400
Attributes:
401
- approvals_required: Required approvals count
402
- approvals_left: Remaining approvals needed
403
- approved_by: List of users who approved
404
- suggested_approvers: Suggested approver users
405
- approvers: Configured approver users
406
- approver_groups: Configured approver groups
407
"""
408
409
class ProjectMergeRequestApprovalManager(GetWithoutIdMixin[ProjectMergeRequestApproval], UpdateMixin[ProjectMergeRequestApproval]):
410
"""Manager for MR approval settings."""
411
412
def get(self, **kwargs) -> ProjectMergeRequestApproval:
413
"""Get MR approval information."""
414
415
def update(self, data: dict, **kwargs) -> ProjectMergeRequestApproval:
416
"""Update MR approval settings."""
417
418
class ProjectApprovalRule(SaveMixin, ObjectDeleteMixin, RESTObject):
419
"""
420
Project approval rule configuration.
421
422
Attributes:
423
- id: Rule ID
424
- name: Rule name
425
- rule_type: Rule type
426
- approvals_required: Required approvals
427
- users: Approver users
428
- groups: Approver groups
429
- protected_branches: Protected branches
430
- contains_hidden_groups: Hidden groups flag
431
"""
432
433
class ProjectApprovalRuleManager(CRUDMixin[ProjectApprovalRule]):
434
"""Manager for project approval rules."""
435
```
436
437
### Discussions and Notes
438
439
```python { .api }
440
class ProjectIssueNote(SaveMixin, ObjectDeleteMixin, RESTObject):
441
"""
442
Issue comment/note object.
443
444
Attributes:
445
- id: Note ID
446
- body: Comment body/content
447
- author: Note author information
448
- created_at: Creation timestamp
449
- updated_at: Update timestamp
450
- system: System note flag
451
- noteable_id: Parent issue/MR ID
452
- noteable_type: Parent type
453
- resolvable: Resolvable discussion flag
454
- resolved: Resolution status
455
- resolved_by: User who resolved
456
"""
457
458
def save(self) -> None:
459
"""Save note changes."""
460
461
def delete(self) -> None:
462
"""Delete note."""
463
464
class ProjectIssueNoteManager(CRUDMixin[ProjectIssueNote]):
465
"""Manager for issue notes/comments."""
466
467
def create(self, data: dict, **kwargs) -> ProjectIssueNote:
468
"""
469
Create new note.
470
471
Required fields:
472
- body: Comment content
473
474
Optional fields:
475
- confidential: Make note confidential
476
- created_at: Creation date (admin only)
477
"""
478
479
# Similar classes exist for MR notes
480
class ProjectMergeRequestNote(SaveMixin, ObjectDeleteMixin, RESTObject):
481
"""Merge request comment/note object."""
482
483
class ProjectMergeRequestNoteManager(CRUDMixin[ProjectMergeRequestNote]):
484
"""Manager for merge request notes/comments."""
485
```
486
487
### Usage Examples
488
489
```python
490
import gitlab
491
492
gl = gitlab.Gitlab("https://gitlab.example.com", private_token="your-token")
493
project = gl.projects.get(123)
494
495
# Issue management
496
issues = project.issues.list(
497
state="opened",
498
labels=["bug", "high-priority"],
499
assignee_id=456
500
)
501
502
# Create issue
503
issue_data = {
504
"title": "Critical Bug Fix",
505
"description": "Detailed bug description...",
506
"labels": "bug,critical",
507
"assignee_ids": [456],
508
"milestone_id": 789,
509
"due_date": "2024-12-31"
510
}
511
issue = project.issues.create(issue_data)
512
513
# Update issue
514
issue.title = "Updated Title"
515
issue.description = "Updated description"
516
issue.save()
517
518
# Issue operations
519
issue.subscribe()
520
issue.todo()
521
issue.move(to_project_id=999)
522
523
# Add time tracking
524
issue.add_spent_time("2h 30m")
525
time_stats = issue.time_stats()
526
527
# Issue comments
528
notes = issue.notes.list()
529
comment_data = {"body": "This is a comment"}
530
comment = issue.notes.create(comment_data)
531
532
# Merge request management
533
mrs = project.mergerequests.list(
534
state="opened",
535
target_branch="main",
536
author_id=456
537
)
538
539
# Create merge request
540
mr_data = {
541
"source_branch": "feature-branch",
542
"target_branch": "main",
543
"title": "Add new feature",
544
"description": "Feature implementation details...",
545
"assignee_ids": [456],
546
"reviewer_ids": [789],
547
"remove_source_branch": True,
548
"squash": True
549
}
550
mr = project.mergerequests.create(mr_data)
551
552
# MR operations
553
mr.approve()
554
mr.merge(
555
merge_commit_message="Merge feature branch",
556
should_remove_source_branch=True,
557
squash=True
558
)
559
560
# MR approval management
561
approval = mr.approvals.get()
562
print(f"Approvals needed: {approval.approvals_left}")
563
564
# Approval rules
565
rules = project.approvalrules.list()
566
rule_data = {
567
"name": "Security Review",
568
"approvals_required": 2,
569
"user_ids": [123, 456],
570
"protected_branch_ids": [1, 2]
571
}
572
rule = project.approvalrules.create(rule_data)
573
574
# Award emojis
575
thumbs_up = issue.award_emojis.create({"name": "thumbsup"})
576
reactions = issue.award_emojis.list()
577
```
578
579
### Error Handling
580
581
```python { .api }
582
class GitlabMRForbiddenError(GitlabOperationError):
583
"""Raised when MR operation is forbidden."""
584
pass
585
586
class GitlabMRApprovalError(GitlabOperationError):
587
"""Raised when MR approval operation fails."""
588
pass
589
590
class GitlabMRRebaseError(GitlabOperationError):
591
"""Raised when MR rebase fails."""
592
pass
593
594
class GitlabMRResetApprovalError(GitlabOperationError):
595
"""Raised when MR approval reset fails."""
596
pass
597
598
class GitlabMRClosedError(GitlabOperationError):
599
"""Raised when operating on closed MR."""
600
pass
601
```
602
603
Example error handling:
604
```python
605
try:
606
mr.merge()
607
except gitlab.GitlabMRForbiddenError as e:
608
print(f"Merge not allowed: {e}")
609
except gitlab.GitlabMRClosedError:
610
print("Cannot merge closed MR")
611
612
try:
613
mr.approve()
614
except gitlab.GitlabMRApprovalError as e:
615
print(f"Approval failed: {e}")
616
```