Git入門

最終更新: 20161008 吉岡琢

ここではGitの使い方を一通り説明します. 特に, Gitを理解するために重要な概念をできるだけ具体的に説明します. 一方, Gitの意義やインストールに関する説明はしません. Gitのディレクトリにパスが通っており, Gitのコマンドが利用可能であると仮定します. 例では一貫して ~/Download/repo[n] ディレクトリを使用します. 断りが無い限り, 例のコマンドは全てこのディレクトリで実行します. 例は最初から順に実行すると仮定します. 例2を確認する場合, 例1が終了していることを前提とします.

0. 初期設定

コンソール上で以下のコマンドを入力し, ユーザ名とメールアドレスを入力します.

$ git config --global user.name "<ユーザ名>"
$ git config --global user.email "<メールアドレス>"

ユーザ名とメールアドレスはコミット(後述)を実行したユーザの識別に使用されます. 上記コマンドを実行すると, ユーザのホームディレクトリに .gitconfig というファイルが作成されます. このファイルの中身を見ると設定内容が分かります. 上記の設定は git config を実行したユーザが扱う全てのリポジトリ(後述)で有効です.

(オプション):code:.gitconfig ファイルに以下の行を追加することで, Gitコマンドのエイリアスを設定します.

[alias]
    lg = log --graph --date=short --format=\"%C(yellow)%h%C(reset) %C(magenta)[%ad]%C(reset)%C(auto)%d%C(reset) %s %C(cyan)@%an%C(reset)\"

上記の設定後, リポジトリ内で git lg と入力すると, ログが分かりやすく表示されます.

1. リポジトリ

リポジトリとはファイルとその変更履歴をまとめたものです. Gitにおけるリポジトリの実体は, リポジトリのルートディレクトリに存在する .git ディレクトリです. 実際, .git ディレクトリを削除するとリポジトリの情報は全て消えます. リポジトリのルートディレクトリ以下でGitコマンド(例えば git addgit status など)を実行すると, そのリポジトリに対して操作が実行されます.

例1: リポジトリの作成

リポジトリを作成するためにコマンド git init を使用します. リポジトリの作成とは, プロジェクトの構成要素(主にテキストファイル)を含むディレクトリをGitのバージョン管理下に置くことです. ディレクトリ名は何でも良いのですが, ここの説明では ~/Download/repo1 という名前にします. ディレクトリをGitのバージョン管理下に置くにはコマンド git init を使用します.

$ mkdir -p ~/Download/repo1
$ cd ~/Download/repo1
$ git init

これでこのディレクトリがリポジトリになりました.

2. ステージングとコミット

コミットとはリポジトリのある時点でのスナップショットです. コミットはその直近の祖先へのポインタを持ちます. これにより, コミットはリポジトリに対する追加・変更の履歴を表現するグラフとして表現されます. これをコミットグラフと呼びます(参考リンク).

コミットという単語はスナップショットをコミットグラフに追加する操作を指すこともあります. この場合, 「ファイルをコミットする」「変更をコミットする」というように使われます.

Note

ネット上で, Gitはコミットをスナップショットとして格納するという説明を見かけることがあります. 実際は, コミットを構成するオブジェクトに対して差分管理が適用されます. スナップショットの系列の差分を抽出してファイルを圧縮するプロセスをGitではパッキングと呼びます. これは新しく追加された機能ということです(参考リンク).

ステージング

ステージングとはコミットする変更内容をリポジトリに通知する事です. ステージングを何度も行う事で, 複数の変更を同時にコミットできます. さらに, 変更されたファイルの一部をステージングすることもできます. 例えば, 一つのファイルに二つの機能を追加した場合, それらを別々のコミットとして扱う事が出来ます. そのためには, コマンド git add のオプション -p を指定します. ステージングされた変更は, コミットする前であれば取り消すことができます.

例2: ステージングとコミット

この例ではコマンド git status, git add, git commit, :code:`git lg`(エイリアスとして追加したもので, 標準のコマンドではありません)を使用します. 適当なファイルを作成し, リポジトリにコミットしてみましょう. 以下のコマンドを入力して空のファイルを作成します.

$ cd ~/Download/repo1
$ touch source.txt # ファイル名は適当です
  • コマンド git status を用いてリポジトリの状態を見てみましょう.
$ git status
On branch master

Initial commit

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

        source.txt

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

まず, 現在”master”という名前のブランチ(後述)にいることが分かります. そして, 作成した source.txt が追跡(バージョン管理)の対象になっていないことが分かります. このファイルを追跡対象とするためにはコマンド git add を使用します.

$ git add source.txt
$ git status
On branch master

Initial commit

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

        new file:   source.txt

ファイルが追跡対象として追加され(new file), かつステージングされました(Changes to be committed). これでファイルをコミットする準備ができました. コマンド git commit でコミットします.

$  git commit -m "First commit"
[master (root-commit) 8c86d01] First commit
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 source.txt

ここでオプション -m "<文字列>" はコミットのメッセージを設定します.

現在のリポジトリの状態を確認します.

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

このメッセージは, このリポジトリの全てのファイル(今は source.txt だけです)に直前のコミット以降変更が無いことを意味します. エイリアスとして登録した git lg を用いてコミットグラフを確認します.

$ git lg
* 8c86d01 [2016-10-05] (HEAD -> master) First commit @taku-y

これで最初のコミットを確認できました. 先頭の c8e4a5c はコミットを識別するハッシュ値を表します.

ファイルに変更を加えてその内容を確認します. 変更箇所の確認のためには git diff を使用します.

$ echo "string" > source.txt
$ git diff
diff --git a/source.txt b/source.txt
index e69de29..ee8a39c 100644
--- a/source.txt
+++ b/source.txt
@@ -0,0 +1 @@
+string

ファイルに対する変更が確認できました. コミットします.

$ git add . # "." は変更があった全てのファイルをステージングすることを意味します.
$ git commit -m "Modify a file."
[master ef783b7] Modify a file
 1 file changed, 1 insertion(+)

コミットグラフを確認します.

$ git lg
* ef783b7 [2016-10-05] (HEAD -> master) Modify a file @taku-y
* 8c86d01 [2016-10-05] First commit @taku-y

新たなコミットが追加されたことが分かります.

3. バージョン管理対象の制御

.gitignore ファイルはGitバージョン管理の対象としないファイル(例えばコンパイラが出力する中間ファイル)を指定するものです. VC++やPythonなど各種プロジェクトに適した .gitignore ファイルのテンプレートがネット上にあります. 必要に応じて検索しましょう.

例3: .gitignore の設定

中間ファイルとして tmp というファイルが生成されたとします.

$ touch tmp

コマンド git status でリポジトリの状態を確認します.

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

        tmp

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

中間ファイルは追跡対象としたくないので, .gitignore ファイルを作成し, 中間ファイル名を追加します.

$ echo tmp > .gitignore

リポジトリの状態を確認します.

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

        .gitignore

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

先ほどの状態と比べると, tmp ファイルが無視されていることが分かります. ただし, 新たに作成した .gitignore ファイルが検出されます. .gitignore ファイル内ではワイルドカードを使用できます. ネット上の例を参照してください.

.gitignore ファイルをコミットします.

$ git add .
$ git commit -m "Add .gitignore."
[master 350b614] Add .gitignore
 1 file changed, 1 insertion(+)
 create mode 100644 .gitignore

4. ブランチ

ブランチとはリポジトリに含まれる異なるバージョンのスナップショットです. ブランチの実体はコミットへのポインタです. リポジトリは必ず「現在のブランチ」を状態として持ちます. これまでの例では「現在のブランチ」は master という名前でした. これはリポジトリを作成する時のデフォルトのブランチ名です. リポジトリを切り替えると, ブランチが移動し, ディレクトリの内容はブランチが指すコミットに含まれるものに置き換えられます. もちろん, 元のブランチに戻ればディレクトリの内容もまた元に戻ります. Gitではブランチの切り替えをチェックアウトと呼びます.

例4: ブランチの確認と切り替え

例3まで完了し, 現在のリポジトリの状態が次のようになっているとします.

$ cd ~/Download/repo1
$ git status
On branch master
nothing to commit, working directory clean
$ git lg
* 350b614 [2016-10-05] (HEAD -> master) Add .gitignore @taku-y
* ef783b7 [2016-10-05] Modify a file @taku-y
* 8c86d01 [2016-10-05] First commit @taku-y

コマンド git branch を使用してリポジトリが持つブランチを表示します.

$ git branch
* master

master ブランチしか存在しないことが確認できます. 先頭のアスタリスクは現在のブランチが master であることを表します.

次に develop という名前のブランチを新規に作成し, 同時にそのブランチをチェックアウトします. コマンド git checkout を使用します.

$ git checkout -b develop
Switched to a new branch 'develop'

オプション -b は存在しないブランチを新たに作成してからチェックアウトすることを指示します.

もう一度ブランチを確認します.

$ git branch
* develop
  master

新たに develop ブランチが作成されると同時に, ブランチが移動したことが分かります.

コミットグラフを確認します.

$ git lg
* 350b614 [2016-10-05] (HEAD -> develop, master) Add .gitignore @taku-y
* ef783b7 [2016-10-05] Modify a file @taku-y
* 8c86d01 [2016-10-05] First commit @taku-y

最新のコミットを見ると, develop ブランチ作成前には HEAD -> master となっていた部分が HEAD -> develop, master となっているのが分かります. この HEAD は現在のブランチを表すコミットへのポインタです.

この状態で適当な修正をリポジトリに加え, コミットします.

$ echo string2 >> source.txt
$ git add .
$ git commit -m "Modify a file"
[develop 88afd83] Modify a file
 1 file changed, 1 insertion(+)
  • コミットグラフを確認します.
$ git lg
* 32fc7a4 [2016-10-06] (HEAD -> develop) Modify a file @taku-y
* 350b614 [2016-10-05] (master) Add .gitignore @taku-y
* ef783b7 [2016-10-05] Modify a file @taku-y
* 8c86d01 [2016-10-05] First commit @taku-y

先ほどの修正に対応するコミットが追加され, develop ブランチがそのコミットを指していることが分かります. 一方, master ブランチが指すコミットは元のままです. master ブランチに移動し, source.txt ファイルの中身を確認します.

$ git checkout master
Switched to branch 'master'
$ cat source.txt
string

develop ブランチでの修正が反映されていないことが確認できました.

最後に, ブランチの分岐の例を示すため, master ブランチに修正を加えます.

$ touch source2.txt
$ git add .
$ git commit -m "Add a new file."
[master 6f1d258] Add a new file
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 source2.txt
$ git lg
* 6f1d258 [2016-10-06] (HEAD -> master) Add a new file @taku-y
* 350b614 [2016-10-05] Add .gitignore @taku-y
* ef783b7 [2016-10-05] Modify a file @taku-y
* 8c86d01 [2016-10-05] First commit @taku-y

コミットグラフには master ブランチしか表示されていませんが, これで正常です. develop ブランチは3番目のコミット以降分岐しているためです. 二つのブランチがマージ(後述)されると, develop ブランチの履歴が master ブランチから参照可能となります. master ブランチに追加した source2.txt は当然 develop ブランチには含まれません.

5. マージ

マージはあるブランチの修正を別のブランチに取り込むことです. 具体的には, 二つのブランチの先頭のコミットがマージされた新たなコミットが作成されます. マージで問題となるのは変更が衝突する場合ですが, まずは衝突がない場合の例を見てみます.

例5: 衝突が無い場合のマージ

  • develop ブランチの内容を master ブランチに取り込みます. そのために, まず master ブランチに移動します.
$ cd ~/Download/repo1
$ git checkout master
Switched to branch 'master'

Note

develop ブランチに他のブランチの変更を取り込む場合は develop ブランチに移動します.

コマンド git merge を用いて develop ブランチとマージします.

$ git merge develop

するとコミットメッセージの入力を促されます.

Merge branch 'develop'

# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.

エディタのコマンドでメッセージをこのまま保存します. 次のようなメッセージが表示され, マージが完了します.

Merge made by the 'recursive' strategy.
 source.txt | 1 +
 1 file changed, 1 insertion(+)

コミットグラフを確認します.

$ git lg
*   ff5e941 [2016-10-06] (HEAD -> master) Merge branch 'develop' @taku-y
|\
| * 32fc7a4 [2016-10-06] (develop) Modify a file @taku-y
* | 6f1d258 [2016-10-06] Add a new file @taku-y
|/
* 350b614 [2016-10-05] Add .gitignore @taku-y
* ef783b7 [2016-10-05] Modify a file @taku-y
* 8c86d01 [2016-10-05] First commit @taku-y

二つのブランチが分岐し, 最後のコミットで両者がマージされていることが分かります. 今回のマージでは, 分岐した後で変更の衝突がなかったため問題は起こりませんでした.

例6: マージにおける衝突の解消

ここではマージの際に衝突が起こる場合を見てみます. master ブランチに移動し, 新たに develop2 ブランチを作成します. そして, source.txt に変更を追加します.

$ cd ~/Download/repo1
$ git checkout master
Already on 'master'
$ git checkout -b develop2
Switched to a new branch 'develop2'
$ echo string3 >> source.txt
$ git add .
$ git commit -m "Add string3."
[develop2 88287ed] Add string3.
 1 file changed, 1 insertion(+)
$ git lg
* 20ee04d [2016-10-06] (HEAD -> develop2) Add string3 @taku-y
*   ff5e941 [2016-10-06] (master) Merge branch 'develop' @taku-y
|\
| * 32fc7a4 [2016-10-06] (develop) Modify a file @taku-y
* | 6f1d258 [2016-10-06] Add a new file @taku-y
|/
* 350b614 [2016-10-05] Add .gitignore @taku-y
* ef783b7 [2016-10-05] Modify a file @taku-y
* 8c86d01 [2016-10-05] First commit @taku-y

次に master ブランチに戻り同じファイルに別の変更を追加します.

$ git checkout master
Switched to branch 'master'
$ cat source.txt
string
string2
$ echo string4 >> source.txt
$ git add .
$ git commit -m "Add string4."
[master 515ba7a] Add string4.
 1 file changed, 1 insertion(+)
* a210d0e [2016-10-06] (HEAD -> master) Add string4 @taku-y
*   ff5e941 [2016-10-06] Merge branch 'develop' @taku-y
|\
| * 32fc7a4 [2016-10-06] (develop) Modify a file @taku-y
* | 6f1d258 [2016-10-06] Add a new file @taku-y
|/
* 350b614 [2016-10-05] Add .gitignore @taku-y
* ef783b7 [2016-10-05] Modify a file @taku-y
* 8c86d01 [2016-10-05] First commit @taku-y

この状態で develop2 ブランチを master ブランチにマージします.

$ git merge develop2
Auto-merging source.txt
CONFLICT (content): Merge conflict in source.txt
Automatic merge failed; fix conflicts and then commit the result.

すると, このように衝突を解消してからコミットを行うよう指示されます. 衝突が起こっている source.txt の中身を確認します.

$ cat source.txt
string
string2
<<<<<<< HEAD
string4
=======
string3
>>>>>>> develop2

master ブランチと develop2 ブランチへの変更内容が衝突している様子が分かります. master ブランチは現在のブランチなので HEAD と示されています. 今回は二つの変更内容を次のように統合することとします.

$ # ファイルを編集します. 編集した結果を確認します.
$ cat source.txt
string
string2
string34

master ブランチの状態を確認します.

$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add <file>..." to mark resolution)

    both modified:   source.txt

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

先ほどの修正内容をコミットします.

$ git add .
$ git commit -m "Resolve conflict"
[master 5e9b133] Resolve conflict
$ git lg -3 # 直近の3コミットのみ表示します.
*   5a871fb [2016-10-06] (HEAD -> master) Resolve conflict @taku-y
|\
| * 20ee04d [2016-10-06] (develop2) Add string3 @taku-y
* | a210d0e [2016-10-06] Add string4 @taku-y
|/

これで衝突を解消してマージすることができました.

Fast-forwardマージ

あるブランチAから分岐したブランチBにコミットを追加し, かつブランチAに何もコミットを追加しない場合を考えます. このときにブランチAにブランチBをマージするためには, ブランチAにブランチBのコミットを追加すれば十分です. したがって, ブランチAが指すコミットへのポインタをブランチBが指すコミットに移動すればよいことになります. このようなマージをfast-forwardマージと呼びます(参考リンク, 図13, 14. master ブランチに hotfix ブランチをマージするときにfast-forwardマージが適用されます).

例7: Fast-forwardマージ

Fast-forwardマージを試してみます. そのために, master ブランチから分岐する develop3 ブランチを作成し, ファイルに修正を追加します.

$ cd ~/Download/repo1
$ git checkout master
Already on 'master'
$ git checkout -b develop3
Switched to a new branch 'develop3'
$ echo string5 >> source.txt
$ git add .
$ git commit -m "Add string5"
[develop3 1c45527] Add string5
 1 file changed, 1 insertion(+)

コミットグラフを確認します.

$ git lg -4
* 1c45527 [2016-10-06] (HEAD -> develop3) Add string5 @taku-y
*   5a871fb [2016-10-06] (master) Resolve conflict @taku-y
|\
| * 20ee04d [2016-10-06] (develop2) Add string3 @taku-y
* | a210d0e [2016-10-06] Add string4 @taku-y
|/

この状態で master ブランチに develop3 ブランチをマージします.

$ git checkout master
Switched to branch 'master'
$ git merge develop3
Updating 5a871fb..1c45527
Fast-forward
 source.txt | 1 +
 1 file changed, 1 insertion(+)

Fast-forwardマージが適用されたことが分かります. コミットグラフを確認します.

$ git lg -4
* 1c45527 [2016-10-06] (HEAD -> master, develop3) Add string5 @taku-y
*   5a871fb [2016-10-06] Resolve conflict @taku-y
|\
| * 20ee04d [2016-10-06] (develop2) Add string3 @taku-y
* | a210d0e [2016-10-06] Add string4 @taku-y
|/

master ブランチのポインタが1つ先に進んだだけであることが分かります.

例8: Fast-forwardマージの明示的な回避

例7のような状況でブランチが分岐したことを履歴に残したい場合があるとします. その場合, git merge のオプション --no-ff を指定します. 先ほどと同様に, master ブランチから新たなブランチを分岐し, 適当な修正を追加します.

$ cd ~/Download/repo1
$ git checkout master
Already on 'master'
$ git checkout -b develop4
Switched to a new branch 'develop4'
$ echo string6 >> source.txt
$ git add .
$ git commit -m "Add string6"
[develop4 3a56f79] Add string6
 1 file changed, 1 insertion(+)

--no-ff オプションを指定してマージします.

$ git checkout master
Switched to branch 'master'
$ git merge --no-ff develop4
Merge made by the 'recursive' strategy.
 source.txt | 1 +
 1 file changed, 1 insertion(+)

コミットグラフを確認します.

$ git lg -4
*   9b566b7 [2016-10-06] (HEAD -> master) Merge branch 'develop4' @taku-y
|\
| * 3a56f79 [2016-10-06] (develop4) Add string6 @taku-y
|/
* 1c45527 [2016-10-06] (develop3) Add string5 @taku-y
*   5a871fb [2016-10-06] Resolve conflict @taku-y
|\

分岐したブランチのマージが履歴に残っていることが分かります.

6. リモートリポジトリ

リモートリポジトリとは手元のリポジトリと別の場所に存在するリポジトリのことです. それが同一マシン上(の別ディレクトリ) に存在したとしてもリモートリポジトリとみなします. 区別のため, 今後は手元のリポジトリをローカルリポジトリと呼ぶこととします.

通常の運用では, リモートリポジトリはローカルリポジトリと共通のコミットを持ちます. 典型的には, リモートリポジトリの内容をコピーしてローカルリポジトリを作成し, 独自に修正を適用するような場合です.

ローカルリポジトリをリモートリポジトリと同期させるためには, ローカルリポジトリにリモートリポジトリを登録する必要があります. 一度登録すれば, 登録を解除するまでリモートリポジトリの情報が現在のリポジトリに記憶されます. リモートリポジトリの情報はローカルリポジトリのリモートトラッキングブランチとして参照できます. リモートトラッキングブランチについては後述します. 先へ進む前に, ここでベアリポジトリについて触れます.

ベアリポジトリ

これまでの例で使用したリポジトリは作業用ファイルとコミットグラフ(.git ディレクトリの中身)の情報を持っていました. 例えば, ブランチをチェックアウトすると対応するコミットに含まれる履歴が作業用のファイルに反映されました. これに対して, 作業用ファイルを持たず .git コミットグラフの情報だけから構成されるリポジトリはベアリポジトリと呼ばれます. これはGitサーバの構成に用いられます.

例9: ベアリポジトリの作成と作業履歴の書き込み

ここでは, ベアリポジトリを作成し, これまでの例での作業履歴を書き込んでみます. このベアリポジトリをリモートリポジトリとみなします. まず, ベアリポジトリを ~Download/outside に作成します. そのためにはコマンド git init にオプション --bare を指定して実行します. ローカルリポジトリと区別するため, リポジトリのディレクトリ名を repo とします.

$ mkdir -p ~/Download/outside/repo
$ cd ~/Download/outside/repo
$ git init --bare
Initialized empty Git repository in /Users/taku-y/Downloads/tmp/outside/repo/

ローカルリポジトリの作業履歴をこのベアリポジトリに追加するため, ローカルリポジトリのディレクトリに移動します.

$ cd ~/Download/repo1

ローカルリポジトリに登録されているリモートリポジトリを確認するため, コマンド git remote を使用します.

$ git remote -v

コマンドを実行しても何も表示されません. ローカルリポジトリにリモートリポジトリが登録されていないためです. 上の手順で作成したベアリポジトリをリモートリポジトリとして登録するため, コマンド git remote add を実行します.

$ git remote add origin ../outside/repo

これで ~/Download/outside/repo に存在するベアリポジトリがリモートリポジトリとして登録されました. origin というのは現在のローカルリポジトリにおける登録したリモートリポジトリの名前です. リモートリポジトリの名前は任意に設定できます. ここではベアリポジトリをリモートリポジトリとして登録しましたが, ベアリポジトリでない通常のリポジトリも同様にリモートリポジトリとして登録できます.

ベアリポジトリがリモートリポジトリとして登録されているかどうか確認します.

$ git remote -v
origin  ../outside/repo (fetch)
origin  ../outside/repo (push)

上記のメッセージで, fetch はリモートリポジトリから情報を取得する操作, push はローカルリポジトリの修正内容をリモートリポジトリに適用する操作を表します. fetch については後述します.

コマンド git push を使用して, リモートリポジトリ( ~/Download/outside/repo )にローカルリポジトリ( ~/Download/repo1 )の作業履歴を書き込みます.

$ git push origin master
Counting objects: 32, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (21/21), done.
Writing objects: 100% (32/32), 2.81 KiB | 0 bytes/s, done.
Total 32 (delta 5), reused 0 (delta 0)
To ../outside/repo
 * [new branch]      master -> master

1行目のコマンドはリモートリポジトリ origin に対してローカルリポジトリの master ブランチの履歴を送信することを意味します.

他のブランチの履歴を送信する例は以下の通りです.

git push origin develop4
Total 0 (delta 0), reused 0 (delta 0)
To ../outside/repo
 * [new branch]      develop4 -> develop4

例10: クローン

この例では, 例9のベアリポジトリをローカルリポジトリとして手元にコピーし, ローカルリポジトリで適用した修正をベアリポジトリに適用します. このベアリポジトリはローカルリポジトリのリモートリポジトリとなります.

この例のために仮想的な別のユーザを考え, そのユーザのためのディレクトリ(これまでと同じユーザアカウントで作業します)を作成し, 移動します.

$ mkdir -p ~/Download/user2
$ cd ~/Download/user2

リモートリポジトリの情報を手元にコピーします. Gitではリポジトリをクローンすると言います. コマンド git clone を使用します.

$ git clone ~/Download/outside/repo
Cloning into 'repo'...
done.

これでリモートリポジトリのクローンが完了しました. ローカルリポジトリの中身を確認します. 例8の最後と同じ状態になっていることが分かります.

$ ls
repo
$ cd repo
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean
$ git lg -4
*   9b566b7 [2016-10-06] (HEAD -> master, origin/master, origin/HEAD) Merge branch 'develop4' @taku-y
|\
| * 3a56f79 [2016-10-06] (origin/develop4) Add string6 @taku-y
|/
* 1c45527 [2016-10-06] Add string5 @taku-y
*   5a871fb [2016-10-06] Resolve conflict @taku-y
|\

Note

例10のローカルリポジトリは例9までのローカルリポジトリと異なることに注意します.

このローカルリポジトリのリモートリポジトリを確認します.

$ git remote -v
origin  /Users/taku-y/Downloads/outside/repo (fetch)
origin  /Users/taku-y/Downloads/outside/repo (push)

このローカルリポジトリではリモートリポジトリを登録していません. しかし, リポジトリをクローンすると, そのリポジトリが自動的に origin という名前でリモートリポジトリとして登録されます.

では, これまでと同様にファイルに変更を加えてコミットします.

$ echo string7 >> source2.txt
$ git add .
$ git commit -m "Add string7"
[master 0b6b375] Add string7
 1 file changed, 1 insertion(+)

コミットグラフを確認します.

$ git lg -4
* 0b6b375 [2016-10-07] (HEAD -> master) Add string7 @taku-y
*   9b566b7 [2016-10-06] (origin/master, origin/HEAD) Merge branch 'develop4' @taku-y
|\
| * 3a56f79 [2016-10-06] (origin/develop4) Add string6 @taku-y
|/
* 1c45527 [2016-10-06] Add string5 @taku-y

ローカルリポジトリの master ブランチが最新のコミットを指していることが分かります. また, 新たに追加されたコミットがリモートリポジトリの master ブランチが指すものより新しいことも分かります. このことは git status によって確認することもできます.

$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)
nothing to commit, working directory clean

例9と同様にコマンド git push を使用してローカルリポジトリの変更をリモートリポジトリに反映させます.

$ git push origin master
Counting objects: 3, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 263 bytes | 0 bytes/s, done.
Total 3 (delta 1), reused 0 (delta 0)
To /Users/taku-y/Downloads/outside/repo/
   9b566b7..0b6b375  master -> master

これで修正内容が送信されました. リモートリポジトリのディレクトリ ~Downloads/outside/repo に移動し, コミットログを確認します.

$ cd ~/Downloads/outside/repo/
$ git lg -4
* 0b6b375 [2016-10-07] (HEAD -> master) Add string7 @taku-y
*   9b566b7 [2016-10-06] Merge branch 'develop4' @taku-y
|\
| * 3a56f79 [2016-10-06] (develop4) Add string6 @taku-y
|/
* 1c45527 [2016-10-06] Add string5 @taku-y

"Add string7" の変更が反映されていることを確認できました.

7. リモートトラッキングブランチとフェッチ

複数人で作業を行うと, 自分が作業中に別の人によってリモートリポジトリの状態が変わり, 競合が起こる場合があります. 具体的には, リモートリポジトリの状態とはリモートリポジトリのブランチの状態です. リポジトリの同期とは, ローカルリポジトリとリモートリポジトリのブランチの同期を意味します.

ローカルリポジトリはリモートブランチのコピーを持ちます. このコピーはリモートトラッキングブランチと呼ばれます. リモートトラッキングブランチは元のリモートリポジトリのブランチと常に同期しているとは限りません. リモートリポジトリのブランチの最新状態を対応するリモートトラッキングブランチに反映する操作がフェッチです. そして, フェッチして最新状態に更新されたリモートトラッキングブランチを対応するローカルリポジトリのブランチにマージすることでローカルリポジトリとリモートリポジトリのブランチが同期されます.

リモートトラッキングブランチはリモートリポジトリの名前とブランチの名前で表されます. 例えば, origin/master はリモートリポジトリ originmaster ブランチのリモートトラッキングブランチです.

Note

上の説明からわかるように, リモートリポジトリとの同期はブランチ単位で行われます.

例11: フェッチ

例10を終了した時点で次の三つのリポジトリが存在します. 便宜的にリポジトリA, B, Cと呼ぶこととします.

  • リポジトリA: ~/Downloads/repo1
  • リポジトリB: ~/Downloads/user2/repo
  • リポジトリC: ~/Downloads/outside/repo

リポジトリCはAとBのリモートリポジトリでした. これら三つのリポジトリの master ブランチの状態を確認します.

  • リポジトリA:
$ cd ~/Downloads/repo1; git lg -4
*   9b566b7 [2016-10-06] (HEAD -> master, origin/master) Merge branch 'develop4' @taku-y
|\
| * 3a56f79 [2016-10-06] (origin/develop4, develop4) Add string6 @taku-y
|/
* 1c45527 [2016-10-06] (develop3) Add string5 @taku-y
*   5a871fb [2016-10-06] Resolve conflict @taku-y
|\
  • リポジトリB:
$ cd ~/Downloads/user2/repo; git lg -5
* 0b6b375 [2016-10-07] (HEAD -> master, origin/master, origin/HEAD) Add string7 @taku-y
*   9b566b7 [2016-10-06] Merge branch 'develop4' @taku-y
|\
| * 3a56f79 [2016-10-06] (origin/develop4) Add string6 @taku-y
|/
* 1c45527 [2016-10-06] Add string5 @taku-y
*   5a871fb [2016-10-06] Resolve conflict @taku-y
|\
  • リポジトリC:
$ cd ~/Downloads/outside/repo; git lg -5
* 0b6b375 [2016-10-07] (HEAD -> master) Add string7 @taku-y
*   9b566b7 [2016-10-06] Merge branch 'develop4' @taku-y
|\
| * 3a56f79 [2016-10-06] (develop4) Add string6 @taku-y
|/
* 1c45527 [2016-10-06] Add string5 @taku-y
*   5a871fb [2016-10-06] Resolve conflict @taku-y
|\

リポジトリAの状態が古いことが分かります. ここではリポジトリAをリモートリポジトリCに同期します. リポジトリAのディレクトリに移動した後( cd ~/Downloads/repo1 ), リモートリポジトリをフェッチします.

$ git fetch origin
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From ../outside/repo
   9b566b7..0b6b375  master     -> origin/master

これでリモートトラッキングブランチ origin/master が最新の状態になりました. このリモートトラッキングブランチをローカルリポジトリの master ブランチにマージすることで同期されます.

$ git merge origin/master
Updating 9b566b7..0b6b375
Fast-forward
 source2.txt | 1 +
 1 file changed, 1 insertion(+)
$ git lg -5
* 0b6b375 [2016-10-07] (HEAD -> master, origin/master) Add string7 @taku-y
*   9b566b7 [2016-10-06] Merge branch 'develop4' @taku-y
|\
| * 3a56f79 [2016-10-06] (origin/develop4, develop4) Add string6 @taku-y
|/
* 1c45527 [2016-10-06] (develop3) Add string5 @taku-y
*   5a871fb [2016-10-06] Resolve conflict @taku-y
|\

8. 競合の解消

二人のユーザがリポジトリAとBで master ブランチを起点として別のブランチを作成し, それぞれの作業が完了後に master ブランチにマージすることを考えます. リポジトリAの作業が完了後にリポジトリBの作業を開始する場合は, 作業の前にリポジトリBで master ブランチをリモートリポジトリと同期すれば問題は起こりません. しかし, リポジトリAの作業完了前, すなわち master ブランチへのマージが完了する前にリポジトリBの作業を開始すると, master ブランチをプッシュするときに競合が発生します.

例12: 競合の発生

リポジトリAでブランチ develop5 を作成し, source.txt に修正を加えた後, master にマージします. ただしリモートリポジトリへのプッシュはまだ行わないとします.

$ cd ~/Downloads/repo1
$ git checkout -b develop5
Switched to a new branch 'develop5'
$ echo string8 >> source.txt
$ git status
On branch develop5
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:   source.txt

no changes added to commit (use "git add" and/or "git commit -a")
$ git add .
$ git commit -m "Add string8"
[develop5 72c756d] Add string8
 1 file changed, 1 insertion(+)
$ git checkout master
Switched to branch 'master'
$ git merge --no-ff develop5
Merge made by the 'recursive' strategy.
 source.txt | 1 +
 1 file changed, 1 insertion(+)
$ git lg -5
*   1641c26 [2016-10-08] (HEAD -> master) Merge branch 'develop5' @taku-y
|\
| * 72c756d [2016-10-08] (develop5) Add string8 @taku-y
|/
* 0b6b375 [2016-10-07] (origin/master) Add string7 @taku-y
*   9b566b7 [2016-10-06] Merge branch 'develop4' @taku-y
|\
| * 3a56f79 [2016-10-06] (origin/develop4, develop4) Add string6 @taku-y
|/

次に, リポジトリBでブランチ develop6 を作成し, 同様に修正を加えて master にマージします.

$ cd ~/Downloads/user2/repo/
$ git checkout -b develop6
Switched to a new branch 'develop6'
$ echo string9 >> source.txt
$ git add .
$ git commit -m "Add string9"
[develop6 2bcb366] Add string9
 1 file changed, 1 insertion(+)
Taku-no-MacBook-Pro:repo taku-y$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ git merge --no-ff develop6
Merge made by the 'recursive' strategy.
 source.txt | 1 +
 1 file changed, 1 insertion(+)
$ git lg -5
*   2bc058f [2016-10-08] (HEAD -> master) Merge branch 'develop6' @taku-y
|\
| * 2bcb366 [2016-10-08] (develop6) Add string9 @taku-y
|/
* 0b6b375 [2016-10-07] (origin/master, origin/HEAD) Add string7 @taku-y
*   9b566b7 [2016-10-06] Merge branch 'develop4' @taku-y
|\
| * 3a56f79 [2016-10-06] (origin/develop4) Add string6 @taku-y
|/

この時点でリポジトリAとBの master ブランチが競合します. 先にリポジトリAがリモートリポジトリにプッシュされるとします. この時点では問題は起こりません.

$ cd ~/Downloads/repo1/
$ git push origin master
Counting objects: 4, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 424 bytes | 0 bytes/s, done.
Total 4 (delta 1), reused 0 (delta 0)
To ../outside/repo
   0b6b375..1641c26  master -> master

この状態でリポジトリBの master ブランチをプッシュしようとすると, 競合のためにエラーが発生します.

$ cd ~/Downloads/user2/repo/
$ git branch
  develop6
* master
$ git push origin master
To /Users/taku-y/Downloads/outside/repo/
 ! [rejected]        master -> master (fetch first)
error: failed to push some refs to '/Users/taku-y/Downloads/outside/repo/'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

例13: 競合の解消

競合を解消するため, リポジトリBの master ブランチの状態をマージ前の状態に戻します.

$ cd ~/Downloads/user2/repo
$ git lg -5
*   2bc058f [2016-10-08] (HEAD -> master) Merge branch 'develop6' @taku-y
|\
| * 2bcb366 [2016-10-08] (develop6) Add string9 @taku-y
|/
* 0b6b375 [2016-10-07] (origin/master, origin/HEAD) Add string7 @taku-y
*   9b566b7 [2016-10-06] Merge branch 'develop4' @taku-y
|\
| * 3a56f79 [2016-10-06] (origin/develop4) Add string6 @taku-y
|/
$ git reset 0b6b375
Unstaged changes after reset:
M   source.txt
$ git lg -5
* 0b6b375 [2016-10-07] (HEAD -> master, origin/master, origin/HEAD) Add string7 @taku-y
*   9b566b7 [2016-10-06] Merge branch 'develop4' @taku-y
|\
| * 3a56f79 [2016-10-06] (origin/develop4) Add string6 @taku-y
|/
* 1c45527 [2016-10-06] Add string5 @taku-y
*   5a871fb [2016-10-06] Resolve conflict @taku-y
|\
Taku-no-MacBook-Pro:repo taku-y$ git checkout .
Taku-no-MacBook-Pro:repo taku-y$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean

次に master ブランチをリポートリポジトリと同期します.

$ git fetch origin
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (4/4), done.
From /Users/taku-y/Downloads/outside/repo
   0b6b375..1641c26  master     -> origin/master
$ git merge origin/master
Updating 0b6b375..1641c26
Fast-forward
 source.txt | 1 +
 1 file changed, 1 insertion(+)
Taku-no-MacBook-Pro:repo taku-y$ cat source.txt
string
string2
string34
string5
string6
string8

この状態で develop6 ブランチをマージしようとすると競合が発生します.

$ git merge develop6
Auto-merging source.txt
CONFLICT (content): Merge conflict in source.txt
Automatic merge failed; fix conflicts and then commit the result.

競合を解消するため source.txt ファイルを修正します.

$ cat source.txt
string
string2
string34
string5
string6
string8
string9

リポジトリの状態は以下の通りです.

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add <file>..." to mark resolution)

    both modified:   source.txt

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

修正後のファイルをコミットします.

$ git add source.txt
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:

    modified:   source.txt

$ git commit -m "Resolve conflict"
[master c0c294f] Resolve conflict

最後に変更をリモートリポジトリにプッシュします.

$ git push origin master
Counting objects: 6, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (6/6), 670 bytes | 0 bytes/s, done.
Total 6 (delta 0), reused 0 (delta 0)
To /Users/taku-y/Downloads/outside/repo/
   1641c26..c0c294f  master -> master

ローカルリポジトリで競合を解消されたので, 今度は正常にプッシュが完了しました.

更新履歴

  • 2016????: 初版を公開しました.
  • 20161008: 体裁を修正し, 項目を追加しました.

免責事項

本文書の情報については充分な注意を払っておりますが, その内容の正確性等に対して一切保障するものではありません. 本文書の利用で起きたいかなる結果について, 一切責任を負わないものとします.