読者です 読者をやめる 読者になる 読者になる

プログラミングメモ

ソフトウェア開発に関する技術メモ。

gitの使用方法メモ

mercurialからの類推で git を使おうとしたが挫折。
gitの説明書を読んで基本的な概念と操作をまとめてみる。

参照した説明書は次の2つ。

動作は git version 1.7.10.4 で確認した。

基礎概念

コミット
ファイルツリーのある時点でのスナップショット。mercurialでのchangeset。コミットは、61c4efc3〜 のような16進数の番号が付けられている。gitコマンドの引数でコミットを指定するときはこの番号を使う。
ブランチ
開発ライン。コミットの連なり。動作上は、コミットを指すポインタと考えてよい。その意味では、ブランチは、一連のコミットの最新を指していると考えることができる。コミットの連なりは途中から分岐できるため、複数のブランチを持つことができる。gitコマンドでコミットを指定する箇所には、基本的にブランチ名も指定できる(ブランチの最新コミットを指定したことになる)。
HEAD
現在のブランチ。作業ディレクトリには、いずれか1つのブランチを選択して、その最新コミットの内容を取り出す。HEADは、作業ディレクトリの取り出し元となったコミットともいえる。mercurialのheadとは異なる。gitコマンドでブランチ名やコミットを指定する箇所には、基本的に"HEAD"を指定できる(作業ディレクトリの取り出し元コミットを指定したことになる)。

ファイルの編集とリポジトリへの登録

  1. ブランチを選択して、ファイル一式を作業ディレクトリに取り出す(checkout)。
  2. ファイルを編集する。または、追加・削除する。
  3. 変更後のファイルを「ステージング領域」に登録する。または、ステージング領域に新規ファイルを登録したり、ファイルの削除を登録する。
  4. 2, 3 を必要なだけ繰り返す。
  5. ステージング領域の内容をcommitしてコミットを作成する。

同じブランチを対象に作業する限り、1は再実行しなくてよい。

git の help に出現する用語 "index" はステージング領域を指す(厳密には別物かもしれないが・・・)。

作業対象のブランチの切り替え (checkout)
git checkout <ローカルブランチ名>

ローカルブランチの代わりにリモートブランチやコミット(の番号)も指定できる。ある時点でのファイル一式を作業ディレクトリに取得してビルドしたり実行確認するのに利用できる。この場合、作業ディレクトリはどのブランチにも属さない状態(detached HEAD)になる。この状態でコミットしても、どのブランチにも影響を与えない。既存のブランチを誤って更新してしまう心配がない。

detached HEADの状態でもコミットは可能。ただし、コミット番号を覚えておかないと、別のブランチに切り替え(checkout)した後に戻ってこられなくなる。detached HEADな状態で今のコミットを新たなブランチとして登録するには、

git checkout -b <新しいブランチ名>

detached HEADな状態でプログラムの修正を試し、うまく行けばコミットし(場合によってはブランチ名を付け)、その後メインのブランチに戻って、そのコミットをマージする、という使い方もできる。

ステージング領域への登録、commit、変更の取り消し

https://cacoo.com/diagrams/fpbnI786Ta8lpOek-960E2.png

備考

  • checkoutコマンドは次の2つの機能を持っているため、混同しないように注意。
    • 引数にパスを指定しない → 作業対象のブランチを選択する。
    • 引数にパスを指定する → 作業対象のブランチは変更せず、指定のコミット、ブランチ、またはステージング領域からファイルを作業ディレクトリ(またはステージング領域)に取得。
  • 「checkout -- <パス>」などの -- は、後続の引数がパスであることを示すマークであって、単独の引数として意味があるわけではない(コミット名やブランチ名を表しているわけではない)。

diff

https://cacoo.com/diagrams/fpbnI786Ta8lpOek-DF6A5.png

マージ作業中のdiffについては、後述する。

リポジトリとの連携、マージ

リポジトリのcloneと、ローカルブランチ・リモートブランチ

cloneによって作られるリポジトリは、次の点で、clone元のリポジトリとは内容が異なる。

  • リポジトリをcloneすると、clone元リポジトリのブランチは、自分のリポジトリには「リモートブランチ」として登録される。
  • commitコマンドにより更新できるのは「ローカルブランチ」である。「リモートブランチ」は直接は更新できない。
  • cloneすると、ローカルブランチも自動的に作成される。

https://cacoo.com/diagrams/fpbnI786Ta8lpOek-80515.png

リポジトリは、複数リポジトリと連携できる。ここで言う連携とは、push/pull(fetch)の相手となることである。複数リポジトリと連携するため、それぞれに名前を付けて管理する。cloneすると、clone元には"origin"という名前が自動的に付く。名前の一覧表示は git remote で、名前の登録は git remote add で行う。

ローカルブランチとリモートブランチの間に「追跡」関係を設定すると、push/pull(merge)の際の相手が自動的に決定できるようになる。cloneしたときは自動作成されたローカルブランチとリモートブランチとの間に追跡関係が自動設定される。

fetch

連携している別リポジトリから内容をコピーするには、git fetch を実行する。

git fetch <リポジトリ>

指定したリポジトリから取得する。

git fetch --all

名前を付けて登録されている(git remoteで登録)別リポジトリすべてから取得する。

git fetch

現在のブランチと追跡関係にあるリポジトリ(と思われる・・・)だけから取得する。

https://cacoo.com/diagrams/fpbnI786Ta8lpOek-0B28B.png

merge

現在のブランチに、指定したブランチの最新コミットの内容をマージする。

git merge <ブランチ名>

https://cacoo.com/diagrams/fpbnI786Ta8lpOek-AE847.png

備考

  • git merge では、ブランチ名の指定は必須。以下の設定をすると、ブランチ名を省略できるようになり、省略した場合は追跡関係にあるリモートブランチ*1が指定されたとみなす。
git config merge.defaultToUpstream true
  • git pull は、fetch と merge をまとめて実行。このときは、ブランチ名の指定は省略できる。
  • fetchの実行後に、分岐が発生しているか(mergeが必要か)を確認するには、git status を実行すればよい。次のような形式で表示される。
# On branch dev
# Your branch and 'origin/dev' have diverged,    ← have diverged = 分岐している
# and have 1 and 1 different commit each, respectively.
#
nothing to commit (working directory clean)
push

リポジトリに自身の内容を送り出す。

https://cacoo.com/diagrams/fpbnI786Ta8lpOek-CE397.png

備考

  • ブランチを更新していなくても、送出先のブランチが更新されていると(自分のほうが古いと)、pushは失敗する。
  • デフォルトでは、ローカルブランチが複数ある場合、それらすべてをpushしようとする。したがって、自分が作業しているのとは別のブランチについて、送出先のブランチが更新されている場合にもpushが失敗する。なお、これは、fetchすれば解消する問題ではなく、mergeが必要である。fetchで更新されるのはリモートブランチであり、push対象となるローカルブランチはfetchしても更新されない。ローカルブランチの内容をリモートブランチと合わせるにはmergeが必要である。
  • push時に、作業中のブランチについてのみpushされるようにするには、次の設定をする。この設定により、前述の問題は回避できる。
git config push.default upstream
マージ作業中のdiffとファイル取得

git merge でマージにコンフリクトが生じた場合は、手作業で解消する必要がある。
このとき、ステージング領域には、commit予定のファイルを置く領域とは別に、stage1, stage2, stage3 という、3つの別の領域が設けられる。これら3つの領域とのdiffを確認することで、マージ対象となるブランチそれぞれとの差分を確認できる。

https://cacoo.com/diagrams/fpbnI786Ta8lpOek-819A2.png

備考

  • マージ中は、マージの相手となるコミットにMERGE_HEADという別名が付く。git diff MERGE_HEAD で、マージ相手と作業ディレクトリの差分を確認できる。
  • git diff MERGE_HEAD と git diff --theirs (stage3との比較) とは異なる。stage3と作業ディレクトリは、自動的にマージできたものは反映済みである。したがって、git diff --theirs はコンフリクトが生じた部分のみ表示される。MERGE_HEADは、マージ相手そのものである。したがって、git diff MERGE_HEADは、自動マージ済みの部分も差分として表示される。
  • 次のコマンドで、statge0〜3のステージング領域のファイルの内容を表示できる。は0〜3の番号。
git show ::<パス>
  • gitがコンフリクトによるマージ中断したファイル(<<< >>> などのマークが入ったもの)を編集後に、編集前の <<< >>>などが入った状態に戻したい場合は、git checkout -- <パス> を実行すればよい。
  • マージがコンフリクトした場合で、マージ作業を中断してマージを取り消すには、 git merge --abort

ブランチの操作

一覧
  • ローカルブランチ一覧、現在のブランチの確認
git branch

現在のブランチは * 記号付きで表示される。

  • ローカルブランチ一覧、最新のコミットコメント付き
git branch -v
  • リモートブランチ一覧
git branch -r
作成
  • ブランチを作成
git branch <名前> <元のコミット>

<元のコミット>省略時は、現在のブランチを元にする。

  • ブランチ作成と同時にcheckout(作業ディレクトリ内容も更新)
git checkout -b <名前> <元のコミット>

branch, checkout -b のどちらの場合も、元のコミットがリモートブランチなら、追跡関係が自動的に設定される。

git push --set-upstream <リポジトリ> <ローカルブランチ>

<リポジトリ>はoriginなど。ローカルブランチと同じ名前で別リポジトリに作成する。
--set-upstreamを付けることで、ローカルブランチと新規追加されるリモートブランチに追跡関係が設定される。
ローカルとは別の名前で別リポジトリに登録したい場合は、

git push --set-upstream <リポジトリ> <ローカルブランチ>:<別リポジトリでのブランチ名>

履歴表示

  • 今のブランチのログを表示
git log

今いるブランチのログだけ表示される。

  • 別のローカルブランチや、リモートブランチのログを表示
git log <ブランチ名>

連携している別リポジトリのログが参照したい場合は、git fetchで内容をコピーして、git log origin/master のようにリモートブランチ名を指定してログを表示する。

表示内容の選択
  • diffも合わせて表示
git log -p
  • 変更されたファイルの一覧と変更量も表示
git log --stat
  • 1コミット1行で表示
git log --oneline  
  • 分岐・マージのわかるグラフ付きで表示
git log --graph
表示する履歴の絞込み
  • master(の最新)の祖先。ただし、v2.5とその祖先は除く。
git log v2.5..master

v2.5の延長にmasterがある場合は、単純にv2.5の次からmasterまで。途中で分岐してv2.5とmasterに分かれた場合は、分岐地点の次からmasterまで。

  • master(の最新)の祖先と、v2.5の祖先。ただし、masterとv2.5の共通の祖先を除く。
git log v2.5...master

途中で分岐してv2.5とmasterに分かれた場合は、分岐地点からv2.5までと分岐地点からmasterまで。

  • コミットコメントにfooが含まれるもの
log --grep foo


hg incoming と同様のこと(リモートブランチにあって、自分のブランチに無いコミットを表示)がしたい場合は、git fetchした後で、たとえば次のようにする。

git log HEAD..origin/master

hg outgoing と同様のこと(pushにより送出されるコミットを表示)がしたい場合は、

git log origin/master..HEAD

HEADは省略できるので、これらは単に、次のように実行してもよい。

git log ..origin/master
git log origin/master..

*1:このブランチは、.git/configの[branch "ブランチ名"]のremote=とmerge= で指定されている