Linux上でのGitコマンド練習
- はじめに
- 集中と分散
- 分散型バージョン管理ソフトウェアGit
- ブランチ管理
- Gitの良く使われるコマンドとリポジトリ、インデックス、作業ツリーの関係
- Gitコマンドの練習
- リポジトリの作成 (init, config)
- ファイルの追加 (add)
- 変更履歴のリポジトリへの保存(commit)
- 変更履歴の閲覧(log)
- 任意の時点との差分の表示(diff)
- ファイル名の変更、移動および削除(mv, rm)
- ブランチの確認、作成、名前変更、削除 (branch)
- ブランチ間の移動 (checkout)
- 他ブランチの変更履歴の現ブランチへの反映 (merge)
- 競合・衝突(conflict)とその解消
- 作業ツリーなしリポジトリの作成(init --bare)
- リモートリポジトリの設定(remote)
- リモートリポジトリにローカルリポジトリの内容を反映させる(push)
- リモートリポジトリのコピー(clone)
- リモートリポジトリの内容をローカルリポジトリへ反映させる(pull)
- ファイルの各行がどのコミットで変更されたのかを調べる(blame)
- ブランチをグラフ形式で見る(log --graph)
- 戻る
はじめに_
バージョン管理システム(Version Control System, VCS)とは、コンピュータ上で作成、編集されるファイルの変更履歴を管理するためのソフトウェアのこと。
VCSは以下のような状況を解決するために使用される。
- 間違ってファイルを消してしまった
- 変更を保存した後に、変更前の方が良いことに気付いた
- 複数人でファイルを編集しているとき、どの変更を誰が行ったのかわからない
代表的なVCSには以下のようなものがある。
- 集中管理:RCS、CVS、Subversion
- 分散管理:Git、Mercurial
集中と分散_
VCSでは、変更履歴(リビジョン、Revision。ファイルの編集前と編集後の差分、編集日時、編集者、および、編集に対する編集者のコメント)をリポジトリと呼ばれるデータベースに格納して保管している。

現在のVCSはリポジトリの管理方法から集中型と分散型の2つの種類に分類できる。
集中型VCSでは、1つのリポジトリを1箇所で管理する。作業ディレクトリのファイル/ディレクトリの変更履歴をリポジトリに保存するのが「コミット (commit)」と呼ばれる操作である。リポジトリから(変更履歴を反映させた)ファイルやディレクトリを取り出すのが「チェックアウト (checkout)」と呼ばれる操作である。

一方、分散型VCSは、複数のリポジトリを複数の箇所で分散的に管理する。分散VCSではリポジトリ間で格納している変更履歴の同期をとることができる。ローカルリポジトリの変更履歴をリモートリポジトリ(別の場所にあるリポジトリ)へ送るのが「プッシュ (push)」という操作であり、その逆が「プル (pull)」という操作である。リモートリポジトリをローカルにコピーするのが「クローン (clone)」という操作である。

分散型バージョン管理ソフトウェアGit_
Gitは分散VCSの一種である。Gitは、Linux kernelのソースコード管理のために提案・開発されているバージョン管理システムであり、ここ2~3年ではもっともよく利用されている分散VCSである。
リポジトリ間の通信方式として以下のものを用意している。
- ファイルシステム(同一計算機上の場合)
- SSH (Secure Shell)
- Git専用プロトコル(認証機能なし)
- HTTP/HTTPS(WebDAV)(pushするためにはWebDAVが必要)
ネットワークに接続できないときでも、リポジトリに変更履歴を保存できるという利点の他に、集中VCSでは大変な作業であったブランチ管理を簡単に行えるという利点がある。
ブランチ管理_
変更履歴の分岐のことを「ブランチ (branch)」という。Gitにおいてはブランチはブランチを管理する操作名でもある。

ブランチを1つにまとめる操作を「マージ(merge)」という。ブランチ間で変更履歴に矛盾がない場合は、マージは問題なく成功するが、矛盾がある場合(同じ部分に異なる変更がほどこされている場合)は、「衝突 (conflict)」が発生したとみなされ、編集者による衝突の解決が必要となる。

Gitの良く使われるコマンドとリポジトリ、インデックス、作業ツリーの関係_

Gitコマンドの練習_
以下の作業はGitがインストール済み(サーチパスも通してある)のLinuxでの作業と仮定する。
まず、作業ディレクトリを作ります。
% cd % mkdir GitSandbox % cd GitSandbox
リポジトリの作成 (init, config)_
gitには「作業ツリー+リポジトリ」と「リポジトリのみ」の作成方法がある。まず、頻繁に利用する「作業ツリー+リポジトリ」を作成する。
% mkdir firstrepo % cd firstrepo % git init
つづいて、このリポジトリにおけるユーザ名とメールアドレスを設定します。Gitでは編集履歴に「誰が」編集したのかを記録するため、ユーザ名とメールアドレスの設定が不可欠である。自分の名前と自分のメールアドレスに置き換えて入力すること。
% git config user.name "Yuichi Goto" % git config user.email gotoh@aise.ics.saitama-u.ac.jp
このリポジトリだけでなく、このコンピュータのアカウントで共通に設定したい場合は global オプションを使う。
% git config --global user.name "Yuichi Goto" % git config --global user.email gotoh@aise.ics.saitama-u.ac.jp
ファイルの追加 (add)_
変更履歴をリポジトリに保管するために、Indexに保管対象のファイルを明示的に指定する必要がある。これは add コマンドを使う。以下の例では、新たに作成した Readme.md をGitでの管理対象にし、かつ、Indexへ登録している。
% touch Readme.md % git status (現在の変更対象、Indexへの追加状況を表示するコマンド) # On branch master # # Initial commit # # Untracked files: # (use "git add <file>..." to include in what will be committed) # # Readme.md nothing added to commit but untracked files present (use "git add" to track) % git add Readme.md % git status # On branch master # # Initial commit # # Changes to be committed: # (use "git rm --cached <file>..." to unstage) # # new file: Readme.md #
既に管理対象になっているファイルであっても、変更をほどこしたならば add コマンドで Indexに追加する必要がある(後述)
変更履歴のリポジトリへの保存(commit)_
Index に列挙されているファイルの変更履歴をリポジトリへ保存する。この操作を commit という。Gitでは commit を行う場合に必ずコメントを登録しなければならない。今回はコメントとして「First commit.」と入力する。
% git commit (コメント追加のためのエディタが起動する) [master (root-commit) febc49e] First Commit. 0 files changed create mode 100644 Readme.md
commitにオプションをつけることで、addコマンドを省いたり、コメントをエディタを開かずにつけることができる。
% git commit (コメント追加のためのエディタが起動する) % git commit -m "コメント" (一言コメントであれば-mオプションをつけると便利) % git commit -a (既に管理対象になっており、かつ、編集済のファイルをコミットする) % git commit -a -m "コメント" (-m と -a は同時に使うこともできる)
たとえば、Readme.mdを以下のように編集する。
## Introduction Hello, World! ## Conclustion Bye, bye!
すると、ファイルに編集済みであるが、Indexに追加されていない状態になっているのがわかる。
% 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: Readme.md # no changes added to commit (use "git add" and/or "git commit -a")
本来は以下のようにコマンドを実行し、変更履歴をリポジトリに格納すべきである。
% git add Readme.md % git commit
上をまとめて実行すると以下のようになる。
% git commit -a -m "Second commit." [master d8dd1fc] Second commit. 1 file changed, 7 insertions(+)
変更履歴の閲覧(log)_
変更履歴を閲覧する場合は log コマンドを使う。「commit」の横に表示されているのがハッシュキー。リビジョンを区別するためのIDとして使われている。「Author」が編集者、「Date」が編集日時、そして、その下には commit 実行時のコメントが表示される。
% git log commit d8dd1fc15a5594bbeee12ef8a076fa0a7c8b9802 Author: Yuichi Goto <gotoh@aise.ics.saitama-u.ac.jp> Date: Tue Jun 2 13:17:41 2015 +0900 Second commit. commit febc49eac79a7c7525122e8166241060a89aa01d Author: Yuichi Goto <gotoh@aise.ics.saitama-u.ac.jp> Date: Tue Jun 2 13:12:31 2015 +0900 First Commit.
任意の時点との差分の表示(diff)_
Readme.md を以下のように変更する。
## Introduction Hello, World! ## Section 1 An apple is a fruit. ## Conclustion Bye, bye!
そして、Indexに登録する。
% git add Readme.md
さらにReadme.md を以下のように変更する。
## Introduction Hello, World! ## Section 1 An apple is a fruit. ## Section 2 A dog is not a fruit. ## Conclustion Bye, bye!
これで、Readme.md について、現在の状態、Indexに登録されている状態、リポジトリに保存されている最新のリビジョンが異なっている状況ができた。現在の当該ファイルと過去の状態の差分を調べるためには diff コマンドを利用する。オプションなしの場合は、Indexに列挙されている状態と現在の状態の差分を表示する。
% git diff Readme.md diff --git a/Readme.md b/Readme.md index fe3df5c..ab37b41 100644 --- a/Readme.md +++ b/Readme.md @@ -6,6 +6,10 @@ Hello, World! An apple is a fruit. +## Section 2 + +A dog is not a fruit. + ## Conclusion Bye, bye!
cachedオプションをつけるとIndexとHEAD(リポジトリに登録された最新の変更履歴)との差分を表示する。
% git diff --cached Readme.md diff --git a/Readme.md b/Readme.md index 4c4b7d0..fe3df5c 100644 --- a/Readme.md +++ b/Readme.md @@ -2,6 +2,10 @@ Hello, World! +## Section 1 + +An apple is a fruit. + ## Conclusion Bye, bye!
オプションなしで、HEADを指定すると現在のファイルとHEADの差分を表示する。
% git diff HEAD Readme.md diff --git a/Readme.md b/Readme.md index 4c4b7d0..ab37b41 100644 --- a/Readme.md +++ b/Readme.md @@ -2,6 +2,14 @@ Hello, World! +## Section 1 + +An apple is a fruit. + +## Section 2 + +A dog is not a fruit. + ## Conclusion Bye, bye!
任意のリビジョンと比較する場合はコミットIDを指定する。最初のコミット「febc49eac79a7c7525122e8166241060a89aa01d」時点でのReadme.txtと現在のファイルの差分を表示する。
% git log commit d8dd1fc15a5594bbeee12ef8a076fa0a7c8b9802 Author: Yuichi Goto <gotoh@aise.ics.saitama-u.ac.jp> Date: Tue Jun 2 13:17:41 2015 +0900 Second commit. commit febc49eac79a7c7525122e8166241060a89aa01d Author: Yuichi Goto <gotoh@aise.ics.saitama-u.ac.jp> Date: Tue Jun 2 13:12:31 2015 +0900 First Commit. % git diff febc49eac79a7c7525122e8166241060a89aa01d Readme.md diff --git a/Readme.md b/Readme.md index e69de29..ab37b41 100644 --- a/Readme.md +++ b/Readme.md @@ -0,0 +1,15 @@ +## Introduction + +Hello, World! + +## Section 1 + +An apple is a fruit. + +## Section 2 + +A dog is not a fruit. + +## Conclusion + +Bye, bye!
一旦、commit する。
% git commit -a -m "Third commit"
ファイル名の変更、移動および削除(mv, rm)_
たとえば、新たに tmp_dir/newfile を追加するとする。
% mkdir tmp_dir % touch tmp_dir/newfile % git add tmp_dir/newfile % git commit -m "Add new file."
一度、リポジトリに登録したあとにこのディレクトリ名やファイル名を変更したい、また、ファイルの場所を移動させたいときには mv コマンドを使う。
% git mv tmp_dir renamed_dir % git status # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # renamed: tmp_dir/newfile -> renamed_dir/newfile # % git mv renamed_dir/newfile ./newfile % git status # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # renamed: tmp_dir/newfile -> newfile # % git commit -a -m "Renamed and moved a directory and a file."
なお、Gitでは空のディレクトリは管理対象から外される。
ファイルやディレクトリを削除するときには rm コマンドを使う。
% git rm newfile % git status # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # deleted: newfile # % git commit -a -m "Removed a file."
ブランチの確認、作成、名前変更、削除 (branch)_
ブランチを作成する。status コマンドのコメントで「On branch master」という表示があったが、デフォルトのブランチは「master」ブランチである。個人でソフトウェアを開発し、かつ、Gitを使って管理する場合には GitHub flowが便利だと思う。
GitHub flowは master(稼働し、公開できるブランチ), dev(開発用ブランチ), その他トピックブランチ(機能追加やバグ修正ごとに作成するブランチ)の3種類のブランチを運用するブランチ管理モデルである。
ブランチの一覧を表示する場合は branch コマンドを使う。アスタリスクがついているブランチが現在のブランチ。
% git branch * master
新たに dev ブランチを作成する。
% git branch dev % git branch dev * master
トピックブランチ「tmp_b」を作成する。
% git branch tmp_b % git branch dev * master tmp_b
ブランチ名を変えたい場合はmオプションを使う。
% git branch -m tmp_b tmp_c % git branch dev * master tmp_c
ブランチを削除する場合はdオプションを使う
% git branch -d tmp_c Deleted branch tmp_c (was 7bb4c22). % git branch dev * master
ブランチ間の移動 (checkout)_
まず、masterブランチで新たにファイルを付け加える。
% touch unmarged_file % git add unmarged_file % git commit -m "Add a file." % ls Readme.md unmarged_file
dev ブランチに移動する。移動は checkout コマンドを使う。
% git checkout dev Switched to branch 'dev' % ls Readme.md
この dev ブランチは unmarged_file を追加する前のリビジョンから分岐しているため、unmarged_file はこのブランチには含まれない。このようにGit はブランチごとに作業ツリーをリポジトリから構築する。
他ブランチの変更履歴の現ブランチへの反映 (merge)_
他ブランチの変更履歴を現ブランチへ反映させる際には merge コマンドを使う。
% git merge master Updating 7bb4c22..caa8706 Fast-forward 0 files changed create mode 100644 unmarged_file
unmarged_file の merge の綴りがまちがっているので修正してコミットする。
% git mv unmarged_file unmerged_file % git commit -m "Modified spell miss." % ls
master ブランチへ戻る。
% git checkout master % git branch % ls
dev ブランチの変更履歴をmaster ブランチへ反映させる。
% git merge dev % ls
競合・衝突(conflict)とその解消_
unmerged_file の中身を以下のように変更する。
## May be conflict. Hello.
コミットする。
% git commit -a -m "Edit unmerged_file."
dev ブランチへ移動する。
% git checkout dev % git branch
unmerged_file の中身を以下のように変更する。
## May be conflict. Good night.
コミットする。
% git commit -a -m "Edit unmerged_file."
master ブランチの変更履歴を devブランチに反映させる。すると衝突が起こっていることが報告される。
% git merge master Auto-merging unmerged_file CONFLICT (content): Merge conflict in unmerged_file Automatic merge failed; fix conflicts and then commit the result.
unmerged_file は以下のようになっている。
% more unmerged_file ## May be conflict <<<<<<< HEAD Good night. ======= Hello. >>>>>>> master
このように変更間に矛盾が発生し、Gitでは機械的に解決できないとき衝突が報告される。この場合は、人間が衝突を解消してあげないといけない。
衝突解決用ツールがインストールされている場合は mergetool コマンドで解決用ツールが立ち上がるがインストールされていない場合は以下のようなメッセージが表示される。
% git mergetool merge tool candidates: opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse ecmerge p4merge araxis bc3 emerge vimdiff No known merge resolution program available.
unmerged_file を以下のように修正する。
## Solved the conflict. Good night. Hello.
コミットする。Gitが勝手にコメントをつけてくれる。
% git commit -a
master ブランチに戻り、mergeする。
% git checkout master % git merge dev
作業ツリーなしリポジトリの作成(init --bare)_
リポジトリ間の操作を練習するために二つ目のリポジトリを作業ツリーなしで作成する。
% cd ~/GitSandbox % mkdir secondrepo % cd secondrepo % git init --bare
以後、この secondrepo をリモートリポジトリとしてリポジトリ間の同期の練習を行なう。
リモートリポジトリの設定(remote)_
ここまで、 firstrepo ではリモートリポジトリを設定していなかった。リモートリポジトリの設定は remote コマンドを用いて行なう。まず、現在の設定を確認する。今のところリモートリポジトリは設定されていないので git remote -v コマンド何も表示されない。
% cd ../firstrepo % git remote -v
先ほど作成した secondrepo をリモートリポジトリとして設定する。リモートリポジトリは複数個設定することができる。標準で使うリポジトリは origin という名前をつけて管理することになっている。secondrepo は同一コンピュータ上に存在するのでファイルアクセスでリポジトリにアクセスする。
% git remote add origin file:///home/gotoh/GitSandbox/secondrepo # git remote addの書式 % git remote add リポジトリ名 リモートリポジトリのURL(file://, ssh://, git://, https://, http://)
正しくリモートリポジトリが設定できているかを確認する。
% git remote -v origin file:///home/gotoh/GitSandbox/secondrepo (fetch) origin file:///home/gotoh/GitSandbox/secondrepo (push)
もし、リモートリポジトリのURL(今回の場合はリポジトリのある場所の絶対パス表記)が間違っているならば、以下のコマンドで修正する。
% git remote set-url リポジトリ名 リモートリポジトリのURL
リモートリポジトリのリポジトリ名を間違えた場合は、以下のコマンドでリポジトリ名を修正する。
% git remote rename 現在のリモートリポジトリ名 新しいリポートリポジトリ名
リモートリポジトリにローカルリポジトリの内容を反映させる(push)_
以下のように実行する。
% git push origin master % git push origin dev ## pushの書式 % git push リモートリポジトリ名 ブランチ名
これで、firstrepoのmasterブランチとdevブランチがsecondrepo に反映された。
リモートリポジトリのコピー(clone)_
すでに存在するリモートリポジトリの内容をコピーするときに clone コマンドを使う。cloneコマンドで3つめのリポジトリを作成する。cloneコマンドは標準では、コピー元のディレクトリ名と同じ名前のディレクトリ名でコピーしたリポジトリを作成してしまう(つまり、この例題だと GitSandbox 以下でcloneコマンドを実行すると失敗してしまう)。なので、この例題では作業用ディレクトリを用意している。
% cd .. % pwd /home/gotoh/GitSandbox % mkdir workspace % cd workspace % git clone file:///home/gotoh/GitSandbox/secondrepo % ls % cd secondrepo
clone コマンドでコピーしてきたリポジトリでは、リモートリポジトリはコピー元のリポジトリになっている。
% git remote -v origin file:///home/gotoh/GitSandbox/secondrepo (fetch) origin file:///home/gotoh/GitSandbox/secondrepo (push)
ブランチは標準でmaster だけコピーされる。
% git branch * master
リモートリポジトリに存在するブランチも見たい場合は、branch コマンドに -a オプションをつける。
% git branch -a * master remotes/origin/HEAD -> origin/master remotes/origin/dev remotes/origin/master
リモートリポジトリに存在するブランチに対して checkout するとローカルにリモートのブランチがコピーされる。
% git checkout dev Branch dev set up to track remote branch dev from origin. Switched to a new branch 'dev'
Readme.mdにSection 3を付け加える。
## Introduction Hello, world! ## Section 1 An apple is a fruit. ## Section 2 A dog is not a fruit. ## Section 3 Everything is a fruit. ## Conclusion Bye, bye!
commitして、リモートリポジトリへpushする。
% git commit -a -m "Edit a file in third repository." % git push Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 359 bytes, done. Total 3 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To file:///home/gotoh/GitSandbox/secondrepo 2dda54e..ff211e8 dev -> dev
リモートリポジトリの内容をローカルリポジトリへ反映させる(pull)_
ここまでで、secondrepoの内容は firstrepo よりもリビジョンが進んでいる。そこで、secondrepo の内容を firstrepo へ反映させる。リモートリポジトリの内容の反映は pull コマンドを用いる。
% cd ~/GitSandbox/firstrepo % git branch dev * master % git pull origin master From file:///home/gotoh/GitSandbox/secondrepo * branch master -> FETCH_HEAD Already up-to-date. # pull コマンドの書式 % git pull リモートリポジトリ名 リモートリポジトリのブランチ名
現在、secondrepoとfirstrepoの内容は devブランチにおいてのみ違うため、masterブランチをpullしても何も変更はおこらない。
devブランチの変更をローカルリポジトリへ反映させる。
% git pull origin dev remote: Counting objects: 5, done. remote: Compressing objects: 100% (3/3), done. remote: Total 3 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. From file:///home/gotoh/GitSandbox/secondrepo * branch dev -> FETCH_HEAD Updating 2dda54e..ff211e8 Fast-forward Readme.md | 4 ++++ 1 file changed, 4 insertions(+)
これで、2つ目のリポジトリを経由して、1つ目と3つ目のリポジトリの同期を行なうことができた。
ファイルの各行がどのコミットで変更されたのかを調べる(blame)_
どの行がどのコミット(リビジョン)で変更されたのかを調べる場合は blame コマンドを使う。
% git blame Readme.md a42166dc (Yuichi Goto 2015-06-02 23:37:05 +0900 1) ## Introduction a42166dc (Yuichi Goto 2015-06-02 23:37:05 +0900 2) a42166dc (Yuichi Goto 2015-06-02 23:37:05 +0900 3) Hello, world! a42166dc (Yuichi Goto 2015-06-02 23:37:05 +0900 4) fdca3887 (Yuichi Goto 2015-06-02 23:37:58 +0900 5) ## Section 1 fdca3887 (Yuichi Goto 2015-06-02 23:37:58 +0900 6) fdca3887 (Yuichi Goto 2015-06-02 23:37:58 +0900 7) An apple is a fruit. fdca3887 (Yuichi Goto 2015-06-02 23:37:58 +0900 8) fdca3887 (Yuichi Goto 2015-06-02 23:37:58 +0900 9) ## Section 2 fdca3887 (Yuichi Goto 2015-06-02 23:37:58 +0900 10) fdca3887 (Yuichi Goto 2015-06-02 23:37:58 +0900 11) A dog is not a fruit. fdca3887 (Yuichi Goto 2015-06-02 23:37:58 +0900 12) ff211e81 (Yuichi Goto 2015-06-03 00:24:15 +0900 13) ## Section 3 ff211e81 (Yuichi Goto 2015-06-03 00:24:15 +0900 14) ff211e81 (Yuichi Goto 2015-06-03 00:24:15 +0900 15) Everything is a fruit. ff211e81 (Yuichi Goto 2015-06-03 00:24:15 +0900 16) a42166dc (Yuichi Goto 2015-06-02 23:37:05 +0900 17) ## Conclusion a42166dc (Yuichi Goto 2015-06-02 23:37:05 +0900 18) a42166dc (Yuichi Goto 2015-06-02 23:37:05 +0900 19) Bye, bye!
ブランチをグラフ形式で見る(log --graph)_
どこで分岐してどこで合流したのかなど見ることができる。
% git log --graph --pretty=oneline * ff211e81eb30414b7f63355db6d73ac09f97a74e Edit a file in third repository. * 2dda54e6dd1369dad35fe8249180319ae7665f4f Merge branch 'master' into dev |\ | * 27220c536364a58dbcbedf87698929f522b46070 Edit unmerged_file. * | c5dec9fb718a142d1e0792244313e5f2b7dee398 Edit unmerged_file. |/ * d22ba348daf8e6f207910c781f15bcff3db888b0 Modified spell miss. * 9822d48618705a63ff18184d79678115977ddd31 Add a file. * 33680717af4a062aa513273081046ebcb700fd1c Removed a file. * 236ce3388b51c7676e4d0353651a1f724fd0d9ff Renamed and moved a directory and a f * 145ad6d3f1cbee739b646412aff68cff9ebe8bbf Add new file. * fdca38879780ec51f8ef919a96284ff1f68c09f1 Third commit. * a42166dcbeb3dae2cb2f24941d51a8f3c13c2dd4 Second commit. * e3df413968a558694d0901e827b6617d5dde8b80 First commit.