89
90

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Gitのコマンドはなぜ使いにくいのか

Posted at

Gitのコマンドは分かり辛く使いにくい

これに異論を唱える人はあまりいないと思うが、もしいたらその人は多分すでにGitの悟りを開いた人で、この記事を読むメリットは全くないので、無駄な時間を過ごさぬようそっ閉じしてもらえればと思う。

Gitのコマンドが分かり辛いのは、Subversionと違い過ぎるからとか、低レベルな操作を中心に設計されているからとか、オプションが多過ぎるからとか言われるが、この記事では、もう少し具体的で卑近で嵌りがちな個別の3つの点を挙げ、それぞれについて解説したい。

① コマンドの引数に何を指定したらいいか分からない

Gitのコマンドの引数は非常に柔軟に指定でき、悟りを開いた人にとっては便利だが、初心者には複雑で分かりにくい。
例えば、git reset -hを実行すると以下のようなusageが表示される。

$ git reset -h
usage: git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]
   or: git reset [-q] <tree-ish> [--] <paths>...
   or: git reset --patch [<tree-ish>] [--] [<paths>...]

これによるとgit resetは、<commit><tree-ish><paths>を引数にとるらしい。
<paths>は簡単だ。ワーキングディレクトリ内のファイルやディレクトリのパスだ。
<commit>はコミットのことに違いないだろうが、ちょっと引っかかる。確かにコミットIDを使ったgit reset d921970みたいなのも見たことはあるけど、どちらかというとgit reset HEADgit reset masterのようなのをよく見る。HEADmasterがコミット???
<tree-ish>なんて何のことやらさっぱり。

こんな感じで、ヘルプやリファレンスで仕様を正確に理解することをあきらめ、使うたびにググって場当たり的に対症的にコマンドを使い、git resetはコミットを取り消すコマンドだというような勘違いを深めてしまう。

このような初心者の域から脱するためには、まずGitのリポジトリ構造(オブジェクトモデル)を理解すべし。
少なくとも、<commit><tree-ish>を理解するには、ブランチやタグは単一のコミットへの参照(i.e. リファレンス)、HEADは(通常は)ブランチへの参照、コミットはそのコミット時点でのプロジェクトのルートディレクトリへの参照であるということを理解しておく必要がある。

<commit>の意味するもの

ヘルプやリファレンスに<commit>と書いてあったら、それはコミットそのものと、コミットに再帰的にデリファレンスできるものと考えていい。デリファレンスとは、参照先のもので置き換えることだ。
ブランチは一回デリファレンスするとコミットになる。HEADは一回デリファレンスするとブランチになり、もう一回デリファレンスするとコミットになる。
といった具合なので、<commit>の部分には、コミットID、ブランチ、タグ、HEAD、タグオブジェクトIDなどを指定できる。

ついでに言えば、<branch>と書いてあっても実際には<commit>と同じ範囲を指す場合が多い。
例えばgit worktreeのヘルプは以下の様なものだが、この<branch>は実際にはタグでもコミットIDでもHEADとかでもいい。

$ git worktree -h
usage: git worktree add [<options>] <path> [<branch>]
   or: git worktree prune [<options>]
   or: git worktree list [<options>]

たまに<commit-ish>というのも見かけるが、これは「コミットっぽいもの」という意味で、<commit>と同様の範囲を指していると考えていい。

$ git revert -h
usage: git revert [<options>] <commit-ish>...
   or: git revert <subcommand>

<tree-ish>の意味するもの

<tree-ish>は、上記の<commit-ish>と同様「ツリーっぽいもの」だ。
ツリーとは、Gitのリポジトリ内で単一のディレクトリを表すツリーオブジェクトのこと。「ツリーっぽいもの」はツリーまたはツリーに再帰的にデリファレンスできるもの。
よって<tree-ish>の部分には、ツリーオブジェクトID、コミットID、ブランチ、タグ、HEAD、タグオブジェクトIDなどを指定できる。
ただ、普通ツリーオブジェクトIDはユーザからは見えないので、これ以外のものと考えると、実用的には<tree-ish>イコール<commit-ish>と考えていい。
<tree-ish>と書いてあるときはファイル群に対する操作で、<commit-ish>と書いてあるときはコミットに対する操作というニュアンスの違いはある気がする。

② 複数の機能をもつコマンドがある

創造神の愛するUNIXの「一つのことを、うまくやれ」という哲学に反し、Gitには引数の与え方によって使い分ける複数の機能をもつコマンドがある。
このことはコマンドやコマンドリファレンスを無暗に複雑にしてしまっていて、Gitのコマンドを覚えにくく使いにくくしている一因だ。

例えばgit reset

$ git reset -h
usage: git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]
   or: git reset [-q] <tree-ish> [--] <paths>...
   or: git reset --patch [<tree-ish>] [--] [<paths>...]

git resetは、引数に<paths>を与えるか与えないかにより、二つの異なる機能を使い分けなければいけない。
<paths>を与えた場合はインデックスを更新する機能で、与えない場合はHEADを付け替える機能。詳しくはここに書いた。

もともとgit resetは後者の機能だけを持っていたが、あとで前者の機能が追加された。
これは蛇足だったと思う。コマンドの一貫性を崩してしまった。

git reset <paths>はたいていgit addを取り消す用途で使われるので、代わりにgit unaddみたいなコマンドにしておけば分かりやすかったのに。
もっといえば、そもそもgit addも名前が微妙なので、git stagegit unstageだったらよかったのに。

などと愚痴を言ってもしょうがないので、git resetは二つの機能を持っていると知り、自分がどちらの機能を使っているのかを意識しながらコマンドをたたけば、そうでない場合に比べて格段に速く深く身につくはずだ。

git resetの他にも、git checkoutもなんだかもやっとする挙動をして分かり辛いと感じる人が多いだろう。
このコマンドも3つの異なる機能を持つと説明されることがある。
その説明も理に適ったものだが、この記事では別の形で説明してみたい(次節)。

これらの他にも複数の機能を持ったコマンドがあるかもしれないので、使っていて違和感を覚えたらリファレンスに当たるべし。

③ 巷の解釈と実装がずれている

Gitのコマンド体系はGitのオブジェクトモデルを念頭に構築されているが、初心者向けにオブジェクトモデルの説明を避けてコマンドの説明をするサイトが多く、不適切な解釈が巷に広まっているケースがあるように思う。
前節で取り上げたgit resetに対する、コミットを取り消すという解釈もその一つだが、ここではgit checkoutを例に挙げる。

git checkoutはほとんどのサイトでブランチを切り替えるコマンドと説明される。
Gitにおけるブランチの切り替えとはHEADの付け替えのことなので、つまりgit checkoutHEADを付け替えるコマンドと解釈されているわけだが、リファレンスには以下の様に書かれている。

Updates files in the working tree to match the version in the index or the specified tree.
If no paths are given, git checkout will also update HEAD to set the specified branch as the current branch.

つまり、git checkoutの主たる機能は、リポジトリに合わせてワーキングディレクトリを更新するものだ。
もう少し噛み砕けば、リポジトリからワーキングディレクトリにファイルを取り出すものだ。
これがGitにおける「チェックアウト」であって、HEADの付け替えは二次的なオプショナルな作用である。
詳しくはここに書いた。

git checkoutをブランチを切り替えるコマンドだと覚えてしまうと、git checkout .を実行したときにaddしていない変更が破棄される挙動にもやっとし、そのもやっに阻まれてなかなかコマンドが手になじまない。
リポジトリからワーキングディレクトリにファイルを取り出すものという正しい解釈を持てば、全ての機能が一貫し、心地よく受け止められるものになるだろう。

git checkoutの他にも間違った解釈をしてしまっているコマンドがあるかもしれないので、使っていて違和感を覚えたらリファレンスに当たるべし。

まとめ

初心者を卒業し、Gitコマンドをちゃんと使いこなせるようになりたいなら、オブジェクトモデルを理解し、リファレンスを読むべし。

89
90
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
89
90

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?