gitを使ったワークフローで困ったなぁ、どうしようと思ったのが、SASSからコンパイルされたCSSの扱い。べき論で言うと、いわゆる「ビルド後に生成されるファイル」はコンフリクトの要因になるためgitで管理すべきではない、です。生成後のファイルは履歴を管理する必要がないのでそもそも対象にすべきではない、ですよね。その通り。
なんですけど、それだとテストアップ前に手元でCSSを生成してFTPするか、テストサーバでコンパイルされるようにするか、テストサーバへの反映に一手間必要になってかなり面倒。せっかくリモートリポジトリへのpushで自動デプロイできるようになっても、CSSだけFTPするとか泣けちゃう。。
CSSをgitで管理しない場合に困る事
CSSを管理しない場合、テストサーバにCSSを反映するためには次のいずれかの作業が必要になります。
- テストアップ時に手動でコンパイルしてFTPする(面倒だ)
- テストサーバにRuby+SASS+Compass環境を構築して、SSHでログインしてテストアップ時にコンパイル(さらに面倒だ!)
- JenkinsなどのCIサーバを構築して、リポジトリへのpush→Jenkinsでコンパイル→テストサーバへ設置を自動化する(大げさだしメンテが大変…)
2つ目3つ目の方法は、テストサーバがVPSやクラウドである必要性も出てきます。リリース駆動型の開発がメインであれば導入する価値は高そうですが、フロントエンド主体のWeb制作には大げさで、インフラエンジニアがいないと実現は難しいのではと。(いや、手動でFTPしなさいよ、と言われそうではありますが)
でもやっぱりコンフリクトは避けたいが、テストアップは自動化したい。のです。
そこでいろいろ調べたところ、ブランチによって内容の異なるファイルをマージの対象外にすることができる(つまり、異なる内容のファイルを持つことができる)ことが分かったので、その技術を使う事にしました。
作戦
「ブランチによって内容の異なるファイルをマージの対象外にすることができる」ということから、次の状態を作ることにしました。
- 開発最新のブランチ[A]とは別に、テストサーバへのデプロイ専用のブランチ[B]を作成
- [A][B]それぞれ内容が異なる
.gitignore
を設置 - [A]のブランチでは
.gitignore
でCSSを管理対象外に指定(CSSなし) - [B]のブランチではその記述はせず、CSSも管理対象とする(CSSあり)
.gitignore
を[A][B]間のマージの対象外とする
具体的にはブランチ[A]は開発最新のdevelop
で、テストサーバへのデプロイを実施する[B]は、テストサーバ同期のmaster
ブランチとなります。テストアップする前に最新のdevelop
をmaster
にマージするので、この際に.gitigonore
が上書き(マージ)されないように設定できれば、FTPを使わず、git経由でテストサーバに最新をデプロイすることができる、ということになります。
.gitignore
の設定
まずはgitignore
を[A][B]ブランチのルートに作成し、次のように異なる内容を指定します。
[A] developブランチ
[A]develop
ブランチではSASSから生成されたCSSが管理されないように、生成先のディレクトリを無視対象に指定します。このようにしておくと、このディレクトリ外に置かれたCSS(JSライブラリに帰属するCSSなど)は管理の対象となるので便利です。
# [A]`develop`ブランチ
# 例)/assets/css/ に生成されるCSSは無視するが、その中の/_plain/*css は管理したい場合
/html/assets/css
!/html/assets/css/_plain
※2行目の除外指定の記法が有効なのは、2016年1月にリリースされた Git 2.7以降です。
[B] masterブランチ
[B]master
ブランチではすべてのCSSを管理するので、CSSに関する記述はなしです。
# [B]`master`ブランチ
# .cssもすべて管理するので指定はなし
この設定により、ビルド後のCSSが[A]develop
ブランチでは管理されず、[B]master
ブランチでは管理されるようになります。
次に、この.gitignore
が[A][B]間マージの対象外になるよう設定します。
ブランチによって内容の異なるファイルをマージの対象外にする
この技術を使うためには、次の2つの設定が必要です。
- マージを実行するクライアントマシンのgitの設定(config)にマージ用の指定を追加する
- 「gitの属性」を設定するファイル
.gitattributes
をプロジェクトディレクトリに設置する
1.クライアントマシンの設定
この設定は、[A][B]間のマージを実施するすべてのマシンで設定する必要があります。この設定がされていないマシンで[A][B]間マージを行った場合、.gitattributes
の設定に関係なく通常通りのマージが実施されてしまいます。
クライアントマシンの設定は、グローバル設定する方法とプロジェクト単位で設定する方法があります。CSSの管理に関してはプロジェクトを問わずチーム内で共通のルールになるので、グローバル設定をしておくといいでしょう。
グローバル設定
クライアントマシンに設置されている.gitconfig
ファイル(※1)に次を追記します。
[merge "ours"]
name = "Keep ours merge"
driver = true
※1 Windows環境の場合、多くはC:Users{ユーザ名}
配下にあります。
※2 プロジェクト単位で設定する場合は、プロジェクトディレクトリ内の.git/config
ファイルに同じ指定を追記します。
2.プロジェクトに.gitattributes
を設置
次に、プロジェクトディレクトリのルートに.gitattributes
というファイルを作成し、具体的にどのファイルをマージの対象外にするかを指定します。今回は.gitignore
ファイルを対象外とするので、次のような指定になります。
/.gitignore merge=ours
記法は<マージの対象外にするファイルへのパス> merge=ours
です。複数ファイルを指定する場合は、改行して複数記述すればOK。例えば、次のようになります。
/.gitignore merge=ours
/sass.bat merge=ours
※今回は.gitignore
を指定していますが、たとえば[A][B]ブランチを異なる環境で動作させるような場合に、DBへの接続設定が記述されたファイルを「管理はするけどマージはさせたくない」といった場合に使えるので、しっかり設計するととても便利に使えます。
これで設定は完了。計画した次の環境ができました!(・∀・)v
- 開発用のブランチ
develop
ではビルド後のCSSが無視されるためコンフリクトは起こらず develop
からmaster
にマージしたタイミングで最新のCSSが生成・管理されmaster
をリモートリポジトリにプッシュすることで、最新の内容がテストサーバに自動で配置される(デプロイされる)
CSSの管理をブランチで分けたワークフロー
CSSの管理をブランチで分けた場合の、テストアップ前のワークフローは次のようになりました。お客さまが見ているテストサーバはmaster
と同期、それより進んだ開発最新がdevelop
となります。
まだまだ不満が残る…
一応、狙った環境はできたのですが、まだまだ不満が。。
このmerge=ours
を利用するマージはローカルで実行しないといけない。グランフェアズは、Bitbucketを使ったプルリクエストベースのワークフローを採用しているので、ここもBitbucket上でプルリクエスト→マージで完了したい。最後だけローカル(しかも設定がしてあるマシンで!)でマージするとか、まだ泣けちゃう。。
テストアップのためにひと手間必要になる点は変わらず、それならCSSだけFTPするのと変わらないのでは?むしろ、master
の制御を任せられるメンテナーにテストアップを頼まないといけないのはロスが多いのでは?とまだまだ課題を感じます。メリットは、develop
の中途半端な状況をお客さまの目に触れさせることがないことと、master
がある分保険が増えることくらいでしょうか。
それでも、CSSを管理下に入れることで本来不要なコンフリクトが乱発するよりはストレスがない方法だと思います。
速度か品質か、両方ほしい
Dreamweaverでファイルの管理をしていた時は、チェックイン/アウトで作業競合を防ぎ、ボタン一つで透過的にテストアップがされるというワークフローで、履歴は1日1回の自動バックアップでした。
git化によるメリットは「コードレビュー」がフローに加わり、互いのソースを指摘してより良くする、という行為が普通になったことで、メンバーの意識も変わり技術の底上げになったこと。
ただ現状デメリットだと感じるのは、テストアップまでに時間がかかること。前のワークフローでは各自の作業内容がほぼリアルタイムでテストサーバに反映されていたため「テストアップのための作業」はおろか「テストアップ」という作業自体気にすることがなく、スピード感がありました。
CSSは手動でFTPするのがいいのかもしれない、と思ったり、merge=ours
の設定がBitbucketやGithubで設定できればいいのに…!と他力本願をしたり、最適解にはまだまだ遠いなぁと悩みは尽きません。
以下のサイトを参考にさせていただきました。多謝!
Git のカスタマイズ – Git の属性
.gitignore ファイルをブランチごとに別の内容にする
Gitで内容が異なるファイルをマージ対象から除外(無視)する設定