Gitの基本的な使い方

はじめに

それではいよいよGitの操作を見ていこう。Gitは「git コマンド オプション 対象」といった形で操作する。Gitには大量のコマンドがあり、さらにそれぞれに多くのオプションがある。それらを全て覚えるのは現実的ではない。まずはよく使うコマンドとオプションだけ覚えよう。また、Gitはヘルプが充実している。「あのコマンドなんだっけ?」と思ったら、git helpを実行しよう。また、コマンドの詳細を知りたければgit help commandで詳細なヘルプが表示されるので、合わせて覚えておくこと。たとえばgit help helpで、helpコマンドのヘルプを見ることができる。

Gitに限らず、使い方がわからないコマンドがあった時に、まずは公式ドキュメントやヘルプを参照する癖をつけておきたい。広く使われているツールは、公式のドキュメントやチュートリアルが充実していることが多い。例えばGitであればPro GitというGitの本がウェブで公開されている。また、git helpで表示されるヘルプも非常に充実している。公式ドキュメント及びヘルプを読めるか読めないか(読むか読まないか)で学習効率が大きく異なる。「困ったらまずは公式」という習慣をつけておこう。

初期設定

まず、最初にやるべきことは、Gitに名前とメールアドレスを教えてやることだ。この二つを設定しておかないと、Gitのコミットができない。名前やメールアドレスが未設定のままコミットをしようとすると、こんなメッセージが表示される。

*** Please tell me who you are.

Run

  git config --global user.email "you@example.com"
  git config --global user.name "Your Name"

to set your account's default identity.
Omit --global to set the identity only in this repository.

Gitはエラーが親切で、何か問題が起きた時に「こうすればいいよ」と教えてくれることが多い。今回も、このメッセージに表示されている通り、git config --global命令を使って、メールアドレスと名前を登録しよう。

git config --global user.name "H. Watanabe"
git config --global user.email hwatanabe@example.com

また、念のためにデフォルトエディタをvimにしておこう。

git config --global core.editor vim

さらに、デフォルトブランチの名前をmasterからmainに変更しておく。

git config --global init.defaultBranch main

以上で設定は完了だ。ここで、--globalオプションは、そのコンピュータ全体で有効な情報を登録するよ、という意味だ。具体的に、今回登録した内容はホームディレクトリの.gitconfigの中に表示されている。見てみよう。

$ cat .gitconfig
[user]
        name = H. Watanabe
        email = hwatanabe@example.com
[core]
        editor = vim
[init]
        defaultBranch = main

git configuser.nameで指定した項目が、userセクションのnameの値として登録されている。基本的にはGitの設定はgit configでコマンドラインから指定するが、直接このファイルを編集して設定することも可能だ。

また、プロジェクト固有の設定を登録したい場合は、そのプロジェクトの中で

git config user.name "John Git"

などと、--globalをつけずに設定すると、そちらの設定が優先される。複数のプロジェクトで名前やメールアドレスを使い分けたいことがあるかもしれないので、覚えておくと良い。

なお、現在の設定はgit config -lで表示できるが、そのオプション-lを忘れたとしよう。その場合は

git help config

を実行し、ヘルプを見よう。

       -l, --list
           List all variables set in config file, along with their values.

という項目を見つけて--listが目的のオプションであり、-lがその短縮形であることがわかる。

Gitの一連の操作

Gitではリポジトリを初期化したのち、「修正をステージングしてはコミット」という作業を繰り返すことで歴史を作っていく。以下では初期化、ステージング、コミットまでの一連の操作を見てみよう。

リポジトリの初期化

リポジトリを作るには、git initコマンドを用いる。作り方は「すでに存在するプロジェクトのディレクトリをGit管理にする方法」と「最初からGit管理されたディレクトリを作る方法」の二通りだ。以下では既存のディレクトリをGitリポジトリとして初期化することにしよう。

まず、ホームディレクトリにprojectというディレクトリを作り、その中へ移動しよう。

cd            # ホームディレクトリへ移動
mkdir project # projectディレクトリを作成
cd project    # projectディレクトリへ移動

カレントディレクトリがprojectディレクトリである時にgit initすることでGitの初期化が行われる。

$ pwd                      # カレントディレクトリの確認
/c/Users/watanabe/project

$ git init                 # カレントディレクトリをGitリポジトリとして初期化
Initialized empty Git repository in C:/Users/watanabe/project/.git/

すると、projectディレクトリ直下に.gitというディレクトリが作られる。Gitの管理情報は全てこのディレクトリに格納される。また、Git Bashを使っているなら、プロンプトに~/project (main)と、Git管理されたディレクトリであり、現在のブランチがmainであることが表示されたはずだ。

最初のコミット

初期化直後のGitリポジトリには、全く歴史が保存されていない。そこで、最初のコミットを作ろう。そのために、管理したいファイルをインデックスに追加する必要がある。すでに述べたように、Gitはコミットを作る前に、インデックスにコミットされるスナップショットを作る。これをステージングと呼ぶ。インデックスにステージングするコマンドがgit addだ。

例えば先ほど作成したprojectの中にREADME.mdを作り、それを追加しよう。

echo "Hello" > README.md
git add README.md

現在の状態を見るのは、git statusコマンドを使う。

$ git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   README.md

これは、

ということを意味している。

早速最初のコミットを作ろう。コミットはgit commitコマンドを使う。

git commit

すると、デフォルトエディタ(本講義の設定ではvim)が起動し、以下のような画面が表示される。


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch main
#
# Initial commit
#
# Changes to be committed:
#       new file:   README.md
#

ここでコミットメッセージを書く。最初のコミットメッセージはinitial commitとすることが多い。なお、#で始まる行はコミットメッセージには含まれない。コミットメッセージを入力し、ファイルを保存してエディタを終了するとコミットが実行される。

$ git commit
[main (root-commit) 9d8aab0] initial commit
 1 file changed, 1 insertion(+)
 create mode 100644 README.md

これは

ということを意味している。Gitはコマンド実行時やエラー時にわりとていねいなメッセージが出る。それらをスルーせず、ちゃんと意味を理解しようとするのがGitの理解の早道だ。

ここでコミットハッシュという言葉が出てきた。Gitでは歴史をコミットで管理しており、コミットは「コミットされた時点でのプロジェクトのスナップショット」を表す。そのコミットを区別する一意な識別子がコミットハッシュである。先ほどはコミットハッシュの上位7桁しか表示されなかったが、実際には40桁ある。ハッシュ値の計算にはSHA-1というアルゴリズムが用いられている(詳細は「Gitの仕組み」の項で触れる)。

これで最初の歴史が作られた。過去のコミットを見てみよう。履歴を見るにはgit logコマンドを使う。

$ git log
commit 9d8aab06e0a1f1b152546db086fe7737a02526e1 (HEAD -> main)
Author: H. Watanabe <hwatanabe@example.com>
Date:   Thu Sep 16 17:15:41 2021 +0900

    initial commit

これは、

ということを表している。繰り返しになるが、Gitの出力するメッセージを面倒くさがらずにちゃんと理解しようとするのがGitの理解の早道だ。

修正をコミット

次に、README.mdを修正し、その修正をコミットしよう。Vimで修正してもよいが、VS Codeで編集しよう。VS Codeの「ファイル」メニューの「フォルダーを開く」から、いまGit Bashで見ているカレントディレクトリを開こう。例えば上記の例なら「PC」→「Windows (C:)」→「ユーザー」→「自分のユーザ名(例えばwatanabe)」と進み、「project」を選択した状態で「フォルダーの選択」を選べばよい。

もしくは、Git Bashでカレントディレクトリがprojectである状態から、

code .

を実行すると、このディレクトリをVS Codeで開くことができる(環境によってはできないこともあるので、その場合は「ファイル」メニューから「フォルダーを開く」で開くこと)。

左のエクスプローラーからREADME.mdを選び、以下のように行を追加する。

Hello
Update

修正した状態でgit statusを実行してみよう。

$ git status
On branch main
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

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

これは、

ということを意味する。また、git statusには-sオプションがあり、表示が簡略化される。

$ git status -s
 M README.md

ファイルの隣にMという文字が表示された。これはワーキングツリーで表示されたが、インデックスには変更がないことを示す。

この状態で差分を見てみよう。git diffを実行する。

$ git diff
diff --git a/README.md b/README.md
index e965047..9c99d1a 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,2 @@
 Hello
+Update

これは、ワーキングツリーとインデックスを比較して、README.mdに変更があり、ワーキングツリーには「Update」という行が追加されていることを示す。

では、この修正をgit addでステージングしよう。

git add README.md

これで、修正がステージングされた。この状態で、ワーキングツリーとインデックスは同じ状態となり、リポジトリにはまだ修正が反映されていない状態となっている。

git diffを実行しても何も表示されない。

$ git diff

これは、git diffに何も引数を渡さないと、ワーキングツリーとインデックスの差分を表示するからだ。リポジトリのmainブランチの状態は古いので、その状態と比較すると差分が表示される。インデックスとリポジトリの差分を表示する場合は--cachedオプションをつける。

$ git diff --cached
diff --git a/README.md b/README.md
index e965047..9c99d1a 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,2 @@
 Hello
+Update

また、git statusの表示も見てみよう。

$ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   README.md

先ほど、「Changes not staged for commit:」となっていた部分が、「Changes to be committed:」となっている。これは我々が修正をインデックスにステージングしたからだ。簡略版も表示させよう。

$ git status -s
M  README.md

先ほどと異なり、二桁目は空白、一桁目に緑色でMが表示された。実は、一桁目がインデックスとリポジトリの差分、二桁目がインデックスとワーキングツリーの差分を示している。慣れたらgit statusよりもgit status -sの方を使うことが多いと思われる。

ではコミットしよう。先ほどはコミットメッセージをエディタで書いたが、-mオプションで直接コマンドラインで指定することもできる。

$ git commit -m "updates README.md"
[main a736d82] updates README.md
 1 file changed, 1 insertion(+)

新たにa736d82というコミットが作られ、歴史に追加された。歴史を表示させてみよう。

$ git log
commit a736d82251279f592a25e38503bb9130bac12481 (HEAD -> main)
Author: H. Watanabe <kaityo@users.sourceforge.jp>
Date:   Thu Sep 16 19:13:34 2021 +0900

    updates README.md

commit 9d8aab06e0a1f1b152546db086fe7737a02526e1
Author: H. Watanabe <kaityo@users.sourceforge.jp>
Date:   Thu Sep 16 17:15:41 2021 +0900

    initial commit

二つのコミットができている。git log--onelineオプションをつけるとコミットを一行表示してくれる。

$ git log --oneline
a736d82 (HEAD -> main) updates README.md
9d8aab0 initial commit

その他、git logには多くのオプションがあるので、必要に応じて覚えると良い。

コミットの後は、ワーキングツリーは「きれいな状態」になる。

$ git status
On branch main
nothing to commit, working tree clean

ここまでのまとめ

以上、リポジトリを初期化し、ファイルをインデックスに登録し、最初のコミットをして、修正してさらにコミットをする、という一連の動作を確認した。その過程で以下のコマンドを使った。

以降、それぞれのコマンドについて簡単に説明しておこう。

git init

リポジトリを作るには、git initコマンドを用いる。作り方は大きく分けて「すでに存在するプロジェクトのディレクトリをGit管理にする方法」と「最初からGit管理されたディレクトリを作る方法」の二通りだ。

projectというディレクトリがあり、そこにGit管理したいファイルやディレクトリがある場合は、そのprojectディレクトリの一番上でgit initすることでGitの初期化が行われる。

$ pwd
/c/Users/watanabe/project  # 現在、projectというディレクトリの中にいる
$ git init                 # カレントディレクトリをGitリポジトリとして初期化
Initialized empty Git repository in C:/Users/watanabe/project/.git/

すると、projectディレクトリ直下に.gitというディレクトリが作られる。Gitの管理情報は全てこのディレクトリに格納される。プロジェクトがディレクトリを含む場合、その下でgit initしないように気を付けよう。親子関係にあるディレクトリに複数の.gitが存在すると動作がおかしくなるので注意したい。

もう一つの方法は、空のリポジトリをディレクトリごと作る方法だ。

$ pwd
/c/Users/watanabe         # 現在、ホームディレクトリにいる
$ git init project        # projectというディレクトリを作成して初期化
Initialized empty Git repository in C:/Users/watanabe/project/.git/

先ほどとコマンドを実行した場所は異なるが、同じ場所に.gitが作られた。

管理したいディレクトリの中でgit initする方法と、git init projectnameとしてディレクトリごと作る方法のどちらを使ってもよいが、一般的にはある程度形になってから「じゃあGitで管理するか」と思うであろうから、前者を使うことが多いであろう。

git init--bareをつけるとベアリポジトリを作成する。ベアリポジトリとは.gitディレクトリの中身しかないようなリポジトリだ。ベアリポジトリについては「リモートリポジトリの操作」の項で触れる。

git add

git addは誤解されやすいコマンドだ。このコマンドは以下の三つの役割で使われる。

実は、これらは全て

という作業を行っているに過ぎない。Gitでは、まずインデックスに「作りたいコミットの姿」を作り、そのあとでコミットをすることでコミットが作られる。git addは、ワーキングツリーからインデックスにファイルをコピーする。

まず、ワーキングツリーにはあるが、リポジトリにもインデックスにも存在しないファイルをgit addすると、ワーキングツリーからインデックスにコピーされる。

add1.png

また、インデックスにもリポジトリにも存在するファイル、つまりGit管理下にあるが、ワーキングツリーで修正されたファイルをgit addすると、ワーキングツリーにあるファイルでインデックスにあるファイルを上書きする。

add2.png

また、後述する「マージの際の衝突」が起きた時に、衝突が解消されたことを教えるにもgit addを使うが、それもインデックスに作りたいコミットの姿を作り、それをコミットしている、ということがわかれば動作を理解しやすい。

git commit

インデックスに必要な修正をステージングしたら、git commitすることでコミットする。コミットとは、インデックスに登録されている状態(スナップショット)を、コミットとして歴史に追加する操作だ。Gitではコミットをする際にコミットメッセージをつけることが必須であり、単にgit commitとオプション無しで実行すると、デフォルトエディタが開いてコミットメッセージを求められる。しかし、-mオプションに続けてコミットメッセージを書けば、エディタを開くことなくコマンドラインからコミットができる。

git commit -m "commit message"

将来、開発チームなどでコミットメッセージのフォーマット指定、特に複数行書く必要がある場合はエディタで書いた方が良いが、そうでない個人用途であれば-mオプションでコミットメッセージを直接記述してしまった方が楽であろう。また、VS Codeなどからコミットする場合は、コミットメッセージもVS Code上で書くことができる。

Gitはコミットの前に修正したファイルをgit addによりインデックスに登録する必要があるが、git commit-aオプションをつけることで、Git管理下にあって、かつ修正されたファイルを全ていきなりコミットすることができる。-mオプションと合わせて、

git commit -am "commit message"

などと使うことになるだろう。個人管理のプロジェクト等でいちいちインデックスに登録する必要性を感じない場合はgit commit -amを使ってよいが、「Gitはインデックスに登録した状態をコミットとして保存する」という感覚になれるまでは、愚直にgit addgit commitした方が良い。

git diff

diff

Gitには、「プロジェクトの状態を表現するもの」として、ワーキングツリー、インデックス、コミットの三つがある。それらの間の差分を表示するのがgit diffコマンドだ。git diffは、引数やオプションの指定により様々なものの差分を表示できる。git diffにはうんざりするほど多くのオプションがあるが、よく使うのは以下の三つであろう。

あとでマージについて説明するが、マージの前にgit diffにより差分を表示する癖をつけておきたい。

git log

過去の履歴を見るにはgit logを用いる。git logは、カレントブランチが指すコミットから、次々と親コミットをさかのぼりながら表示する。その「歴史」に含まれるコミットを指すブランチがあれば、それも表示してくれる。git logも非常に多くのオプションがあるが、使いそうなものをいくつか紹介する。

その他のオプションについては、一度git help logを見てみると良い。

git config

git configで様々な設定を行う。--globalをつけるとそのマシン全体での設定、つけなかった場合は、そのリポジトリローカルでの設定を行う。リポジトリごとに異なるメールアドレスや名前を使いたいことはよくあるので、その場合は--globalを付けずに設定すると良い。

また、git configの便利な機能としてエイリアスの設定がある。これは

git config --global alias.短縮コマンド名 "実際のコマンド"

という形で、「実際のコマンド」に「短縮コマンド名」という別名を与えることができる。

例えば、git log --graph --onelineというコマンドをよく使うとして、いちいちこんな長いコマンドを入力したくない場合、

git config --global alias.g "log --graph --oneline"

として設定すると、以後

git g

を実行すれば、

git log --graph --oneline

を実行したのと同じ効果が得られる。他にも

git config --global alias.st "status -s"

などが良く使われるエイリアスである。git log系のエイリアスは各自の趣味が強く反映されるため、興味のある人は「git log エイリアス」などで検索してみると良い。

.gitignore

プロジェクトディレクトリには置いておきたいが、Git管理したくないファイルというものがある。C++のオブジェクトファイルや実行ファイル、数値計算の実行結果などだ。例えばtest.datというデータファイルがあり、かつこれをGit管理していないとしよう。この状態でgit status -sを実行すると、

$ git status -s
?? test.dat

と、「Git管理下に置かれていないよ」と表示される。特に多数のデータファイルを生成するようなリポジトリでは、git status -sの出力が??で埋め尽くされて、肝心の管理しているファイルの状態が見えづらくなる。この時、Gitに「このファイルを無視せよ」と指示するのが.gitignoreファイルだ。Gitは.gitignoreファイルに書かれたファイルを無視する。例えば.gitignore

test.dat

と書くと、test.datを無視する。見てみよう。

$ git status -s
?? .gitignore

今度は「.gitignoreというファイルが管理されていないよ」というメッセージが表示された。通常、.gitignoreはGit管理したいファイルであるから、作成したら

git add .gitignore
git commit -m "adds .gitignore"

などとしてコミットしておこう。これにより、test.datがあってもgit status等のコマンドが無視するようになる。

$ ls
test.dat  test.txt # test.datとtest.txtがある
$ git status -s    # 何も表示されない

なお、.gitignoreに記載されたファイルは、.gitignoreが置いてあるディレクトリを含む、サブディレクト以下全てで無視される。したがって、ディレクトリ毎に無視したいファイルが異なる場合は、ディレクトリ毎に.gitignoreファイルを置くと良い。また、無視するファイルにはワイルドカートが使える。例えば.gitignore

*.dat

と記載すると、拡張子が.datであるようなファイル全てが無視される。ワイルドカードの詳細についてはここでは述べないが、「パターンマッチのような方法で複数のファイルをまとめて無視する方法がある」ということだけ覚えておけばよい。

まとめ

ここでは、以下のコマンドについて触れた。

Gitのコマンドはオプションが非常に多いが、それらは必要に応じて覚えておけば良い。コマンドについては使っているうちに慣れるであろうが、git addだけは「ワーキングツリーからインデックスにファイルをコピーするコマンドである」というイメージを大事にすると良い。git commitについては、メッセージをコマンドラインから指定する-mは使ってよいが、ステージングを省く-aオプションについては慣れるまでは使わないようにすると良いだろう。