前書き
こんにちは,@ukaznilです。普段は大学院で機械学習研究の傍ら,趣味でAndroidアプリをちまちま作って公開しています。サイトもよければご覧ください。
さて今回はエンジニアのの強い味方,普段の生活に役立つ__「git」__をちょっと便利にするTipをご紹介しようと思います。
gitで「納品」してますか?
もはやエンジニアの必須ツールであるgit。趣味でプログラムを書いている方はもちろん,お仕事で日々コミットしている方も多いですよね。
ご存じの通り,gitの素晴らしい点はその履歴が取れることです。変更の多いプログラムを書いているのであれば,「あとから戻れる」という安心感は何事にも変えられませんね。
さて,それでは「開発」が終わった後のことを考えましょう。
趣味として個人でプログラムを書いているのであればあまり関係のないことかもしれませんが,gitで管理しつつ作成したプロジェクト・ソースコードを他人に渡す,という機会は少なからず存在します。むしろ,他人に渡すために作っているものをgit管理している,と言った方が正しいのかもしれませんね。
そんな時,どうやってソースコードを「納品」すればいいのでしょう?
普通にプロジェクトをzip化すると,いわゆる「gitのゴミ」(.git/
とか.gitignore
とか)みたいなものも含まれてしまいます。せっかくgit管理下ではignoreしていたのに,納品段階に至って手動で削除するような事態になれば二度手間です。
というか二度手間で済めば良い方で,場合によっては本当に必要なファイルを消してしまって納品先でコンパイルが通らない,なんてことになってしまったら目も当てられません。キーなどの機密情報を消し忘れたら,なんて考えたくもない人もいらっしゃると思います。
これを,どう自動化するか。
.gitignore
で無視しているファイルを含めない形で,ラクに圧縮・納品したい。
これが今回の目的になります。
ポイント
考えるにあたってのポイントは,__「gitで管理されているものだけを圧縮すればよい」__という至極簡単なものです。
この考え方は,「相手に渡したくないものはgitにも管理させたくない」という意識・事実に依存しています。APIキーなどはもちろんですが,プログラムによっては自動生成されるもの(Android Studioであれば*.iml
ファイルや/build/
ディレクトリなど)は通常.gitignore
で無視していると思います。
そう思ってgitのコマンドを調べていくと,git archive
という,まさにぴったりなものが見つかりました。
git archive - Create an archive of files from a named tree
https://git-scm.com/docs/git-archive
更に調べていくと,同様の取り組みをされている方も多く見つかります。下のリンクはあくまで参考ですが,皆さん似たようなことを考えています。
Git で管理しているファイルをアーカイブする
http://tnakamura.hatenablog.com/entry/20090508/1241789584
git archiveでgzip以外の圧縮フォーマットを使用する
http://qiita.com/habu1010/items/53e9f22abf593a20bbd1
しかし,実際使っていくには関数化していくのがよいでしょう。
さぁ実装するぞ,という段になって,いくつか標準のgit archive
だけでは達成できない要求があることに気がつきました。
- いちいち出力先のzipファイル名を決めるのが面倒。
- どのコミットを出力したものなのか,あとからでも一目で分かるような名前にしたい。
- git statusがcleanでない状態では出力できないようにしたい。
- 上記とも関連しますが,コミットの状態と出力ファイルを一意に対応づけるためです。
- git管理されているファイルのみ含めるのはいいが,例えば
.gitignore
などは除外したい。- 見落としがちなのですが,
.gitignore
もしっかりgitに追跡されているのでgit archive
には含まれてしまいます。でも渡された側からすれば,.gitignore
ファイルは無用の長物なのでなるべく含めたくはないのです。
- 見落としがちなのですが,
まぁざっくり言えば上のような要求仕様を満たす関数を作る必要がありました。
上の2つはgitコマンドでどうにもなりそうですが,問題は3つ目の「git管理されているけど含めたくないファイル」,具体的には.gitignore
ファイルの存在です。
さらに調べていくと,.gitattributes
というファイルの存在を知りました。
gitでsvn export的なことをやる
http://qiita.com/scalper/items/1905b47209989dda5648
どうやら,.gitattributes
というファイルにexport-ignore
と追記することで,git archive
の対象から除外されるらしい。見事に,欲しい機能を見つけました。
つまるところ,.gitattributes
には以下のような記述があれば事足りることになります。
*~ export-ignore # backupファイルは絶対に納品しない
.DS_Store export-ignore # DS_Storeファイルも絶対いらない
.gitignore export-ignore # .gitignoreだけもらっても,先方には無用の長物
.gitattributes export-ignore # .gitattributes自身も納品する必要はない←ポイント!
当然,このような.gitattributes
を自前で書くのは面倒なので自動生成してしまいましょう。
実現の目処が立ったので,あとは関数をガリガリ書いていくだけです。
できました
結果,できあがったものが以下になります。
zsh環境ではfunction名にハイフン(-)が使えるので,git-archive
として私は運用していますが,bash環境の方のことも考えて以下ではgit_archive
と表記しています。
function git_archive() {
# 現在の場所
readonly local CURR_DIR=`\pwd`
# gitリポジトリのroot
readonly local REPOSITORY_DIR=`\git rev-parse --show-toplevel 2> /dev/null`
# gitリポジトリかチェック
if [ -z "${REPOSITORY_DIR}" ]; then
echo '### This is not the repository root'
return
fi
# リポジトリrootにcd
\cd ${REPOSITORY_DIR} > /dev/null
# .gitattributesの作成(存在していなかった場合)
readonly local GIT_ATTRIBUTES_FILENAME='.gitattributes'
if [ ! -f ${GIT_ATTRIBUTES_FILENAME} ]; then
{
echo '*~ export-ignore'
echo '.DS_Store export-ignore'
echo '.gitignore export-ignore'
echo "${GIT_ATTRIBUTES_FILENAME} export-ignore"
} > ${GIT_ATTRIBUTES_FILENAME}
fi
# リポジトリがcleanかチェック
if [ -n "$(\git status --porcelain)" ]; then
echo '### There are uncommited changes'
\git status
\cd ${CURR_DIR} > /dev/null
return
fi
# ディレクトリ名取得,先頭のドットがあれば除去する
readonly local REPOSITORY_DIRNAME=`echo $(\basename ${REPOSITORY_DIR}) | sed s:^[\.]*::`
# パス取得
readonly local REPOSITORY_PARENT_DIR=`\dirname ${REPOSITORY_DIR}`
# ブランチ名取得
readonly local BRANCH_NAME=`echo $(\git symbolic-ref --short HEAD) | sed s:/:-:g`
# hash値取得
readonly local HASH=`\git rev-parse --short=7 HEAD`
# 納品!!
readonly local ZIP_NAME="${REPOSITORY_PARENT_DIR}/${REPOSITORY_DIRNAME}-${BRANCH_NAME}-${HASH}.zip"
\git archive --format=zip HEAD > ${ZIP_NAME} && {
echo '#========#'
echo '# Result #'
echo '#========#'
echo "Archived this repository as ${ZIP_NAME}"
}
# 元の場所に戻る
\cd ${CURR_DIR} > /dev/null
}
cdコマンドのあとに> /dev/null
しているのは,私のzsh環境が「cd
したあとに自動でls
」設定になっているためです。不要なら削除してください。
上のfunctionをお使いのシェルの設定ファイルに追加するだけで,魔法の呪文git_archive
が使えるようになります!
使い方についての補足
説明の都合上,git管理されているプロジェクトのパスを/path/to/hoge/
と表記します(つまり/path/to/hoge/.git/
が存在するということです)。
- git管理されているプロジェクトの,どこにいても
git_archive
関数は利用できます。-
/path/to/hoge/piyo
とかにいる状態でも問題ありません。
-
- 出力zip名は,
${projectName}-${branchName}-${commitHash}.zip
です。hashは先頭7桁を切り出すようにしています。- 今回の例では,
hoge-master-a1b2c3d.zip
のようになります(masterブランチの場合)。 - ブランチがfeatureの場合(
feature/some_impl
など),${brachName}
はfeature-some_impl
などのようにスラッシ(/)ュはハイフン(-)に置換されます。 - (好みの問題だと思いますが)ドット(.)から始まるzipが気持ち悪いので,
${projectName}
は先頭にドットがあったら消しています。例えば,.zsh.d/
以下をgit_archive
する場合,zsh.d-${branchName}-${commitHash}.zip
のようになります。
- 今回の例では,
- zipの出力先はプロジェクトの親ディレクトリです。
- つまり,
/path/to/hoge-master-a1b2c3d.zip
が生成されます。
- つまり,
- statusがcleanでない場合は出力できません。これは,いつ,どのタイミングでzip化したとしてもzipの内容に一意性を持たせるためです。
以上です。
長文にお付き合いいただき,ありがとうございました。
皆さんもカスタムgit archive
で楽しい納品ライフを!!