Git
章 ▾ 第2版

2.2 Gitの基本 - リポジトリへの変更の記録

リポジトリへの変更の記録

この時点で、ローカルマシンに *bona fide* のGitリポジトリがあり、そのすべてのファイルのチェックアウトまたは*作業コピー*が目の前にあるはずです。通常、変更を開始し、プロジェクトが記録したい状態に達するたびに、それらの変更のスナップショットをリポジトリにコミットします。

作業ディレクトリ内の各ファイルは、*追跡対象*または*未追跡*の2つの状態のいずれかになる可能性があることに注意してください。追跡対象ファイルとは、最後のスナップショットにあったファイルと、新しくステージングされたファイルのことです。それらは、変更されていない、変更された、またはステージング済みのいずれかになります。要するに、追跡対象ファイルとは、Gitが認識しているファイルのことです。

未追跡ファイルは、それ以外のすべてです。つまり、作業ディレクトリ内の最後のスナップショットになく、ステージング領域にもないファイルです。最初にリポジトリをクローンすると、Gitがそれらをチェックアウトしたばかりで何も編集していないため、すべてのファイルが追跡され、変更されていない状態になります。

ファイルを編集すると、Gitはそれらを変更済みと認識します。最後のコミット以降に変更したためです。作業を進めると、これらの変更されたファイルを選択的にステージングし、ステージングされたすべての変更をコミットします。このサイクルが繰り返されます。

The lifecycle of the status of your files
図8。ファイルの状態のライフサイクル

ファイルの状態の確認

どのファイルがどの状態にあるかを判断するために使用する主なツールは、git statusコマンドです。クローン直後にこのコマンドを実行すると、次のような結果が表示されます。

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working tree clean

これは、作業ディレクトリがクリーンであることを意味します。つまり、追跡対象のファイルは変更されていません。また、Gitは未追跡のファイルを認識していません。もしあれば、ここにリストされます。最後に、コマンドはどのブランチにいるかを伝え、サーバー上の同じブランチから分岐していないことを通知します。今のところ、そのブランチは常にデフォルトであるmasterです。ここでは心配する必要はありません。Gitブランチでは、ブランチと参照について詳しく説明します。

注意

GitHubは、2020年半ばにデフォルトのブランチ名をmasterからmainに変更し、他のGitホストもそれに続きました。そのため、新しく作成された一部のリポジトリでは、デフォルトのブランチ名がmasterではなくmainになっている場合があります。さらに、デフォルトのブランチ名は(デフォルトのブランチ名で見たように)変更できるため、デフォルトブランチの名前が異なる場合があります。

ただし、Git自体はまだmasterをデフォルトとして使用しているため、この書籍全体でそれを使用します。

プロジェクトに新しいファイル、単純なREADMEファイルを追加するとしましょう。ファイルが以前に存在せず、git statusを実行すると、次のように未追跡のファイルが表示されます。

$ echo 'My Project' > README
$ 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)

    README

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

新しいREADMEファイルが未追跡であることがわかります。ステータス出力の「Untracked files」ヘッダーの下にあるためです。未追跡とは、基本的に、Gitが以前のスナップショット(コミット)になかったファイルを見ており、まだステージングされていないことを意味します。Gitは、明示的にそうするように指示するまで、コミットスナップショットにファイルを含めることは開始しません。これは、生成されたバイナリファイルや、含めるつもりでなかったその他のファイルを誤って含め始めないようにするためです。READMEを含め始めるので、ファイルを追跡しましょう。

新しいファイルの追跡

新しいファイルの追跡を開始するには、git addコマンドを使用します。READMEファイルの追跡を開始するには、次を実行できます。

$ git add README

ステータスコマンドを再度実行すると、READMEファイルが追跡され、コミットされるようにステージングされていることがわかります。

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)

    new file:   README

「コミット対象の変更」という見出しの下にあるので、それがステージングされた状態であることがわかります。この時点でコミットすると、git addを実行した時点でのファイルのバージョンが、その後の履歴スナップショットに含まれます。以前にgit initを実行したときに、git add <files>も実行したことを覚えているかもしれません。これは、ディレクトリ内のファイルの追跡を開始するためでした。git addコマンドは、ファイルまたはディレクトリのパス名を受け取り、ディレクトリの場合は、そのディレクトリ内のすべてのファイルが再帰的に追加されます。

変更されたファイルのステージング

すでに追跡されているファイルを変更してみましょう。すでに追跡されているCONTRIBUTING.mdというファイルを変更し、再度git statusコマンドを実行すると、次のようになります。

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   README

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:   CONTRIBUTING.md

CONTRIBUTING.mdファイルは、「コミット用にステージされていない変更」というセクションの下に表示されます。これは、追跡されているファイルが作業ディレクトリ内で変更されたが、まだステージングされていないことを意味します。ステージングするには、git addコマンドを実行します。git addは多目的コマンドであり、新しいファイルの追跡を開始したり、ファイルをステージングしたり、マージ競合したファイルを解決済みとしてマークしたりするなど、さまざまな用途で使用します。「プロジェクトにこのファイルを追加する」というよりも、「この内容を次のコミットに正確に追加する」と考えると役に立つでしょう。ここでgit addを実行してCONTRIBUTING.mdファイルをステージングし、再度git statusを実行してみましょう。

$ git add CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   README
    modified:   CONTRIBUTING.md

両方のファイルがステージングされ、次のコミットに含まれる予定です。この時点で、コミットする前にCONTRIBUTING.mdに少し変更を加えたいと思い出したとします。再度ファイルを開き、変更を加えてコミットする準備が整いました。しかし、もう一度git statusを実行してみましょう。

$ vim CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   README
    modified:   CONTRIBUTING.md

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:   CONTRIBUTING.md

一体どういうことでしょう? 今度はCONTRIBUTING.mdがステージング済みとステージングされていないの両方としてリストされています。これはどのようにして可能なのでしょうか? 実は、Gitはgit addコマンドを実行した時点のファイルの状態を正確にステージングします。今コミットすると、git addコマンドを最後に実行した時点のCONTRIBUTING.mdのバージョンがコミットに含まれ、git commitを実行した時点の作業ディレクトリ内のファイルのバージョンではありません。git addを実行した後でファイルを変更した場合は、最新バージョンのファイルをステージングするためにもう一度git addを実行する必要があります。

$ git add CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   README
    modified:   CONTRIBUTING.md

短いステータス

git statusの出力は非常に包括的ですが、かなり冗長でもあります。Gitには、変更をよりコンパクトに表示できる短いステータスフラグもあります。git status -sまたはgit status --shortを実行すると、コマンドの出力が大幅に簡略化されます。

$ git status -s
 M README
MM Rakefile
A  lib/git.rb
M  lib/simplegit.rb
?? LICENSE.txt

追跡されていない新しいファイルには??が、ステージング領域に追加された新しいファイルにはAが、変更されたファイルにはMなどが付いています。出力には2つの列があります。左側の列はステージング領域のステータスを示し、右側の列は作業ツリーのステータスを示します。たとえば、この出力では、READMEファイルは作業ディレクトリ内で変更されていますが、まだステージングされていません。一方、lib/simplegit.rbファイルは変更されてステージングされています。Rakefileは変更、ステージングされた後、再度変更されたため、ステージング済みとステージングされていない両方の変更があります。

ファイルの無視

多くの場合、Gitに自動的に追加させたり、追跡されていないものとして表示させたりしたくないファイルのクラスがあります。これらは一般的に、ログファイルやビルドシステムによって生成されたファイルなど、自動的に生成されるファイルです。そのような場合は、.gitignoreという名前で、それらに一致するパターンのリストを記述したファイルを作成できます。以下は.gitignoreファイルの例です。

$ cat .gitignore
*.[oa]
*~

最初の行は、Gitに「.o」または「.a」で終わるファイル(コードのビルド結果であるオブジェクトファイルとアーカイブファイル)を無視するように指示します。2番目の行は、多くのテキストエディタ(Emacsなど)で一時ファイルをマークするために使用されるチルダ(~)で終わるすべてのファイルを無視するようにGitに指示します。ログ、tmp、pidディレクトリ、自動生成されたドキュメントなども含めることができます。新しいリポジトリで作業を開始する前に、.gitignoreファイルを設定することは、Gitリポジトリに本当に含めたくないファイルを誤ってコミットしないようにするために一般的に良い考えです。

.gitignoreファイルに記述できるパターンのルールは次のとおりです。

  • 空白行または#で始まる行は無視されます。

  • 標準のglobパターンが機能し、作業ツリー全体に再帰的に適用されます。

  • パターンをスラッシュ(/)で開始して、再帰を回避できます。

  • パターンをスラッシュ(/)で終了して、ディレクトリを指定できます。

  • パターンを感嘆符(!)で開始して、パターンを否定できます。

globパターンは、シェルが使用する単純化された正規表現のようなものです。アスタリスク(*)は、ゼロ個以上の文字に一致します。[abc]は、角かっこ内の任意の文字(この場合はa、b、またはc)に一致します。疑問符(?)は、1つの文字に一致します。ハイフンで区切られた文字を角かっこで囲むと([0-9])、それらの間の任意の文字に一致します(この場合は0から9)。また、2つのアスタリスクを使用してネストされたディレクトリを照合することもできます。a/**/zは、a/za/b/za/b/c/zなどに一致します。

以下は、.gitignoreファイルの別の例です。

# ignore all .a files
*.a

# but do track lib.a, even though you're ignoring .a files above
!lib.a

# only ignore the TODO file in the current directory, not subdir/TODO
/TODO

# ignore all files in any directory named build
build/

# ignore doc/notes.txt, but not doc/server/arch.txt
doc/*.txt

# ignore all .pdf files in the doc/ directory and any of its subdirectories
doc/**/*.pdf
ヒント

GitHubは、プロジェクトの開始点として使用できる、数十のプロジェクトと言語に対応した適切な.gitignoreファイルの包括的なリストをhttps://github.com/github/gitignoreで管理しています。

注意

単純なケースでは、リポジトリのルートディレクトリに1つの.gitignoreファイルがあり、リポジトリ全体に再帰的に適用される場合があります。ただし、サブディレクトリに複数の.gitignoreファイルを作成することも可能です。これらのネストされた.gitignoreファイルのルールは、配置されているディレクトリの下のファイルにのみ適用されます。Linuxカーネルのソースリポジトリには、206個の.gitignoreファイルがあります。

複数の.gitignoreファイルの詳細については、本書の範囲外とします。詳細についてはman gitignoreを参照してください。

ステージング済みおよびステージングされていない変更の表示

git statusコマンドが曖昧すぎると感じる場合、つまり、どのファイルが変更されたかだけでなく、変更内容を正確に知りたい場合は、git diffコマンドを使用できます。git diffについては後で詳しく説明しますが、おそらく次の2つの質問に答えるためによく使用するでしょう。「変更したがまだステージングしていないものは何か」と「コミットしようとしているステージング済みは何なのか」です。git statusはファイル名を一覧表示することでこれらの質問に大まかに答えますが、git diffは追加および削除された正確な行、つまりパッチを示します。

READMEファイルを編集してステージングし、CONTRIBUTING.mdファイルをステージングせずに編集したとします。git statusコマンドを実行すると、次のようになります。

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   README

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:   CONTRIBUTING.md

変更したがまだステージングしていないものを確認するには、引数を指定せずにgit diffと入力します。

$ git diff
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8ebb991..643e24f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -65,7 +65,8 @@ branch directly, things can get messy.
 Please include a nice description of your changes when you submit your PR;
 if we have to read the whole diff to figure out why you're contributing
 in the first place, you're less likely to get feedback and have your change
-merged in.
+merged in. Also, split your changes into comprehensive chunks if your patch is
+longer than a dozen lines.

 If you are starting to work on a particular area, feel free to submit a PR
 that highlights your work in progress (and note in the PR title that it's

このコマンドは、作業ディレクトリの内容とステージング領域の内容を比較します。結果は、まだステージングしていない変更を示します。

次のコミットに含まれるステージング済みを確認する場合は、git diff --stagedを使用できます。このコマンドは、ステージングされた変更を最後のコミットと比較します。

$ git diff --staged
diff --git a/README b/README
new file mode 100644
index 0000000..03902a1
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+My Project

git diff自体は、最後のコミット以降のすべての変更を示すわけではなく、まだステージングされていない変更のみを示すことに注意することが重要です。すべての変更をステージングした場合、git diffは何も出力しません。

別の例として、CONTRIBUTING.mdファイルをステージングしてから編集した場合、git diffを使用して、ファイル内でステージングされている変更とステージングされていない変更を確認できます。環境が次のようになっている場合

$ git add CONTRIBUTING.md
$ echo '# test line' >> CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   CONTRIBUTING.md

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:   CONTRIBUTING.md

ここで、git diffを使用して、まだステージングされていないものを確認できます。

$ git diff
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 643e24f..87f08c8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -119,3 +119,4 @@ at the
 ## Starter Projects

 See our [projects list](https://github.com/libgit2/libgit2/blob/development/PROJECTS.md).
+# test line

また、git diff --cachedを使用して、これまでにステージングしたものを確認できます(--staged--cachedは同義です)。

$ git diff --cached
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8ebb991..643e24f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -65,7 +65,8 @@ branch directly, things can get messy.
 Please include a nice description of your changes when you submit your PR;
 if we have to read the whole diff to figure out why you're contributing
 in the first place, you're less likely to get feedback and have your change
-merged in.
+merged in. Also, split your changes into comprehensive chunks if your patch is
+longer than a dozen lines.

 If you are starting to work on a particular area, feel free to submit a PR
 that highlights your work in progress (and note in the PR title that it's
注意
外部ツールでのGit Diff

本書の残りの部分では、さまざまな方法でgit diffコマンドを使用し続けます。グラフィカルまたは外部のdiff表示プログラムを使用したい場合は、これらのdiffを表示する別の方法があります。git diffの代わりにgit difftoolを実行すると、emerge、vimdiff、その他多くのソフトウェア(商用製品を含む)でこれらのdiffを表示できます。システムで利用可能なものを確認するには、git difftool --tool-helpを実行してください。

変更のコミット

ステージング領域が目的どおりに設定されたら、変更をコミットできます。まだステージングされていないもの、つまり編集後にgit addを実行していない作成または変更したファイルは、このコミットに含まれないことに注意してください。これらは、ディスク上の変更されたファイルとして残ります。この場合、最後にgit statusを実行したときに、すべてがステージングされていることを確認したので、変更をコミットする準備が整ったとします。コミットする最も簡単な方法は、git commitと入力することです。

$ git commit

そうすると、選択したエディタが起動します。

注意

これは、シェルのEDITOR環境変数によって設定されます。通常はvimまたはemacsですが、はじめにで説明したように、git config --global core.editorコマンドを使用して、好きなように設定できます。

エディタには、次のテキストが表示されます(この例はVim画面です)。

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Your branch is up-to-date with 'origin/master'.
#
# Changes to be committed:
#	new file:   README
#	modified:   CONTRIBUTING.md
#
~
~
~
".git/COMMIT_EDITMSG" 9L, 283C

デフォルトのコミットメッセージには、git statusコマンドの最新の出力がコメントアウトされて含まれており、上に空行が1つあることがわかります。これらのコメントを削除してコミットメッセージを入力することも、コミットしている内容を思い出すためにそのままにしておくこともできます。

注意

変更した内容をより明確に確認するために、git commit-v オプションを渡すことができます。これを行うと、変更内容の差分がエディターに表示され、コミットする変更内容を正確に確認できます。

エディターを終了すると、Git はコミットメッセージ(コメントと差分は削除されます)でコミットを作成します。

あるいは、commit コマンドで -m フラグの後にコミットメッセージをインラインで記述することもできます。例えば、このようになります。

$ git commit -m "Story 182: fix benchmarks for speed"
[master 463dc4f] Story 182: fix benchmarks for speed
 2 files changed, 2 insertions(+)
 create mode 100644 README

これで最初のコミットが作成されました!コミットによって、どのブランチ(master)にコミットしたか、コミットの SHA-1 チェックサム(463dc4f)、変更されたファイル数、コミットで追加および削除された行数に関する情報が出力されていることがわかります。

コミットはステージングエリアに設定したスナップショットを記録することを覚えておいてください。ステージングしなかったものはすべて変更されたまま残っています。履歴に追加するには、別のコミットを実行できます。コミットを実行するたびに、後で元に戻したり比較したりできるプロジェクトのスナップショットを記録しています。

ステージングエリアをスキップする

ステージングエリアは、コミットを思い通りに作成するのに非常に役立つ場合がありますが、ワークフローに必要なよりも少し複雑な場合があります。ステージングエリアをスキップしたい場合、Git は簡単なショートカットを提供します。git commit コマンドに -a オプションを追加すると、Git はコミットを実行する前に、すでに追跡されているすべてのファイルを自動的にステージングするため、git add の部分をスキップできます。

$ git status
On branch master
Your branch is up-to-date with 'origin/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:   CONTRIBUTING.md

no changes added to commit (use "git add" and/or "git commit -a")
$ git commit -a -m 'Add new benchmarks'
[master 83e38c7] Add new benchmarks
 1 file changed, 5 insertions(+), 0 deletions(-)

この場合、コミットする前に CONTRIBUTING.md ファイルで git add を実行する必要がないことに注目してください。これは、-a フラグが変更されたすべてのファイルを含むためです。これは便利ですが、注意が必要です。このフラグを使用すると、不要な変更が含まれる場合があります。

ファイルを削除する

Git からファイルを削除するには、追跡対象のファイルから削除し(より正確には、ステージングエリアから削除し)、コミットする必要があります。git rm コマンドはこれを行い、ワーキングディレクトリからもファイルを削除するため、次回は未追跡ファイルとして表示されません。

単にワーキングディレクトリからファイルを削除すると、git status 出力の「コミット用にステージされていない変更」エリア(つまり、ステージされていない)に表示されます。

$ rm PROJECTS.md
$ git status
On branch master
Your branch is up-to-date with 'origin/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:    PROJECTS.md

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

次に、git rm を実行すると、ファイルの削除がステージングされます。

$ git rm PROJECTS.md
rm 'PROJECTS.md'
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    deleted:    PROJECTS.md

次回コミットすると、ファイルは削除され、追跡されなくなります。ファイルを変更した場合、またはすでにステージングエリアに追加している場合は、-f オプションを使用して削除を強制する必要があります。これは、スナップショットにまだ記録されておらず、Git から復元できないデータの誤った削除を防ぐための安全機能です。

もう一つ便利なことは、ファイルをワーキングツリーに保持したまま、ステージングエリアから削除することです。言い換えれば、ファイルをハードドライブに保持したまま、Git で追跡しないようにしたい場合があります。これは、.gitignore ファイルに何かを追加するのを忘れて、大きなログファイルやコンパイルされた .a ファイルのようなものを誤ってステージングした場合に特に役立ちます。これを行うには、--cached オプションを使用します。

$ git rm --cached README

git rm コマンドには、ファイル、ディレクトリ、およびファイルグロブパターンを渡すことができます。つまり、次のようなことができます。

$ git rm log/\*.log

* の前のバックスラッシュ(\)に注意してください。これは、Git がシェルによるファイル名展開に加えて、独自のファイル名展開を行うため必要です。このコマンドは、log/ ディレクトリにある .log 拡張子を持つすべてのファイルを削除します。または、次のようなこともできます。

$ git rm \*~

このコマンドは、名前が ~ で終わるすべてのファイルを削除します。

ファイルを移動する

他の多くの VCS とは異なり、Git はファイルの移動を明示的に追跡しません。Git でファイルの名前を変更した場合、ファイルの名前を変更したことを示すメタデータは Git に保存されません。ただし、Git は後でそれを把握することに非常に優れています。ファイルの移動の検出については、後で詳しく説明します。

したがって、Git に mv コマンドがあるのは少し紛らわしいです。Git でファイルの名前を変更する場合は、次のようなものを実行できます。

$ git mv file_from file_to

これは問題なく機能します。実際、これのようなものを実行してステータスを確認すると、Git はそれを名前が変更されたファイルと見なしていることがわかります。

$ git mv README.md README
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    renamed:    README.md -> README

ただし、これは次のようなものを実行するのと同等です。

$ mv README.md README
$ git rm README.md
$ git add README

Git はそれが暗黙的な名前の変更であると判断するため、そのようにファイルを名前変更するか、mv コマンドを使用するかは関係ありません。唯一の本当の違いは、git mv が 3 つのコマンドではなく 1 つのコマンドであることです。これは便利な機能です。さらに重要なことは、ファイルの名前を変更するために好きなツールを使用でき、コミットする前に後で add/rm を処理できることです。

scroll-to-top