# 機能設計書 27-リポジトリミラーリング

## 概要

本ドキュメントは、GitLabにおけるリポジトリミラーリング機能の設計を定義する。外部リポジトリとの同期（プッシュミラー）を実現し、コードの複製・バックアップを可能にする機能である。

### 本機能の処理概要

リポジトリミラーリング機能は、GitLabプロジェクトのリポジトリを外部のGitリポジトリにプッシュ（同期）する機能である。

**業務上の目的・背景**：企業のソフトウェア開発において、コードを複数のリポジトリに同期させる必要がある場面は多い。本機能により、オンプレミスのGitLabからGitHub/Bitbucketへの公開同期、災害復旧用のバックアップ、異なるチーム間でのコード共有が実現できる。特にハイブリッド環境やマルチクラウド戦略を採用する組織にとって重要な機能である。

**機能の利用シーン**：
- GitLabからGitHubへのコード同期（オープンソース公開）
- 災害復旧用のリポジトリバックアップ
- 異なるGitLabインスタンス間の同期
- CI/CD用の別リポジトリへの同期
- 保護ブランチのみの選択的ミラーリング

**主要な処理内容**：
1. プッシュミラーの設定（URL、認証情報）
2. 認証方式の設定（パスワード/SSHキー）
3. 同期オプションの設定（保護ブランチのみ/分岐の維持）
4. 手動/自動での同期実行
5. 同期状態の監視（成功/失敗/進行中）
6. SSHホストキーの検証

**関連システム・外部連携**：外部Gitリポジトリ（GitHub、Bitbucket、他のGitLab等）、Gitalyサーバー、Sidekiqワーカー

**権限による制御**：プロジェクト管理者権限（admin_project）が必要。リモートミラーの作成・削除には admin_remote_mirror 権限が必要。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 111 | リポジトリ設定 | 主機能 | リポジトリ関連設定の管理 |

## 機能種別

CRUD操作 + 非同期処理（CREATE/READ/UPDATE/DELETE + Background Job）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| url | String | Yes | ミラー先リポジトリURL | 有効なGit URL形式 |
| enabled | Boolean | No | ミラーリング有効フラグ | true/false |
| only_protected_branches | Boolean | No | 保護ブランチのみ同期 | true/false |
| keep_divergent_refs | Boolean | No | 分岐した参照を維持 | true/false |
| auth_method | String | No | 認証方式 | password/ssh_public_key |
| user | String | No | ユーザー名 | - |
| password | String | No | パスワード/トークン | - |
| ssh_known_hosts | String | No | SSH既知ホスト | - |
| regenerate_ssh_private_key | Boolean | No | SSH秘密鍵の再生成 | true/false |

### 入力データソース

- URL パス: `/project_namespace/project_name/-/settings/repository`（設定画面）
- URL パス: `/project_namespace/project_name/-/mirrors`（API）
- フォーム入力による設定

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| remote_mirror | RemoteMirror | リモートミラーオブジェクト |
| remote_mirror.id | Integer | ミラーID |
| remote_mirror.url | String | ミラー先URL（認証情報マスク済み） |
| remote_mirror.enabled | Boolean | 有効フラグ |
| remote_mirror.update_status | String | 同期状態 |
| remote_mirror.last_update_at | DateTime | 最終同期日時 |
| remote_mirror.last_successful_update_at | DateTime | 最終成功日時 |
| remote_mirror.last_error | String | 最終エラー |

### 出力先

- HTML: リダイレクト先のリポジトリ設定画面
- JSON: API レスポンス

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信・認証確認
   └─ authorize_admin_project!, check_mirror_available!
2. ミラー設定の作成/更新/削除
   └─ CreateService/DestroyService 呼び出し
3. 同期実行（update_now）
   └─ Sidekiq ワーカーのエンキュー
4. 状態遷移
   └─ state_machine による状態管理
5. レスポンス出力
   └─ リダイレクトまたはJSON
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B{管理者権限?}
    B -->|No| C[アクセス拒否]
    B -->|Yes| D{ミラー利用可能?}
    D -->|No| E[404エラー]
    D -->|Yes| F{操作種別}
    F -->|CREATE| G[CreateService実行]
    F -->|DELETE| H[DestroyService実行]
    F -->|UPDATE_NOW| I[同期ジョブ登録]
    G --> J{成功?}
    J -->|Yes| K[成功通知]
    J -->|No| L[エラー通知]
    H --> M[リダイレクト]
    I --> N[処理中通知]
    K --> O[リダイレクト]
    L --> O
    N --> O
    O --> P[終了]
    M --> P
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-27-01 | ミラー利用可能性 | インスタンス設定でミラーが有効である必要 | 常時 |
| BR-27-02 | 保護ブランチのみ | only_protected_branches有効時は保護ブランチのみ同期 | 設定時 |
| BR-27-03 | バックオフ遅延 | 保護ブランチのみ: 1分、全体: 5分 | 同期スケジュール時 |
| BR-27-04 | 最大実行時間 | 初回: 3時間、増分: 1時間 | 同期実行時 |
| BR-27-05 | スタック検出 | 最大実行時間超過時はスタック状態とみなす | 同期監視時 |

### 計算ロジック

**同期状態遷移**：
- none -> started: 同期開始
- started -> finished: 同期成功
- started -> failed: 同期失敗
- started -> to_retry: リトライ予定

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 作成 | remote_mirrors | INSERT | リモートミラーレコード作成 |
| 更新 | remote_mirrors | UPDATE | 設定・状態更新 |
| 削除 | remote_mirrors | DELETE | リモートミラー削除 |
| 同期 | - | - | Gitalyによるリモートプッシュ |

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

#### remote_mirrors

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | url, enabled, only_protected_branches, keep_divergent_refs, credentials | フォーム入力値 | credentials は暗号化 |
| UPDATE | update_status, last_update_at, last_successful_update_at, last_error | 同期結果 | - |
| DELETE | - | id = params[:id] | - |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 404 | NotFound | ミラー機能無効 | エラーページ表示 |
| 403 | Forbidden | 権限不足 | アクセス拒否 |
| 400 | BadRequest | 無効なSSH URL | エラーメッセージ返却 |
| 422 | UnprocessableEntity | バリデーションエラー | エラーメッセージ表示 |

### リトライ仕様

- 同期失敗時は to_retry 状態に遷移
- バックオフ遅延後に再実行

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

状態更新はトランザクション内で実行、認証情報は暗号化して保存

## パフォーマンス要件

- 同期はバックグラウンドジョブで非同期実行
- バックオフ遅延による負荷分散

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

- authorize_admin_project! による管理者権限の確認
- can?(:admin_remote_mirror) による操作権限の確認
- 認証情報（パスワード/SSHキー）の暗号化保存
- URLからの認証情報サニタイズ
- SSHホストキーの検証機能

## 備考

- サイレントモード時はミラーリング無効
- pending_delete プロジェクトではミラーリング無効
- リポジトリが存在しない場合はミラーリング無効

---

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

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

### 推奨読解順序

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

リモートミラーのデータモデルを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | remote_mirror.rb | `app/models/remote_mirror.rb` | リモートミラーモデル、状態遷移 |

**読解のコツ**: state_machine による状態遷移、認証情報の暗号化、バックオフ遅延の仕組みを理解する。

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

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

**主要処理フロー**:
1. **15-17行目**: show - 設定画面へリダイレクト
2. **19-49行目**: update - ミラー作成/削除
3. **51-58行目**: update_now - 手動同期実行
4. **60-74行目**: ssh_host_keys - SSHホストキー取得
5. **94-102行目**: execute_push_mirror_service - サービス実行

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | create_service.rb | `app/services/remote_mirrors/create_service.rb` | 作成サービス |
| 3-2 | destroy_service.rb | `app/services/remote_mirrors/destroy_service.rb` | 削除サービス |

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

```
MirrorsController#update
    │
    ├─ authorize_admin_project!
    │
    ├─ check_mirror_available!
    │      └─ can?(:admin_remote_mirror)
    │
    ├─ push_mirror_create_or_destroy?
    │      ├─ push_mirror_create?
    │      └─ push_mirror_destroy?
    │
    └─ execute_push_mirror_service
           ├─ CreateService#execute
           │      └─ RemoteMirror.create
           └─ DestroyService#execute
                  └─ RemoteMirror.destroy

MirrorsController#update_now
    │
    └─ project.update_remote_mirrors
           └─ RepositoryUpdateRemoteMirrorWorker（Sidekiq）
                  └─ RemoteMirror#update_repository
                         └─ Gitlab::Git::RemoteMirror
```

### データフロー図

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

フォーム入力       ───▶  MirrorsController         ───▶  リダイレクト/JSON
(url, auth)              │
                         ▼
                    CreateService
                         │
                         ▼
                    RemoteMirror.create ──────────────▶  DB INSERT
                         │
                         ▼
                    Sidekiq Worker ───────────────────▶  非同期同期実行
                         │
                         ▼
                    Gitaly RemoteMirror ──────────────▶  外部リポジトリへプッシュ
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| mirrors_controller.rb | `app/controllers/projects/mirrors_controller.rb` | コントローラー | ミラーリングのエントリーポイント |
| remote_mirror.rb | `app/models/remote_mirror.rb` | モデル | リモートミラーモデル |
| create_service.rb | `app/services/remote_mirrors/create_service.rb` | サービス | 作成サービス |
| destroy_service.rb | `app/services/remote_mirrors/destroy_service.rb` | サービス | 削除サービス |
| repository_update_remote_mirror_worker.rb | `app/workers/repository_update_remote_mirror_worker.rb` | ワーカー | 同期ワーカー |
| remote_mirror.rb | `lib/gitlab/git/remote_mirror.rb` | ライブラリ | Git操作 |
| ssh_host_key.rb | `app/models/ssh_host_key.rb` | モデル | SSHホストキー |
