ストア公開後のブランチをGitHub Actionsで自動マージする運用

# はじめに

メグリ株式会社のモバイルアプリチームでは、以下のような Git flow もしくは GitHub flow を模したブランチ運用を採用しています。

![mgre-branch.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361241/a934545d-e1a3-4eba-909e-0e8c6f81dfeb.png)

このブランチ管理では、main (古いリポジトリでは master) ブランチに、現在ストア公開されているアプリの内容が反映されていて、develop ブランチは開発中の最新情報が反映されるようにしています。

アプリを新しくストア申請する際には、まず releaseブランチを作成し、そのブランチ上で必要なアプリバージョンを設定して、申請用のアプリをビルドしています。

これまでアプリがストア公開された際には、releaseブランチを手動で main と develop にマージしていました。しかしリリースが頻繁になるにつれて、手動作業によるマージ漏れや遅延のリスクが無視できなくなりました。

特に、ビジネスサイドの制約でストア公開時期が変わるため、releaseブランチの作成タイミングと実際のストア公開のタイミングがズレることが頻繁にあり、マージ作業が忘れがちという問題がありました。

また、ストア公開したアプリに不具合があった場合は、mainブランチからhotfixのブランチを作成して対応するため、mainブランチは常に最新の内容が反映されている必要があります。

幸い、現在の開発フローでは、releaseブランチへのpushをトリガーとして、自動的に main ブランチへのPull Requestが作成される仕組みがあります。この仕組みをさらに発展させ、ストア公開をトリガーとして Releaseブランチを自動で main と develop にマージする運用をGitHub Actionsで実現しました。本記事ではその背景と実装の詳細を紹介します。

# 移行の背景と目的

### 移行前のフロー

```mermaid
%%{init: {"theme": "base", "themeVariables": {"background": "#ffffff"}} }%%
graph TB
    subgraph エンジニア
        A1(releaseブランチ作成)
        B1(バージョン設定)
        C1(ビルド)
        M1["手動マージ作業<br/>(release→main, main→develop)"]
    end

    subgraph ビジネスサイド
        D1(ストア申請)
        E1(公開管理)
        N1(公開をエンジニアへ通知)
    end

    subgraph ストア
        S1(ストアに公開)
    end

    subgraph リポジトリ
        H1(releaseブランチ)
        I1(mainブランチ)
        J1(developブランチ)
    end

    A1 --> B1 --> C1
    C1 --> D1
    D1 --> E1
    E1 --> S1
    S1 --> N1
    N1 --> M1
    M1 --> I1
    H1 --> M1
    I1 --> J1

```

手動マージには以下の課題がありました。

- 時間的コスト: リリースの度にエンジニアや関係者の工数が発生
- 人的ミス: マージ漏れ、タグ付け忘れ、ブランチ間違いなど
- 属人化: 担当者による対応遅延や実施内容の差異

これらを解決するため、GitHub Actionsを利用してストア公開を監視し、対象ブランチを自動的にマージするなどのクロージング作業を自動化しました。

Play Console の API や App Store の API を利用してストア公開を検知する方法も検討しましたが、クライアントごとにアカウントの管理方法が異なり、統一的にAPIを利用することが難しかったため、定期的にストア情報をチェックする方法を採用しました。

## ストア公開バージョンの取得方法

ストア公開されたアプリのバージョンを監視するため、AndroidアプリとiOSアプリのそれぞれについて、以下のようなPythonスクリプトを実装しています。

### Androidアプリの場合（Google Play）

Pythonの [google-play-scraper](https://github.com/facundoolano/google-play-scraper) を利用して最新バージョンを取得します。

```python
from google_play_scraper import app

def get_android_version(package_name):
    """
    Get app version from Google Play
    """
    result = app(package_name)
    return result['version']

latest_version = get_android_version('com.example.app')
print(latest_version)
```

### iOSアプリの場合（App Store）

[iTunes Search API](https://performance-partners.apple.com/search-api)を利用して最新バージョンを取得します。

```python
import requests

def get_app_version(bundle_id):
    """
    Get app version from App Store
    """
    base_url = "https://itunes.apple.com/lookup"
    params = {
        "bundleId": bundle_id,
        "country": "JP"
    }
    response = requests.get(base_url, params=params, timeout=(10.0, 10.0))
    data = response.json()
    if data["resultCount"] == 0:
        return "not_found"

    app_info = data["results"][0]
    return app_info["version"]

latest_version = get_app_version('com.example.app')
print(latest_version)
```

## 実装の詳細

実装した自動化の流れは以下の通りです。

1. 定期的実行: 毎日決まった時刻にGitHub Actionsが起動
1. ストアの最新バージョンを取得: 上記の方法でGoogle Play/App Storeから公開バージョンを取得
1. releaseブランチ存在確認: 取得したバージョンのreleaseブランチのPRがGitHub上に存在するかを確認
1. main へのマージ: 該当するPRがあれば自動でマージ
1. develop へのマージ: main マージ成功後に自動マージ
1. Slack通知: マージ結果をSlackに通知

### 主要スクリプト（概要）

自動マージの核となるスクリプト の要点は以下の通りです。

- リポジトリをクローンして最新バージョン取得
- 対応するreleaseブランチとPRの存在を確認
- GitHub CLIでPRを自動マージ
- マージ結果をSlackに通知

#### 主要コードの抜粋

```bash
# PR番号を取得
pr_number=$(gh pr list --base $main_branch --head release/v${version} --state open --json number --limit 10 2>/dev/null | jq -r '.[0].number // empty')

# PRが見つからなかった場合の処理
if [[ -z "$pr_number" || ! "$pr_number" =~ ^[0-9]+$ ]]; then
    echo "No valid PR found."
    exit 0
fi

# PRをマージ
if gh pr merge $pr_number --merge; then
    echo "PR $pr_number merged successfully."
else
    echo "Failed to merge PR $pr_number."
    exit 1
fi
```

スクリプト内で参照している変数`version`は前述したストア公開バージョン取得のスクリプトを利用して設定しています。

### GitHub Actionsワークフローの設定

ワークフローの具体例は以下です。

- 毎日２回ほど定期実行
- 手動実行にも対応
- 対象リポジトリに対してBashスクリプトを実行

#### 主要コードの抜粋

```yaml
on:
  workflow_dispatch:
  schedule:
    - cron: '0 9,21 * * *' # JSTで18:00, 6:00

jobs:
  auto-pr-merger:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
    - name: Checkout code
      uses: actions/checkout@v3

    - name: merge release PRs
      run: |
        bash run.sh
      env:
        GH_TOKEN: ${{ secrets.GH_TOKEN  }}
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL  }}

```

### 移行後のフロー

```mermaid
%%{init: {"theme": "base", "themeVariables": {"background": "#ffffff"}} }%%
graph TB
    subgraph エンジニア
        A2(releaseブランチ作成)
        B2(バージョン設定)
        C2(ビルド)
    end

    subgraph ビジネスサイド
        D2(ストア申請)
        E2(公開管理)
    end

    subgraph ストア
        S2(ストアに公開)
    end

    subgraph GitHub_Actions
        style K fill:#e6f7ff,stroke:#0052cc,stroke-width:3px
        K[ストアのバージョン監視]
        style L fill:#e6f7ff,stroke:#0052cc,stroke-width:3px
        L["自動マージ実行<br/>(release→main, main→develop)"]
        style M fill:#e6f7ff,stroke:#0052cc,stroke-width:3px
        M[Slack通知]
    end

    subgraph リポジトリ
        H2(releaseブランチ)
        I2(mainブランチ)
        J2(developブランチ)
    end

    A2 --> B2 --> C2
    C2 --> D2
    D2 --> E2
    E2 --> S2
    S2 --> K
    K -- "新バージョン検知" --> L
    H2 --> L
    I2 --> J2
    L -- "マージ結果" --> M
    L --> I2  
```

## 運用してみての効果

### 実際の運用データ
自動化システムの運用から得られたreleaseブランチ作成からストア公開完了までのリードタイムデータを分析しました。

![lead_time.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361241/16639822-11d1-44f0-9da1-5c3c5ac83727.png)


- 平均リードタイム: 17.6日
- 中央値: 11.0日
- 最短: 0.2日
- 最長: 208.3日

期間別の分布は以下の通りです：

- 1週間以内: 36.5%
- 1ヶ月以内: 83.7%
- 3ヶ月以内: 98.6%
- 3ヶ月超: 1.4%

最長のケースでは、releaseブランチ作成から実際のストア公開まで約7ヶ月の期間を要していました。

自動化により、リードタイムの長さに関わらずストア公開と同時にマージが実行されるようになりました。

また、自動化に効果として、年間を通しておおよそ１〜２人月のエンジニア工数を削減することできました。

### 感想

この仕組みを導入したことで、以下のようなメリットがありました。

- 工数削減: PRをマージするタスクがなくなった（ビジネスサイドによる公開の通知、それに伴うエンジニアの手動マージ作業など）
- 人的ミス削減: 自動化による安定運用

- 安定した開発フロー: 常に最新の状態を保持

対応前は、ストアへの公開後にもエンジニア・ビジネスサイドの双方で追加作業が必要となっていました。特に、ストア公開のタイミングは都度異なり、申請から公開まで時間が空くことも多かったため、「いつの間にか公開されていて手続きを忘れてしまった」「久しぶりに戻って作業するのでコンテキストスイッチが大きい」といった課題が発生していました。

対応後は、エンジニア・ビジネスサイドともに、それぞれ一度の作業で完結できるようになりました。ストア公開の完了を待つ必要がなくなり、公開後の手動作業や待ち時間も発生しません。その結果、全体の工数も削減でき、何よりも「いつ作業するのか」を気にするストレスや、再度作業に戻る際の心理的な負担が大きく軽減されたと感じています。

以下の記事にも記載しましたが、これらの自動化で各種アプリの最新バージョンを把握することが容易になりました。

https://qiita.com/mgre_tanabe/items/eb200dcac971af063f01

以下のように社内ではDocbaseとGoogleスプレッドシートに情報を集約していますが、今回の対応によって、エンジニアに限らず関係者が簡単に最新アプリバージョンを把握することができるようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361241/17d3cca0-cd2b-4889-8bed-3f4f54b2688a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361241/bc57c9e6-95d6-4ea9-83f7-3ed372e73955.png)


# まとめ

GitHub Actionsを活用することで、煩雑な手動マージ作業を効率化し、より快適な開発環境を実現できました。今後も積極的にGitHub Actionsを活用して効率化を模索していこうと思います。


# お知らせ（採用情報）

メグリでは、こうした開発環境の改善や自動化、生成AIの活用に興味があるエンジニアを募集しています。
GitHub Actionsを始めとする最新技術を活用した効率的な開発環境の構築に加えて、モバイルアプリ開発、サーバサイド開発に興味がある方はぜひ以下のページをご覧ください。

https://mgre.co.jp/careers

カジュアル面談も随時実施していますので、お気軽にお問い合わせください。