GitHub Actionsにおけるpathsとpaths-ignoreをベン図で理解する

# はじめに

GitHub Actionsにおいて、 `on: pull_request` のときに追加で指定できる `paths` や `paths-ignore` の使い方がしょっちゅうこんがらがるので、具体例を並べて整理しました。

公式ドキュメントはこちら
https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#onpushpull_requestpaths

# 基礎知識の整理

pathsにマッチするファイルの変更が1個でもあったら、ワークフローを起動する。
https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#example-including-paths

paths-ignoreにマッチするファイルの変更 *しか* なかったら、ワークフローを起動しない。
paths-ignoreにマッチしないファイルの変更が1個でもあったら、ワークフローを起動する。
https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#example-ignoring-paths

1個のワークフローにおいて、pathsとpaths-ignoreは併用不可。
https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#excluding-paths

pathsでは、 `!` が使える。pathsに複数の値を指定するときに使える。
`!` 付きの指定（negative patternと呼ぶ）を含む場合は、値の順序が重要になってくる。「後勝ち」と覚えれば分かりやすい。

あるpositive patternの後ろにnegative patternを書くと、先のpositive部分から後のnegative部分を差し引いた指定になる。negative部分が「例外」と思えば良い。

あるnegative patternの後ろにpositive patternを書くと、後のpositive部分をやっぱり含める指定になる。positive部分が「例外の例外」と思えば良い。

https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#example-using-positive-and-negative-patterns-1


# 具体例

まずは一番単純な例から。

```yml
# pathsもpaths-ignoreもないので、常に起動する。
on: pull_request
```

ベン図にするとこんな感じです。

![always.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188748/4ce2dfac-7e7b-02c6-4467-af4b2712d484.png)

---

pathsの単純な例はこちら。

```yml
# jsファイルの変更が含まれていたら、起動する。
on:
  pull_request:
    paths:
    - '**.js'
```

![any-js.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188748/2d8523ed-e26c-7207-bb19-0ef64f7c2ba2.png)


---

pathsに複数の値を指定すると、ORになります。

```yml
# （jsファイルまたはmdファイル）の変更が含まれていたら、起動する。
on:
  pull_request:
    paths:
    - '**.js'
    - '**.md'
```

![and-js-or-any-md.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188748/e513b841-c747-cb53-90a1-1054716338b6.png)

---

次はpaths-ignoreの単純な例。

```yml
# jsファイルの変更しか含まれていなかったら、起動しない。
# jsファイル以外の変更が含まれていたら、起動する。

on:
  pull_request:
    paths-ignore:
    - '**.js'
```

![not-js.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188748/fe12c3c4-aa2c-edcb-1088-b9f1448eae18.png)


---

paths-ignoreに複数の値を指定すると、ignoreする部分がORになります。

```yml
# （jsファイルまたはmdファイル）の変更しか含まれていなかったら、起動しない。
# （jsファイルまたはmdファイル）以外の変更が含まれていたら、起動する。

on:
  pull_request:
    paths-ignore:
    - '**.js'
    - '**.md'
```

![not-js-or-not-md.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188748/5d0ed99c-6475-9e98-c308-536556091c13.png)


---

今度はpathsにnegative patternを含めてみます。

```yml
on:
  pull_request:
    paths:
    - 'module-a/**' # module-aディレクトリの子孫に変更があったら、起動する。
    - '!**.md'      # あっ、でも、mdファイルの変更はノーカンで。
```

![module-a-without-md.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188748/38aff774-2289-b437-27ba-22067c9793c9.png)


---

続いて、negative patternの後にpositive patternを加えてみます。

```yml
on:
  pull_request:
    paths:
    - 'module-a/**' # module-aディレクトリの子孫に変更があったら、起動する。
    - '!**.md'      # あっ、でも、mdファイルの変更はノーカンで。
    - 'module-a/**/important.md' # あっ、でもでも、module-a子孫のimportant.mdの変更だったら、
                                 # ノーカンにするのをやっぱ無しで。
```

![pos-neg-pos.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188748/758c4e5c-1e68-66db-8867-f741e527cceb.png)

# まとめ

落ち着いて整理すると、そんなに難しくない気がしてきました。とはいえ、あまり複雑な指定をすると自分にしか解読できなくなりそうですので、ほどほどにしたいところです。

実用性はあまりないですが、paths-ignoreで表現できる集合はpathsでも表現できそうですね。全体集合（ `**` ）から、negative patternで差し引いていくと。