Git入門:バージョン管理のきほん

いまの状態を確かめる(status と diff)

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

このレッスンで分かること

  • git status の 3 つの見出しが、それぞれどの場所の話をしているのか
  • git diff+- の読み方と、--staged を付けたときの違い
  • コミット前に中身を確認する習慣で、どんな事故を防げるのか

status はいつでも打っていい

Git を使っていて迷ったら、まず git status を打ちます。このコマンドは何も変更せず、現在の状態を報告するだけです。何回打っても害はありません。

エラーが出た、思った通りに動かない、次に何をすればいいか分からない。そのすべての場面で git status が答えの手がかりを出します。しかも Git は次に打つべきコマンドまで括弧の中に書いてくれます。

前のレッスンの続きとして git-practice フォルダで進めます。

ターミナル

cd ~/git-practice git status

プレーンテキスト

On branch main nothing to commit, working tree clean

この 2 行が Git の「異常なし」の表示です。working tree clean は、ワークツリーの内容が最後のコミットと完全に一致していることを意味します。ステージにも何も残っていません。

status の見出しを読み分ける

状態を作って、見出しがどう変わるか確かめます。ファイルを 1 つ編集し、1 つ新規作成し、1 つを編集してステージに乗せます。

ターミナル

echo "## 使い方" >> README.md echo "console.log('bye');" > bye.js echo "// 実験用" >> app.js git add app.js git status

プレーンテキスト

On branch main Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: app.js Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: README.md Untracked files: (use "git add <file>..." to include in what will be committed) bye.js

3 つの見出しが同時に出ました。それぞれの意味は次の通りです。

見出し場所状態
Changes to be committedステージ次の git commit に含まれる
Changes not staged for commitワークツリー追跡済みだが、まだ add していない
Untracked filesワークツリー一度も記録したことがないファイル

この 3 つは、前のレッスンで扱ったワークツリー・ステージ・リポジトリの 3 層にそのまま対応しています。Changes to be committed に載っているものだけがコミットされる、という 1 点を押さえれば git status は読めます。

ファイル名の前に付く語も情報を持っています。

表示意味
new fileリポジトリに存在しない新規ファイル
modified中身が変わった
deletedファイルが消えた
renamed名前が変わった

deleted はエディタではなくファイル操作で消したときにも出ます。Git は削除も 1 つの変更として記録するので、消したことを履歴に残すには git add してコミットする必要があります。削除したのに git add を忘れると、コミットには残ったままになります。

renamed は少し特殊で、Git は名前の変更という操作を直接記録していません。削除と追加のペアを見つけて、中身がほぼ同じなら「名前が変わった」と推測して表示しています。だからファイル名を変えると同時に中身も大きく書き換えると、renamed ではなく deletednew file の 2 件として出ます。表示が違うだけで、記録される内容に差はありません。

同じファイル名が 2 つの見出しに同時に出ることもあります。git add したあとにもう一度編集した場合です。この状態でコミットすると、記録されるのはステージにある内容のほうです。

git status -s を使うと 1 行 1 ファイルの短い形式になります。左の 1 文字がステージの状態、右の 1 文字がワークツリーの状態です。慣れると速いですが、案内文が出ないので学習中は通常の表示を勧めます。

diff で中身の差を見る

git status はどのファイルが変わったかを教えますが、どう変わったかは教えません。それを見るのが git diff です。

ターミナル

git diff

プレーンテキスト

diff --git a/README.md b/README.md index 3b18e51..8c7e5a3 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # Git practice Git の練習用リポジトリです。 +## 使い方

上から順に読みます。

diff --git a/README.md b/README.md は比較対象の宣言です。a/ が変更前、b/ が変更後を指します。

index 3b18e51..8c7e5a3 は内部で使うIDなので、読み飛ばして構いません。

--- a/README.md+++ b/README.md は、以降の行で - が変更前、+ が変更後を表すという凡例です。

@@ -1,2 +1,3 @@ は位置の情報です。変更前は 1 行目から 2 行分、変更後は 1 行目から 3 行分を表示しているという意味です。

そして本体が続きます。行頭の記号がすべてです。

行頭意味
半角スペース変わっていない行。前後の文脈として表示される
-削除された行
+追加された行

この例では ## 使い方 の 1 行が追加されただけだと分かります。行を書き換えた場合は、- で古い行が出て + で新しい行が出ます。Git は行単位で見るので、1 文字だけ直しても 1 行削除と 1 行追加として表示されます。

多くの環境では削除が赤、追加が緑で色分けされます。色が付かない場合は次の設定を入れます。

ターミナル

git config --global color.ui auto

もう 1 つ知っておくと便利なのが --word-diff です。行ではなく単語の単位で差を見せるので、文章の細かい修正を追うときに読みやすくなります。

ターミナル

git diff --word-diff

長い 1 行の中の 1 語だけを直したときも、どこが変わったのかがすぐ分かります。逆にプログラムのコードでは行単位のほうが読みやすいので、使い分けます。

なお git diff は未追跡ファイルを一切表示しません。bye.js は新規作成したばかりなので、git status には出ても git diff には出てこないままです。Git にとって未追跡ファイルは比較対象が存在しないため、差を計算できないからです。中身を確認したい場合は、いったん git add してから git diff --staged を見ます。

ステージの中身を見る

ここで重要な性質があります。git diffワークツリーとステージの差だけを見ます。すでに git add したものは表示されません。

さきほどの例では app.jsgit add してあるので、git diff の出力に app.js は出てきませんでした。ステージの中身を見るには --staged を付けます。

ターミナル

git diff --staged

プレーンテキスト

diff --git a/app.js b/app.js index 8c5f1a2..d4e7b09 100644 --- a/app.js +++ b/app.js @@ -1 +1,2 @@ console.log('hello'); +// 実験用

これで app.js の変更が見えます。--staged--cached と書いても同じです。

3 つの比較を整理します。

コマンド比較するもの使う場面
git diffワークツリー と ステージadd する前に中身を見る
git diff --stagedステージ と 最後のコミットcommit する前に中身を見る
git diff HEADワークツリー と 最後のコミット未コミットの変更を全部見る

HEAD は今いる場所の先端のコミットを指す目印です。git diff HEADadd 済みかどうかに関係なく、最後のコミットからの変更をすべて見せます。

git diff で何も出ないときは、多くの場合すでに add 済みです。「変更したはずなのに diff が空」と感じたら git diff --staged を試してください。この 2 つを混同するのは初学者の定番のつまずきです。

コミット前の確認を習慣にする

addcommit の前に確認を挟むと、事故のほとんどが防げます。手順は次の 4 つです。

ターミナル

git status # どのファイルが変わったか git diff # 中身がどう変わったか git add . # ステージに乗せる git diff --staged # 乗ったものを最終確認 git commit -m "メッセージ"

この流れを守っていると、次のような事故が起きる前に気付けます。

デバッグ用に埋め込んだ出力を消し忘れている。設定ファイルに書いたAPIキーやパスワードが混ざっている。エディタが自動整形して、関係ない 200 行が変更扱いになっている。誤ってファイルを削除している。

特にパスワードの混入は、GitHub に公開してしまうと履歴からの完全な削除が非常に面倒になります。コミットを取り消してもファイルの中身は履歴に残り続けるため、後始末には専用のツールが要ります。第 1 章の最後で扱う追跡しないファイルを決める(.gitignore)と組み合わせて、二重に防ぐのが実務のやり方です。

慣れてくると git add . を打つ前の git diff を省きたくなりますが、省いていいのは変更が 1 ファイルだけだと確信できるときだけです。複数ファイルに手を入れた日は必ず見ます。確認にかかる 10 秒と、公開してしまった鍵を回収する数時間は釣り合いません。

ありがちな失敗と対処

diff の画面から抜けられない

出力が画面に収まらないとき、Git はページャという表示ツールを起動します。矢印キーで動くのに、いくら押しても終わらないという状態になります。

q キーを押すと終了します。あわせて次を覚えておくと快適です。

キー動き
q終了する
スペース次のページへ
b前のページへ
/文字列検索する

このページャは git log でも使われます。同じ操作で抜けられます。出力が短いときは起動しないので、毎回出るわけではありません。

どうしてもページャを使いたくない場合は、無効にする設定もあります。

ターミナル

git config --global core.pager cat

ただし長い出力が一気に流れるようになるため、慣れるまでは既定のままを勧めます。

変更していないのに全行が差分に出る

git diff を打つと、触っていないはずのファイルの全行が -+ の両方に出ることがあります。原因はほぼ 2 つです。

1 つは改行コードの違いです。Windows と Mac でファイルの行末の表し方が違うため、環境をまたぐと全行が変更扱いになります。第 2 レッスンで設定した core.autocrlf が入っていれば防げます。

もう 1 つはエディタの自動整形です。保存時にインデントを揃える設定が入っていると、開いて保存しただけで全体が変わります。この場合は整形設定をチームで揃えるしかありません。

diff が長すぎて読めない

ファイル名と変更行数だけを見たいときは --stat を付けます。

ターミナル

git diff --stat

プレーンテキスト

README.md | 1 + 1 file changed, 1 insertion(+)

特定のファイルだけ見たいときはファイル名を渡します。

ターミナル

git diff README.md

次のレッスンでは git log を使い、積み上げたコミットの履歴を読む方法を扱います。statusdiff が「今」を見る道具なのに対して、log は「これまで」を見る道具です。

この章のポイント
  • git status は状態を報告するだけで何も壊さない。迷ったらまず打つ
  • 3 つの見出しはワークツリー・ステージ・リポジトリの 3 層にそのまま対応している
  • git diff はステージに乗る前の差、git diff --staged は乗ったあとの差を見る
  • add の前に git diffcommit の前に git diff --staged を挟むと事故が減る