【Git】リポジトリ立ち上げの手引
はじめに
対象
- リポジトリ管理者向け。
- Webアプリ開発者向け。
- チーム開発を想定。
三原則
Gitのリポジトリを立ち上げるにあたり、まず基本となる三つの原則について説明する。
- 再現性…同じ変更を再現できる。
- 記録性…履歴が記録として有用である。
- 規格性…メッセージ・コミット粒度の標準となる。
再現性
各コミットは同じ環境を構築し、同じ操作を行えば再現可能なものとする。
例えばプロジェクト作成のとき、指定したオプションやバージョン情報まで含めて残しておく。そして生成されたファイルをほぼそのまま、手を加えずにコミットする。直近の開発において何らかの問題が発生した場合、コマンドを調整した上での差分確認・再生成が可能になる。
記録性
各コミットは、記録として価値のあるものにする。
例えばプロジェクト管理ツール・ホスティングサービスと連携させ、調査中に知り得た知識や細かい手順をまとめておく。障害調査時・仕様変更前の調査において、選択した部分・ファイル単位でコミットログを確認すれば、役に立つ情報がそこにはある。
規格性
運用における規約を策定し、デファクト・スタンダード(事実上の標準)を規定する。
例えばコミットメッセージは手本となるよう心掛け、コミットの粒度もコントロールする。全てを明文化することは不可能だが、やって見せることはできる。
手引書
プロジェクト管理ツール・ホスティングサービスによって細かい用語が異なるため、以後の説明はRedmine・GitHubを前提に話を進める。例えば課題・Issueなどの単語はチケットに、マージリクエストはプルリクエストに統一する。
プロジェクト識別子を決める
プロジェクトにおける様々な場面で利用する一意の英数小文字を決める。
- Gitのリポジトリ名
- Redmineプロジェクトの識別子
- Slackのチャンネル名
- Webアプリケーションの名称
- AWSのリソース名 etc...
ハイフン・アンダースコア・大文字小文字などによる、複数単語の識別子は避ける。例えばデータベース名はhoge_fuga_dev
なのに、リポジトリ名がhogefuga
で、AWSではhoge-fuga
で、README.mdの見出しにはHogeFuga
、ライブラリ名は……このように識別子を使うタイミングで毎回、表記揺れが発生する。他にも悩む場所・面倒な場面が増えるので極力一単語にしたい。
製品名や部署名は変わる恐れがあるが、ツール類の識別子は変えられないか、変えられてもインフラ周りなので大変なことが多い。全く関係のない単語やランダムな文字列を用いても構わないが、視認性は下がるのでよりけり。
個人的にはCLIでタブを押したとき・ブラウザのURLサジェスト時に面倒なので、最初の数文字が他のディレクトリと重複するような名前も避けている。その他、字面が紛らわしい・発音が似ている・検索しやすさなどの、利便性も考慮に入れている。
プロジェクト管理ツールを用意する
プロジェクト管理ツールとは、以下のようなものを指す。
リポジトリを作成する前、もっといえばプロジェクトが発足した瞬間、真っ先にプロジェクト管理ツールを立ち上げておく。Gitも含めた各種ツールの選定理由・開発体制などを記録しておく。
コミットログに残らない情報は多い。関係各所との調整結果、社内政治の変遷、詳細な調査ログ。「なぜこのツールを選んだのか」を問う日は、製品が提供され続ける限り必ず訪れる。そのときになってから慌てても、空白の歴史は何も語ってくれない。
コミットメッセージの仕様を決める
コミットメッセージを後から修正することは原則できない。チーム開発においてmain
ブランチにマージされた履歴は正史となる。
適当なコミットメッセージで開発をし続ければ次第に治安は失われていき、「WIP」「一旦コミット」などという罪深いコミットメッセージが跋扈するようになる。
コミットメッセージの書き方については、検索すればいくらでも記事が出てくる。またプロジェクトによって、そのルールは大きく変化する。特にこだわりや理由がないのであれば、Conventional Commitsという仕様に則るのが良い。
コミットメッセージの型を決める
Conventional Commitsでいうところの型を決める。詳細はプロジェクト次第だが、途中からの追加・変更をすると旨味が減ってしまうため、最初にある程度の叩き台は作ってしまいたい。
例えばtest
というプレフィックスがあるが、テストを分けてコミットするか、対象の修正箇所と同じコミットにまとめるかは好みが分かれるところだろう。仮にコミットをまとめる場合、test
は使用不可としてfix
やfeat
などに限定した方が混乱しない。
また細かすぎる沢山のプレフィックスも避けた方が無難だろう。例えばrename
やmove
はrefactor
と何が違うのか。hotfix
はgit-flowにも同様の単語が存在するが、特定の役割のブランチの名称であり、コミットに依存するものではない。では何を表現するのに使用するのか。
これらの運用上の性質の理解が不十分であるならば、まずは使いこなせる範囲のプレフィックスを定義し、必要になったら少し細かいプレフィックスを追加していくという方法を提唱する。
混乱を招くプレフィックスの乱用は、コミット時に悩んでしまい、時間がかかる原因になる。最初から用意できるに越したことはないが、完璧な状態から始めるのは不可能だ。開発を進めていく過程で、日々改善を重ねていく方が良い。
コミットメッセージの内容を決める
Conventional Commitsでいうところの、タイトル・本文に何を書くべきかを大体で決めておく。自由度が高い部分なので、ガチガチに規則を決めるというよりは、「変更理由を書くこと」のようなふわっとしたものになる。
機能名・画面名・識別子などを含めて、ブログのタグのように使うのも良い。例えば以下のように、含める文言を決めておく。
- プロジェクト機能
- プロジェクト一覧・プロジェクト詳細・プロジェクト編集
- [プロジェクト], project
肝心なのは同機能の変更に必ず特定の単語を入れておき、履歴を検索する際に引っかるようにすること。引っかかりさえすれば特定のコミットに辿り着ける。コミットに辿り着ければプルリクエストやチケットに辿り着ける。
工夫できる部分なので、プロジェクトに合わせて試行錯誤してみるのも一興。
プロジェクト管理ツールとの紐付
チケットとコミットあるいはプルリクエストを紐付ける。Redmineにはリポジトリ連携機能があるため、refs #nnn
などをコミットメッセージに含めるだけで紐付けが可能だ。
この機能を利用したくない場合は、プルリクエストテンプレートにチケット番号を含める。その際、GitHubであれば外部リンクの構成をしておくと良い。プロジェクト管理ツールの移転が行われても、ターゲットを変更するだけで済む。
空コミットを作成する
空コミットを作るか否かは賛否両論あるが、私個人はチーム開発において「空コミットでmain
ブランチを作成し、次のコミットからはプルリクエストを通してレビューを済ませてからマージする」運用を推奨している。自分一人だけのリポジトリなら自由にやればよい。
git init
git commit --allow-empty -m "Initialize Repository"
リベースのしやすさも重要だ。GitをCLIから操作すれば修正は可能だが、多くのGUIはデフォルトの状態で--allow-empty
に対応していない。現場によって異なるGUIの設定をわざわざ調べるくらいなら、空のコミットを一つ増やすだけの方が楽だ。
後述の失敗事例『全てをひとつのコミットに集約する』も大きな理由だ。「最初が空であることが保証されている」という意味は非常に大きい。
ホスティングサービスの設定を行う
チーム開発では何らかのホスティングサービスを利用することになる。
真っ先に行うのはmain
ブランチの保護だ。空コミットを作成したmain
ブランチのプッシュを忘れずに。その後、リポジトリ管理者も含め直接プッシュを禁止とする。これで誰かが誤って直接プッシュしても安心だ。
次に使わない機能は無効にする。特に課題管理やWikiとしてのGitHubを使用しない場合、IssueやWikiなどの重複する機能は必ず無効化しておくこと。詳しくは後述の失敗事例『散らばる知見』を参照。
ブランチ戦略を立てる
製品の性質・開発体制を考慮した上で、軸にするGitフローを決める。必要に応じてカスタマイズしても構わないが、途中参加者の教育コスト・改造に失敗する可能性も考えると、既に確立されているものをそのまま使うことを勧める。
これは母数の少ない私の経験則にはなるが。リリース計画をきちんと立てられない体制の場合は、デメリットを呑んだ上でGitFeatureFlowを採択する方が無難だ。少なくとも急な予定変更には耐えられる。
他のフローにも細かな差異はあるが、これだけで記事が一つ書けてしまうのでリンク先を参照。
リリースタグの命名規則を決める
必ず命名規則を決めておくこと。分からない場合はセマンティックバージョニングにする。
単にv20240918
などの日付にすると、V2024-09-18
v2024-0918
などの形式違い、当日中に2回目のリリースをすることになりv20240918.0800
が生まれるなどの不足の事態が起こる。またタグを切った後にリリース日が変更、CI/CDツールなどの兼ね合いから手間が増えることもある。
自分でタグの命名規則を定義する場合、起きうる事象に対処できる命名規則にすること。
プルリクエストテンプレートを作成する
プルリクエストテンプレートを作成する。『コミットメッセージの仕様を決める』でも言及したが、最初から種類を作り込みすぎないこと。
チケットに書くこととプルリクエストに書くことは明確に区別する。プロジェクト管理ツールはマネージャーや営業など開発者以外のメンバーが参照する場所であり、仕様に関する議論はこちらで行わなければ情報格差が生まれてしまう。一方、プルリクエストにはコードレビューに必要な情報のみ書けば良い。
極端な話だが先述の検索性も考えると、全ての知見をプロジェクト管理ツールに集約し、プルリクエストにはリンクを貼るだけも全然ありだ。この辺りはメンバーや開発体制との相談になる。明確な答えというものはない。
プルリクエストまでの導線を引く
以下をとりまとめて手順化し、WikiやREADME.mdにまとめる。
- プロジェクト管理ツールのステータス操作
- Gitのブランチ戦略と命名規則の共有
詳しくは割愛するが、チケットの終了条件・プルリクエストのマージ条件は確実に明文化し、可能であればツール側の設定で守られるようにすること。
以降の対応はプルリクエストを通して、レビューを行ってからマージする。
開発環境を構築する
Dockerによる仮想化・Ansibleによる自動化など、いくつかのソフトのインストールとコマンド一発で開発環境を構築できるようにする。社内のサーバー管理者に依頼が必要なケースでは、誰に・どのような手順で・どの情報を渡した上で、依頼するかを明記する。
どのような技術でも仕組でも構わないが、肝心なのはREADME.mdに手順を残すこと。他のメンバーが自力で環境構築できるか確認する。分かりにくい説明や環境差異があればなくす。
以下の手順は一部競合するため、優先順位はフレームワークや各自の傾向に合わせてカスタマイズすること。
アプリを生成する
フレームワーク・ライブラリのプロジェクトの生成を行う。具体的にはrails new
・django-admin startproject
などが該当。生成時のコマンドはコミットメッセージの三行目以降に残しておく。
オプションの数はそれなりに多いので、付け過ぎ・付け忘れは発生しがち。コマンドをメモしておけば、後から要不要を判断して、生成ファイルの調整を行うことができる。
もしリベースを行った場合は、忘れずにコメントの編集を行う。リベースをせずに差分のみコミットしても良いが、生成されなくなるファイルを削除し忘れないように気を付けること。
EditorConfigを設定する
追加ファイルの文字コードと改行コードをチェックするために、なるべく早めに追加したい。もっともEditorConfigが走るタイミングはIDEやエディタによって異なるので、正しく適用操作をしていく前提にはなる。
文字コード・改行コードの違いは予期せぬ不具合に繋がる。また後から修正すると、履歴を範囲指定したときにGitの差分がえらいことになってしまう。空行・空白を無視するオプション等もあるが、最初から変なファイルをコミットしないに越したことはない。
gitignoreを設定する
gitignore.ioなどのツールで生成するか、github/gitignoreから対象の.gitignoreをコピペする。
github/gitignoreの場合は、対象のURLとコピペ時のリビジョンを残しておく。どこから持ってきたのか分からない状態でコミットしない。
優先順位についての補足。
プロジェクトの生成時にEditorConfigやgitignoreが合わせて生成されるものがある。フレームワークやライブラリによっては、git init
すら行ってくれるものもある。
EditorConfigの文字コード・改行コードはなるべく早めに設定したい。RubyのソースコードがSJISだとか、UTF-8のWindowsバッチがあるだとか、そんな治安の悪いリポジトリは避けたい。
だが余計なファイルをコミットしないために、gitignoreは最優先で設定したい。
実際のところ、個人ではEditorConfigとgitignoreは同時にファイルを作成し、順次ステージに上げてからコミットする方法を取ることが多い。
また言語ごとに標準のLinterやFormatterが存在している。RubyであればRuboCopだが、これはファイル末尾の改行など競合する指定がある。あらかじめLinter・Formatterまで見据えてプロトタイプで確認しておくとスムーズだ。
CIを整備する
文法チェック・フォーマットチェック・テストの自動化を済ませておく。後から追加・変更しようとすると倍以上の修正コストがかかる。
CIツールとは以下のようなものを指す。CDを兼ねるツールが多い。ホスティングサービスのプルリクエストと連携し、チェック項目として追加する。
設定した内容は理由と共にチケットに記録しておく。またアカウントに関する情報の共有方法、有料のツールであれば請求周りの管理を誰がしているかも合わせて伝承しておく。
CDを整備する
検証環境・ステージング環境など、本番環境以外の環境を整備する。完全自動またはボタン一つでデプロイが行われるように設定を行う。手動でキックが必要な場合は、プロジェクト参加者が実行できるよう手順化しておく。
特にインフラ周りの歴史は途絶えやすいので、意識的にコメントや資料を残すようにする。前任者がおらず資料もない状況から、各環境に入り設定を探っていく作業は骨が折れる。
より万全を期すなら、
- 今後の設定変更でどこを修正すればいいか
- 環境変数を追加するには
- 障害時のログの調査方法は
などの利用シーン別のドキュメントも欲しいところ。
失敗事例
全てをひとつのコミットに集約する
どうしてもプロジェクト初期は特定のメンバーがある程度動くところまで作り込んで、しばらく経過してからプルリクが稼働し始めることが多い。そのような現場をいくつも見てきたし途中参画してきたが、やはりこの時期の対応がブラックボックス化しがちだ。最初の一人が知識を抱え落ちしてしまい、残された開発者が苦労するケースは多い。
特に最悪なのは、ある程度動くところまで持っていた大量のファイルを、一気に一つのコミットにぶち込む所業だ。別のプロジェクトから丸ごとコピーしてきた(ただし少々手は加えた)ということもある。デザイナーが作成したHTML/CSSを開発者名義で放り込むこともある。何が修正されているか、どういった経緯かが分からない。
これの何が困るかといえば、設定変更・仕様変更・バージョンアップなどの際、「何が正しい状態か」「デフォルトの設定から何が変更されているのか」が分からないことだ。
例えばフレームワーク側が用意している生成コマンドで作成したファイルを正としよう。その後、メーラーに関する設定変更を行いコミットする。更にその後、画像ファイルのアップロード先の設定を変更する。このように正しい状態から、修正を繰り返すことで開発は進む。
ところがこれらの変更が全て、一つのコミットにまとまっていたらどうだろうか。当然プロジェクト管理ツールとは紐付いていない。プルリクエストも存在しない。
数十あるいは百以上もあるファイルの中から、修正箇所を判別することは困難で、何らかの不具合が発生したときに見つけるのは一苦労だ。例えばRailsであれば対象のコミットに変更しておき、同じバージョンで生成コマンドを叩き、ソースコードを読んでオプションを推測し、差分を取る。更にその上でコミット履歴を推測し、どこまでがカスタマイズの範囲かを特定する。最初からコミットを分けて記録を残しておけばもっと楽に発見できただろう。
更に修正箇所を見つけたとして、まず現状のコードが正しいかを確認する作業が必要になる。何を根拠にその修正をしたか、コード上から見つけることは難しい。コメントも作業記録もなければ、あとは公式ドキュメントを読んで技術的に正しいかどうかを判断するしかない。結果的に多くの時間と精神力を消耗することになる。
同じ状況を再現できない
実際にはOSやインストールされているソフトウェア・ミドルウェアなどの違いで動かないケースもあるが、最近ではWebアプリの開発といえばDockerでほぼ同じ環境を構築することが簡単にできるようになったため、その心配も減った。
当時と全く同じ条件で、同じコマンドを叩けば、同じプロジェクトが生成される筈だ。しかし「生成してからちょっと修正したけど、コミットは分けませんでした」「生成コマンドはコミットメッセージにもチケットにも書いてません」これが非常に多い。できればこれは分けて欲しい・書いて欲しい。理由は先述の通り、対応に時間がかかってしまうからだ。
バグ票の再現手順にも通ずるものがあるが、こういった後の世代に共有しておいた方が良い情報は、意図的に残した方が良い。
また対象ファイルの更新に必要な情報も合わせて残す。例えばgitignore.ioでファイルを生成すると、ファイルの先頭・末尾に生成時のURLが記載されている。このURLさえ分かれば最新の同じ種類のgitignoreが生成される仕組みだ。
# Created by https://www.toptal.com/developers/gitignore/api/windows,macos
# Edit at https://www.toptal.com/developers/gitignore?templates=windows,macos
完全に同じファイルの再現はできないが、gitignoreの場合は更新さえできれば問題ないだろう。もしこのURLがなければ、まずどのツールで生成されたかを調査する必要がある。仮に見つからなければ、正誤が判断できないコードとした上で、改めてgitignoreを生成し直してしまう可能性もある。
このように後で困らないように、また余計な仕事を作ってしまわないように、ソースに辿り着くための情報を残す意識を常に持つことが必要だ。
散らばる知見
ついつい使いたくなってしまう新しいツール。しかし役割が重複するなら容赦なく削る。Wikiなどはその最たる例で、今はどのツールを開いてもドキュメント化の便利なツールが入っている。最初にプロジェクト内で、どこに知見を集約する場所は決めておくことを強く勧める。
例えばRedmineには文書・Wiki・ニュースなどの機能があるが、それらは本当に必要か。もし企業でGoogleドライブやSharePointを利用しているなら、WordやPDFなどのドキュメント類はそこに置く方が良い。GitHub Wikiとは使い分けするのか、どちらかを無効化するのか。
これの何が問題になるかといえば、各種ツールは同時に検索ができず、閲覧できるメンバーにも差異が発生するという点に尽きる。ある障害が発生したとき該当機能の過去の修正を検索しても、あるプラットフォームでは検索に引っかからない。調査するためには全てのWiki・全てのドライブを検索する必要がある。
想像してみて欲しい。あなたはある不具合の対応を任され、様々な単語を使って過去チケットやWikiを探す。あるプラットフォームで検索して情報が引っかかり、少々時間を割いて調べたが関係なかった。もう一度検索を再開するとき、同じ単語を別のプラットフォームで検索できるだろうか。恐らく忘れて他の調査を行うが、実はそちらに解決のヒントがあったとしたら……。
移行するにしても多大なコストがかかる。タイムスタンプ・登録者も貴重な情報のため、元データを消す訳にもゆかない。安易に途中で乗り換えると更に情報が分散する。このような事態に陥らないために、ツールの精査は十二分に行うこと。そして情報をできる限り集約すること。
成功事例
実のところ、この一点に尽きるのではなかろうか。
前任者不在でもコミュニケーションが取れる
次々に現場からITエンジニアが消える、開発者に忌み嫌われてしまった製品があった。メンバーとして参加していたのも束の間、とうとう自分がインフラ担当に繰り上がるときがやってきた。
初代インフラ担当者はWikiやREADME.mdに、事細かに設定内容および更新手順をまとめてくれていた。直接会話をしたことはないが、困ったときに何度も残された記録に救われた。途中記録を残さない担当者もいたようだが、初期設定と現在の設定から何が変更されたかを知るのは、何も残っていない状態から調査するより遥かに容易だった。
ここで改めてお礼を書いておきたい。もうその方にメッセージが届くことはないかもしれないが、きちんと文章で書いて残すことができる全てのITエンジニアにお礼を言いたい。
いつも本当にありがとうございます。ご心労お察しします。
機会があったらご一緒しましょう。
おわりに
できる限り思い出して、整理して書いてみたが。正直なところ忘れていること・抜けていること・説明不足は大いにあり得るので、何かあれば追記する。
毎回これだけのことを考えてリポジトリを立ち上げていたとは。自分でも驚いているが、どこでこの知識を獲得したのか思い出せない。面倒でなければ学習履歴も残しておくと、筆者のように困ることはなくなるかもしれない。
Discussion