Git完全に理解してるひと向けコミットツリーこねこねコマンド一覧

この記事は、

* 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は構造がシンプルなので難しいことはなにもない……と言いたいところでしたが、**コマンド名がとにかく難しい。**オプションの数とかネーミングとか、どれもこれも正気じゃない。何も考えずに追加しまくった結果みたいな。まあ、そこらへんは内部構造を念頭に置いて覚えてしまえばいいので、それくらいでしょうか。

あなたも今日からどうです？ツリこねは良いぞ。