株式会社SocialDogの募集・採用・求人情報 - Wantedly
株式会社SocialDogの新卒・中途・インターンの募集が234件あります。気軽に面談して話を聞いてみよう。職種や採用形態からあなたにあった募集を見つけることができます。募集では「どんなことをやるのか」はもちろん、「なぜやるのか」「どうやるのか」や実際に一緒に働くメンバーについて知ることができます。
https://www.wantedly.com/companies/socialdog/projects
こんにちは!
8月からSocialDogにジョインしたあっきーこと上田です!現在はチームリーダーとしてSocialDogの新規開発を仲間たちと励んでいます!開発たのしい〜〜〜!!
ところで私はgitが大好きです。なぜ好きなのかを語るとこの記事が終わらないので割愛しますが、
SocialDogにジョインした後、なんと早速gitの運用変更に携わったので、その経緯や何をしたか、またおまけとしてマニアックなgitの活用方法について紹介していきます!
SocialDogでは以下の目的から2年ほどでプルリクエストをスカッシュマージで運用していました。
次のようなメリットがあるためです。
プルリクエストがマージされたときにコミットが1つになるため、流れが追いやすくなります。
つまりgitのログ検索の操作でハマる時間の削減ができます。
プルリクエストのタイトルをそのままコミットメッセージとして流用すること[^1] で、ベースブランチ[^2] のコミットメッセージは常にプルリクエストのタイトルになります。
つまり、コミットのメッセージに都度悩む必要がなくなります。
またマージコミットを作る運用だと コミットをきれいにするため にrebase操作をしたりと、ややgitの上級的な操作をする必要が出てきます。
スカッシュマージによる運用であれば、意識しなくともベースブランチのコミットをキレイに保ち続けることができます。
スキルのグラデーションがある中でも 開発サイクルを高速に回しつつコミットをキレイに保つ というバランスを取れるので当時の開発チームにフィットしていました。
[^1]: GitHubではPull Requests設定で可能です。
[^2]: 開発のもとになるブランチ。よくあるのがmain・master・developブランチなど
スカッシュマージによる運用は当初数年はうまくいっていましたが、開発の体制やコンテキストが大きくより複雑になるにつれて、いくつかの問題点が発生しました。
大きな機能の開発では、当該機能のためのトピックブランチを作成し、段階的に開発を進めることがあります。
この場合トピックブランチ上ではサブタスクごとにプルリクエストが作られるので、サブタスクの単位でコミットも作成されますが…
機能が完成し、いざベースブランチにマージするとき、今までのコンテキストを含んだコミットは1つのコミットにスカッシュされます。
この図では3個のコミットが1つのコミットにスカッシュされましたが、普通のコミットよりも情報量が3倍程度になったといっていいでしょう。
つまり、ソースコードを git blame[^3]などで検査しても、本来どのプルリクエストで修正されたのか がわからなくなります。
[^3]: 各プログラムコード行がどのコミットで追加されたかを調べる操作
ある機能の開発が一定期間にわたるなら、定期的にトピックブランチへベースブランチをマージしたくなります。これは、ソースコードの実態をベースブランチと乖離させたくないためです。
ただ
①このマージをスカッシュマージ で行い
②ベースブランチのコミットが進んで…
③もう一度ベースブランチをトピックブランチにマージする
と、コンフリクトが発生します。
なぜでしょうか。
「どこまでマージしたか」という①の情報はスカッシュしたときに消え去ったため、③のマージのときに取り込み済のコミットも再度取り込もうとするからです。
なぜならコミットログだけでは、トピックブランチ上で反映済のコードが「ベースブランチ上で修正されたコードをすでに取り込んだコード」なのか「トピックブランチ上で修正されたコードがたまたまベースブランチ上のコードと一致したのか」を区別できないため、gitはコンフリクトを起こして開発者に解決を任せるのです。
SocialDogではこの「同じコンフリクトを何度も解消する」工数が一部の開発者に重くのしかかっていました。
結論からいうとプルリクエストのマージはマージコミットを作る運用に変えて上記課題を解決しました。
マージコミットを作るということは、スカッシュマージによってコミットが巨大になるデメリットをそのまま避けることに繋がります。
先程の①と③の図をマージコミットを作るマージで行うと、
①のマージは次のようになります。
そして③のマージを行うときには①でマージ済のコミット以外のコミットだけが取り込まれるので、先程のコンフリクトは発生しません。
gitのマージ原理を簡単に説明すると
の3つのコミット同士のファイルを比較してマージを行う3-wayマージというアルゴリズムでマージされます。
3-wayマージでは共通祖先コミット(図中 C_base)からマージ先コミット(図中 C_to) 、マージ元コミット(図中 C_from)へのファイル差分(diff)を取り(図中 p1、p2)、共通祖先コミットのファイルツリーに適用するという方法を取ります。(図中では C_base に p1、p2 を適用してマージコミット C_merge を作成しています)
①のマージでマージコミットを作ると、ベースブランチとトピックブランチとの共通祖先コミットが繰り上がり、①で取り込んだコミットを無視することができるのです。
マージコミットを作る運用へ切り替えるということは、いままでスカッシュマージで得ていたメリットがそのまま利用できなくなるというのがそのままデメリットになりそうです。
ここは次の2点で可能な限り軽減できるように配慮しました。
git log コマンドでは --first-parent というフラグが使えます。
このフラグを有効にすると、スカッシュマージと同等のログを表示できます。
画像引用元リポジトリ: https://github.com/containers/podman
CLIやサポートするGitクライアント限定の方法ではありますが、これで1トピック1コミットのログを見られるというスカッシュマージのメリットを利用できることを紹介し、ドキュメントにも記載しました。
マージ以外のコミットの作成方法についてはルールを増やしませんでした。
具体的には以下のようなルールを設けませんでした。
次のような理由から、移行のコストを必要以上に上げないためです。
間もなく、SocialDogは複数のチームへ分かれることが決まっていました。社内リソースが増えたことによる開発体制のスケールが背景にあります。
マージコミットを作る運用に切り替えたことで、開発体制に合わせたブランチ戦略を採用しやすくなります。開発体制の変更に合わせ、ブランチの運用を次のように切り替えました。
masterから開発ブランチを切ってmasterにマージするというシンプルな方法で運用していました。
リリースは所定のタイミングでmasterブランチをリリースします。
結論として次の図のようなブランチ運用方法を採用しました。
環境ごとにブランチを分けて管理するようになりました。
具体的には次の2つを用意しています。
これはマージコミットを作るからこそ可能になった管理方法です。スカッシュマージでは前述のようなコンフリクトの問題が起きるため、複数ブランチを管理する考え方は採用しづらいです。
ブランチを目的別で用意することで、以下のようなメリットを得ることができました。
SocialDogのリリースフローは次のような流れでした。
1~3の間は数日かかることもあり、レビュー済のブランチもmasterにマージできなくなるため、この間に開発が停滞することがありました。
環境ごとにブランチを分けることで、このフローを次のように変えられました。
①masterブランチをstagingブランチへマージする
②stagingブランチをテストする
③stagingブランチをproductionブランチへマージしてproductionブランチをリリースする
リリースに
というブランチの使い分けによって、masterブランチの開発を止めずに済むようになりました。
productionブランチは本番環境のコードと一致するため、本番環境と同等のコードを調査しやすくなりました。
またHotFix対応もシンプルになります。
SocialDogのHotFixとは「本番へすぐ適用したい修正」を意味します。
以前はmasterブランチを任意の時点でリリースしていたので、HotFixのために本番環境のコミットを調査してHotFix用ブランチを作成する必要がありました。
新しい運用ではHotFixのブランチはproductionブランチから切る形になりました。
修正を一刻も速くリリースしたいHotFix対応において、対応フローがシンプルになることは大事な点だと考えています。
以上のことを踏まえると、ブランチの運用方法を考えることは、コードをリリースするまでのプロセスを考えることに繋がります。
もしコードをリリースするサイクルを効率化できれば、ユーザーへ価値を提供するサイクルも高速化できます。
たかがブランチ運用と思えるかもしれませんが、プロダクトの価値をより速く高めるためにも、ブランチ戦略ともいうべきこの運用について、折々で真面目に向き合いたいものです。
冒頭にも書きましたが、この記事を書いてる上田は8月にSocialDogにジョインし、なんと10月にはブランチ運用の変更について担当を任せていただきました。
実は入社早々に「プルリクエストのマージ方法を変更したい」とバックログを起票しており、ここから
について意見を求めていただき、運用変更に至ったという経緯があります。
SocialDogには「バックログは誰でもいつでも追加してOK!」という文化があります。
Jira 利用ガイドライン - SocialDog情報ポータル - Confluence
ブランチ運用における問題がたまたま起きていた、というタイミングのよさもあったと思いますが、「追加してOK!」からのまさかジョイン数ヶ月にも満たない時点で担当を任せてもらえるとは思いませんでした。非常に変化に対して前向きな会社で日々ワクワクしながら業務に当たっています。
SocialDogは「テクノロジーで世界をスマートに」のミッションに共感できるデベロッパーを募集しています!
ぜひ興味のある方は弊社のアツイ代表小西がアツイ話を語りますので、お気軽にご連絡ください!!!
スカッシュマージの運用で困ったときのノウハウ も溜まったので、どこかの誰かのために残しておきます。
あくまでワークアラウンドのため、運用に困ったのであればマージコミットを作る運用 への切り替えができないかの検討をまずおすすめします。
2度以上ベースブランチを取り込もうとしてコンフリクトしてしまうのを避ける方法です。
この図のケースでは、1度目にマージしたコミット Cx を
つまりファイル変更なしで再マージしておくことで、2度目のマージではコミット Cx 以前の差分は無視されます。
具体的には
git checkout topic # マージ先のブランチをチェックアウト
git merge -s ours Cx # Cx を変更なしでマージ
というコマンドで、コード変更なしでマージコミットを作成できます。
マージコミットさえできてしまえば、ベースブランチとの共通祖先が繰り上がるため、新しいコミットのことだけ考えればよいということになります。
SocialDogでは当時コンフリクトが93→18まで減ったりしました:
スカッシュマージを行ってもGitHub上にスカッシュする前のコミットは残っています。
これは git ls-remote コマンドでGitHubリポジトリ上のrefsを一覧することで確認できます。
画像引用元リポジトリ: https://github.com/containers/podman
これを利用して、あたかもマージコミットを作った場合のコミットツリーを表示することができます。
replace というコマンドで「あるgitのオブジェクトを別のオブジェクトに置き換えて表示する」ことで実現します。
replaceの解説については Git - Git オブジェクトの置き換え が参考になるので、解説はこちらにゆずります。
具体的には次のようなスクリプトで置き換え用のオブジェクトを作成します。
#!/bin/bash
set -Eeuo pipefail
# refs/pull/*/head 以下をローカルに取得してから実行します
# 通常以下のコマンドで取得できます
# git fetch origin 'refs/pull/*/head:refs/pull/*/head'
if [[ $# -lt 1 ]] ; then
echo "usage: $0 <revision range>"
echo "example:"
echo " $0 HEAD"
echo " $0 abc01234..ef56789"
exit 1
fi
git rev-list --oneline $@ | while read line ; do
# 「プルリクエストタイトル (#XXX)」というコミットメッセージから
# コミットハッシュとpull request numberのXXXを抜き出す
if [[ "$line" =~ ^([0-9a-f]+).+\(#([0-9]+)\)$ ]] ; then
hash=${BASH_REMATCH[1]}
pr=${BASH_REMATCH[2]}
else
echo "skip: $line" >&2
continue
fi
# 親コミットの数を取得
# 「マージコミット」は2つ以上の親コミットを持つ
parents=$(git cat-file commit $hash | perl -nle '/^$/ and exit; /^parent/ and print' | wc -l)
if [[ $parents -gt 1 ]] ; then
# すでにマージコミットなので無視
continue
fi
# プルリクエストでマージしたコミット参照
pr_head="refs/pull/$pr/head"
# 置き換えオブジェクトの作成
git replace --graft $hash $hash^ $pr_head
echo "done: $line" >&2
done
詳しいスクリプトの解説は割愛します。gitのデータ構造を理解した操作が必要なので、興味のある方はGit公式が用意している Git - 配管(Plumbing)と磁器(Porcelain) のページが参考になると思います。
置き換えオブジェクトが作成されると、無事にスカッシュされていないコミットツリーを参照できます。
ただし置き換えオブジェクトを使う場合、git操作のパフォーマンスが落ちたり、一部の挙動にバグがあったり(執筆時点 v2.38.1)するため、あくまでワークアラウンドな手段であるということにご留意ください。