細かすぎて伝わりにくいバージョニングを自動化した話

約 4 ヶ月ぐらい前に、アプリにバージョンを付けて運用してみました:santa:
不具合の際に、ユーザーとの認識も合わせやすくなり開発体験も向上したので、共有したいと思います:santa:

やりたいと思った背景については長くなったので、最後にしました:santa:

最終的に人間がやることは、コミットメッセージにプレフィクスつけるだけっす:santa:

![screenshot 2019-12-12 15.59.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183059/29e70b66-bb8b-8b25-d07a-c453e38b6f88.png)

# アプリのバージョニングとは

git のコード管理とは違い、アプリ自体のバージョニングです:santa:
ある区切りにおいて、バージョニング(1.0.0 → 1.0.1)を行うわけですが、アプリのバージョンを区切るのはどこが適切か迷いました:santa:
自分が担当しているプロジェクトでは、master(origin)が staging、production ブランチにマージされたタイミングで
各環境にデプロイがはしるので、master ブランチに PR がマージされた直後にしました:santa:

# アプリのバージョニングをしたいと思った背景

## ユーザーとの認識合わせ

ユーザーからアプリの不具合が問い合わせであった場合、事象の再現ができなかったりしないですか？
どういった状況・現象の把握も大事ですが、バージョンの共有で、更に再現性が高くしたかった:santa:

## どのブランチが、ステージング環境にデプロイされているんだっけ

複数人で複数環境(dev, stg, prod 等)を同時に開発していると、本番にデプロイされる前に
ステージング環境で試して、本番にデプロイするのがほとんどだと思います:santa:
しかし、どの環境にどのブランチがデプロイされているのか Git 管理をしていても把握が面倒だと思います:santa:

そこで、web アプリなら、バージョンを画面のどこかに記載しておけば
どのバージョンが動いているのかが一目瞭然になりますね:santa:

## とはいっても、バージョン管理面倒だよね

バージョンをつけようとなった時に壁になるのが下記だと思います:santa:

1. バージョン上げの判断基準どうしよう
   → 何が major で、何があると patch？
2. １を毎回やるの面倒

・・・・:santa:

そんなのは、判断基準を作っちゃって、全部 CI で行いましょう:santa:

# やりたいこと

1. アプリにバージョンを表示したい
2. 区切り方どうしよう
3. 意味のある何かで、バージョンを区切りたい
4. 自動でバージョンを上げたい

# こうしよう！

- アプリにバージョンを表示したい

package.json の version 使おう！

- 区切り方どうしよう

`npm version hoge` を使用しました:santa:

- 意味のある何かで、バージョンを区切りたい

コミットメッセージのプレフィクスを判断基準:santa:

- 自動でバージョンを上げたい
- 毎回人間が区切るの面倒！機械的にやりたい → CircleCI で実行

## 1. アプリにバージョンを表示したい

package.json を引っ張ってくるだけです:santa:

あくまでサンプルで、どんな引っ張りかたでも構いません
AngularJS ではコントローラー(コンポーネント) → view に表示です

```sample.component.ts

const packageJson = require('./package.json');
const version = packageJson.json

```

```package.json
{
  "name": "sample",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
```

## 2. 区切り方どうしよう

npm コマンドで package.json の version 区切れるって知ってました？:santa:

[npm version コマンドで出来ること。](https://qiita.com/minamo173/items/8b8b27bc6ecd17ad925e)
[ドキュメント](https://docs.npmjs.com/cli/version)

例：

```bash
npm version major

# 1.0.0 => 2.0.0
```

```bash
npm version minor

# 1.0.0 => 1.1.0
```

```bash
npm version patch

# 1.0.0 => 1.0.1
```

他にも、premajor とかたくさんありますが、上記 3 つのシンプル運用です:santa:

## 3. 意味のある何かで、バージョンを区切りたい

### 意味のある何か：区間とバージョン上げの判断基準

#### 判断基準

コミットメッセージにプレフィクス

プレフィクスをつけたコミットメッセージは下記を参考にしてみてください

[【今日からできる】コミットメッセージに 「プレフィックス」 をつけるだけで、開発効率が上がった話](https://qiita.com/numanomanu/items/45dd285b286a1f7280ed)

AngularJS では下記を運用しているみたいです

```
feat: A new feature
fix: A bug fix
docs: Documentation only changes
style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
refactor: A code change that neither fixes a bug nor adds a feature
perf: A code change that improves performance
test: Adding missing or correcting existing tests
chore: Changes to the build process or auxiliary tools and libraries such as documentation generation
new: create new files
delete: delete files
```

自分はこれにプラスで packages も プレフィクスに追加しました（理由後述）

#### 自分が担当しているプロジェクトの場合

- major → フレームワークの major バージョンが上がった時
- minor → git pull したら、すぐに build が必要になりそうな範囲
- patch → git pull しても、build がすぐには必要なさそうな範囲

major は AngularJS 使っているので、そんなになさそうで、手動です！（← え）

minor か patch かは切り分けるの難しいと思いますが、プルしたら、再ビルドコマンドをしないといけないかどうかで切り分けてみました:santa:

#### 区間

master からプルリクの通ったブランチまでのコミットメッセージ

## 4. 自動でバージョンを上げたい

CircleCI を使いました！

ただ、CircleCI で乗り越えるべき壁が存在します(後述します)

### CI の流れ

1. master にプルリクが通った直後に、バージョニングを行う
2. バージョンが上がる
3. 終了

まずは CircleCI の config ファイルです:santa:

```.circleci/config.yml

version: 2.1

executors: (省略)

jobs:
  versioning:
    executor: container
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run: ./version.sh

workflows:
  jobs-after-pullrequest-merged:
    jobs:
      - versioning:
          filters:
            branches:
              only: master
```

## ポイント

### executors は node 系のコンテナ

npm コマンドと git コマンド使えるのであれば特になにも下準備要らないので省略です:santa:

### branches: only: master を設定

これ設定すると、master ブランチにプッシュが走るタイミング(プルリク通った直後)のみにできます:santa:

### version.sh というファイルを実行する

[CircleCI の command ](https://circleci.com/docs/ja/2.0/reusing-config/#%E5%86%8D%E5%88%A9%E7%94%A8%E5%8F%AF%E8%83%BD%E3%81%AA%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%81%AE%E3%82%AA%E3%83%BC%E3%82%B5%E3%83%AA%E3%83%B3%E3%82%B0)もありますが、yaml の特性とか[シバン](https://qiita.com/jkr_2255/items/84366f677be3365331cd)とか考えたら、普通にスクリプト用意したのを実行するほうがいいです:santa:

```version.sh
#!/bin/bash -eu

# ①マージされた直前(current_branch_hash)のhash値を取得
current_branch_hash=$( git log --oneline --merges | awk '{print $1}' | head -1 );

# ②マージされた2つ前(pre_branch_hash)をhash値を取得
pre_branch_hash=$( git log --oneline --merges | awk '{print $1}' | head -2 | tail -n 1 );

# ③マージされた直前(current_branch_hash)からマージされた 2 つ前(pre_branch_hash)の間のコミットメッセージ内容(commit_messages)を抽出
any_minor_messages=$(git log --oneline ${pre_branch_hash}...${current_branch_hash} --grep=feat --grep=packages --grep=new)

# ④これからpushする人を設定
git config user.email "circleci@example.com"
git config user.name "circleci"

# ⑤変更差分がキャッシュに残っている場合は stashに保存
git diff --quiet || git stash;

# ⑥、③で何か取得できたら、minor、なかったら、patch
if [ ${#any_minor_messages} -ne 0 ]; then # 結果の文字列の長さが 0ではない場合
  npm version minor -m "%s [ci skip]" # 0.0.0 => 0.1.0
else
  npm version patch -m "%s [ci skip]" # 0.0.0 => 0.0.1
fi

# ⑦package.jsonのversionが更新されるので、push
git push

```

## version.sh の内容

### ①②③

```version.sh
# ①マージされた直前(current_branch_hash)のhash値を取得
current_branch_hash=$( git log --oneline --merges | awk '{print $1}' | head -1 );

# ②マージされた2つ前(pre_branch_hash)をhash値を取得
pre_branch_hash=$( git log --oneline --merges | awk '{print $1}' | head -2 | tail -n 1 );

# ③マージされた直前(current_branch_hash)からマージされた 2 つ前(pre_branch_hash)の間のコミットメッセージ内容(commit_messages)を抽出
any_minor_messages=$(git log --oneline ${pre_branch_hash}...${current_branch_hash} --grep=feat --grep=packages --grep=new)

```

CircleCI でこの version.sh が走るタイミングが、既にプルリクがマージされた直後なので、
1 つ前と２つ前のマージされた地点のハッシュ値を取得します
git log に`--grep=hoge`を渡すと、コミットメッセージを hoge で絞ってくれます。
複数渡せば、OR 検索します

つまり、

- feat（機能追加）
- packages（パッケージ追加・削除）
- new(ファイル新規作成)

のプレフィクスがあれば、any_minor_messages に何かコメントが格納されます

### ④

```version.sh

# ④これからpushする人を設定
git config user.email "circleci@example.com"
git config user.name "circleci"

```

これ設定しないと、git history 見た時に、名無しのごんべえが push することになっちゃいます

### ⑤

```version.sh

# ⑤変更差分がキャッシュに残っている場合は stashに保存
git diff --quiet || git stash;
```

変更差分をキャッシュで持っていると、`npm version`が実行できないので、差分があれば、git stash しちゃいます

### ⑥

```version.sh

# ⑥、③で何か取得できたら、minor、なかったら、patch
if [ ${#any_minor_messages} -ne 0 ]; then # 結果の文字列の長さが 0ではない場合
  npm version minor -m "%s [ci skip]" # 0.0.0 => 0.1.0
else
  npm version patch -m "%s [ci skip]" # 0.0.0 => 0.0.1
fi

```

any_minor_messages に何かコメントが格納されていれば
`npm version minor -m "%s [ci skip]"`
含んでいなければ、
`npm version patch -m "%s [ci skip]"`
です:santa:

↓ こんな感じのコミットメッセージになります

![screenshot 2019-12-12 20.36.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183059/b81dde31-7e65-a06d-c354-6539e0eb398c.png)

`-m "%s [ci skip]"`で、commit メッセージを残すことができ、`%s`バージョンを表示するタグで、
`[ci skip]`は、⑦ で git push をした際に、再度、CircleCI を発火させないためのメッセージです:santa:

どういうことかというと、CircleCI は、git push をトリガーにして動きます

今回の場合、master にプルリクが通ると version.sh が動くようにしているので

プルリクが通る(master に git push が動いている)

↓

CircleCI 発動

↓

version.sh が動く

↓

バージョンを上げた package.json を master に git push

↓

CircleCI 発動

↓

version.sh が動く・・・

のように永遠と繰り返さないために、`[ci skip]`というコミットメッセージを使って CI を１回のみで終わらせることができます:santa:

⑦、⑥ で package.json が変更されて`git commite`されたのを master に変更をかけるため、`git push`を最後に行っています:santa:

# プレフィクスをつけるの自動化

今回紹介したことは、プレフィクスをつけなければ何も始まりません:santa:

コミットテンプレートを設定することで、自動でプレフィックスを設定できちゃいますね:santa:

## GitKraken 編

Preference( command + , ) → Commite Template
![screenshot 2019-12-12 21.00.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183059/af6ed642-3ec0-37ca-8ae6-2e56edc52900.png)


に設定するだけで、コミット後、再度コミットテンプレ呼び起こせるようになりました:santa:

![output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183059/cf6ab900-12a8-eeb5-5b15-a3049d358c94.gif)


GitKraken で設定すると、VSCode も勝手に表示されるようになります:santa: