# 機能設計書 29-マージリクエスト編集

## 概要

本ドキュメントは、GitLabにおけるマージリクエスト編集機能の設計を定義する。作成済みのマージリクエストの属性（タイトル、説明、担当者、レビュアー等）を変更する機能である。

### 本機能の処理概要

マージリクエスト編集機能は、既存のマージリクエストの各種属性を更新する機能である。

**業務上の目的・背景**：マージリクエストは作成後もレビュープロセスの進行に応じて変更が必要になることが多い。タイトルの明確化、説明の補足、担当者やレビュアーの追加・変更、ラベルやマイルストーンの設定変更などを通じて、効率的なコードレビューとマージプロセスを実現する。

**機能の利用シーン**：
- MRタイトル・説明の修正
- Draft（下書き）状態の切り替え
- 担当者・レビュアーの追加・変更
- ラベル・マイルストーンの設定変更
- ターゲットブランチの変更
- マージスケジュールの設定
- ディスカッションロックの切り替え
- クイックアクション（/rebase, /merge等）の実行

**主要な処理内容**：
1. 編集可能な属性の更新
2. Draft状態の切り替え処理
3. ターゲットブランチ変更時の特殊処理
4. TODO解決処理
5. 通知の送信（メンション追加、ラベル追加等）
6. クイックアクションの処理

**関連システム・外部連携**：通知システム、Sidekiqワーカー、GraphQL API

**権限による制御**：update_merge_request 権限が必要。担当者・レビュアー設定にはread_merge_request権限を持つユーザーのみ指定可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 61 | MR一覧 | 参照 | MR一覧からの編集遷移 |
| 63 | MR詳細 | 主機能 | MRの編集操作 |

## 機能種別

CRUD操作（UPDATE）+ イベント処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| title | String | No | MRタイトル | 空でない文字列 |
| description | String | No | MR説明 | - |
| draft | Boolean | No | Draft状態フラグ | true/false |
| target_branch | String | No | ターゲットブランチ | 存在するブランチ |
| assignee_ids | Array<Integer> | No | 担当者IDリスト | read_merge_request権限を持つユーザー |
| reviewer_ids | Array<Integer> | No | レビュアーIDリスト | read_merge_request権限を持つユーザー |
| label_ids | Array<Integer> | No | ラベルIDリスト | 有効なラベルID |
| milestone_id | Integer | No | マイルストーンID | 有効なマイルストーンID |
| discussion_locked | Boolean | No | ディスカッションロック | true/false |
| time_estimate | Integer | No | 見積もり時間（秒） | 正の整数 |
| spend_time | Hash | No | 作業時間記録 | duration, summary |
| merge_after | DateTime | No | マージスケジュール | 将来の日時 |
| state_event | String | No | 状態変更イベント | close/reopen |

### 入力データソース

- URL パス: `/project_namespace/project_name/-/merge_requests/:iid/edit`
- フォーム入力による設定
- クイックアクション（/assign, /label, /milestone, /draft, /ready, /rebase, /merge等）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| merge_request | MergeRequest | 更新されたMRオブジェクト |
| merge_request.title | String | 更新後タイトル |
| merge_request.description | String | 更新後説明 |
| merge_request.state | String | 状態 |
| merge_request.draft | Boolean | Draft状態 |
| previous_changes | Hash | 変更された属性の一覧 |

### 出力先

- HTML: MR詳細ページへリダイレクト
- JSON: API レスポンス
- GraphQL: サブスクリプショントリガー

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信・認証確認
   └─ 権限チェック（update_merge_request）
2. パラメータフィルタリング
   └─ ソースプロジェクト/ブランチ変更不可
3. 特殊サービスの選択
   └─ 単一属性更新の場合は専用サービスへ
4. Draft状態の処理
   └─ draft: true の場合、タイトルにDraft:を付与
5. マージスケジュールの処理
   └─ UpdateMergeScheduleService 呼び出し
6. before_update
   └─ スパムチェック
7. MR更新
   └─ ActiveRecord save
8. handle_changes
   └─ 変更内容に応じた後処理
9. 通知・TODO処理
   └─ 各種通知とTODO解決
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B{更新権限?}
    B -->|No| C[アクセス拒否]
    B -->|Yes| D{特殊サービス対象?}
    D -->|Yes| E[専用サービス実行]
    D -->|No| F[一般更新処理]
    E --> G{成功?}
    F --> H[スパムチェック]
    H --> I[MR保存]
    I --> J{target_branch変更?}
    J -->|Yes| K[パイプライン再実行/auto_merge中止]
    J -->|No| L[handle_changes]
    K --> L
    L --> M[TODO解決]
    M --> N[通知送信]
    N --> O[GraphQLトリガー]
    O --> P[成功レスポンス]
    G -->|Yes| P
    G -->|No| Q[エラーレスポンス]
    C --> R[終了]
    P --> R
    Q --> R
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-29-01 | ソース変更不可 | ソースプロジェクト・ブランチは変更不可 | 常時 |
| BR-29-02 | クローズ後制限 | マージ済み/フォーク切断後はターゲット変更不可 | closed_or_merged_without_fork? |
| BR-29-03 | Draft切り替え | タイトルにDraft:/WIP:付与で下書き状態 | 常時 |
| BR-29-04 | マージステータスリセット | ブランチ変更時はmark_as_uncheckedを実行 | target/source_branch変更時 |
| BR-29-05 | auto_merge中止 | ターゲットブランチ変更時はauto_merge中止 | target_branch変更時 |
| BR-29-06 | 担当者権限 | read_merge_request権限を持つユーザーのみ担当可 | assignee_ids設定時 |

### 計算ロジック

**Draft判定**：
- `MergeRequest.draft?(title)` でタイトルからDraft状態を判定
- "Draft:", "WIP:" プレフィックスの有無で判定

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 更新 | merge_requests | UPDATE | MR本体の更新 |
| 更新 | merge_request_assignees | DELETE/INSERT | 担当者関連の更新 |
| 更新 | merge_request_reviewers | DELETE/INSERT | レビュアー関連の更新 |
| 更新 | label_links | DELETE/INSERT | ラベル関連の更新 |
| 更新 | system_note_metadata | INSERT | 変更ノート作成 |
| 更新 | todos | UPDATE | TODO解決 |

### テーブル別操作詳細

#### merge_requests

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | title, description, target_branch, milestone_id, discussion_locked, time_estimate | フォーム入力値 | - |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------
| 403 | Forbidden | 権限不足 | アクセス拒否 |
| 422 | UnprocessableEntity | バリデーションエラー | エラーメッセージ表示 |
| Spam | SpamDetected | スパム判定 | CAPTCHA表示 |

### リトライ仕様

特になし

## トランザクション仕様

MR更新と関連レコード（担当者、ラベル等）の更新はトランザクション内で実行

## パフォーマンス要件

- 単一属性更新は専用サービスで最適化
- TODO解決は非同期（ResolveTodosService.async_execute）

## セキュリティ考慮事項

- update_merge_request 権限の確認
- before_update でスパムチェック（check_for_spam）
- 担当者設定時のread_merge_request権限確認
- XSS対策（タイトル・説明のサニタイズ）

## 備考

- クイックアクション /rebase は rebase_async で非同期実行
- クイックアクション /merge は MergeOrchestrationService で実行
- ターゲットブランチ削除による再ターゲット時はパイプライン再生成をスキップ

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

マージリクエストのデータモデルと更新可能な属性を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | merge_request.rb | `app/models/merge_request.rb` | MRモデル、draft判定、状態管理 |

**読解のコツ**: `draft?` メソッド、`mark_as_unchecked` メソッド、state_machine の遷移を理解する。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | merge_requests_controller.rb | `app/controllers/projects/merge_requests_controller.rb` | コントローラー実装 |

**主要処理フロー**:
1. edit アクション - 編集フォーム表示
2. update アクション - 更新処理（IssuableActions concernで定義）

#### Step 3: サービス層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | update_service.rb | `app/services/merge_requests/update_service.rb` | 更新サービス |
| 3-2 | base_service.rb | `app/services/merge_requests/base_service.rb` | 基底サービス |
| 3-3 | update_assignees_service.rb | `app/services/merge_requests/update_assignees_service.rb` | 担当者更新サービス |

**主要処理フロー**:
- **13-24行目**: execute - 更新処理のメインロジック
- **26-63行目**: handle_changes - 変更後の処理
- **115-128行目**: general_fallback - 一般更新処理
- **212-238行目**: handle_target_branch_change - ターゲットブランチ変更処理
- **240-262行目**: handle_draft_status_change - Draft状態変更処理
- **286-288行目**: before_update - スパムチェック
- **290-304行目**: handle_quick_actions - クイックアクション処理

### プログラム呼び出し階層図

```
MergeRequestsController#update
    │
    ├─ authorize_update_issuable!
    │
    └─ UpdateService#execute
           │
           ├─ update_merge_request_with_specialized_service
           │      ├─ UpdateAssigneesService（assignee_ids変更時）
           │      └─ AddSpentTimeService（spend_time追加時）
           │
           └─ general_fallback
                  ├─ params filtering（source変更不可）
                  ├─ update_task_event || update
                  │      ├─ before_update（スパムチェック）
                  │      ├─ MergeRequest#save
                  │      └─ handle_changes
                  │             ├─ resolve_todos
                  │             ├─ todo_service.update_merge_request
                  │             ├─ handle_target_branch_change
                  │             │      ├─ create_branch_change_note
                  │             │      ├─ refresh_pipelines_on_merge_requests
                  │             │      └─ abort_auto_merge
                  │             ├─ handle_draft_status_change
                  │             │      ├─ notify_draft_status_changed
                  │             │      └─ publish_draft_change_event
                  │             ├─ notify_if_labels_added
                  │             └─ notify_if_mentions_added
                  └─ after_update
                         └─ cache_merge_request_closes_issues!
```

### データフロー図

```
[入力]                    [処理]                         [出力]

フォーム入力       ───▶  MergeRequestsController   ───▶  リダイレクト/JSON
(title, assignees等)     │
                         ▼
                    UpdateService
                         │
                    ┌────┴────┐
                    ▼         ▼
             専用サービス   一般更新
             (Assignees等)    │
                              ▼
                         MergeRequest.save ────────────────▶  DB UPDATE
                              │
                              ▼
                         handle_changes
                              │
                    ┌────┬────┼────┬────┐
                    ▼    ▼    ▼    ▼    ▼
                 TODO  通知  Note Event GraphQL
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| merge_requests_controller.rb | `app/controllers/projects/merge_requests_controller.rb` | コントローラー | MRのエントリーポイント |
| update_service.rb | `app/services/merge_requests/update_service.rb` | サービス | 更新サービス |
| base_service.rb | `app/services/merge_requests/base_service.rb` | サービス | 基底サービス |
| update_assignees_service.rb | `app/services/merge_requests/update_assignees_service.rb` | サービス | 担当者更新 |
| add_spent_time_service.rb | `app/services/merge_requests/add_spent_time_service.rb` | サービス | 作業時間追加 |
| update_merge_schedule_service.rb | `app/services/merge_requests/update_merge_schedule_service.rb` | サービス | マージスケジュール更新 |
| resolve_todos_service.rb | `app/services/merge_requests/resolve_todos_service.rb` | サービス | TODO解決 |
| merge_request.rb | `app/models/merge_request.rb` | モデル | MRモデル |
| issuable_actions.rb | `app/controllers/concerns/issuable_actions.rb` | Concern | 共通CRUD処理 |
| edit.html.haml | `app/views/projects/merge_requests/edit.html.haml` | ビュー | 編集フォーム |
