OpenHarmonyアプリ開発におけるブランチ管理とコラボレーション機能の最適化

機能開発の目的と成果

従来のリポジトリ閲覧機能を拡張し、複数開発者間の協働をサポートするため、ブランチ管理機能を追加しました。主な実装内容は以下の通りです:

  • リポジトリ詳細ページにブランチ一覧と管理パネルを追加
  • コラボレーションページでブランチ作成、切り替え、削除、マージリクエストの発行機能を実装

ブランチモデルの実装

lib/models/branch.dartの実装例:

class Branch {
  final String branchName;
  final String repositoryOwner;
  final String repositoryName;
  final String commitHash;
  final String commitMessage;
  final bool isProtected;
  final DateTime? updatedAt;
  final bool isDefaultBranch;

  factory Branch.fromMap(Map data, String repoFullName) {
    final name = data['name']?.toString() ?? '';
    final commitData = data['commit'] as Map;
    final sha = commitData['sha']?.toString() ?? '';
    final commitInfo = commitData['commit'] as Map;
    final message = commitInfo['message']?.toString() ?? 'コミット情報なし';
    bool protected = false;
    if (data.containsKey('protection')) {
      protected = data['protection']['enabled'] as bool? ?? false;
    }
    final parts = repoFullName.split('/');
    final owner = parts.sublist(0, parts.length - 1).join('/');
    final repo = parts.last;
    final date = _parseDateTime(commitInfo['committer']['date']);
    return Branch(
      branchName: name,
      repositoryOwner: owner,
      repositoryName: repo,
      commitHash: sha,
      commitMessage: message,
      isProtected: protected,
      updatedAt: date,
      isDefaultBranch: data['default'] ?? false,
    );
  }

  static DateTime? _parseDateTime(String? str) {
    if (str == null) return null;
    try {
      return DateTime.parse(str);
    } catch (e) {
      return null;
    }
  }
}

コードの特徴:リポジトリ名を分割し、所有者とリポジトリ名を取得。保護状態やデフォルトブランチの識別を強化し、API操作のためのパラメータを準備しています。

ブランチリストと操作インターフェース

lib/pages/repository_detail_page.dartの実装例:

Widget _buildBranchList() {
  return RefreshIndicator(
    onRefresh: _loadBranches,
    child: ListView.separated(
      padding: EdgeInsets.all(16),
      itemCount: branches.length,
      separatorBuilder: (_, __) => Divider(height: 8),
      itemBuilder: (context, index) {
        final branch = branches[index];
        return ListTile(
          leading: branch.isDefaultBranch
              ? Icon(Icons.star, color: Colors.amber)
              : Icon(Icons.code_branch),
          title: Text(branch.branchName),
          subtitle: Text(
            '${branch.commitHash.substring(0, 7)} ・ ${branch.commitMessage}',
            maxLines: 1,
            overflow: TextOverflow.ellipsis,
          ),
          trailing: _buildActionButtons(branch),
          onTap: () => _switchBranch(branch),
        );
      },
    ),
  );
}

Widget _buildActionButtons(Branch branch) {
  return Row(
    mainAxisSize: MainAxisSize.min,
    children: [
      IconButton(
        icon: Icon(Icons.merge),
        onPressed: () => _showMergeDialog(branch),
        tooltip: 'マージリクエストを発行',
      ),
      IconButton(
        icon: Icon(Icons.delete),
        onPressed: branch.isProtected || branch.isDefaultBranch
            ? null
            : () => _confirmDelete(branch),
        tooltip: branch.isProtected ? '保護されたブランチ' : 'ブランチを削除',
        disabledColor: Colors.grey[300],
      ),
    ],
  );
}

APIサービス層の実装

lib/services/api_service.dartの実装例:

static Future>> fetchRepositoryBranches(String owner, String repo) async {
  final url = Uri.parse('https://api.example.com/repos/$owner/$repo/branches');
  final response = await _httpGet(url);
  if (response.statusCode == 200) {
    final jsonData = json.decode(response.body);
    if (jsonData is List) {
      return jsonData.cast>();
    }
    throw Exception('予期しないフォーマット');
  }
  throw Exception('ブランチ取得失敗: ${response.statusCode}');
}

static Future> createBranch(String owner, String repo, String branchName, String baseSha) async {
  final url = Uri.parse('https://api.example.com/repos/$owner/$repo/branches');
  final body = json.encode({
    'branch': branchName,
    'ref': baseSha
  });
  final response = await _httpPost(url, body: body);
  if (response.statusCode == 201) {
    return json.decode(response.body) as Map;
  }
  throw Exception('ブランチ作成失敗: ${response.statusCode}');
}

static Future<void> deleteBranch(String owner, String repo, String branchName) async {
  final url = Uri.parse('https://api.example.com/repos/$owner/$repo/branches/$branchName');
  final response = await _httpDelete(url);
  if (response.statusCode != 204) {
    throw Exception('ブランチ削除失敗: ${response.statusCode}');
  }
}

マージリクエスト機能

lib/pages/merge_request_page.dartの実装内容:

  • ソースブランチとターゲットブランチの選択機能
  • コンフリクト検出APIによる事前チェック
  • マージタイトル、説明、レビュアーの指定
  • マージリクエストの状態追跡(未審査、承認、却下、マージ中、完了)

ユーザーエクスペリエンスの最適化

  • 操作結果のトースト通知(3秒自動消去)
  • コンフリクト結果のハイライト表示とファイル確認リンク
  • ローディングダイアログで重複操作防止
  • デフォルトブランチと保護ブランチのアイコン表示
  • ブランチリストの更新順ソート
  • キーワード検索によるフィルタリング
  • ネットワークエラー時のリトライボタン
  • 権限不足時の明確なエラーメッセージと権限申請リンク
  • 削除時の二次確認ダイアログ

テスト結果

テストケース 内容 結果
ブランチ管理 一般ブランチ、デフォルトブランチ、保護ブランチの作成・削除 成功(保護・デフォルトブランチの削除は無効化)
マージリクエスト コンフリクトなし、あり、レビュアー指定 成功(コンフリクト検出正確、状態追跡正常)
エラーシナリオ ネットワーク中断、権限不足、重複ブランチ作成 成功(エラーメッセージ明確、リトライ可能)
パフォーマンス 100+ブランチの読み込み、一括削除 成功(1秒以内、カクつきなし)

今後の改善計画

  • ブランチ履歴のビジュアル化
  • ブランチネットワーク拓扑図の実装
  • 細かい権限管理の追加
  • マージリクエストテンプレートの導入
  • レビューノーティフィケーション機能
  • オフラインキャッシュの強化
  • オフライン操作の同期機能

タグ: OpenHarmony Dart Git REST_API BranchManagement

6月29日 23:59 投稿