ミクシィ主催の git challenge というイベントの第5回 に参加できるようになった.

git は普段から使っているが,基本的に AtlassianSourceTree を使ってポチポチしてるだけであり,知識は必要に応じてググったものしかない. ということで,とても良い機会なので本を一冊読んで,一から丁寧に学んでみることにした.

今回読んだのは,「Gitによるバージョン管理」という書籍. 年末に大学の理工書店に行ったときに,あったものから適当に選んで買った. なんか初級者向けでは無いらしいので,面白いかなぁと.

git コマンド備忘録

書籍を読んで得た git コマンドに関する知見のメモ

「なんかこんな感じのことしてくれるコマンドなかったっけ??」を検索する用. 自分で書いておけば同じ「なんかこう」が出るでしょう,たぶん.

git init

リポジトリを作成してくれるコマンド.

Git のリポジトリは大きく分けて2つ

  • .git がサブディレクトリに格納されているリポジトリ(non-bare)
  • それがむき出し(bare) の bare リポジトリ

GitHub なんかを使って普段使ってるのは前者の non-bare リポジトリ. bare リポジトリはリポジトリの更新情報だけを持つため,ワーキングディレクトリを持たない. ローカルでは普通,ワーキングディレクトリが要るので non-bare だが,リモートは bare (と non-bare)をあげるべきらしい(そうしないとワーキングツリーとインデックスの差異でうまく動作しないことがるそうな).

git-init には --bare というオプションを与えると bare リポジトリを作成できる.

また,--shared オプションを付けることで,リポジトリの共有をするのに適した設定をしてくれる.

git config

リポジトリの設定を行ってくれるコマンド.

リポジトリの設定は .git/config に,ログインユーザー全体の設定は ~/.gitconfig に書かれている(前者が優先される). 直接書き換えることもできるが,このコマンドでも書き換え可能である.

ユーザー名やメールアドレスはもちろん,メッセージ編集用のエディタやテキストビューア,git コマンドのエイリアスも設定できる. git rebase -i などで vi を使うのが嫌な emacs 派の人々はぜひ emacs に設定しなおしておこう!

git add

最も基本的なコマンドの一つ.

ファイルをインデックスに登録するコマンド. インデックスとは,コミットするファイルのキャッシュのようなモノ. つまり,コミットするファイルを指定するコマンドのようなモノ.

ちなみに,git では空のディレクトリを git-add できない. どうしてもしたい場合によく使われるのは,中に何も書かれていない .gitignore ファイルを入れておくことだそうな.

更にちなみに,SD1月号 にあった,「特定のファイルだけ git-add するシェル芸」として

$ git ls-files -m | grep 'A' | xargs git add

というのがあった. git-ls-files が便利で,-m オプションで「変更が加えられたままコミットされていないファイル」のみを表示してくれたり,-o オプションで「新規ファイル」だけを表示してくれたりする.

git diff

管理されてるファイルの差分を出力するコマンド.

ブランチ,インデックス,ワーキングツリーの差分を確認できる. 単純に git diff とした場合はインデックスとワーキングツリーの差分を出力する.

git diff の後にファイル名やブランチ名を指定することで,ファイル単位やブランチ単位で比較できる.

-M オプションでリネームも確認できる.

git log

コミットログを出力する.

git shortlog であれば要約したコミットログが出力される.

git log 1c26396e..d9e8f42 のようにコミットログのハッシュ値の範囲を指定することで,出力するコミットの範囲を指定できる. 1c26396e では8桁で指定しているが,一意に定まれば何桁まででも省略してよい.

-p オプションで差分も表示すし,-1 など数字を指定すると最新から n 回分を出力する. 出力を整形するオプションもある.

git status

ワーキングツリー,インデックス,リポジトリの状態を確認するコマンド. -s オプションで短縮表示する.

git mv

git で管理されているファイルやディレクトリのリネームや移動を行うコマンド. 単なるファイルシステム上のコマンドを使わずに,git mv を使えば変更や移動をインデックスに登録してくれる.

git rm

git で管理されてるファイルの削除を行うコマンド. git mv 同様,インデックスに削除したことが登録される.

削除したファイルを元に戻すには,git checkout HEAD hoge みたいにすると良い.

git commit

インデックスに登録された変更をローカルリポジトリにコミットするコマンド.

コミットメッセージは慣例的に

  • 1行目にはコミットが何をするものなのかを簡潔に書く
  • 2行目は空行
  • 3行目以降はより詳しく書く

git reset

特定のコミットまでコミットを取り消すコマンド.

(よくわからないので,検索した.)

git resetはHEADが指すコミットの指す先を変えるもの

なるほど

原則,git reset は指定したコミットに HEAD を移動している(何も指定しなければ移動しない).

で,--soft オプションを使うと,移動前との差分をインデックスに戻してくれる(そうすると,移動前と移動前の時点でのインデックスにあげてない変更の差分をワーキングツリーに残せる). --hard オプションはその差分を全て破棄する. といった感じかなぁ.

ちなみに,前の HEAD は .git の **ORIG_HEAD** にあるらしい.

(このあたりいじる問題が git challenge で出たら面白そう)

git clean

リポジトリで管理されていないファイルを一掃するコマンド.

(.gitignore で無視されてるファイルは削除できない.それを削除するためには -x オプションを付ける.)

-n オプションで削除されるファイルを確認できる. まぁどっちみち -f オプションを付けないと削除はできない. ディレクトリも削除したい場合は -d オプションを付ける.

ちなみに,git config-f を付けなくても削除できるようになる.

git grep

git で管理しているファイルを grep するコマンド.

ブランチをまたいで検索してくれる(はず). git grep <pattern> <branch> で特定のブランチ(コミットも?)検索できる. --cached オプションでインデックスからのみ検索できる.

git show

ハッシュ値を指定してコミットの内容を出力する(何も指定しなければ HEAD).

git clone

リモートリポジトリをローカルリポジトリにコピーするコマンド.

git remote

リモートリポジトリの管理や更新を行うコマンド.

origin がどこのリモートブランチなのかとかそんなところを管理する.

git merge

ブランチとブランチをマージするコマンド.

マージのされ方は大きく分けて2つある. マージ用のコミットができる場合とそうでない場合(Fast-Forward). 枝分かれしたコミットを持つブランチ同士をマージする場合(コンフリクトの可能性がある)は必ず前者になるが,そうでない場合は Fast-Forward マージができる.

--squash オプションを指定するとマージするコミットを1つにまとめれる.

git push

リモートリポジトリのブランチにローカルリポジトリのブランチを反映させるコマンド.

だいたい git push origin master って書くが,本来は git push origin master:master と書く. 左がローカルで右がリモート

git pull

ローカルリポジトリにリモートリポジトリのブランチを反映させるコマンド.

git branch

ローカルブランチを作成・削除・確認・トラッキングするコマンド.

git branch はローカルブランチの確認. -r オプションはトラッキングの確認(-a で両方).

git branch <name> でブランチの作成. 現在チェックアウトしているブランチ(コミット)以外を起点にしたい場合は <name> の後に指定する.

-m オプションでリネーム,-d オプションで削除.

トラッキングの情報は .git/cinfig にある. トラッキングはブランチ作成時に -t オプションで指定できる.

git blame

ファイルの特定の行が最後に編集された履歴を追跡するコマンド.

(git challenge で使えそう...)

git blame test.sh でファイルそのものを最後に変更したコミットを出力. git blame -L 3,7 test.sh で3行目から7行目,git blame -L 3,+4 test.sh で3行目から4行分を探索.

-C-M でファイル間でコピーした個所や,ファイル内で移動した個所を特定できるが,英数字しかチェックしないみたい(今も可は分からん).

git checkout

ブランチのチェックアウトをするコマンド. チェックアウトとは特定のブランチ(コミット)のデータをインデックスやワーキングツリーに展開する事.

ファイルまで指定すれば,特定のファイルだけを持ってくることもできる. -b オプションでブランチの作成とチェックアウトを同時に行える.

git tag

タグをつけるコマンド.

git tag と何も指定しなければ一覧を出力する. オプションでメッセージを付けたり(-m),署名を付けたり(-s),削除したり(-d)できる.

(リモートブランチ側のタグを削除するには git push)

最も近いタグを表示したり,コミットがどのタグに含まれてるかは git describe で行える.

git archive

指定したコミットのスナップショットを作成するコマンド.

git fetch

リモートリポジトリのデータを更新(取得)するコマンド.

git remote update との違いは,それがリポジトリ単位で操作するコマンドに対し,git fetch はブランチ単位で更新できること.

git stash

未コミットの状態を一時的に退避(保存)するコマンド.

git revert

コミットした内容を打ち消す 新たな コミットをするコマンド.

git rebase

敷居が高いがスゲー便利なコマンド.

使い方はなんとなくわかったけど,どういったものかをちゃんと知るために 「実用 Git」を参照した.

一連のコミットの基点を変更するコマンド

部分木を切り貼りするためのコマンドという感じ.

git rebase <branch> とすると,現在チェックアウトしてるブランチの基点を指定したブランチの先頭に移動させる. <branch> の後にさらにブランチ名を指定すれば,チェックアウトしてるブランチではなく,指定したブランチの基点を変更する.

--onto オプションを使うと全く別のブランチ(コミット)に移植できる(競合したらダメだけど).

競合したら --continue(修正してコミット),--abort(諦めて戻す),--skip(無視して次のコミット) でなんとかする.

git rebase -i

-i オプションを付けるとインタラクティブにリベースを行える.

実行すると,config で指定してあるエディタが起動して(デフォルトは vi),まずは各コミットをどのように変更するか(コミットメッセージを編集したり,コミットそのものを編集したり,前のコミットとくっ付けたり)を指定する(ちなみに上が古いコミット).

そしたら,一個一個指定した変更を行う.

ちなみに,git reset --hard @{1} でリベースをもとに戻せる.

git format-patch

一連のコミットをパッチとして出力するコマンド.

(今も使われているのかはよくわからないけど)オープンソース系のプロジェクトはリポジトリへの書き込み権限などの関係からパッチをメールでやり取りして,取り込むという開発の仕方が多かった. ため,こういうコマンドがあるみたい.

git send-email

git format-patch で作成したパッチをメールで送信するコマンド.

すごいピンポイント.

git am

メールで受信したパッチを取り込むコマンド.

すごいピンポイント.

git request-pull

git pull を申請するため 情報を出力する コマンド.

プルしたリポジトリとか,コミットの情報とか,誰がしたかとかが出力される.

GitHub のプルリクエストってこれで動いてるんだぁ(確かめてないけど). GitHub とかが無い場合は,これをメールで送るそうな.

git cherry

パッチが取り込めるかをチェックするコマンド.

git cherry-pick

特定のコミットをブランチに取り込むコマンド.

git cherry で確認して,良かったやつを git cherry-pick で取り込むって感じ.

git bisect

バグが入り込んだ位置を特定するコマンド.

二分探索をするっぽいんだが,バグがあった・なかったを簡単に指定して,自動で半分のところにチェックアウトしてくれる.

全自動するコマンドもある git bisect run make

show-branch

ブランチの状態を表示するコマンド.

各ブランチの最新コミットや,どこから派生したかとか.

git submodule

他のリポジトリ(同じリポジトリの違うブランチでもいい)をモジュールとするコマンド.

git gc

リポジトリのオブジェクトを掃除するコマンド.

git rebase なんかで到達できなくなったコミットオブジェクトを削除する. git は各コミットを差分ではなくスナップショットで保持してるので,コミットが多くなるとどうしても重くなる. そのとき用らしい.

基本的に各種コマンドが実行されるときに自動で実行されているらしい. どれぐらいの期間や,どのタイミングで削除するかは config で設定されているらしい.

git reflog

HEAD の時系列的な遷移を出力するコマンド.

git では HEAD の時系列的な遷移を参照ログとして残している(そのうち削除されちゃうけど). git resetgit rebase でもとのコミットが tree 上にはなくなったときに便利.

Git リポジトリの中身

git オブジェクトは全部で4種類ある.

  • blob オブジェクト
    • ファイルに対応するオブジェクト
  • tree オブジェクト
    • ワーキングツリーを構成するツリー(ディレクトリ)に対応するオブジェクト
  • commit オブジェクト
    • コミットの情報を保持するオブジェクト
  • tag オブジェクト
    • タグの情報を保持するオブジェクト

全てのオブジェクトには SHA-1ハッシュで生成された名前が付けられている. 名前が 00174b4bc3458089ed7c870d8774ca428b371836 だった場合,オブジェクトは .git/objects/00/174b4bc3458089ed7c870d8774ca428b371836 にある.

git cat-file

オブジェクトの中身は deflate圧縮(zlib圧縮の一つ)されており,各オブジェクトの種類によってフォーマットが異なる. しかし,git cat-file <object_name> でその中身を読みやすいようにして出力してくれる.

-t オプションでオブジェクトの種類(blob やら tree やら)を返し,-s でオブジェクトのサイズを返し,-p でデータを読みやすいようにして返し,-e でそもそも存在するかどうかを返してくれる.

blob オブジェクト

ファイルサイズと実際のファイルのデータが圧縮されて保存されている.

git rev-parse :hogehoge ファイルの blob ファイル名を出力してくれる.

tree オブジェクト

各ファイルのパーミッション,オブジェクトの種類,ハッシュ値,実際のファイルやディレクトリの名前が圧縮されて保存されいる.

git rev-parse HEAD^{tree} で HEAD の tree オブジェクト(ルートディレクトリ)のハッシュ値を返す.

commit オブジェクト

tree オブジェクト名(ルートディレクトリ, tree),親コミットの commit オブジェクト名(複数あるかも, parent),変更の作成者(author),commit オブジェクトの作成者(commiter),コミットメッセージが保存されている.

git rev-parse HEAD で HEAD のハッシュ値を返す.

その他

~^

特定のコミットから相対的に指定する方法には ~^ がある.

HEAD~1 は深さを指定してる. 要するに何個前か.

HEAD^1 は幅を指定してる. マージとかして親が複数ある時に,どの親に戻るかを指定してる.

おしまい