これで、れっきとした Git リポジトリを準備して、そのプロジェクト内のファイルの作業コピーを取得することができました。 次は、そのコピーに対して何らかの変更を行い、適当な時点で変更内容のスナップショットをリポジトリにコミットすることになります。
作業コピー内の各ファイルには追跡されている(tracked)ものと追跡されてない(untracked)ものの二通りがあることを知っておきましょう。 追跡されているファイルとは、直近のスナップショットに存在したファイルのことです。これらのファイルについては変更されていない(unmodified)」「変更されている(modified)」「ステージされている(staged)」の三つの状態があります。 追跡されていないファイルは、そのどれでもありません。直近のスナップショットには存在せず、ステージングエリアにも存在しないファイルのことです。 最初にプロジェクトをクローンした時点では、すべてのファイルは「追跡されている」かつ「変更されていない」状態となります。チェックアウトしただけで何も編集していない状態だからです。
ファイルを編集すると、Git はそれを「変更された」とみなします。直近のコミットの後で変更が加えられたからです。変更されたファイルをステージし、それをコミットする。この繰り返しです。
どのファイルがどの状態にあるのかを知るために主に使うツールが git status
コマンドです。
このコマンドをクローン直後に実行すると、このような結果となるでしょう。
$ git status
On branch master
nothing to commit, working directory clean
これは、クリーンな作業コピーである (つまり、追跡されているファイルの中に変更されているものがない) ことを意味します。 また、追跡されていないファイルも存在しません (もし追跡されていないファイルがあれば、Git はそれを表示します)。 最後に、このコマンドを実行するとあなたが今どのブランチにいるのか、サーバー上の同一ブランチから分岐してしまっていないかどうかがわかります。 現時点では常に ``master'' となります。これはデフォルトであり、ここでは特に気にする必要はありません。 ブランチについては ch03-git-branching.asc で詳しく説明します。
ではここで、新しいファイルをプロジェクトに追加してみましょう。シンプルに、READMEファイルを追加してみます。
それ以前に README ファイルがなかった場合、git status
を実行すると次のように表示されます。
$ echo 'My Project' > README
$ git status
On branch 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)
出力結果の ``Untracked files'' 欄にREADMEファイルがあることから、このファイルが追跡されていないということがわかります。 これは、Git が「前回のスナップショット (コミット) にはこのファイルが存在しなかった」とみなしたということです。明示的に指示しない限り、Git はコミット時にこのファイルを含めることはありません。 自動生成されたバイナリファイルなど、コミットしたくないファイルを間違えてコミットしてしまう心配はないということです。 今回は README をコミットに含めたいわけですから、まずファイルを追跡対象に含めるようにしましょう。
新しいファイルの追跡を開始するには git add
コマンドを使用します。
READMEファイルの追跡を開始する場合はこのようになります。
$ git add README
再び status コマンドを実行すると、READMEファイルが追跡対象となってステージされており、コミットする準備ができていることがわかるでしょう。
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: README
ステージされていると判断できるのは、 `Changes to be committed'' 欄に表示されているからです。
ここでコミットを行うと、`git add
した時点の状態のファイルがスナップショットとして歴史に書き込まれます。
先ほど git init
をしたときに、ディレクトリ内のファイルを追跡するためにその後 git add (ファイル)
としたことを思い出すことでしょう。
git add
コマンドには、ファイルあるいはディレクトリのパスを指定します。ディレクトリを指定した場合は、そのディレクトリ以下にあるすべてのファイルを再帰的に追加します。
すでに追跡対象となっているファイルを変更してみましょう。
たとえば、すでに追跡対象となっているファイル CONTRIBUTING.md
を変更して git status
コマンドを実行すると、結果はこのようになります。
$ git status
On branch 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
ファイルは Changed but not staged for commit'' という欄に表示されます。これは、追跡対象のファイルが作業ディレクトリ内で変更されたけれどもまだステージされていないという意味です。
ステージするには
指定したファイルをプロジェクトに追加(add)する''コマンド、というよりは、git add
コマンドを実行します。 git add
にはいろんな意味合いがあり、新しいファイルの追跡開始・ファイルのステージング・マージ時に衝突が発生したファイルに対する「解決済み」マーク付けなどで使用します。`指定した内容を次のコミットに追加(add)する''コマンド、と捉えるほうがわかりやすいかもしれません。
では、`git add
で CONTRIBUTING.md
をステージしてもういちど git status
を実行してみましょう。
$ git add CONTRIBUTING.md
$ git status
On branch 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
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
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文字の組み合わせです。1文字目はステージされたファイルの状態を、2文字はファイルが変更されたかどうかを示しています。 この例でいうと、`README`ファイルは作業ディレクトリ上にあって変更されているけれどステージされてはいません。 `lib/simplegit.rb`ファイルは変更済みでステージもされています。 `Rakefile`のほうはどうかというと、変更されステージされたあと、また変更された、という状態です。変更の内容にステージされたものとそうでないものがあることになります。
ある種のファイルについては、Git で自動的に追加してほしくないしそもそも「追跡されていない」と表示されるのも気になってしまう。そんなことがよくあります。
たとえば、ログファイルやビルドシステムが生成するファイルなどの自動生成されるファイルがそれにあたるでしょう。
そんな場合は、無視させたいファイルのパターンを並べた .gitignore
というファイルを作成します。
.gitignore
ファイルは、たとえばこのようになります。
$ cat .gitignore
*.[oa]
*~
最初の行は .o'' あるいは
.a'' で終わる名前のファイル (コードをビルドする際にできるであろうオブジェクトファイルとアーカイブファイル) を無視するよう Git に伝えています。次の行で Git に無視させているのは、チルダ (~
) で終わる名前のファイルです。Emacs をはじめとする多くのエディタが、この形式の一時ファイルを作成します。これ以外には、たとえば log、tmp、pid といった名前のディレクトリや自動生成されるドキュメントなどもここに含めることになるでしょう。実際に作業を始める前に .gitignore
ファイルを準備しておくことをお勧めします。そうすれば、予期せぬファイルを間違って Git リポジトリにコミットしてしまう事故を防げます。
.gitignore
ファイルに記述するパターンの規則は、次のようになります。
-
空行あるいは
#
で始まる行は無視される -
標準の glob パターンを使用可能
-
再帰を避けるためには、パターンの最初にスラッシュ (
/
) をつける -
ディレクトリを指定するには、パターンの最後にスラッシュ (
/
) をつける -
パターンを逆転させるには、最初に感嘆符 (
!
) をつける
glob パターンとは、シェルで用いる簡易正規表現のようなものです。
アスタリスク () は、ゼロ個以上の文字にマッチします。
[abc]
は、角括弧内の任意の文字 (この場合は a、b あるいは c) にマッチします。
疑問符 (?
) は一文字にマッチします。
また、ハイフン区切りの文字を角括弧で囲んだ形式 ([0-9]
) は、
ふたつの文字の間の任意の文字 (この場合は 0 から 9 までの間の文字) にマッチします。
アスタリクスを2つ続けて、ネストされたディレクトリにマッチさせることもできます。
a/
*/z
のように書けば、a/z
、a/b/z
、`a/b/c/z`などにマッチします。
では、.gitignore
ファイルの例をもうひとつ見てみましょう。
# no .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 the build/ directory
build/
# ignore doc/notes.txt, but not doc/server/arch.txt
doc/*.txt
# ignore all .pdf files in the doc/ directory
doc/**/*.pdf
Tip
|
GitHubが管理している |
git status
コマンドだけではよくわからない
(どのファイルが変更されたのかだけではなく、実際にどのように変わったのかが知りたい)
という場合は git diff
コマンドを使用します。
git diff
コマンドについては後で詳しく解説します。
おそらく、最もよく使う場面としては次の二つの問いに答えるときになるでしょう。
「変更したけどまだステージしていない変更は?」「コミット対象としてステージした変更は?」
git status
が出力するファイル名のリストを見れば、
これらの質問に対するおおまかな答えは得られますが、
git diff
の場合は追加したり削除したりした正確な行をパッチ形式で表示します。
先ほどの続きで、ふたたび README
ファイルを編集してステージし、
一方 CONTRIBUTING.md
ファイルは編集だけしてステージしない状態にあると仮定しましょう。
ここで git status
コマンドを実行すると、次のような結果となります。
$ git status
On branch 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
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
Note
|
GitのDiffを他のツールで見る
この本では、引き続き`git diff`コマンドを様々な方法で使っていきます。 一方、このコマンドを使わずに差分を見る方法も用意されています。GUIベースだったり、他のツールが好みの場合、役に立つでしょう。 `git diff`の代わりに`git difftool`を実行してください。そうすれば、emerge、vimdiffなどのツールを使って差分を見られます(商用のツールもいくつもあります)。 また、`git difftool --tool-help`を実行すれば、利用可能なdiffツールを確認することもできます。 |
ステージングエリアの準備ができたら、変更内容をコミットすることができます。
コミットの対象となるのはステージされたものだけ、
つまり追加したり変更したりしただけでまだ git add
を実行していないファイルはコミットされないことを覚えておきましょう。
そういったファイルは、変更されたままの状態でディスク上に残ります。
ここでは、最後に git status
を実行したときにすべてがステージされていることを確認したとしましょう。つまり、変更をコミットする準備ができた状態です。
コミットするための最もシンプルな方法は git commit
と打ち込むことです。
$ git commit
これを実行すると、指定したエディタが立ち上がります
(シェルの $EDITOR
環境変数で設定されているエディタ。
通常は vim あるいは emacs でしょう。しかし、
それ以外にもch01-introduction.ascで説明した 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
# Changes to be committed:
# new file: README
# modified: CONTRIBUTING.md
#
~
~
~
".git/COMMIT_EDITMSG" 9L, 283C
デフォルトのコミットメッセージとして、
直近の git status
コマンドの結果がコメントアウトして表示され、
先頭に空行があることがわかるでしょう。
このコメントを消して自分でコミットメッセージを書き入れていくこともできますし、
何をコミットしようとしているのかの確認のためにそのまま残しておいてもかまいません
(何を変更したのかをより明確に知りたい場合は、git commit
に -v
オプションを指定します。
そうすると、diff の内容がエディタに表示されるので何をコミットしようとしているかが正確にわかるようになります)。
エディタを終了させると、Git はそのメッセージつきのコミットを作成します
(コメントおよび diff は削除されます)。
あるいは、コミットメッセージをインラインで記述することもできます。その場合は、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
)」「変更されたファイルの数」「そのコミットで追加されたり削除されたりした行数」
といった情報が表示されているのがわかるでしょう。
コミットが記録するのは、ステージングエリアのスナップショットであることを覚えておきましょう。 ステージしていない情報については変更された状態のまま残っています。 別のコミットで歴史にそれを書き加えるには、改めて add する必要があります。 コミットするたびにプロジェクトのスナップショットが記録され、あとからそれを取り消したり参照したりできるようになります。
コミットの内容を思い通りに作り上げることができるという点でステージングエリアは非常に便利なのですが、
普段の作業においては必要以上に複雑に感じられることもあるでしょう。
ステージングエリアを省略したい場合のために、Git ではシンプルなショートカットを用意しています。
git commit
コマンドに -a
オプションを指定すると、追跡対象となっているファイルを自動的にステージしてからコミットを行います。
つまり git add
を省略できるというわけです。
$ 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: CONTRIBUTING.md
no changes added to commit (use "git add" and/or "git commit -a")
$ git commit -a -m 'added new benchmarks'
[master 83e38c7] added new benchmarks
1 file changed, 5 insertions(+), 0 deletions(-)
この場合、コミットする前に CONTRIBUTING.md
を git add
する必要がないことに気づいたでしょうか。
-a
というフラグのおかげで、変更したファイルがすべてコミットに含まれたからです。
このように -a
は便利なフラグですが、ときには意図しない変更をコミットに含んでしまうことにもなりますので気をつけましょう。
ファイルを Git から削除するには、追跡対象からはずし (より正確に言うとステージングエリアから削除し)、そしてコミットします。
git rm
コマンドは、この作業を行い、そして作業ディレクトリからファイルを削除します。
つまり、追跡されていないファイルとして残り続けることはありません。
単に作業ディレクトリからファイルを削除しただけの場合は、git status
の出力の中では ``Changed but not updated'' (つまり ステージされていない) 欄に表示されます。
$ 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
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
deleted: PROJECTS.md
次にコミットするときにファイルが削除され、追跡対象外となります。
変更したファイルをすでにステージしている場合は、-f
オプションで強制的に削除しなければなりません。
まだスナップショットに記録されていないファイルを誤って削除してしまうと Git で復旧することができなくなってしまうので、それを防ぐための安全装置です。
ほかに「こんなことできたらいいな」と思われるであろう機能として、
ファイル自体は作業ツリーに残しつつステージングエリアからの削除だけを行うこともできます。
つまり、ハードディスク上にはファイルを残しておきたいけれど、もう Git では追跡させたくないというような場合のことです。
これが特に便利なのは、.gitignore
ファイルに書き足すのを忘れたために巨大なログファイルや大量の .a
ファイルがステージされてしまったなどというときです。
そんな場合は --cached
オプションを使用します。
$ git rm --cached README
ファイル名やディレクトリ名、そしてファイル glob パターンを 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
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
コマンドを使おうがどちらでもかまいません。
唯一の違いは、この方法だと 3 つのコマンドが必要になるかわりに mv
だとひとつのコマンドだけで実行できるという点です。
より重要なのは、ファイル名の変更は何でもお好みのツールで行えるということです。あとでコミットする前に add/rm を指示してやればいいのです。