pubDate: 2024-03-04
author: sakakibara
gitを使用する際にエイリアスを使うとgit commitの粒度が細かくなる。 これはコマンド長が短くなると心理的ハードルが下がるためであると思われる。そこで、いくつか自分が設定しているエイリアスを紹介する。
基本2文字で統一している。
log
が少々長いが、このようにするとキラキラlogが表示される。
gitではconflictなどが生じた際に差分を表示することができる。
git mergetool
などを使うと差分をきれいに表示してくれるが、
この差分アルゴリズムはいくつか指定することができる。
いろいろ紹介するのもいいが、git-diffにある通り--histogram
を使うといいらしい。
とりあえず設定しておくといいかもしれない。
.env
に環境変数としてアクセストークンなどを保存することが多い。
これをリポジトリで管理しないように.gitignore
で無視することが通常の運用である。
しかし、.env
ファイルのアクセス権限を変更しなかったり、そもそも平文でアクセストークンを保存するのはセキュリティ上問題がある。
また、ELFバイナリなんかはリバースエンジニアリングされるとアクセストークンが漏洩する可能性がある。
awsやhasicorpはアクセストークンを保存するためのツールを提供している。 これで全てが防げるかどうかはわからないが、無いよりマシだろう。
.gitディレクトリには何がはいっているのか。
適当にgit init
して中身を確認してみる。
このようになっている。
なお、*
がついているものはディレクトリであることを示す。
hookはgitの操作の前後に実行されるshellスクリプトであるが、ここでは省略する。
exclude
を見てみる。
これはgitで管理されていないファイルを指定するためのファイルであることがわかる。 ここには他のリポジトリにとっては不要だが、自分にとっては必要な設定を記述する。 こうすることによって自分の環境のみ必要なファイルを他人に迷惑を書けることなく管理することができる。 .gitignoreのような形で書くことができる。
config
を見てみる
ここにはgit config
のこのリポジトリ限定の設定が書かれている。
例えば、適当に
のような設定をしてみると、config
に以下のような設定が追加される。
description
はどうやらリポジトリの説明文を書くファイルのようだ。
どうやらここはgithubのリポジトリの説明文(description)に相当する部分のようだ。
そしてHEAD
を見てみる。
HEAD
は現在のブランチを指している。これがどのように変化するかに着目する。
config
やdescription
はリポジトリの設定や説明文を書くファイルであることがわかった。
gitがどのようにファイルを管理しているかについて知るためには残りの
HEAD
ファイルとobjects
ディレクトリrefs
ディレクトリbranch
ディレクトリの4つのディレクトリに注目する。
さて、適当なファイルを作成し、git add
してみよう。
すると以下のようにindex
ファイルとobjects/40/38...4f
というファイルが作成される。
objects/40
以下のファイルを見てみよう。
まず、
どうもzlibで圧縮されているようだ。
これを適当に解凍してみる。
なお、hoge=objects/40/381e26feb9944a22c5c11b6f5516f2abc77f4f
とすること。(醜いので)
おぉ、どうやらblob 5
という文字列にファイルAの内容が格納されているようだ。
index
ファイルを見てみる。
読めるような、、読めないような
git-indexに詳しく書いてある。
また、git ls-files --stage
でindex
ファイルの内容を見ることができる。
1列名はファイルのモードを示している。ファイルのモードには以下のようなものがある。
次に、ファイルを編集してみる。3を消して456を追加してみる。
すると.git
ファイルは以下のように新たなファイルがobjects
に作成される。
index
ファイルの中身が変更されており、
それをgit ls-files --stage
で確認すると以下のようになる。
先程と変わっていることがわかる(ファイルのハッシュを再計算したので当たり前だが)。
また、objects
の新しいファイルを見てみると
のようになっていることがわかる。
以上をひとまずまとめると、
git add
するとファイルを圧縮したファイルobjects/num/compress...1
が作成される。また、indexファイルが作成される。git add
するとobjects/num/compress...2
ファイルが新たに作成される。また、indexファイルが変更される。git rm --cached
してもobjects
には圧縮したものが残っている。ただ、indexファイルの中身が変わるだけである。
git ls-files --stage
で確認すると何も表示されない。
そこで、git add
をしてみる。
ここで、objects
ファイルは変わらない。
どうもファイルのハッシュ値を計算して、新たにファイルを作成しているが、ファイル名も内容も被っているので更新されていないようにみえるのである。
これはls -lt -c objects/36/..
で確認すると作成した時間が更新されているので新たに36
ディレクトリが作られたことがわかる。
git commit
するとobjects
, COMMIT_EDITMSG
, refs
logs
にファイルが作成される。
COMMIT_EDITMSG
はコミットメッセージが格納されているファイルである。
logs
は後回しにする。
注目したいのはobjects
とindex
とrefs
である。
index
ファイルから見ていく。
相変わらず読めないが、
とあり、先程と変わらないことがわかる。
objects
ファイルを見てみる。
objects/32
というファイルが新しく作成されている。
これを解凍して見てみると
最初の行にcommit 201tree df14..
とあり、
objects
ディレクトリにもdf
というディレクトリがあり、14f7...
と続くファイルがある。
おそらくこれがcommitを表したファイルなのだろう。
そこでobjects/df/14f..
を解凍してみると
もうちょっといい感じに解凍できるのかもしれないけどここで力尽きた。
とりあえず、tree
, 100644
, A6..
という文字列が格納されていることがわかる。
おそらくtree
とはディレクトリ構造(木構造)を表すもので、100644
はファイルのモードを示している。
ここからcommitの情報はtree(ディレクトリ)への参照であることがわかる。
自力で解凍するのは諦めてgit cat-file -p df14...
で中身を見てみる。
ファイルAの場所を指すハッシュ値が記述されていることがわかる。
以上でだいたいgitがどのようにファイルを管理しているかがわかったかもしれない。
git add
するとファイルが圧縮されてobjects
に保存される。git add
するたびに変更される。git commit
するとobjects
にtreeを記述するファイルとcommitに関するファイルが作成される。refs/heads
にはブランチ名のファイルが作成され、
中にはcommitのハッシュ値が記述されている。staging areaというのはindex
ファイルのことで,
git add
することでファイルが圧縮されてobjects
にその時のワーキングディレクトリの状態が保存される。
git commit すると、indexファイルに記述されたファイルの名前を元に、objects
にcommitを記述するファイルが作成され、その中でtreeファイルを指定する。
treeにはobjects
に保存されたファイルの名前が記述されている。
おそらく、バージョン管理システムで最も重い処理というのはファイルの圧縮と解凍であろう。
git add
とgit commit
をするたびに圧縮されたファイルを作成するのは処理に時間がかかる。
そこで、git add
をするたびに、objects
ファイルを作成して(git add
で登録したものしかgit commit
しないので、どうせそのうちgit commit
はするだろうから)先に圧縮する処理を細かく入れておく。
そして、git commit
すると、objects
にその時のワーキングディレクトリの状態とobjects
に保存されたファイルの対応づけを行うような処理を行うことで(これもobjects
に保存しておく)効率よくファイルを管理することができる。
git add
すると、
git commit
すると、
長々と.gitの中身を見てきたが、ここでgitオブジェクトについて説明しよう。 とは言え、Gitの内部に詳しくかいてあるので、正直これを見てもらえれば理解できると思う。
ファイルをzipで圧縮したものをblobオブジェクトと呼ぶ。 これはそのファイルのSHA-1の前2文字がディレクトリ名、残りをファイル名として保存される。 blogオブジェクトをディレクトリ構造として保存するためのオブジェクトをtreeオブジェクトと呼ぶ。 treeオブジェクトも名前にハッシュ値がつけられる。 treeオブジェクトはblobオブジェクトとtreeオブジェクトの名前が保存される。
これにより、履歴をとるためにはtreeオブジェクト指定すれば良いことになる。 履歴とtreeオブジェクトを対応付けるオブジェクトをcommitオブジェクトと呼ぶ。 commitオブジェクトはtreeオブジェクトのハッシュ値、著者やcommitが実行された時間を保存している。
HEAD
にはref: refs/heads/main
とあり、
refs/heads/main
には直前に行ったcommitのcommitオブジェクトのハッシュ値が保存されている。
git sw -c dev
のようにブランチを切り、
のようにファイルを作成する。
HEAD
にはref: refs/heads/dev
となり、
refs/heads/dev
が新たに作成されている。
refs/heads/dev
には直前に行ったcommitのcommitオブジェクトのハッシュ値が保存されている。
git sw -d HEAD^
のようにHEADを1つ前に戻すと、
HEAD
には32d38...
となり、直前に行ったcommitのcommitオブジェクトのハッシュ値が保存されている。
refs/heads/{dev, main}
はそのままである。
このようにするとHEADが指しているcommitオブジェクトの中身を見ることができる。(ワーキングディレクトリに展開される。)
gitではどのようなブランチを切って開発していくべきだろうか。 gitのブランチの切り方、運用の仕方をまとめたものをブランチ戦略と呼ぶ。 ブランチ戦略にはいくつかの種類がある。 この記事がよくまとまっているので主に参考にするが、
の主に3つを使うことがおおいだろう。 git-flowは大規模開発用、github-flowはシンプルだがgithubの使用を前提としたもの、gitlab -flowはgithub-flowに改良を加えたものである。 個人開発ではgithub-flowをよく使うことが多いだろう。
git-flowは大規模開発用のブランチ戦略であり、5つのブランチを使う。
開発の軸足となるdevelopブランチから個別の機能を開発するfeatureブランチを切り、 featureブランチからdevelopブランチへマージする。 release-branchではbugの修正のみを行い、適当なタイミングでmainブランチにマージする。 mainブランチでリリースされたものに対してbugが発生した場合はhotfixブランチを切り、mainブランチにマージする。
言葉にすれば簡単だが、ブランチの数が大きく運用が複雑になる。 少なくとも個人開発では使わない。
github-flowは3つのブランチを使う。
git-flowと比較してみるとだいぶシンプルになった。develop(es)ブランチはfeatureブランチに該当し、integrationブランチはdevelopブランチに該当する。 integrationブランチからmainブランチにマージする前にpull requestを作成し、レビューを受ける。また、mainへマージした直後にデプロイを継続的に行う。 CI/CDの整備が必要となる。
gitlab-flowは4つのブランチを使う。
gitlab-flowはmainがリリースするためのブランチではない。 mainからfeatureブランチを切り、featureからmainへマージする。 mainからpre-productionブランチを切り、pre-productionからproductionブランチへマージする。
pre-production以降の流れというのは複数環境でデプロイが行われたかを確認するためのものである。これはgithub-flowの欠点であった複数環境でのデプロイにおいてデプロイのタイミングとブランチの結びつきが不明瞭という欠点を補うものである。 これもまた、CI/CDの整備が必要となる。
gitをを使う際にコミットメッセージをどうするかは悩ましい問題である。 その際に参考になりそうなのがどのようにGit commit messageを書くかである。 重要なことは振り返って見やすいコミットメッセージを書くことである。 そのため、コミットメッセージにいくつか規格や制約を設けることが重要である。 コミットメッセージの長さや、コミットメッセージの形式を統一することで、コミットメッセージを見返す際に見やすくなる。
実際、LinuxカーネルやGitの開発でも規格的なコミットメッセージが使われている。
逆にこのように規格を設けないと、blame, revert, rebase, log, shortlogなどが効果を発揮しづらくなる。 参考記事ではいいコミットメッセージの要件を3つ挙げている。
また、たった7つのルールを守るだけで、コミットメッセージが見やすくなるという。
例1
ただ1行で書くこともできる。
自分が現時点で守れていないと思うルールは以下の2つである。
なぜGitは命令形で書くべきなのかというと、Git自身がデフォルトで命令形を使っているからである。
このようなメッセージがデフォルトで表示される。
Gitはデフォルトで命令形を使っているので、それに合わせるべきである。
だからといって、そもそも命令形で書くことは難しい。
通常、我々は報告をする際に命令形を使うことは無い。
注意すべき点として、まず、
関係代名詞は命令形ではない。
また、説明文は命令形ではない。
ではどうすればいいだろうか。 簡単な対処だが、以下の太字の部分を抜き出せば良い。
なぜこれがいいかというと、Fixed bug with Y
のようなコミットメッセージを防げるからだ。
また、commit messageを参考にしてコミットメッセージの先頭にprefixをつけることもできる。
のようにすることで、全体の統一が取れる。
なぜ変えたかの良い例としては
基本的には変更がどのように行われたかについては除くことができる。
この3つについて書くことができればいいコミットメッセージになる。