【初めてのGithub Actions】mainブランチに自動でタグをつけよう

## はじめに
個人でiOSのアプリを開発しています。毎回アップデートするたびにタグを自分でつけるのだるいな〜。と思っていたので、Github Actionsを使用して、自動化しました。Github Actionsに関してはほぼ知識ないので、もっとこういう書き方できるよ〜とかあれば教えてください！

## 実際にやってみよう
### 今回作るものの詳細
- mainブランチに```release/ver⚪︎.⚪︎.⚪︎```というブランチがマージされた時に、```ver⚪︎.⚪︎.⚪︎```というタグが自動でつく。

### Github Actionsのファイルの置き場所
自分のプロジェクトファイルに```.github/workflows```というディレクトリを作成します。
そして、このディレクトリ配下にGithub Actionsで処理したい内容を書いた```yml```ファイルを配置しておくことで、処理が行われるようになります。

以下画像のような構成です。

![スクリーンショット 2024-06-29 13.37.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2644705/074b6101-29eb-e68f-c36d-c7a5ed2f666e.png)


これ以外に特にGithub Actionsを始めるために必要な準備は必要ありません。

### ymlファイルの内容
では次にymlファイルを作成して、tagを自動でつけるための処理を書いていきましょう。
書き込む内容は以下のとおりです。

```.yml
name: add_tag

on:
  push:
    branches:
      - main

permissions:
  contents: write

jobs:
  add_tag:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout branch
        uses: actions/checkout@v4
        with:
          fetch-depth: 2

      - name: Extract version and add tag
        uses: actions/github-script@v7
        with:
          script: |
            const { execSync } = require('child_process');

            // 最新のマージメッセージの取得
            const mergeCommit = execSync('git log --merges -1 --pretty=format:"%s"').toString().trim();
            console.log(`Merge commit message: ${mergeCommit}`);

            // バージョンの抽出
            const versionMatch = mergeCommit.match(/Merge pull request .* from .*release\/ver(.*)/);
            if (!versionMatch) {
              throw new Error('No version extracted.');
            }
            const version = versionMatch[1];
            console.log(`version: ${version}`);

            // タグ付け
            execSync(`git tag ver${version} ${process.env.GITHUB_SHA}`);
            execSync(`git push origin ver${version}`);
```

これをリモートにプッシュして、release/ver1.0.0のようなブランチをmainブランチにマージしてみてください！おそらくタグが自動でつくはず！
Github Actionsの処理の成功失敗はリポジトリのActoinsタブで見れます。
緑のマークが付いていればtagもついてるはずです！
![スクリーンショット 2024-06-29 14.15.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2644705/d0659b91-2bc3-cb90-29b4-cde3e63e2c57.png)

### ちょっとだけ詳細を説明
#### name
処理を分割してちょっとだけ詳細を説明していこうと思います。
まず以下のnameの部分はこのActionの名前です。なんでもいいです。

```yml
name: add_tag
```

#### on
次に以下の```on```の部分です。
ここではいつ、このActionを行うかを定義します。
今回はmainブランチにマージされた時なので、以下のように設定しています。
この他にもissueがopenされたときなどさまざまなタイミングでActionは行うことができます。
僕はtagをつけるのの他に、リリースブランチがリモートにプッシュされた時には自動でPRを作成するという使い方もしています。

```yml
on:
  push:
    branches:
      - main
```

#### permission

次に以下の部分ですが、今回tagをつける作業を行います。
リポジトリに変更を加えるため、writeの権限を持たせています。
これがないと、権限がなくてタグをつけられなかったというエラーが出てしまいます。

```yml
permissions:
  contents: write
```

#### jobとstep
Github Actionsは1つのjobの中に複数のstepが含まれる形になっています。

今回の場合は、```Checkout branch```と```Extract version and add tag```というstepを1つのjobの中に入れています。

以下の画像にあるようにstepごとの実行結果もわかります。

#### runs-on
```runs-on```ではActionをどのような環境で動かすかを指定します。
こんかいは最新のubuntuを指定しています。```macos```で動かすこともできるので、アプリxcode関連のコマンドを実行したいときは変えると良さそうです。



```yml
jobs:
  add_tag:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout branch
        ... 省略 ...

      - name: Extract version and add tag
        ... 省略 ...
```

![スクリーンショット 2024-06-29 14.29.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2644705/a436388f-9647-49ee-d38e-f4d9d0411b6d.png)


#### checkout
1つ目のstepはそのままですがブランチのチェックアウトをしています。

withの部分でfetch-depthを指定していますが、これはブランチの履歴をどれだけ取得するかを指定するところです。デフォルトでは1になっているので、例えば10個くらい前の履歴を使用したい場合には指定する必要があります。

```0```を指定することで全ての履歴を取得することもできます。
ただ、プロジェクトが大きい場合には取得に時間がかかってしまうみたいです。
自分のプロジェクトで0にしてみた感じは、全然時間かからなかったので個人でやってる分には0でいいかもしれません。
今回は2にしておきます。

```yml
   steps:
      - name: Checkout branch
        uses: actions/checkout@v4
        with:
          fetch-depth: 2
```


#### versionの抽出とtag付け

以下のようにusesには何を使ってscriptを書くかを宣言できます。

github-script@v7を使うとJavaScriptっぽくscriptをかけます。他にもシェルコマンドっぽくかけるものがあったりします。

この辺まだ何があるのかとか、何が書きやすいのかとか分かってません。swiftっぽく書くやつとかないんかなぁ。

```yml
uses: actions/github-script@v7
```

script内には実際にtagをつけるための処理を書きます。
どこで何をしているのかはコメントでざっくりと書いておきます。

```yml
        with:
          script: |

            # シェルコマンドを実行するために必要なモジュールを取得
            const { execSync } = require('child_process');

            # 最新のマージメッセージの取得
            const mergeCommit = execSync('git log --merges -1 --pretty=format:"%s"').toString().trim();
            
            # 取得したメッセージを出力
            console.log(`Merge commit message: ${mergeCommit}`);

            # バージョンの抽出
            const versionMatch = mergeCommit.match(/Merge pull request .* from .*release\/ver(.*)/);

            # もしバージョンが取得できなかったらエラーで終了する
            if (!versionMatch) {
              throw new Error('No version extracted.');
            }

            # 取得したバージョンをを出力
            const version = versionMatch[1];
            console.log(`version: ${version}`);

            # タグをつけてプッシュ
            execSync(`git tag ver${version} ${process.env.GITHUB_SHA}`);
            execSync(`git push origin ver${version}`);
```

## おわりに
まだまだGithub Actionsについてはわからないことだらけです、
でもちょっと個人開発で使うようになって、便利さがわかりました！
ではまた！！