個人で始めるGit

タグ: Laravel5.1LTS   Laravel   Git  

Laravelリファレンス発売記念!アドベントカレンダー!!の2015年12月7日です。(順番が違う?日付が違う?そんな細かいことは気にしてはいけません。)

開発の経験を積み始めた方は、ソース管理の必要性を感じていないと思います。「やらなくてはならない」ことが増えるだけだと感じているでしょう。

経験を積むようになると、ソースを元に戻す作業を繰り返したする経験を得ることで、やっとソース管理が便利かもと思えるようになってくるのです。

それで良いのです。経験が浅い、というより新しいことを習得しようするなら、学習対象だけに集中したほうが当然覚えは早くなります。必要性を感じなければ、人間は本気で学ぼうとしません。必要性と興味は学習の促進剤です。

ソース管理とは「ソースのバージョン」を管理することです。区切りが良いタイミングで関連するソースをまとめて管理します。そのひとまとめをバージョンとして、何らかの「識別子」で区別し、その時点に戻せるようにする機能です。

ソース管理にもいろいろなソフトウェアがあります。現在一番良く使われているのはGitでしょう。

Gitを学び始めると、リポジトリがどこか他にあって、そことやり取りができるという話が概論であっても、初めのほうで説明されます。面倒ですね。ひとまず忘れましょう。

今回は、個人プロジェクトでとにかくソース管理を行い、「ああ便利だな」と感じてもらうのが目的です。

この記事は…

書籍の「Laravelリファレンス」は執筆陣が張り切りすぎて、ページを大幅にオーバーしたため、スピンオフ企画として、半分の量を電子書籍として別に出版することになりました。そちらへ、どんな内容を盛り込むか、一度頭の中の内容をまとめるために書いています。(Gitに関してはこの電子書籍に含めるかも決まっていません。できれば、Laravel周りのエコシステムとして紹介したいと考えています。)

目標としては、電子書籍に載せようと考えている内容の前段の知識が紹介できれば良いかなと思います。

準備

この記事では紹介しませんが、Gitをインストールする必要があります。

インストールしたら、使いはじめるにあたり、自分の名前とメールアドレスを登録しておいてください。「誰がこのソースを保存したのか」を記録に残すための情報です。

git config --global user.name "名前"
git config --global user.email "メールアドレス"

この記事ではまず個人利用に限定し、使いはじめる方法を紹介します。「とにかく元に戻せる」利便性に絞って理解してもらうためです。しかし、使い慣れるにつれ開発チームで使いたくなり、そのうち世間に公開されているオープンソースへバグフィックスなどで貢献したくなるかもしれません。

それを踏まえ、名前とメールアドレスを登録してください。後ほど紹介するバージョンの保存単位である、「コミット」へ含まれます。

まずは、ワーキングディレクトリー

不思議なことに「ワーキングディレクトリー」の説明がサラッと流されるため、最初にGitを使おうとすると面食らってしまうのです。英米人なら、この言葉が示すのは言葉通りですから、なんとなく「ははぁん」と理解できるのでしょうが、日本人相手にはきちんと説明する必要があるでしょう。

ファイラーを開くと、通常自分のホームディレクトリ(フォルダー)が開かれます。選択して名前変更したり、削除したりするのは、その開いているフォルダーの中のフォルダーやファイルに対して行われます。

ターミナル操作でも同様です。ターミナルを開くと通常は最初にホームディレクトリーが操作対象として選ばれています。その中にlaravelフォルダーがあり、'cd laravel'とすると、デフォルトの操作対象ディレクトリーはlaravelへ移動します。このデフォルトの操作対象ディレクトリーのことを「カレントワーキングディレクトリー」と言います。

カレントは「現在の」、ワーキングは「作業中」です。ですから、「今作業中のディレクトリー」という意味です。ここまでが、基本的な知識の復習です。OSやシェルの話でした。

ここからGitの話になります。Gitでワーキング(作業中)ディレクトリーとは、「ソース管理下のディレクトリー」を指します。「作業」とはソース管理を当然意味します。私達開発者が「作業する」ディレクトリーと考えることもできます。Laravelプロジェクトで考えていきましょう。

Laravelインストールディレクトリー
├── app
│   ├── Console
│   ├── Events
│   ├── Exceptions
│   ├── Http
│   ├── Jobs
│   ├── Listeners
│   ├── Policies
│   └── Providers
├── bootstrap
│   └── cache
├── config
├── database
│   ├── factories
│   ├── migrations
│   └── seeds
├── public
├── resources
│   ├── assets
│   ├── lang
│   └── views
├── storage
│   ├── app
│   ├── framework
│   └── logs
├── tests
└── vendor
   (vendor下は省略)

インストール状態のフォルダー構造です。2層だけリストしています。

アプリケーションのロジックはappフォルダー、設定はconfig、データベース設定はdatabase、Web公開に必要なファイルはpublic、コード以外に必要なファイルはresources、テストコードはtestsです。これらのディレクトリーは開発時に直接変更する必要があります。変更する必要があるため、ソース管理下に置くと便利です。

vendorディレクトリー下はComposerにより変更されますが、含まれるコードを直接変更しません。コード量も膨大ですから、スピードを考えると、管理したくありません。composer.jsonか、composer.lockファイルがあれば、vendor下はいつでも元の状態へ戻せます。ですから、ソース管理の対象外にします。

storageはアプリの実行により変更されますが、生成される一時ファイルを管理する必要は通常ありません。ソース管理対象外です。

bootstrap/cacheディレクトリー下にはLaravelが効率化のために生成するファイルが置かれますが、これも私達が直接触る内容ではありません。管理対象外にします。

これらの必要のないディレクトリーに関して、Laravelは予めGitの管理対象に含めないように用意してあります。ですから、LaravelのプロジェクトをGitでソース管理する場合、何も考えずにこのプロジェクトディレクトリーを管理対象、つまりワーキングディレクトリーにすればよいのです。

実際にGitでこのインストールディレクトリーを管理してみましょう。まず、失敗して壊してもかまわないように、真新しいLaravelプロジェクトをインストールしてください。続いて、インストールしたディレクトリーへ「カレントワーキングディレクトリー」を移動します。cdコマンドで移動します。

Git管理外であることの確認

まだ、何も行っていません。プロジェクトはGitの管理対象外です。「Gitの管理対象外である」ことをコマンドで確認しましょう。

$> git status
fatal: Not a git repository (or any parent up to mount point /home)
…(省略)…

表示はいろいろ変わるかもしれませんが、Fatal(重大な)エラーとして"Not a git repository"とエラーが表示されます。Gitリポジトリーでないというエラーです。リポジトリーに関してはまた後ほど。

Gitでソース管理を始める

では、インストールディレクトリーから移動せず、次のコマンドを打ちましょう。

$> git init
Initialized empty Git repository in /home/…(プロジェクトディレクトリーのパス)…/.git/

カレントワーキングディレクトリーに.gitディレクトリーが作成されます。これで、このカレントディレクトリー(Gitのワーキングディレクトリーと混乱しやすいので、以降は「ワーキング」を抜き、「カレントディレクトリー」と記載します)下はGitのソース管理下に置かれます。

見た目は変わりません。ディレクトリーもファイルも、そのものが変わるわけでありません。lsコマンドで情報を見ることもできますし、エディターで内容を確認することもできます。特別な変化は一切起きません。

Gitがソース管理を行うために必要な情報は、全て.gitディレクトリー下に収納されます。Gitはこのディレクトリーが存在するディレクトリー下を自分の管理下だと考えます。

Gitの管理下に入りましたので、Gitの用語で言うところの「ワーキング(作業中)ディレクトリー」になりました。見た目は今までどおりです。そこが重要です。

Gitでリポジトリーやらステージングやらという概念が出てきますが、OSのコマンドやエディターで直接操作できるのは、この普通のディレクトリーであり、そこに含まれているファイルです。つまり、私達が直接見たり、変更したり、新しく追加したりできるのはワーキングディレクトリー下のみなのです。それ以外はGitコマンドを通じて操作します。

ワーキングディレクトリーという言葉が出てきたら、「いつも見たり、いじったり、使ったりしている普通のディレクトリー」で、特別な何かではないことを思い出してください。

Git管理されていることの確認

カレントディレクトリーがGitの管理対象であるかを調べるために、ディレクトリーを遡り、自分で.gitディレクトリーを探す必要はありません。Gitの管理対象外であることを確認するために使用した、git statusコマンドにより確認できます。

$> git status
On branch master

Initial commit

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        .env.example
        .gitattributes
        .gitignore
        .idea/
…以降省略…

statusは状態という英語です。git statusコマンドはGitの管理状態を表示するためのコマンドです。そのため、表示される内容はいろいろ変化します。gitの中で一番使用するコマンドかもしれません。

管理対象外のカレントディレクトリーで、このコマンドを実行した時とは異なり、Fatalエラーが表示されていません。それによりGit管理されているのが分かります。

注意: Gitに慣れてくると、「Gitを使ってホームディレクトリー全体をソース管理すればいいんじゃないか」という考えを誰も一度は抱くものです。失敗も経験ですからやってみるのもいいでしょう。ホームディレクトリー下は一般に非常に多くのファイルが存在しています。それらを全部管理するということは、かなり大きな作業です。時間もかかります。ですから、ソース管理は開発プロジェクトごとに行いましょう。バックアップの代わりとして使用するのは、おすすめしません。(実際、バックアップとしては役立ちません。.gitファイルをバックアップしない限り…)

元に戻す

今私達は、まっさらな状態のLaravelプロジェクトをワーキングディレクトリーとして見ています。

Gitの立場からすると、「ワーキングディレクトリーが指定された」段階であり、「どのファイルをソース管理するか」はまだ指定されていません。

普通のGitチュートリアルですと、ファイルを個別に追加する方法から入りますが、今回はLaravel特化の説明です。既にLaravelが事前にGitに含めないほうが良いファイルやディレクトリーはソース管理されないように準備していると説明しました。ならば、一気にそれ以外、「ソース管理が必要なファイル全部」をまとめて登録する方法から初めましょう。

$> git add -A
$> git commit -m "インストール直後"

git add -Aの代わりに、git add --allも使えます。覚えやすい方を使ってください。詳しい説明は後ほど。

git commit -mはGitにおける「バージョン保存」コマンドです。-mで保存する理由やどういう状態なのかを説明する文(メッセージ)を書いておきます。後ほど前の状態に戻す時に、どこへ戻すのかを決めるため、メモを残しておくのです。今回は状態をそのまま表す「インストール直後」とメモっておきました。

"add"は付け加える、"commit"は委ねるという意味です。コミットは「コミットする」という動詞で使われますし、コミットして保存された状態を表す名詞としても使われます。

これでインストール直後のワーキングディレクトリーの状態がソース管理に保存されています。この状態へいつでも戻せるようになりました。試してみましょう。

まずは手っ取り早く、ワーキングディレクトリーですぐ見つかるartisanファイルを編集してみましょう。エディターで開いてください。そして自由に変更してください。私はこんなふうに変更しました。

#!/usr/bin/env php
<?php

echo "バーカ、バーカ、テーラーのバーカ!";

やばい!Laravel開発者のTaylorさんに見つかる前に、元に戻しておきましょう。

$> git checkout artisan

artisanの中身を確認してください。元に戻っていますよ。ふーぅ、危なかった…

これはファイル名を指定し、そのファイルのみ最後にコミットした内容に戻します。

まれに、ワーキンディレクトリ全体を直前のコミット内容へ戻したいことがあります。今まで行った変更(多分ごちゃごちゃになり、どうしようも無くなったやつ)は全部捨てて、直前のコミットからやり直したい場合です。artisanやその他のファイルに、今度は私の悪口を書いてください。ええ、心は強い…つもりです…

続いて実行してください。

$> git reset --hard

ワーキングディレクトリーで行った変更は全部なくなります。全部直前の保存(コミット)状態に戻ります。ですから、十分に気をつけて使用してください。(必要な変更が吹っ飛んでも、私を恨んではいけません。)

とにかく、これで現状を保存する方法と、直前の保存(コミット)した状態へ戻す方法が分かりました。

新規ファイル

現在、ワーキングディレクトリーの状態は「インストール直後」に戻っています。今度は、新しいファイルを追加します。エディターでも何でもかまいませんので、新しいファイルを作成してください。

それではgit reset --hardで「インストール直後」に戻しましょう。実行してください。

ファイルを確認してください。ちゃんと削除されて…いません…

どういうことでしょう?これを理解するにはGitの立場から考えてください。

Gitはソース管理を仕事としています。一度、管理を頼まれればしっかりと仕事をします。

お客さんがワーキングディレクトリーへ新しいファイルを追加しました。さて、これはソース管理すべきでしょうか?

一時ファイルで、「ちょっとそこに置かせてね」というものかもしれません。盗品で証拠が残ってしまうのはまずいのかもしれません。貴重品でお客さん以外に触ってほしくないかもしれません。

そこで、Gitとしては頼まれていないものは「管理外」として扱います。

新しく追加したファイルは「管理外」です。Gitに「管理してくれ」と依頼していません。ですから、git reset --hardで「インストール直後」の状態に戻しても、管理外のファイルには影響がなかったわけです。

git statusは状態を確認するためのコマンドでした。活用しましょう。

$> git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)

        monaka.md

nothing added to commit but untracked files present (use "git add" to track)

これは"monaka.md"ファイルを新しく追加した場合の例です。

"Untracked files"とは「追跡していないファイル」、つまりソース管理の対象外です。対象外のファイルとしてmonaka.mdがリストされています。

ファイルをソース管理下に置く

Gitは親切です。メッセージで括弧の中は"git add" to track"、「ソース管理するならgit addしろ」と表示しています。

では新しいファイルをソース管理下に置いてみましょう。

$> git add monaka.md

何も表示されず、静かにコマンドは終了されました。状態を確認しましょう。

$> git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        new file:   monaka.md

"new file:"と表示されています。これでGitの管理下に入ったことが確認できます。

コミット

ここで一度、現時点のワーキングディレクトリーの変更をGitに対して保存するように指示しましょう。

$> git commit -m "monaka.mdファイル追加"
[master d56f45b] monaka.mdファイル追加
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 monaka.md

-mを付けなくてもOKです。その場合、設定されたエディターが開きます。


# Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # On branch master # # …省略…

メッセージを入力して保存し、エディターを抜けます。表示される通りに#で始まる行はコメントとして無視され、コミットメッセージには含まれません。メッセージを入力せずにエディターを抜けると、commitコマンドがキャンセルされます。

いちいちエディターで入力するのが面倒な場合に、-mオプションを使います。このオプションで引数としてメッセージを指定できます。

さて、Gitは今までのソースの状態を管理しています。状態を管理するための保存領域を「リポジトリ」と呼んでいます。(この場合、要は.gitディレクトリーのことです。)

そのリポジトリーにワーキングディレクトリーの状態を保存することを「コミット」と呼びます。(ええ、実際にはステージングされ、それからリポジトリーで、直接保存されるわけではありませんが、それを知っている人は続きを読む必要はありません。)

今まで2回コミットしました。ですから2つコミットが存在しているはずです。確認してみましょう。

$> git log
commit d56f45bac07a7a89dd99cdf94852a21db33507f1
Author: Hirohisa Kawase <hirokws@gmail.com>
Date:   Mon Nov 23 07:45:04 2015 +0900

    monaka.mdファイル追加

commit 0e752636fb0d2bbd1de445403bd73edab22f73fa
Author: Hirohisa Kawase <hirokws@gmail.com>
Date:   Mon Nov 23 06:05:09 2015 +0900

    インストール直後
lines 1-11/11 (END)

qキーを叩けば抜けられます。このようにログの出力はページャーを通されます。ページャーを通さない場合は、次のようにします。

git --no-pager log

logの後にこのオプションを付けても無視されます。gitに対する指示はlogなどのサブコマンドの前に付けます。

ファイルの変更

次にファイルを変更しましょう。今追加したファイルの内容を変更してください。変更を保存したら、状態を確認しましょう。

$> git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   monaka.md

no changes added to commit (use "git add" and/or "git commit -a")

"modified"は「変更された」です。Gitは変更を認識しています。

では、変更をコミットしてみます。

$> git commit -m "monaka.md変更"
On branch master
Changes not staged for commit:
        modified:   monaka.md

no changes added to commit

今までコミットした時と、表示が異なります。最後に"no changes added to commit"、「変更はコミットに追加しなかったよ」と言われました。本当でしょうか。ログで確認しましょう。

$> git log
commit d56f45bac07a7a89dd99cdf94852a21db33507f1
Author: Hirohisa Kawase <hirokws@gmail.com>
Date:   Mon Nov 23 07:45:04 2015 +0900

    monaka.mdファイル追加

commit 0e752636fb0d2bbd1de445403bd73edab22f73fa
Author: Hirohisa Kawase <hirokws@gmail.com>
Date:   Mon Nov 23 06:05:09 2015 +0900

    インストール直後
lines 1-11/11 (END)

確かに追加されていません。これはどういうことでしょうか。Gitが怠けているのでしょうか。

実は、新規ファイルと同様に、明示的に指示しない限り、変更したファイルもコミットには入りません。

細かくコミットに含めるかの調整ができるようになっています。Gitに慣れると、細かい調整が便利に使えます。

しかし、Gitを使い始めの人には、多少面倒でしょう。そこでまず、大雑把でダイナミックな方法を覚えましょう。既に紹介している方法です。

$> git add -A
$> git commit -m "monaka.md変更"
[master b5c5173] monaka.md変更
 1 file changed, 1 insertion(+)

git addで個別に追加したり、正規表現で追加しても良いですが、git add -Aを使えば、全新規ファイル、全変更ファイルをまとめてコミットに含めるように指示できます。

もう一つ、覚えておくと便利なのは、git commit -aです。全変更をコミットに含めるように指定しながら、同時にコミットします。(新規ファイルはこの方法ではコミットに含まれません。)コミットする際に新規ファイルがいつもあるとは限りません。ですから、大抵はこの-aオプションで用が済むでしょう。ありがちなのは、これに慣れてしまい、新規ファイルを追加した時にコミットに含まれなくて悩むことです。しっかり、違いを覚えておきましょう。

削除も試してね

最後に、追加後にコミット、それから変更後にコミットしたファイルを削除しましょう。

削除したら、状態を確認します。

$> git status
On branch master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        deleted:    monaka.md

no changes added to commit (use "git add" and/or "git commit -a")

削除が認識されています。とりあえずコミットしてみましょう。

$> git commit -m "monaka.md削除"
On branch master
Changes not staged for commit:
        deleted:    monaka.md

no changes added to commit

コミットに追加しないと言われています。削除したファイルも「削除」という変更をコミットに含めるため、指定する必要があります。

$> git add -A
$> git commit -m "monaka.md削除"
[master 4b72ff7] monaka.md削除
 1 file changed, 1 deletion(-)
 delete mode 100644 monaka.md

確認しておきましょう。

$> git status
On branch master
nothing to commit, working directory clean

"nothing to commit, working directory clean"、「コミットできるものは何もない、ワーキングディレクトリーはクリーンじゃ。」

ステージングエリア

ここまでの操作で理解できたでしょうが、Gitでは変更を直接リポジトリーへコミットするのではありません。コミットの対象を一度選び、それからやっとコミットするのです。

Web上に存在するGitの解説ページや解説書を見ると「ワーキングディレクトリー」と「ステージングエリア」、「リポジトリーもしくはGitディレクトリー」と3つに分けて説明しています。

ワーキングディレクトリーとリポジトリーについては説明しました。復習ですがワーキングディレクトリーは普段私達が目にしているディレクトリーのことです。Gitのソース管理の対象になっているディレクトリーのことです。リポジトリーはコミットした結果が保存されている領域です。

ステージングエリアもシンプルです。ステージングは英語でもコンピューターの専門用語として扱われているようで、中間領域を指す言葉です。似た言葉に「キャッシュ」があります。コンピューターの専門用語としては、似たような使われ方をします。Gitの場合は、コミット候補の変更の内容を登録する仮想の場所を指します。

ワーキングディレクトリー以外は直接見えないと既に説明してあります。ですから、ステージングエリアも直接見えません。Gitコマンドを経由して操作したり、確認します。

今まで使用したgit addは指定したファイルの内容をステージングエリアへ登録していました。-Aオプションを付けて実行し、新しいファイルも、変更も、削除もまとめてステージングエリアへ登録していました。

イメージ的には、「コミットしたい変更をステージングエリアに溜めておいて、ある程度まとまったらコミットする」と言う感じです。まとまりとは「新機能の追加」とか「バグフィックス」とかに通常なるでしょう。

たとえば、あるバグを潰すためにUserController.phpファイルを変更したとしましょう。バグが潰れたのでgit addでステージングエリアへ登録します。その後、別のバグを入れ込んだかも?と心配になります。そこでUserController.phpファイルに確認のためにddメソッドを挿入し内容を確認しました。

どうやら大丈夫だったようです。新しいバグは入り込んでいませんでした。では、コミットしましょう。ワーキングディレクトリーのUserController.phpファイルにはデバッグコードのddメソッドが含まれていますが、コミットの対象はステージングエリアの内容です。ステージングエリアのUserController.phpには、このデバッグコードは含まれていませんので、コミットにも含まれません。安心してコミットできます。

逆に、バグを入れ込んでしまったと気づくこともあるでしょう。該当箇所を変更し、ddメソッドを外し、今度はだいじょうぶと自信を持ってUserController.phpをgit addし直せます。ステージングエリアへ新しい内容を登録すれば、以前に登録した内容は消えてなくなります。ここでコミットすれば、最後にgit addした新しい内容がコミットされます。

こうした柔軟な対応ができるため、ステージングエリアが用意されています。それに、ちょっとコードを変更するたびにコミットしていたのでは、コミットが増えすぎて何がなんだかわからなくなりますし、コミット時のコメントを考えるのも大変です。

ステージングエリアを生かすには、何かを修正したり、新しくファイルを作成したりしたら、すぐにaddしておきます。そして、区切りのよい所でコミットを行うワークフローになるでしょう。

一度ステージングエリアへ登録した内容をキャンセルする方法もあります。登録内容全部まとめてステージングエリアから削除したい場合はgit resetです。特定のファイルのみ登録をキャンセルする場合は、git reset ファイル名です。

差分を調べる

git statusはファイルごとの状態を確認するため、多用します。もう一つ、「どんな変更をしたのか」を確認できれば便利です。

たとえば、app/Http/routes.phpファイルの最後へ、以下のルート定義を追加してください。

Route::get('git', function() {
    return 'ギットギットにしてやる!';
});

これはそのまま、コミットしましょう。新規ファイルの追加ではないので、1コマンドで登録できるパターンです。

$> git commit -am "gitルート追加"
[master feeeef4] gitルート追加
 1 file changed, 4 insertions(+)

すると、どこかから天の声が聞こえてきます。「アカン…コントローラー、ちゃんと使わなきゃあかんで…」

手が動き、新しいファイルのapp/Http/Controllers/GitController.phpに次のようなコントローラーを作成します。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class GitController extends Controller
{
    public function showGitWork()
    {
        return 'ギットギットにしてやる!';
    }
}

それに合わせて、routes.phpファイルも変更する必要が起きます。gitルートの定義を変更しましょう。

Route::get('git', 'GitController@showGitWork');

Gitの状態を確認しておきましょう。

$> git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   app/Http/routes.php

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        app/Http/Controllers/GitController.php

no changes added to commit (use "git add" and/or "git commit -a")

routes.phpは"modified"で変更が認識されていますが、"Change not staged ..."ですので、ステージングされていません。GitControllerはuntracked filesとしてリストされていますので、まだソース管理の対象外です。

ここで一度、全部まとめてコミット候補としてステージングエリアに上げましょう。

$> git add -A

このコマンドは、ソース管理対象外の新しいファイルも、ステージングエリアに追加してくれますから、2つとも追加されているはずです。確かめましょう。

$> git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        new file:   app/Http/Controllers/GitController.php
        modified:   app/Http/routes.php

"Changes to be committed:"で、「コミットされる変更」のリストへ両方共に乗っています。

また天の声が聞こえてきます。「ルートに名前をつけたほうが…」、やや「うるさいなあ」と思いながら、手が動きます。routes.phpの内容を変更します。

Route::get('git', 'GitController@showGitWork')
    ->name('show-git-work');

さて、ここで一息つきましょう。現在、最後のコミット、ステージングエリア、ワーキングディレクトリーの状態は全て異なっています。

せっかくですので、それぞれの差異を確認しましょう。調べるためにはgit diffを使用します。これもログと同様にデフォルトではページャーで表示されます。ページャーを抑制したい場合はgit --no-pager diffです。コピペの都合上、以下は--no-pagerオプションを使用しますが、通常はページャーで表示したほうが便利でしょう。

$> git --no-pager diff
diff --git a/app/Http/routes.php b/app/Http/routes.php
index 30ea2ed..4677b73 100644
--- a/app/Http/routes.php
+++ b/app/Http/routes.php
@@ -15,4 +15,5 @@ Route::get('/', function () {
     return view('welcome');
 });

-Route::get('git', 'GitController@showGitWork');
+Route::get('git', 'GitController@showGitWork')
+    ->name('show-git-work');

オプションを付けないgit diffは、ステージングエリアとワーキングディレクトリーとの違いを表示します。ステージングエリアの内容を元に、ワーキングエリアではどんな変更が行われているかを示しています。-で始まる行はステージングエリアの内容、+で始まる行はワーキングエリアの内容です。

$> git --no-pager diff HEAD
diff --git a/app/Http/Controllers/GitController.php b/app/Http/Controllers/GitController.php
new file mode 100644
index 0000000..a81e146
--- /dev/null
+++ b/app/Http/Controllers/GitController.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+
+class GitController extends Controller
+{
+    public function showGitWork()
+    {
+        return 'ギットギットにしてやる!';
+    }
+}
diff --git a/app/Http/routes.php b/app/Http/routes.php
index b91bd99..4677b73 100644
--- a/app/Http/routes.php
+++ b/app/Http/routes.php
@@ -15,6 +15,5 @@ Route::get('/', function () {
     return view('welcome');
 });

-Route::get('git', function() {
-    return 'ギットギットにしてやる!';
-});
+Route::get('git', 'GitController@showGitWork')
+    ->name('show-git-work');

比較対象として"HEAD"を指定しました。HEADは最後のコミットを指します。比較対象を一つだけ指定した場合、指定したコミットとワーキングディレクトリーとの違いを表示します。ですから、最後のコミットとワーキングディレクトリーの違いが表示されています。

$> git --no-pager diff --staged
diff --git a/app/Http/Controllers/GitController.php b/app/Http/Controllers/GitController.php
new file mode 100644
index 0000000..a81e146
--- /dev/null
+++ b/app/Http/Controllers/GitController.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+
+class GitController extends Controller
+{
+    public function showGitWork()
+    {
+        return 'ギットギットにしてやる!';
+    }
+}
diff --git a/app/Http/routes.php b/app/Http/routes.php
index b91bd99..30ea2ed 100644
--- a/app/Http/routes.php
+++ b/app/Http/routes.php
@@ -15,6 +15,4 @@ Route::get('/', function () {
     return view('welcome');
 });

-Route::get('git', function() {
-    return 'ギットギットにしてやる!';
-});
+Route::get('git', 'GitController@showGitWork');

これは何と何との違いであるか分かりますか? ヒントはオプションです。

--stagedをつけると、最後のコミットとステージングエリアの内容の差異が表示されます。

ここで過去のログを見てみましょう。

$> git --no-pager log
commit feeeef4afc498a57044b29fe47f88fde5c0a3c35
Author: Hirohisa Kawase <hirokws@gmail.com>
Date:   Mon Nov 23 15:34:56 2015 +0900

    gitルート追加

commit b5c5173eeecda711d488e751f6f3f024d85d4b4a
Author: Hirohisa Kawase <hirokws@gmail.com>
Date:   Mon Nov 23 09:26:54 2015 +0900

    monaka.md変更

commit d56f45bac07a7a89dd99cdf94852a21db33507f1
Author: Hirohisa Kawase <hirokws@gmail.com>
Date:   Mon Nov 23 07:45:04 2015 +0900

    monaka.mdファイル追加

commit 0e752636fb0d2bbd1de445403bd73edab22f73fa
Author: Hirohisa Kawase <hirokws@gmail.com>
Date:   Mon Nov 23 06:05:09 2015 +0900

    インストール直後

まだ説明していませんでしたが、commitの右側に表示されている不思議な文字列は、16進数のハッシュ値です。これにより各コミットを表しています。

最後の「gitルート追加」はfeeeef4afc498a57044b29fe47f88fde5c0a3c35です。その前の「monaka.md変更」のコミットはb5c5173eeecda711d488e751f6f3f024d85d4b4aです。この二つのコミット間の違いを確認してみましょう。

$> git --no-pager diff b5c517 feeeef
diff --git a/app/Http/routes.php b/app/Http/routes.php
index 1ad3549..b91bd99 100644
--- a/app/Http/routes.php
+++ b/app/Http/routes.php
@@ -14,3 +14,7 @@
 Route::get('/', function () {
     return view('welcome');
 });
+
+Route::get('git', function() {
+    return 'ギットギットにしてやる!';
+});

ハッシュ値は先頭のいくつかを指定するだけで判別されます。ハッシュ値は、まず重なることはありませんので全桁をいつも指定する必要はありません。

2つのコミットを指定する場合、ベースのコミットを最初に、比較するコミットを後に指定します。

Gitには直前のコミット、その前のコミット、更にその前のコミットと、コミットした順番を遡って指定する指定方法が用意されています。これを使用すれば、いちいちログでコミットのハッシュ値を調べる必要はありません。

git --no-pager diff HEAD~1 HEAD
diff --git a/app/Http/routes.php b/app/Http/routes.php
index 1ad3549..b91bd99 100644
--- a/app/Http/routes.php
+++ b/app/Http/routes.php
@@ -14,3 +14,7 @@
 Route::get('/', function () {
     return view('welcome');
 });
+
+Route::get('git', function() {
+    return 'ギットギットにしてやる!';
+});

HEADは直前のコミットを表しています。チルダ('~')に数字を続けることで、「HEADより〜つまえ」のコミットを表せます。そのため、git diff HEAD~1 HEADで直前のコミットで行われた変更が確認できます。

git diff HEADで、直前のコミットと現在のワーキングディレクトリーの内容を比較できました。応用して、git diff HEAD~1なら、更に一つ前のコミットとワーキンディレクトリとの違いを確認できます。

$> git --no-pager diff HEAD~1
diff --git a/app/Http/Controllers/GitController.php b/app/Http/Controllers/GitController.php
new file mode 100644
index 0000000..a81e146
--- /dev/null
+++ b/app/Http/Controllers/GitController.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+
+class GitController extends Controller
+{
+    public function showGitWork()
+    {
+        return 'ギットギットにしてやる!';
+    }
+}
diff --git a/app/Http/routes.php b/app/Http/routes.php
index 1ad3549..4677b73 100644
--- a/app/Http/routes.php
+++ b/app/Http/routes.php
@@ -14,3 +14,6 @@
 Route::get('/', function () {
     return view('welcome');
 });
+
+Route::get('git', 'GitController@showGitWork')
+    ->name('show-git-work');

もちろん、HEADの代わりにコミットに付けられたハッシュ値を指定することもできます。いろいろやってみましょう。

コミットのやり直し

いろいろ変更しましたし、ここらで一度コミットしておきましょう。

$> git commit -am "Gitルート定義"
[master f424d58] Gitルート定義
 2 files changed, 16 insertions(+), 3 deletions(-)
 create mode 100644 app/Http/Controllers/GitController.php

$> git --no-pager log -n 3
commit f424d58ff87438bee9f95eafe4c25e44c7662454
Author: Hirohisa Kawase <hirokws@gmail.com>
Date:   Mon Nov 23 16:52:17 2015 +0900

    Gitルート定義

commit feeeef4afc498a57044b29fe47f88fde5c0a3c35
Author: Hirohisa Kawase <hirokws@gmail.com>
Date:   Mon Nov 23 15:34:56 2015 +0900

    gitルート追加

commit b5c5173eeecda711d488e751f6f3f024d85d4b4a
Author: Hirohisa Kawase <hirokws@gmail.com>
Date:   Mon Nov 23 09:26:54 2015 +0900

    monaka.md変更

git log-nオプションで表示されるログの数を指定できます。

ログを見てみると…ああ、残念。大文字小文字の違いはあれど、コミットしたコメントが被っています!

直前のコミットを取り消し、やり直したい場合はgit commit--amendオプションを付けます。

$> git commit -m "Gitルート修正" --amend
[master 35adfc3] Gitルート修正
 Date: Mon Nov 23 16:52:17 2015 +0900
 2 files changed, 16 insertions(+), 3 deletions(-)
 create mode 100644 app/Http/Controllers/GitController.php

$> git --no-pager log -n 3
commit 35adfc3385edec99ff9b831e264b1f6b5bfaf5fa
Author: Hirohisa Kawase <hirokws@gmail.com>
Date:   Mon Nov 23 16:52:17 2015 +0900

    Gitルート修正

commit feeeef4afc498a57044b29fe47f88fde5c0a3c35
Author: Hirohisa Kawase <hirokws@gmail.com>
Date:   Mon Nov 23 15:34:56 2015 +0900

    gitルート追加

commit b5c5173eeecda711d488e751f6f3f024d85d4b4a
Author: Hirohisa Kawase <hirokws@gmail.com>
Date:   Mon Nov 23 09:26:54 2015 +0900

    monaka.md変更

しまった!ミドルウェアを指定し忘れていた。routes.phpを変更だ。

Route::get('git', 'GitController@showGitWork')
    ->middleware(['auth'])
    ->name('show-git-work');

再度、コミットをし直しましょう。今度はこの変更を追加する必要があるため、git addでファイルを追加するか、git add -Aを使うか、それとも…

git commit -am "Gitルート修正" --amend
[master 43df232] Gitルート修正
 Date: Mon Nov 23 16:52:17 2015 +0900
 2 files changed, 17 insertions(+), 3 deletions(-)
 create mode 100644 app/Http/Controllers/GitController.php

17:07:30 [hiro-suse] > ~/temp1111
◆ (master ✔) : git --no-pager log -n 3
commit 43df23223dbed0ddb3fb0434634f75faf6fe832b
Author: Hirohisa Kawase <hirokws@gmail.com>
Date:   Mon Nov 23 16:52:17 2015 +0900

    Gitルート修正

commit feeeef4afc498a57044b29fe47f88fde5c0a3c35
Author: Hirohisa Kawase <hirokws@gmail.com>
Date:   Mon Nov 23 15:34:56 2015 +0900

    gitルート追加

commit b5c5173eeecda711d488e751f6f3f024d85d4b4a
Author: Hirohisa Kawase <hirokws@gmail.com>
Date:   Mon Nov 23 09:26:54 2015 +0900

    monaka.md変更

git commit-aオプションを追加です。変更のあるファイルを全部、ステージングエリアへ追加してコミットするオプションでした。

ログを比較すると、一つ前のコミットは'35adfc338…'で、今回のコミットは'43df2322…'です。別物です。変更されたようです。

最後に

Gitにはもっとたくさんの機能がありますが、個人で自分の仕事に取り入れるには、最初にこれくらい知っていれば便利さが実感できると思います。ソースのバージョン管理と言っても、ただ全体のバックアップを取るだけでなく、コードを管理するために役に立つ機能が満載です。

通常、Gitの説明でメインに扱われるブランチやらの説明は行っていません。シンプルなワークフローに沿って、最低限度の使い方を説明しました。より高度な内容については、手頃なGitの解説をご覧ください。更に便利さを感じられるようになるでしょう。