or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cicd.mdclient-auth.mdgraphql.mdindex.mdissues-mrs.mdprojects.mdrepository.mdusers-groups.md

issues-mrs.mddocs/

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

```