LoginSignup
10
1

More than 3 years have passed since last update.

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

Posted at

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

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

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

screenshot 2019-12-12 15.59.39.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. 1を毎回やるの面倒

・・・・: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 コマンドで出来ること。
ドキュメント

例:

npm version major

# 1.0.0 => 2.0.0
npm version minor

# 1.0.0 => 1.1.0
npm version patch

# 1.0.0 => 1.0.1

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

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

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

判断基準

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

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

【今日からできる】コミットメッセージに 「プレフィックス」 をつけるだけで、開発効率が上がった話

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 もありますが、yaml の特性とかシバンとか考えたら、普通にスクリプト用意したのを実行するほうがいいです: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 つ前と2つ前のマージされた地点のハッシュ値を取得します
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

-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 を1回のみで終わらせることができます:santa:

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

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

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

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

GitKraken 編

Preference( command + , ) → Commite Template
screenshot 2019-12-12 21.00.17.png

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

output.gif

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

10
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
1