この記事は、
- Git完全に理解してる
- ツリー構造の意味はわかる
- Gitは便利なのでなんか書くとすぐ
git init
する
みたいな人を対象にしています。
Gitとは
でぃすとりびゅーてっどばーじょんこんとろーるしすてむ。という話は今回はどうでもいいので置いときます。
GitはOSのファイルシステムを活かして実装されたオブジェクトデータベースです。データベース内に作成される無数のコミットオブジェクトは、自分以外のコミットオブジェクトを参照していて、それを再帰的に辿ることで長い履歴のチェーンが構成されます。いわゆるハッシュツリーの一種。データ構造のビジュアルイメージだと、暗号通貨のブロックチェーンが類似していますね。ブロックに相当するのがコミットで、それが連なったチェーンがコミットログに相当します。まあGitは分岐と合流があるので根本的に用途が違うんですけどそれは置いといて。
利用者は「ファイル」をGitの内部表現に直したblob
と、複数のblobへの参照に名前をつけてまとめて管理するtree
、一つのtreeといろんなメタデータをまとめたcommit
の3種類のオブジェクトをがんがん作ります。Gitオブジェクトデータベース内では一度作成したオブジェクトは不変なので、ツリーの書き換えは新しいノードを作り直すことを意味します。
……こんなのを長々と書いててもしょうがないのでさっさと紹介していきましょう。
(※ 個人が多用するコマンドを挙げているものですので効能には個人差があります。)
基本
git status
ステータスを確認しろ。
自分が何をやっているのか、今内部がどういう状態になっているのか。少しでも引っかかったら、いや引っかからなくても、すぐにステータスを確認するのです。
git log --graph --oneline --decorate --all
コミット履歴をツリー表示します。これがないとはじまらない。ツリーの構造と各refの位置を常に頭にいれておこう。--all
は全てのrefから辿れる場所を表示するオプションなので、状況によって--branches
や--remotes
に変えたり、外したりします。あとはたまに--date-order
とかつけたりする。aliasにしとこう。
git branch temp
ツリーを書き換える前に現状のツリーを一旦バックアップしておきたいですね?なにかミスったとき、reflogを確認して、目Grepで目的のコミットを探って、@@{NN}
とか打つの?そんなめんどくさい……。とりあえずブランチを作っておけば、diffで比較対象にするにもresetで戻るにも名前で辿れるようになるので楽です。使い終わったらgit branch -D
で消そう。
git reset --hard
git clean -d(n|f)
ツリー操作する場面でワーキングなんていう具象は不要です。まっさらきれいにしておきましょう。git clean
はUntrackedを消し去るのでめっちゃ注意して使ってださい。statusの確認を忘れない。
git checkout -
一つ前にチェックアウトしていたブランチに飛びます。そりゃベンリだ!
ラベル操作
git reset
resetコマンドがどういうコマンドだか知っていますか?「HEADが参照しているブランチラベルを任意のコミットに付け直す(Re-Set)」コマンドです。例えばブランチラベルを親に付け替えて最先端コミットへの参照を切ったり、ラベルを全く別のツリーに付け替えてみたり、画面見ないで最新のリモートに追従するときなど、いろいろできます。とってもベンリ。
resetには3つの大事なオプションがあって、
-
--hard
: 「移動した先のコミットが保持するツリーをインデックスツリーとワーキングに反映する」 -
--mixed(でふぉると)
: 「移動した先のコミットが保持するツリーをインデックスツリーには反映するがワーキングに反映しない」 -
--soft
: 「ラベルを移動するだけでインデックスに影響を及ぼさない」
というものがあります。必要に応じて3つのオプションから適切なものを選びましょう。resetはワーキングへの影響があるコマンドなので、チェックアウトしているブランチに対する操作しかできません。
git branch -f mybranch target
ブランチをつくるコマンド、git branch
。これに-f
をつけると……既に存在しているブランチであっても強制的に作成、つまり、「任意のブランチを任意の位置に自由に付け替える」コマンドに化けます。git reset
がチェックアウト中のブランチのみを操作できるのに対して、こちらは逆に「チェックアウトしていないブランチのみを操作できる」ことになります。使えるほうを選んでください。
ちなみに、target側に任意のリモートブランチを指定すると自動的にトラッキングされてしまうというおせっかいな挙動があるので、そういうときはあとで--unset-upstream
するか、--no-track
を付けといてトラックしないようにするのがよいです。branch.autoSetupMerge
というconfigもありますが、これをいじると他のところがだいぶ不便になるのでおすすめしません……
あと、git branch -f mybranch origin/mybranch
とするとトラッキング中のリモートの位置にラベルを移動となるので、チェックアウトしてないブランチをリモートの最新に追従するのに使えます。めっちゃベンリ。だけど長くて書きづらい。aliasにしてもいいけど、もっといい方法はないものか……
コミット操作
git rebase -i
言わずと知れた万能コマンド。特定の一本のチェーンを組み直したいシーンではまずこれでいい。コミットをまとめたり入れ替えたり、履歴を分割するにも使える。おおよそ「書き換え」に相当する操作は全部これでできる。最強。他にいろんなコミット操作コマンドはあるけど、それらを全部覚えるより見た目にわかりやすくて機能も十分なrebase -i
使えばいいやって感じ。
そういえば、pick
reword
edit
fixup
は使うけどそれ以外は使ったことないな……drop
とかはただ行消せばいいわけだし……
git cherry-pick
任意のコミット一つを好きな場所に生やすことができる。それだけ。必要なときは必ず来るので覚えておこう。
git rebase --onto
任意のコミット列を好きな場所に生やすことができる。ツリーこねこねのときはとても役に立つ。思い返すとcherry-pickよりよく使ってる。
コマンドに渡す引数は、
git rebase --onto <付けたい場所> <付け替えるコミット列の**ルート**> <付け替えるコミット列の最終端>
の順番なので間違えないようにしよう。3つ目の引数を省略するとHEADになる。2つ目の引数はコミットチェーンの根本を指定するもので、指定したコミットそのものは付け替えるコミット列には含まれないので注意。
git commit --amend
普通のcommit
だと新しいコミットを作るときに今いるコミットを親にするけど、--amend
を付けると「今いるコミットの親」を親として新しいコミットを作成する。要するに今のコミットを置き換えるコミットを作るコマンド。いくつかよく使うオプションがあって、
-
--reset-author
: Author情報とか作成日時とかを全部リセットして、その瞬間作られたようなコミットを作ります。user.name
とかuser.email
の設定を間違えてたときとかに戻すのに使ったりする。 -
--no-edit
:--amend
はその名の通り修正用途でよく使われるので、デフォルトではコミットメッセージのテンプレートを書き換え対象コミットから持ってきます。これをエディタすら起動せずそのままおっけーするオプションが--no-edit
です。え、これを打つほうがだるい?そんな気もする……
おわり
いまパッと思いつくものを挙げてみただけなのでいろいろ書き忘れてるのは間違いないけど、とりあえずこれらはよく使ってるやつです。これもいいぞ~ってコマンド知ってたら是非教えてください。だいたいツリこね勢には常識ばかりですが、そもそも身の回りにはあんまり見かけないですね、ツリこね勢。
実際いろいろツリーをいじれるようになると、あとからいくらでもツリーを整理できる安心感からか、カジュアルにGitを振り回せるようになってスピードがでます。あと、本能的に「いじるのがめんどくさい」ようなコミットをあまり作らなくなる効能もあるような気がします。気がするだけ。
Gitは構造がシンプルなので難しいことはなにもない……と言いたいところでしたが、**コマンド名がとにかく難しい。**オプションの数とかネーミングとか、どれもこれも正気じゃない。何も考えずに追加しまくった結果みたいな。まあ、そこらへんは内部構造を念頭に置いて覚えてしまえばいいので、それくらいでしょうか。
あなたも今日からどうです?ツリこねは良いぞ。