はじめに
本日mixiが主催するGit challengeというイベントに参加してきました。
git版CTFみたいなもんなので、普段ちょっと使っているだけの自分が解けるか不安でしたが、参加した結果大変勉強になったので、学んだことや知っとくと得になりそうなことを纏めておきたいと思います。
イベントの性質的に普段使うか微妙ものも多いですがいざという時のために知っておくと便利だと思います。(今後もgit challengeは開かれるようですしね!)
え、結果?別にどうだっていいじゃないですか(お察し下さい)
基礎知識
すっごく大雑把に説明します。分からなかったら調べて下さい(放棄)
commit
gitの一番コアな部分。gitはこれの積み重ねでversion管理をしています。
大雑把な説明ですが、コミットは自分の親コミットと変更点を持っています。(よく有向グラフで表せられますよね。)
branch
コミットへのポインタのようなもの。多くの場合ブランチで作業をします。(「多くの場合」ということはそうでないこともあるのか?→ある。"detached HEAD"とか)
あれ、説明が難しい?イメージ的に単方向連結リストなんだが...
merge
2つのコミットの 共通祖先を見つけて そこからの変更点を一方に取り込みます。
このときfast-forwardとかno-fast-forwardとかある。
git-mergeの--ff, --no-ff, --squashの違い
pull
pull=fetch+merge
fetchはremoteのレポジトリを取ってくることです。
push
pushはpush。
--forceとかいう恐ろしいオプションがあって極力使うべきではないが、歴史を改ざんするときは諦めて使ったほうがいい時もあるかも。
gitの便利なやつら
amend
使い方
git commit --amend
直前のコミットを変更します。このときインデックスに入れていたやつもコミットに含まれます。
直前のコミットにファイルを追加したい時やコミットメッセージを変えたい時に使います。
ただし、コミットオブジェクトを変更してしまうのですでにpushしてあるコミットを変えては駄目です。
rebase -i
rebase --interactiveの略。とても便利。
以下のことが出来きます。
- コミットメッセージを変更する
- コミット内容を修正する
- コミットを分割する
- コミットをまとめる
- コミットを削除する
tip
過去すべてのコミットについて見たかったら以下のようにしたら出来きます。
git rebase -i $(git rev-list --reverse HEAD | head -n 1)
reset
headの位置を変更できます。前のコミットに戻りたい時に使えます。
--softと--hardというオプションがあって、softだとheadだけを戻します。つまりworking tree自体はそのままです。逆にhardだとそのコミットの時点までworking treeも戻します。(コミット以降の変更は消されます。)
reflog
git reflogをすると、コミットだけではなくcheckoutの履歴までみることが出来ます。
resetを元に戻したい時や、branchを復活したい時に使えます。
show
違うコミットのファイルをみることが出来ます。
git show <branch名やコミットのsha1>:<ファイル名>
checkout
多くの場合branchの移動に使っている思います。でもbranchって結局コミットのポインタのようなものなので、コミットに対しても使えます。このときdetached headになります。
git checkout <コミットのsha1>
また、特定のファイルだけ持ってくる事もできます。
git checkout <branch名やコミットのsha1> <ファイル名やディレクトリ名>
log
あたりまえのように使っていると思いますが、様々なオプションがあるということを知っておくと便利です。詳細を見たり、ワンラインで表示したり、色つけたり、楽しい!
よく使うオプション
オプション | 効果 |
---|---|
-n <数字> | <数字>分のコミットを表示します。 |
-p | コミットでのdiffを表示します。 |
--pretty | フォーマット別に表示を切り替えれます。 |
--after | 指定日以降のコミットだけを表示します。 |
--before | 指定した日付以前のコミットだけを表示します。 |
--grep | キーワードに一致するものだけ表示します。 |
自分はgit logをglogにエイリアスして使っています。
$ which glog
glog: aliased to git log --oneline --decorate --color --graph
revert
すでにpushしてしまったものなど、reset出来ないコミットを打ち消すために使います。
その他
hook
gitはコマンドを実行した時にフックしてスクリプトを走らせることが出来ます。
なので、pushされたらテストを走らせるとかデプロイするとかそのようなことが出来ます。
git フック
履歴含めて完全削除
gitに管理されては困るものは.gitignoreに書くべきなのですが、間違ってコミットしてしまうことは多々あります。pushする前ならどうともなるのですが、pushしてしまっていたらrevertなどで消せません。もしくは昔から含まれて閉まっていた場合もresetして戻すわけにはいきません。そこでgitの歴史から特定のファイルを消す方法があります。
filter-branchを使うと、コミットに機械的にコマンドを実行できます。
git filter-branch -f --index-filter 'git rm --ignore-unmatch filename' HEAD
あとはpush --forceしましょう。
bisect
いつの間にかバグっていたり、新たな脆弱性が発見された時何時からあったのか知りたい時があります。(たぶん)
そのときテストは作ったけど全てのリビジョンについて実行するにはO(n)の時間がかりますし、スクリプトかなにか書かないといけません。
そこでgitにはbisectというテストスクリプトを二分探索で実行できる素晴らしい機能があります。
いや知らねーよ、といった感じです。
gitの内部
キーワード:tree,blob,commit,tag,ref,sha1,pack,cat-file
コミットのツリー構造がイメージが出来たらgitを使う分には十分だと思うんですが、gitの中身を知っておいて損はないです。
まあ、説明は 面倒 こちらの方が分かりやすいのでここを参考にして下さい。
また、公式リファレンスも丁寧に説明してくれます。
頑張って理解したら、普段gitの中の人がやってくれる.git以下の分からないオブジェクトを手動で作れるようになると思いますよ。
さらなる高みに
もっと知りたいって?好奇心旺盛ですね。仕方ありません、とっておきの参考資料を教えてあげましょう。これを理解できたらgitの全てを理解したといって過言ではないでしょう。
gitの真髄
さいごに
git challengeの問題を見返しながら、必要だった知識を書きだしているのでかなりとっちらかってしまいました。「こんな機能があるんだ」と参考程度に読んでくれれば幸いです。
最後にmixiさん、楽しくためになるイベントを企画していただきありがとうございました。