最終更新: 20161008 吉岡琢
Contents
ここではGitの使い方を一通り説明します. 特に, Gitを理解するために重要な概念をできるだけ具体的に説明します. 一方, Gitの意義やインストールに関する説明はしません. Gitのディレクトリにパスが通っており, Gitのコマンドが利用可能であると仮定します. 例では一貫して ~/Download/repo[n]
ディレクトリを使用します. 断りが無い限り, 例のコマンドは全てこのディレクトリで実行します. 例は最初から順に実行すると仮定します. 例2を確認する場合, 例1が終了していることを前提とします.
コンソール上で以下のコマンドを入力し, ユーザ名とメールアドレスを入力します.
$ 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
と入力すると, ログが分かりやすく表示されます.
リポジトリとはファイルとその変更履歴をまとめたものです. Gitにおけるリポジトリの実体は, リポジトリのルートディレクトリに存在する .git
ディレクトリです. 実際, .git
ディレクトリを削除するとリポジトリの情報は全て消えます. リポジトリのルートディレクトリ以下でGitコマンド(例えば git add
や git status
など)を実行すると, そのリポジトリに対して操作が実行されます.
リポジトリを作成するためにコマンド git init
を使用します. リポジトリの作成とは, プロジェクトの構成要素(主にテキストファイル)を含むディレクトリをGitのバージョン管理下に置くことです. ディレクトリ名は何でも良いのですが, ここの説明では ~/Download/repo1
という名前にします. ディレクトリをGitのバージョン管理下に置くにはコマンド git init
を使用します.
$ mkdir -p ~/Download/repo1
$ cd ~/Download/repo1
$ git init
これでこのディレクトリがリポジトリになりました.
コミットとはリポジトリのある時点でのスナップショットです. コミットはその直近の祖先へのポインタを持ちます. これにより, コミットはリポジトリに対する追加・変更の履歴を表現するグラフとして表現されます. これをコミットグラフと呼びます(参考リンク).
コミットという単語はスナップショットをコミットグラフに追加する操作を指すこともあります. この場合, 「ファイルをコミットする」「変更をコミットする」というように使われます.
Note
ネット上で, Gitはコミットをスナップショットとして格納するという説明を見かけることがあります. 実際は, コミットを構成するオブジェクトに対して差分管理が適用されます. スナップショットの系列の差分を抽出してファイルを圧縮するプロセスをGitではパッキングと呼びます. これは新しく追加された機能ということです(参考リンク).
ステージングとはコミットする変更内容をリポジトリに通知する事です. ステージングを何度も行う事で, 複数の変更を同時にコミットできます. さらに, 変更されたファイルの一部をステージングすることもできます. 例えば, 一つのファイルに二つの機能を追加した場合, それらを別々のコミットとして扱う事が出来ます. そのためには, コマンド git add
のオプション -p
を指定します. ステージングされた変更は, コミットする前であれば取り消すことができます.
この例ではコマンド 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
新たなコミットが追加されたことが分かります.
.gitignore
ファイルはGitバージョン管理の対象としないファイル(例えばコンパイラが出力する中間ファイル)を指定するものです. VC++やPythonなど各種プロジェクトに適した .gitignore
ファイルのテンプレートがネット上にあります. 必要に応じて検索しましょう.
.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
ブランチとはリポジトリに含まれる異なるバージョンのスナップショットです. ブランチの実体はコミットへのポインタです. リポジトリは必ず「現在のブランチ」を状態として持ちます. これまでの例では「現在のブランチ」は master
という名前でした. これはリポジトリを作成する時のデフォルトのブランチ名です. リポジトリを切り替えると, ブランチが移動し, ディレクトリの内容はブランチが指すコミットに含まれるものに置き換えられます. もちろん, 元のブランチに戻ればディレクトリの内容もまた元に戻ります. Gitではブランチの切り替えをチェックアウトと呼びます.
例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
ブランチには含まれません.
マージはあるブランチの修正を別のブランチに取り込むことです. 具体的には, 二つのブランチの先頭のコミットがマージされた新たなコミットが作成されます. マージで問題となるのは変更が衝突する場合ですが, まずは衝突がない場合の例を見てみます.
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
二つのブランチが分岐し, 最後のコミットで両者がマージされていることが分かります. 今回のマージでは, 分岐した後で変更の衝突がなかったため問題は起こりませんでした.
ここではマージの際に衝突が起こる場合を見てみます. 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
|/
これで衝突を解消してマージすることができました.
あるブランチAから分岐したブランチBにコミットを追加し, かつブランチAに何もコミットを追加しない場合を考えます. このときにブランチAにブランチBをマージするためには, ブランチAにブランチBのコミットを追加すれば十分です. したがって, ブランチAが指すコミットへのポインタをブランチBが指すコミットに移動すればよいことになります. このようなマージをfast-forwardマージと呼びます(参考リンク, 図13, 14. master
ブランチに hotfix
ブランチをマージするときに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つ先に進んだだけであることが分かります.
例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
|\
分岐したブランチのマージが履歴に残っていることが分かります.
リモートリポジトリとは手元のリポジトリと別の場所に存在するリポジトリのことです. それが同一マシン上(の別ディレクトリ) に存在したとしてもリモートリポジトリとみなします. 区別のため, 今後は手元のリポジトリをローカルリポジトリと呼ぶこととします.
通常の運用では, リモートリポジトリはローカルリポジトリと共通のコミットを持ちます. 典型的には, リモートリポジトリの内容をコピーしてローカルリポジトリを作成し, 独自に修正を適用するような場合です.
ローカルリポジトリをリモートリポジトリと同期させるためには, ローカルリポジトリにリモートリポジトリを登録する必要があります. 一度登録すれば, 登録を解除するまでリモートリポジトリの情報が現在のリポジトリに記憶されます. リモートリポジトリの情報はローカルリポジトリのリモートトラッキングブランチとして参照できます. リモートトラッキングブランチについては後述します. 先へ進む前に, ここでベアリポジトリについて触れます.
これまでの例で使用したリポジトリは作業用ファイルとコミットグラフ(.git
ディレクトリの中身)の情報を持っていました. 例えば, ブランチをチェックアウトすると対応するコミットに含まれる履歴が作業用のファイルに反映されました. これに対して, 作業用ファイルを持たず .git
コミットグラフの情報だけから構成されるリポジトリはベアリポジトリと呼ばれます. これはGitサーバの構成に用いられます.
ここでは, ベアリポジトリを作成し, これまでの例での作業履歴を書き込んでみます. このベアリポジトリをリモートリポジトリとみなします. まず, ベアリポジトリを ~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
この例では, 例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"
の変更が反映されていることを確認できました.
複数人で作業を行うと, 自分が作業中に別の人によってリモートリポジトリの状態が変わり, 競合が起こる場合があります. 具体的には, リモートリポジトリの状態とはリモートリポジトリのブランチの状態です. リポジトリの同期とは, ローカルリポジトリとリモートリポジトリのブランチの同期を意味します.
ローカルリポジトリはリモートブランチのコピーを持ちます. このコピーはリモートトラッキングブランチと呼ばれます. リモートトラッキングブランチは元のリモートリポジトリのブランチと常に同期しているとは限りません. リモートリポジトリのブランチの最新状態を対応するリモートトラッキングブランチに反映する操作がフェッチです. そして, フェッチして最新状態に更新されたリモートトラッキングブランチを対応するローカルリポジトリのブランチにマージすることでローカルリポジトリとリモートリポジトリのブランチが同期されます.
リモートトラッキングブランチはリモートリポジトリの名前とブランチの名前で表されます. 例えば, origin/master
はリモートリポジトリ origin
の master
ブランチのリモートトラッキングブランチです.
Note
上の説明からわかるように, リモートリポジトリとの同期はブランチ単位で行われます.
例10を終了した時点で次の三つのリポジトリが存在します. 便宜的にリポジトリA, B, Cと呼ぶこととします.
~/Downloads/repo1
~/Downloads/user2/repo
~/Downloads/outside/repo
リポジトリCはAとBのリモートリポジトリでした. これら三つのリポジトリの master
ブランチの状態を確認します.
$ 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
|\
$ 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
|\
$ 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
|\
二人のユーザがリポジトリAとBで master
ブランチを起点として別のブランチを作成し, それぞれの作業が完了後に master
ブランチにマージすることを考えます. リポジトリAの作業が完了後にリポジトリBの作業を開始する場合は, 作業の前にリポジトリBで master
ブランチをリモートリポジトリと同期すれば問題は起こりません. しかし, リポジトリAの作業完了前, すなわち master
ブランチへのマージが完了する前にリポジトリBの作業を開始すると, master
ブランチをプッシュするときに競合が発生します.
リポジトリ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.
競合を解消するため, リポジトリ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
ローカルリポジトリで競合を解消されたので, 今度は正常にプッシュが完了しました.