タグをトリガーとしたGitHub Actionsでタグが特定のブランチに存在することをチェックする方法

## TL;DR

タグをトリガーとするGitHub Actionsでそのタグがmainブランチに存在しない場合、エラー終了させるには以下のようなstepを組み込めば良い。

```yaml
- uses: actions/checkout@v4
  with:
    fetch-depth: 0
- name: Fail if tagged commit is not in main branch
  run: |
    BRANCHES=$(git branch -r --contains)
    echo $BRANCHES
    for BRANCH in $BRANCHES ; do
      if [[ "$BRANCH" == "origin/main" ]]; then
        exit 0
      fi
    done
    exit 1
- name: Deploy
  run: echo "Deploy sesame"
```

## 背景

あるリポジトリで、特定のタグを付けたらデプロイするというGitHub Actionsを構築していた。
このとき、タグが最終的な統合ブランチであるmainブランチに所属していることをチェックしたいと考えた。

同じようなことをやろうとしている、以下のような先行記事があった。
[Github Actionsでpushされた時に特定ブランチとタグでトリガーする(Zenn)](https://zenn.dev/na_it_o/articles/cb7d0b9496496d)

しかし、こちらの求めるものと少し相違があった。

## 先行記事の内容

対比しやすいように、先行記事のコードの内容を損ねないように整理したものがこちらになる。

```yaml
# (1) mainをチェックアウト
- uses: actions/checkout@v4
  with:
    ref: main
    fetch-depth: 0
# (2) github.ref_name が指すタグがmainブランチ上のコミットに付いていない場合はエラー終了
- name: Fail if tagged commit is not in main branch
  run: |
    BRANCHES=$(git branch --contains ${{ github.ref_name }})
    echo $BRANCHES
    for BRANCH in $BRANCHES ; do
      if [[ "$BRANCH" == "main" ]]; then
        exit 0
      fi
    done
    exit 1
# (3) デプロイを実行
- name: Deploy
  run: echo "Deploy sesame"
```

意図通りで無かった点はただ1つ。
(3)の時点で、本来はタグがチェックアウトされた状態になっていてほしいのだが、mainブランチがチェックアウトされていることである。

## 解決方法

まず、チェックアウト対象としてmainを指定するのをやめる。

```yaml
- uses: actions/checkout@v4
  with:
    fetch-depth: 0
```

ただし、こうすると `git branch --contains` としたときにmainがヒットしなくなる。
`fetch-depth: 0` を指定したにも関わらずである。
これは `actions/checkout` でrefを指定しない場合、トリガーとなったrefがチェックアウトされ、ローカルブランチとしてのmainは存在していないからである。

ローカルブランチとしてのmainは存在していないのだが、リモート追跡ブランチ([こちらの用語を参照した](https://git-scm.com/book/ja/v2/Git-%E3%81%AE%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81%E6%A9%9F%E8%83%BD-%E3%83%AA%E3%83%A2%E3%83%BC%E3%83%88%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81))である｀`origin/main` はfetchされている( `fetch-depth: 0` の効果)。
リモート追跡ブランチで対象のタグが含まれているものをリストアップするためには `-r` オプションを追加して `git branch -r --contains` とする。

```yaml
- name: Fail if tagged commit is not in main branch
  run: |
    BRANCHES=$(git branch -r --contains)
    # ※対象を省略した場合は現在のHEADが対象となるため、タグをチェックアウトしていれば `${{ github.ref_name }}` は不要

```

以上を踏まえた完成形は以下のようになる。

```yaml
name: Deploy

on:
  push:
    tags:
      - 'v*'

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Fail if tagged commit is not in main branch
        run: |
          BRANCHES=$(git branch -r --contains)
          echo $BRANCHES
          for BRANCH in $BRANCHES ; do
            if [[ "$BRANCH" == "origin/main" ]]; then
              exit 0
            fi
          done
          exit 1
      - name: Deploy
        run: echo "Deploy sesame"
```

## Q&A形式の補足

### Q1: refをmainにした状態でcheckoutして、後で `git checkout ${{ github.ref_name }}` しても良いのでは？

それでも良いと思う。
ただ、タグをトリガーとしたworkflowであればタグがチェックアウトされた状態を期待すると思うので、他のコミット(ブランチ)がチェックアウトされた状態はなるべく避けたかった。

### Q2: `git branch --contains ${{ github.ref_name }}` したものをechoするとなんかファイル名が列挙されるんだが？

mainブランチをチェックアウトしている状態では、 `git branch(以下略` は次のようなテキストを出力する。

```sh
❯ git branch --contains v1.2.3
* main
```

アスタリスクは現在チェックアウトされているブランチを指す。
したがって、結果をechoするということは `echo * main` というコマンドを実行することになる。
このとき `*` はglob展開され、実行ディレクトリ直下のファイルが列挙される。