🏷

GitHub Actions: PRのdiff行数に応じてラベルを貼る簡単なスニペット

2024/04/22に公開

ちょっとしたスニペットの紹介です。結構雑なやつです

PRのdiff行数に応じてラベルを貼るワークフローの書き方の紹介です。完成図は以下のようになります

このようなラベルが、PRを作成した際に自動的に作られるようになります。PRが更新されればラベルも貼り直しされます。

スニペット

.github/workflows/label-pr-diff-line-count.yml などのような名前で(ファイル名は何でもいいです)以下のような内容のワークフローを作成します。

name: PRの差分行数カウント
on:
  pull_request:
    types: [opened, synchronize]
env:
  # 差分行数を表すラベルのprefix
  DIFF_LABEL_PREFIX: diff/
  # 差分行数を表すラベルの色
  DIFF_LABEL_COLOR: '63D2D0'
jobs:
  label:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - name: チェックアウト
        uses: actions/checkout@v4
      - name: 差分の情報を取得
        id: diff
        uses: technote-space/get-diff-action@v6
        with:
          GET_FILE_DIFF: true
      - name: 差分行数をもとにラベルを設定
        run: |
          node -e "$(cat <<'EOF'
            const { execSync } = require('node:child_process');
            const repo = process.env.REPO;
            const prefix = process.env.DIFF_LABEL_PREFIX ?? '';
            const labelColor = process.env.DIFF_LABEL_COLOR;
            const prNumber = ${{ github.event.pull_request.number }};
            const diffLines = ${{ steps.diff.outputs.lines }};
            // ラベル区分を追加する場合は以下を編集する
            const labels = [
              { name: 'tiny', max: 10 },
              { name: 'small', max: 30 },
              { name: 'middle', max: 100 },
              { name: 'large', max: 200 },
              { name: 'xl', max: 400 },
              { name: 'xxl', max: 800 },
              { name: '3xl', max: 1500 },
              { name: '4xl', max: 5000 },
              { name: '5xl', max: 10_000 },
              { name: '6xl', max: 20_000 },
              { name: 'too-large', max: Number.POSITIVE_INFINITY },
            ];
            const createdLabels = new Set(JSON.parse(execSync(`gh label list --repo ${repo} --json name`).toString()).map(({ name }) => name));
            const addIndex = labels.findIndex(({ max }) => diffLines <= max);
            const add = labels[addIndex];
            const remove = labels.filter(e => e !== add && createdLabels.has(`${prefix}${e.name}`));
            const addArgs = `--add-label '${prefix}${add.name}'`
            const removeArgs = remove.length === 0 ? '' : `--remove-label '${remove.map(({ name }) => `${prefix}${name}`).join(',')}'`;
            const addedLabelDescription = `PRの差分が${(labels[addIndex - 1]?.max ?? -1) + 1}行以上${Number.isFinite(add.max) ? `${add.max}行以下` : ""}`;
            execSync(`gh label create '${prefix}${add.name}' --description '${addedLabelDescription}' --color ${labelColor} --repo ${repo} --force`);
            execSync(`gh issue edit ${prNumber} ${addArgs} ${removeArgs}`);
          EOF
          )"
        env:
          GH_TOKEN: ${{ github.token }}
          REPO: ${{ github.repository }}

これさえ配置すれば完了です。

このまま使うと、

  • 0行〜10行の変化で diff/tiny ラベル
  • 11行〜30行の変化で diff/small ラベル
  • ...
  • 10001行〜20000行の変化で diff/6xl ラベル
  • 20001行〜の変化で diff/too-large ラベル

がつくというような設定になっています。

const labels = [ ... ] の中身の部分を書き換えれば独自の設定にできますので、ぜひ書き換えて利用してみてください。

使用したアクションの紹介

最新版に関する情報は各自でご確認ください

専用のパブリックな共有アクションがほしい

私もそう思います。気が向いたら...

でも部品となるアクションが充実していると、nodeを使ってサクッとこういうのがかけるのはいいですよね。

シンプルなバージョン

ラベルの用意は事前にやっておくのでよい、という場合は以下のシンプルなバージョンで良いかもしれません。

シンプルなバージョン
name: PRの差分行数カウント
on:
  pull_request:
    types: [opened, synchronize]
env:
  # 差分行数を表すラベルのprefix
  DIFF_LABEL_PREFIX: diff/
jobs:
  label:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - name: チェックアウト
        uses: actions/checkout@v4
      - name: 差分の情報を取得
        id: diff
        uses: technote-space/get-diff-action@v6
        with:
          GET_FILE_DIFF: true
      - name: 差分行数をもとにラベルを設定
        run: |
          node -e "$(cat <<'EOF'
            const { execSync } = require('node:child_process');
            const prefix = process.env.DIFF_LABEL_PREFIX ?? '';
            const prNumber = ${{ github.event.pull_request.number }};
            const diffLines = ${{ steps.diff.outputs.lines }};
            // ラベル区分を追加する場合は以下を編集する
            const labels = [
              { name: 'tiny', max: 10 },
              { name: 'small', max: 30 },
              { name: 'middle', max: 100 },
              { name: 'large', max: 200 },
              { name: 'xl', max: 400 },
              { name: 'xxl', max: 800 },
              { name: '3xl', max: 1500 },
              { name: '4xl', max: 5000 },
              { name: '5xl', max: 10_000 },
              { name: '6xl', max: 20_000 },
              { name: 'too-large', max: Number.POSITIVE_INFINITY },
            ];
            const add = labels.find(({ max }) => diffLines <= max);
            const remove = labels.filter(e => e !== add);
            const addArgs = `--add-label '${prefix}${add.name}'`
            const removeArgs = `--remove-label '${remove.map(({ name }) => `${prefix}${name}`).join(',')}'`;
            execSync(`gh issue edit ${prNumber} ${addArgs} ${removeArgs}`);
          EOF
          )"
        env:
          GH_TOKEN: ${{ github.token }}

で、ラベルはスクリプトなどで事前に用意しておく。

ラベル用意のスクリプトの例(雑なやつ)
import { execFileSync } from "node:child_process";

export const deleteLabel = (name: string, repo: string) => {
  execFileSync("gh", ["label", "delete", name, "--repo", repo, "--yes"], {
    stdio: ["ignore", "inherit", "inherit"],
  });
};
export const createLabel = (
  name: string,
  description: string,
  color: string,
  repo: string,
) => {
  execFileSync(
    "gh",
    [
      "label",
      "create",
      name,
      "--description",
      description,
      "--color",
      color,
      "--repo",
      repo,
    ],
    {
      stdio: ["ignore", "inherit", "inherit"],
    },
  );
};

// ワークフローyamlと同じもの
const labels = [
  // ...
];

const repo = "<your org>/<your repo>";
const prefix = "diff/";
const color = "63D2D0";
let last = 0;
for (const { name, max } of labels) {
  const label = `${prefix}${name}`;
  const description = `PRの差分が${last}行以上${
    Number.isFinite(max) ? `${max}行以下` : ""
  }`;
  createLabel(label, description, color, repo);
  // deleteLabel(label, repo);
  last = max + 1;
}

bunで実行する前提でTypeScriptです。

diffのラベルの何が嬉しいか

私のチームではPRサイズを(小さくするように)意識しようという話があって、その際に一覧ページを見た際にラベルがあったほうが振り返りがしやすかったり、PR担当者も意識しやすくなったりするだろうと思い、ラベルをつけることにしました。

GitHubで編集を提案
OPTIMINDテックブログ

Discussion