チームで開発とかしているわけではないので、Gitなんか使う必要あるのか?と言う気がしないでもないですが、バージョン管理のために使っています。
Gitが管理しているバージョンとは
Gitのマニュアルを読むと大体最初に書いてあるのが、バージョン管理システムの進化の歴史です。
最も原始的なバージョン管理はファイル名を変えたり違うディレクトリにファイルをコピーしたりというファイル単位の履歴管理。で、問題点として新旧ファイルの取り違えや誤ったファイル操作などが挙げられていて、Gitを使えばよろしいということになる。
表も数式も使っていない報告書に「20180119_最新版2.xlsx」などと訳のわからないファイル名をつけている場合ではないのです。
ProGitというマニュアルを見つけてウンウン唸りながら読んでいて(日本語版もあるなんて気が付かなかったんだよう)はたと気が付いたのは、Gitが管理しているバージョンはファイル単位じゃないってこと。
何をいまさらと言われるかもしれないけれど、やっと気が付きました。原始的なファイル単位のバージョン管理という意識を捨てなくちゃいけない。プロジェクト単位の発生イベントの管理と捉えるとしっくりくる気がします。
First commitはプロジェクトの発足
別に巨大プロジェクトだからソースコードが複数のファイルに分かれちゃって…とかいう規模じゃなくても、一本のソースコードとドキュメントとサンプルデータと…なんて考えれば、極端な話ちょっとしたスクリプトでもプロジェクトになり得るわけで。
Gitのマニュアルなどを見ると最初のコミットのコメントは大抵「First commit」。もちっと気の利いたコメントはないんかと思っていたんですが、プロジェクトの始まりなんだからこれでいいじゃん。
巨大ゲーム開発の始まりに「First commit」。Webサイト開発の始まりに「First commit」。ちょっとしたツールをスクリプトで書き始めるときに「First commit」。原稿の書き始めに「First commit」。
事を進めてゆくうちにファイルの追加や削除もあるかもしれないけれど、とりあえず今はプロジェクトに必要と思われるファイル一式はこれなんだ!と。そんな感じの「First commit」。
2回目以降は「こんな機能追加したよ」とか「このバグ潰した」とか「リファクタリングしたよ」とか「typo…」とか、ひとつひとつの出来事単位でコミットしていけばいい。
とある機能を追加したら三つぐらいファイルが更新された
とかね。追加機能のソースファイルが新たに発生して、メインコードのファイルが更新されて、該当機能に関する記述がマニュアルに書き加えられて。。。
イベント単位でコミットするからみんな同時にファイルが更新されないと不整合が起きるんだ。
そんな感じで履歴管理をするのがバージョン管理システム。
リファクタリングとかtypoとかはファイル一本づつになるかもしれない。何かしらのミスがあって当該のコミットを取り消したら一気に元に戻るからね。キャンセルしたいリファクタリングは5本あるファイルのうちのひとつだけなんだ!なんて時、目も当てられない。
最もシンプルな運用 – ローカル運用
以上を踏まえて、あらためてGitの運用方法をメモしておきます。ローカルで一人で使うのが、一番単純な運用。
プロジェクトファイル群の保管場所を作るよ
必要なファイルを置いておくディレクトリ内で最初の儀式をおこないます。
$ mkdir project
$ cd project
$ git init
Initialized empty Git repository in /user/home/project/.git/
これでプロジェクトを管理する場所ができました。そのディレクトリ内に設定ファイル群を収めた.gitディレクトリが作られたとか、そんなことは知らなくてもGitは使えます。
管理すべきファイルを登録しよう
git initしたディレクトリ内に必要なファイルを放り込みます。何ならディレクトリを掘ったっていい。でもファイルやディレクトリを置いただけではGitは自動認識してくれない。
Gitに必要なファイルを知らせるにはaddコマンドを使う。
$ git add [filename]
ファイルはひとつづつaddしてもいいし、スペースで区切って複数一気にaddしてもいい。ワイルドカードも使える。ディレクトリ指定もできる。
とにかく管理したいものはすべてaddだ。
この作業をステージングなんて言うみたいだけど、そんなのはどうでもいい。
コミットしよう
対象ファイルが決まったらcommitだ。この時、イベントの内容をメモできる。てかメモしなくちゃいけない。
commitコマンドの後に続く-mオプションが「メモするよ!」って意味。
$ git commit -m "First commit"
[master (root-commit) b3e8874] First commit
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 [filename]
初回なら当然「First commit」だ。
おめでとう!これがプロジェクト管理の始まりだ!
あとはaddしてcommitの繰り返し
さて、開発は細かい改善の繰り返し。うまく行ったり行かなかったり。Gitでバージョン管理をしていれば失敗してもすぐに元に戻せる。
でもバージョン間の変更がすごく多岐に渡っていると何日分も後戻りしちゃうかもしれない。
だから細かくcommitしていく。そうすると「あ、しくじった!」って時にすぐなかったことにできる。
そうやってプロジェクトを進めていくんだ。
プロジェクトに歴史あり
これ、実際に今作ってる最中のWebアプリ。ホントに細かすぎない?ってくらいコミットしてる。
$ git log
commit fe81cb69ea86b60efb5f7fdec280b41a65cb8f42 (HEAD -> master, origin/master, origin/HEAD)
Author: Takeru-chan <mail_address@gmail.com>
Date: Fri Jan 19 07:47:08 2018 +0900
マークダウンtypo
commit 6397e70e3aa944237f351d8ffa3c400a9c6e0c3b
Author: Takeru-chan <mail_address@gmail.com>
Date: Fri Jan 19 07:44:42 2018 +0900
今後の対応リスト追記
commit 5d7c0dc2a3da030aee9886bd4362ad80c03d3d0c
Author: Takeru-chan <mail_address@gmail.com>
Date: Thu Jan 18 23:10:01 2018 +0900
メイン画面高さ指定を1箇所に集約。CSSリファクタリング
...
とまぁ、このようにコミットの歴史を見るのがlogコマンド。
最初の行にあるcommitに続く文字列をチェックサムっていうんだけど、各コミット固有の番号と思っていればいい。
続いてAuthorとDate、そしてコミット時につけたメモ。これらを頼りに「あの日に戻りたい…」とか当たりをつける。
あの日に戻りたい。。。
さて、こんな感じで開発を続けていると「あ、失敗した!」って思う時が来る。てかよくある。
さっきのlogコマンドほど詳細な情報が要らないときは–onelineオプションだ。
$ git log --oneline
fe81cb6 (HEAD -> master, origin/master, origin/HEAD) マークダウンtypo
...
70da21f ランダム表示を一旦中止
27d82d7 currパラメータが負数の時、ランダム表示するようにふるまいを変更
a747d10 設定ファイルのオプション指定順をmax,minからmin,maxに変更
a7e287c First commit
頭の7桁は先ほどのハッシュ。メモさえ表示されていればどのコミットかを特定できる。てか特定できるようにメモするべき。
3番目のコミット27d82d7に対する4番目のコミット70da21fが「あの時に戻る」ってやつ。機能追加したのだけれどちょっとうまくできなかったのでrevertコマンドで2番目のコミットa747d10に戻している。
$ git revert 27d82d74b38e1ddc2eb013942d5688f21d78e1db
ここではハッシュ値をフルに指定しているけど–onelineオプションで表示される7桁分だけで問題なく特定できる。
revertって「元に戻す」って意味。元には戻すけど直前のコミットをなかったことにするわけじゃない。なので27d82d7の履歴は残ってる。70sa21fとa747d10は全く同じ内容。
失敗した変更内容の全てがダメなわけじゃないからメモ的に残しておけるのも便利だし、チームで開発しているときなんか、履歴を完全に消してしまうと各人の間で不整合が生じてしまうので履歴としては残しておくのが正解らしい。
ステージングに失敗したら。。。
さて、コミットの取り消しは前述の通り。ではコミット前にaddするファイルを間違えたら?そんな時はresetコマンド。
$ git reset HEAD [filename]
addしたファイルを個別に取り消す時はファイル名を指定する。全取り消しならresetだけでいい。
このコマンドはあくまでaddのキャンセルだけなので、ファイルそのものに加えた変更は残ってる。たとえばaddした後に編集の内容を少し直したいとか、二つに分けるべきコミットなので一部のコミットを後回しにするとかいう時に使う。
HEADとかは今は気にしなくていい。resetには他にも色々オプションがあるけれどaddの取り消しはこれだけ知っていればいい。
編集をなかったことにする
さて、まだaddもしていない状態。ファイルの編集をしたけれど途中で気が変わったんだかなんだか、とにかくこれまでの編集内容をなかったことにしたければcheckoutコマンド。
$ git checkout [filename]
これで指定したファイルに対する編集内容は全て取り消されて最後にコミットした状態に戻る。全てのファイルを元に戻したければファイル名の指定の代わりに.(ピリオド)を指定すればいい。
git addしてたっけ?
今ファイルを編集しただけなのかaddしていたのか。そもそもadd対象になる編集済みのファイルはどれだっけ?なんて時はstatusコマンド。
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Untracked files:
(use "git add <file>..." to include in what will be committed)
.DS_Store
glob.php
nothing added to commit but untracked files present (use "git add" to track)
こんな感じでadd対象となる編集済みまたは新規作成ファイルや、ここにはないけれどadd済みのファイルリストが表示される。
変更内容を知りたい
先ほど出てきたrevertコマンドでも使ったハッシュ値。これを使うと各コミット間の差分を表示できる。使うのはdiffコマンド。
$ git diff a2a2d02 29bc3c4
diff --git a/oboeru.php b/oboeru.php
index cd0d27b..c0f10e3 100644
--- a/oboeru.php
+++ b/oboeru.php
@@ -43,7 +43,7 @@ if ($dir != "") {
echo "<p id='translate' ontouchstart='' ".$style.">".$line[1]."</p>";
echo "<hr>";
echo "<p id='prev'><a href='?dir=".$dir."&min=".$min."&max=".$max."&rnd=".$rnd."&curr=".$prev."&mode=".$mode."'>Prev</a></p>";
- echo "<p id='next'><a href='?dir=".$dir."&min=".$min."&max=".$max."&rnd=".$rnd."&curr=".$next."&mode=".$mode."'>Next</a></p>";
+ echo "<p id='next'><a href='?dir=".$dir."&min=".$min."&max=".$max."&rnd=".$rnd."&curr=".$prev."&mode=".$mode."'>Next</a></p>";
} else {
$line = @file(__DIR__ . "/oboeru.list", FILE_IGNORE_NEW_LINES);
echo "<ul>";
このように、比較したいコミット同士ハッシュ値を指定すると差分を表示してくれる。それぞれのコミットは隣り合ったものでもいいし、数コミット離れたもの同士でもいい。
ハッシュ値を省略すると、指定したハッシュ値と最新のコミットとの比較になる。
Gitはバイナリファイルも管理できるけれど、テキストファイルならこんな風に差分を取ることができるので変更内容がよくわかる。
これぞGitによるバージョン管理の醍醐味!
というわけで長くなってしまったけれど、Gitの基本的な使い方をあらためてまとめてみました。