# 画面設計書 33-ファイル編集

## 概要

本ドキュメントは、GitLabのリポジトリ内の既存ファイルを編集する画面（ファイル編集画面）の設計を記述したものである。

### 本画面の処理概要

本画面は、Webブラウザ上から既存のファイル内容を編集し、変更をコミットするための画面である。Monaco Editorを使用したリッチな編集体験を提供し、プレビュー機能やシンタックスハイライトにより効率的なファイル編集を支援する。

**業務上の目的・背景**：開発プロジェクトにおいて、軽微なコード修正、ドキュメント更新、設定ファイルの変更などをローカル環境なしで行いたい場合がある。本画面は、Webインターフェースから直接ファイルを編集・コミットできる機能を提供し、クイックフィックスやドキュメント更新の効率化を実現する。また、コードレビュー中の修正提案を即座に適用する際にも活用される。

**画面へのアクセス方法**：
- ファイル表示画面の「Edit」ボタンをクリック
- URL直接入力: `/{namespace}/{project}/-/edit/{ref}/{path}`
- Web IDEの「Edit in single file editor」

**主要な操作・処理内容**：
1. ファイル内容の編集（Monaco Editor使用）
2. 変更のプレビュー（Markdown、差分表示）
3. コミットメッセージの入力
4. ターゲットブランチの選択（既存または新規）
5. マージリクエスト作成オプションの選択
6. コミット実行
7. ファイルの削除（オプション）

**画面遷移**：
- 遷移元: ファイル表示画面、マージリクエスト詳細画面
- 遷移先: 編集後のファイル表示画面、マージリクエスト作成画面（オプション選択時）、差分画面（MRからの編集時）

**権限による表示制御**：
- 編集権限がない場合はアクセス不可またはフォーク促進
- 保護ブランチへの直接プッシュ権限がない場合は新規ブランチ作成が必須
- ファイルロック時は編集不可

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 16 | ファイル編集 | 主機能 | ファイル内容の編集・コミット |
| 18 | ファイル削除 | 補助機能 | ファイル削除オプション |
| 19 | ブランチ管理 | 補助機能 | 新規ブランチへのコミット |
| 28 | マージリクエスト作成 | 遷移先機能 | MR作成オプション選択時 |

## 画面種別

編集

## URL/ルーティング

```
GET /{namespace}/{project}/-/edit/{ref}/{*path}
PUT /{namespace}/{project}/-/update/{ref}/{*path}
```

**ルート定義**: `config/routes/repository.rb`
```ruby
scope controller: :blob do
  get '/edit/*id', action: :edit, as: :edit_blob
  put '/update/*id', action: :update, as: :update_blob
end
```

## 入出力項目

| 項目名 | 入出力 | 型 | 必須 | 説明 |
|--------|--------|-----|------|------|
| namespace | 入力 | String | Yes | プロジェクトの名前空間 |
| project | 入力 | String | Yes | プロジェクト名 |
| ref | 入力 | String | Yes | ブランチ名/タグ名 |
| path | 入力 | String | Yes | ファイルパス |
| content | 入力 | Text | Yes | 編集後のファイル内容 |
| commit_message | 入力 | String | Yes | コミットメッセージ |
| branch_name | 入力 | String | Yes | ターゲットブランチ名 |
| last_commit_sha | 入力 | String | Yes | 編集開始時のコミットSHA（コンフリクト検出用） |
| start_new_mr | 入力 | Boolean | No | MR作成フラグ |
| encoding | 入力 | String | No | エンコーディング（text/base64） |
| file_path | 入力 | String | No | 新しいファイルパス（リネーム時） |

## 表示項目

| 項目名 | 説明 | データソース |
|--------|------|-------------|
| ファイルパス | 編集中のファイルパス | @blob.path |
| ブランチ名 | 現在のブランチ | @ref |
| エディタ | ファイル内容編集領域 | Monaco Editor + @blob.data |
| プレビュータブ | 変更のプレビュー | 差分計算結果 |
| コミットメッセージ欄 | コミットメッセージ入力 | 入力項目 |
| ブランチ名入力欄 | ターゲットブランチ指定 | 入力項目 |
| MRチェックボックス | MR作成オプション | 入力項目 |
| コンフリクト警告 | 編集中の変更検出時 | @conflict |

## イベント仕様

### 1-ページ読み込み

編集フォームを初期化し、ファイル内容をエディタに読み込む。

**処理フロー**:
1. URLからref、pathを抽出
2. ファイルの存在確認
3. 編集権限の確認
4. ファイルサイズの確認（10MB制限）
5. last_commit_shaの取得
6. Monaco Editorにファイル内容を読み込み

### 2-Writeタブ選択

編集モードでファイル内容を表示する。

### 3-Previewタブ選択

変更内容のプレビューを表示する。

**処理**:
- Markdownファイルの場合：レンダリング結果を表示
- その他のファイル：差分を表示

**APIエンドポイント**: `POST /{namespace}/{project}/-/preview/{ref}/{path}`

### 4-Commit changesボタン押下

ファイル変更をコミットする。

**処理フロー**:
1. 入力値のバリデーション
2. コンフリクトチェック（last_commit_sha比較）
3. Files::UpdateServiceを呼び出し
4. リポジトリのファイルを更新
5. コミットを作成
6. 成功時：ファイル表示画面へリダイレクト
7. コンフリクト検出時：警告表示して再編集

### 5-Cancelボタン押下

編集をキャンセルしてファイル表示画面へ戻る。

**遷移先**: ファイル表示画面

## データベース更新仕様

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| Commit changes | events | INSERT | ファイル更新イベントの記録 |
| Commit changes | push_event_payloads | INSERT | プッシュイベント詳細 |
| Commit changes | project_statistics | UPDATE | プロジェクト統計の更新 |

### テーブル別更新項目詳細

#### events

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | action | 5 (PUSHED) | プッシュアクション |
| INSERT | author_id | current_user.id | 実行ユーザー |
| INSERT | project_id | @project.id | 対象プロジェクト |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 発生条件 |
|-------------|------|---------------|----------|
| M001 | 警告 | "File exceeds 10MB and can't be edited in the browser. Edit locally and push your changes." | ファイルサイズが10MB超 |
| M002 | エラー | "You are attempting to update a file that has changed since you started editing it." | コンフリクト検出 |
| M003 | エラー | "Someone edited the file the same time you did. Please check out the file and make sure your changes will not unintentionally remove theirs." | コンフリクト時の詳細メッセージ |
| M004 | エラー | "Your changes could not be committed..." | コミット失敗 |
| M005 | エラー | "You can not push to this branch" | 保護ブランチへのプッシュ不可 |

## 例外処理

| 例外 | 発生条件 | 処理内容 |
|------|---------|---------|
| 403 Forbidden | 編集権限なし | アクセス拒否またはフォーク促進画面 |
| FileChangedError | 編集中に他者が変更 | コンフリクト警告表示、フォームを再表示 |
| PreReceiveError | pre-receiveフック失敗 | エラーメッセージ表示 |
| FileSizeError | ファイルサイズ超過 | ファイル表示画面へリダイレクト |

## 備考

- MAX_EDIT_SIZE = 10MB（blob_controller.rb で定義）
- Monaco Editorがプリロードされ高速な編集体験を提供
- コンフリクト検出にはlast_commit_shaを使用した楽観的排他制御を採用
- フォーク経由の編集では、フォーク先プロジェクトに変更がコミットされる

---

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

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

### 推奨読解順序

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

ファイル編集で使用されるパラメータとコンフリクト検出の仕組みを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | blob_controller.rb | `app/controllers/projects/blob_controller.rb` | editor_variables、last_commit_sha |
| 1-2 | update_service.rb | `app/services/files/update_service.rb` | FileChangedErrorとfile_has_changed? |

**読解のコツ**: `last_commit_sha`（294-297行目）がコンフリクト検出の鍵。編集開始時のSHAと更新時のSHAを比較することで、他者の変更を検出する。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | blob_controller.rb | `app/controllers/projects/blob_controller.rb` | editアクション、updateアクション |

**主要処理フロー**:
1. **87-98行目**: `def edit` - 編集画面表示、サイズチェック
2. **89-91行目**: サイズ超過時のリダイレクト（10MB制限）
3. **93行目**: `blob.load_all_data!` - 全データ読み込み
4. **100-113行目**: `def update` - 更新処理
5. **108-112行目**: FileChangedError時のコンフリクト処理

#### Step 3: ビューテンプレートを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | edit.html.haml | `app/views/projects/blob/edit.html.haml` | 編集フォーム全体構造 |
| 3-2 | _editor.html.haml | `app/views/projects/blob/_editor.html.haml` | エディタパーシャル |

**主要処理フロー**:
- **7-20行目**: コンフリクト警告の表示条件分岐
- **32-33行目**: Write/Previewタブの切り替え
- **37行目**: `form_tag` - フォーム送信先（update_blob_path）
- **38行目**: `render 'projects/blob/editor'` - エディタ描画

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | update_service.rb | `app/services/files/update_service.rb` | ファイル更新サービス |
| 4-2 | base_service.rb | `app/services/files/base_service.rb` | file_has_changed?メソッド |

**主要処理フロー**:
- **5-10行目**: `create_commit!` - LFS変換とコミット作成
- **13-26行目**: `create_transformed_commit` - リポジトリ更新
- **30-36行目**: `validate!` - コンフリクトチェック

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

```
Projects::BlobController#edit
    │
    ├─ can_collaborate_with_project?
    │
    ├─ blob.raw_size > MAX_EDIT_SIZE ?
    │      └─ redirect_to (if too large)
    │
    └─ blob.load_all_data!

Projects::BlobController#update
    │
    ├─ editor_variables
    │
    └─ create_commit(Files::UpdateService, ...)
           │
           ├─ Files::UpdateService#execute
           │      ├─ validate! (file_has_changed?)
           │      ├─ Lfs::FileTransformer#new_file
           │      └─ create_transformed_commit
           │             └─ repository.update_file
           │
           └─ rescue FileChangedError
                  └─ @conflict = true, render "edit"
```

### データフロー図

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

Form Parameters ───▶ BlobController#update ───▶ Redirect / Conflict
  - content           │
  - commit_message    ├─▶ Files::UpdateService
  - branch_name       │      │
  - last_commit_sha   │      ├─▶ file_has_changed?
                      │      │      └─▶ FileChangedError (if conflict)
                      │      │
                      │      └─▶ Repository#update_file
                      │             │
                      │             └─▶ Git Repository
                      │
                      └─▶ Event Recording
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| blob_controller.rb | `app/controllers/projects/blob_controller.rb` | コントローラー | リクエスト処理 |
| edit.html.haml | `app/views/projects/blob/edit.html.haml` | テンプレート | 編集フォームビュー |
| _editor.html.haml | `app/views/projects/blob/_editor.html.haml` | パーシャル | エディタ部品 |
| update_service.rb | `app/services/files/update_service.rb` | サービス | ファイル更新ロジック |
| base_service.rb | `app/services/files/base_service.rb` | サービス | 共通処理・コンフリクト検出 |
| creates_commit.rb | `app/controllers/concerns/creates_commit.rb` | Concern | コミット作成共通処理 |
| file_transformer.rb | `app/services/lfs/file_transformer.rb` | サービス | LFS変換 |
| repository.rb | `config/routes/repository.rb` | ルーティング | URL定義 |
