1人で開発するのに最適なGitワークフローとリリースを自動化する方法について紹介します。
リリース時の作業を以下3ステップにできます。

  1. 対象バージョンの最新release branchをcheckout
  2. make release
  3. Pull Requestのマージ

Table of Contents

はじめに

皆さんは業務でリリース作業をしていますか?
リリースの為に手順書と睨めっこして緊張しながらリリースしていませんか?

旧GitHubフローみたく『masterブランチを常にデプロイすればリリースは必要ない』と言われるかもしれません。
しかし常にデプロイされてしまう環境は業務フローにあわなかったり、場合によってはデプロイを躊躇させてしまうこともあるでしょう。

私は業務とプライベートあわせて計10にも及ぶツールを運用しています。
その末に辿り着いた1人で開発するのに適したGitワークフローとリリース自動化手法を紹介します。

前提

以下がインストールされている必要があります。

  • git
  • make
  • bash

Owlフロー

私が使用しているGitワークフローのルールを紹介します。
名前が無いと文書が書きにくいのでOwlフローという名前を付けます。

コミットグラフは以下のようになります。

どこかで見たことがあるなら...
ここで紹介するワークフローは、もしかすると既に存在するものかもしれません。
その場合は名前を教えていただけると嬉しいです ☺

前提

リリースバージョン1.4.1を次回リリースするケースを説明します。

release branchを作成する

次回リリースバージョンの名前でbranchを作成します。
これをrelease branchと呼びます。

$ git checkout -b 1.4.1 origin/1.4.1

Gitフローではリリース直前にリリースブランチを作成しますが、本フローでは開発開始時にrelease branchを作成します。

release branchからtopic branchを作成する

新機能や不具合修正に関するtopic branchを、リリースしたいバージョンに紐づくrelease branchから作成します。

# current branchが1.4.1
$ git checkout -b feature/topic1 origin/feature/topic1

topic branchreleaes branchにマージする

topic branchの開発が完了したらrelease branchにマージします。

$ git commit ...
$ git commit ...
.
.
$ git checkout 1.4.1
$ git merge feature/topic1 --no-ff

こうしていくつかのtopicに対してtopic branchを作成し、それら全てをrelease branchにマージします。

release branchmasterにマージする

実際はリリース作業後にmasterへマージします。

リリース作業の概要と自動化は次のセクションで説明します。

Owlフローのメリット/デメリット

メリット

コミットグラフがとても見やすい

トピックの対応バージョンを予め決めているのでコミットグラフが非常に見やすくなります。
release branchの終点にタグが打たれます。

リリース時のバージョン指定で緊張しない

リリース作業のバージョン指定… 間違えると面倒なので緊張しますよね。
Owlフローではカレントブランチ名称からバージョンを取得可能なため、ブランチ名があっていればバージョン指定は不要です。

1人開発の場合は他の影響を気にする必要がなく、手元のブランチも常に最新であることが多いです。
そのため、スムーズに開発からリリース作業へ入ることができます。

デメリット

トピックのリリースバージョンを後から変更しにくい

トピックの対応バージョンを先に決めてしまうので、リリースバージョン変更時は面倒です。
topic branchrelease branchにマージしなければいいのですが、次のrelease branchへ綺麗にリベースできる保証はありません。

その点、Gitフローはdevelopにマージするだけなのでよくできていますね。

以下のケースではデメリットが顕著ではないため気にしなくていいと思います。

  • 機能ベースでリリースする戦略
  • 複数人で作業する機会が少ない

リリース作業

Makefileに記載することでリリース作業を自動化します。

前提

リリース実行の前に以下が完了していることを確認してください。

  • リリースversionの対応が全てcommit/pushされていること (リリースノート更新含む)

ここから実際の作業です。

対象バージョンのrelease branchをチェックアウト

release branchの最新情報を取得します。
JenkinsなどのCIを使用する場合は、release branchを指定すると思います。

既にrelease branchがcheckout済みのローカルで実行する場合は以下のようになると思います。

$ git checkout 1.4.1 && git pull

make release

チェックアウトしたブランチが最新で正しければコマンドはこれだけです。

Pull Requestのマージ

make releaseに成功すると、最後にPull RequestのURLが表示されます。
デプロイされた成果物の動作確認後、URLを開いてPull Requestを作成=>マージしましょう。

Makefileの中身

前セクションのmake releaseについて、Makefileの一例を見てみましょう。

Makefile
MAKEFLAGS += --warn-undefined-variables
SHELL := /bin/bash
ARGS :=
.SHELLFLAGS := -eu -o pipefail -c
.DEFAULT_GOAL := help

.PHONY: $(shell egrep -oh ^[a-zA-Z0-9][a-zA-Z0-9_-]+: $(MAKEFILE_LIST) | sed 's/://')

help: ## Print this help
	@echo 'Usage: make [target]'
	@echo ''
	@echo 'Targets:'
	@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z0-9][a-zA-Z0-9_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)

version := $(shell git rev-parse --abbrev-ref HEAD)

#------

package-linux:
	@mkdir -p dist
	GOOS=linux GOARCH=amd64 go build -a -tags netgo -installsuffix netgo --ldflags '-extldflags "-static"' -o dist/miroir

clean-package:
	rm -rf dist

release: clean-package
	@echo '1. Update versions'
	@sed -i -r 's/const version = ".+"/const version = "$(version)"/g' args.go

	@echo '2. Packaging'
	make package-linux
	tar zfc dist/miroir-$(version)-x86_64-linux.tar.gz dist/miroir --remove-files

	@echo '3. Staging and commit'
	git add args.go
	git commit -m '📦 Version $(version)'

	@echo '4. Tags'
	git tag v$(version) -m v$(version)

	@echo '5. Push'
	git push

	@echo '6. Deploy package'
	ghr v$(version) dist/

	@echo 'Success All!!'
	@echo 'Create a pull request and merge to master!!'
	@echo 'https://github.com/tadashi-aikawa/miroir-cli/compare/$(version)?expand=1'
	@echo '..And deploy package!!'

ポイントだけピックアップして紹介します。

versionの取得

git rev-parseコマンドの結果からリリースするversionを取得します。

version := $(shell git rev-parse --abbrev-ref HEAD)

git rev-parseはGitの情報を取得するコマンドのようです.. (詳しくは分からず..)

それぞれ以下のような結果を返します。

# HEADのハッシュ値
$ git rev-parse HEAD
1c1338854431979c8ff1420d9526fe213b4fcda2
# --shortで短縮ハッシュ値
$ git rev-parse --short HEAD
1c13388

# --abbrev-ref でカレントブランチ名
$ git rev-parse --abbrev-ref HEAD
0.2.0

sed -iによるバージョン置換

リリースの度、機械的に更新するのが各ファイルに記載されたバージョンです。
sed-iオプションを使って入力ファイルを直接書き換えます。

	@sed -i -r 's/const version = ".+"/const version = "$(version)"/g' args.go

上記はargs.goの中に記載されたconst version = "x.y.z"const version = "1.4.1"に置換します。

テンプレートファイルを使う方法
他にもテンプレートファイルを作成し、それを入力に固定文字列を置換した結果を正規ファイルとして上書きする方法もあります。

commit/tag

バージョンに依存するコミットメッセージやタグ付けは以下のように自動化してpushもできます。

	@echo '3. Staging and commit'
	git add args.go
	git commit -m '📦 Version $(version)'

	@echo '4. Tags'
	git tag v$(version) -m v$(version)

	@echo '5. Push'
	git push

デプロイ

今回の例では成果物をGitHubのリリースページにアップロードするため、ghrを使用します。

	@echo '6. Deploy package'
	ghr v$(version) dist/

先日ghrでGitHubに成果物をアップロードする記事を書きましたのでこちらも参考にしてください。

ghrを使うならタグ付けコマンドは不要...?
不要です。ghrにはタグを作成してpushする機能があります。
本記事ではghr以外でデプロイする場合も考慮しているため冗長な記述になっています。

Pull Request URLの案内

最後にrelease branchmasterにマージするGitHubのプルリクURLを表示します。

	@echo 'Success All!!'
	@echo 'Create a pull request and merge to master!!'
	@echo 'https://github.com/tadashi-aikawa/miroir-cli/compare/$(version)?expand=1'

他のGitワークフローへの適応

ここで紹介した自動化の手法はOwlフローでなくても適応できます。
Gitフローならブランチ名の取り方を変更..、リリースブランチがないフローでもversionを外から指定すればOKです。

Owlフローを使用する場合もcheckoutでrelease branchを指定するため、実質version指定と変わりありません。

テストについて

テストがあるならバージョン置換の前に実行しておきましょう。
例えば以下のような記載です。

test: ## Test
	@echo Start $@
	@pipenv run pytest $(ARGS)
	@echo End $@


test-cli: ## Test on CLI
	@echo Start $@
	@make start-api 2> /dev/null
	@-bats test.bats
	@make stop-api
	@echo End $@


release:
	@echo '0. Install packages from lockfile and test'
	@pipenv install --deploy
	@make test
	@make test-cli

総括

1人で開発するのに適したOwlフロー、リリースを自動化するMakefileの記載例について紹介しました。

選択肢の1つとして『こんな方法もあるんだ』と思っていただければ幸いです。