tukaのブログ

気が向いた時に何か書きます

graftedなgitコミットとは何者か・・・

前回の記事にちょっと出てきたのですが、gitのコミットログを見た時にハッシュ値の横にgraftedというものを初めて見たので調べてみました。

f:id:tukaelu:20180409224423p:plain

ここに至る経緯

ざっくりまとめると

  • Homebrewで1つ前のバージョンのものをインストールしたかった
  • brew switchが出来なかったのでgit checkoutでFormulaを古いバージョンに戻そうとした
  • 該当ファイルのgit logを見たらコミット履歴が1つしかなくて戻せなかった
  • そのコミットのハッシュ値の隣に(grafted)の表記があった

という感じ。

ググってみた

率直にgit graftedをキーワードでググって幾つか記事を見てみます。

stackoverflow.com

タイトルの「shallow clone した際の "grafted" なコミットとは一体何ですか?」のコレ感がパない\(^o^)/

このStackOverflowの質問の要約は

  • gitで--depthオプションを使ってshallow cloneするとgraftedマークが付く
  • ググっても納得行く情報が見つからなかった
  • git graftsとは違いそうだけど同じ意味をするもの?
  • コミット履歴を省略しているフラグに過ぎないのか?それとももっと特別な意味がある?

という感じですかね。

ここでわかったのは

  • git clone --depthするとgraftedマークが付く
  • その他にもgraftsという概念がgitにはある

ということです。

StackOverflowの回答でも触れられていましたけど、上記の記事の質問に以下のページへのリンクがありました。

GraftPoint - Git SCM Wiki

超絶ざっくりまとめると

  • 異なる開発ラインを結合することが出来る
  • 別のSCMからインポートした場合など別リポジトリとの履歴と結合するのに便利
  • Git 1.6.5で追加されたgit replaceでこれが出来る

ということですかね。なんか間違っていそうですが。。

試してみる

git clone --depth

近々試そうとしているdeployerを使わせてもらって試してみます。

github.com

普通にgit cloneしてみます。

$ git clone git@github.com:deployphp/deployer.git
Cloning into 'deployer'...
remote: Counting objects: 8810, done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 8810 (delta 0), reused 0 (delta 0), pack-reused 8805
Receiving objects: 100% (8810/8810), 1.85 MiB | 578.00 KiB/s, done.
Resolving deltas: 100% (5649/5649), done.

$ cd deployer
$ git log --graph

* commit 006e0b16f3a92025759fc1da30481000e00c0320 (HEAD -> master, origin/master, origin/HEAD)
| Author: Martin Supiot <martin@webaaz.com>
| Date:   Tue Apr 10 09:49:32 2018 +0200
|
|     fix typo (#1585)
|
* commit 35027de18ba2eadd6205336961910f0f400e557c
| Author: Keith Bremner <kmbremner@gmail.com>
| Date:   Sat Mar 17 17:39:37 2018 +0000
|
|     Update shared.php (#1571)
|
|     fix typos
|
:

ついでに.gitディレクトリ配下を確認してみます。

$ ls .git
HEAD        description index       logs        packed-refs
config      hooks       info        objects     refs

ふむ。

では次はgit clone --depth=1をしてみます。

$ git clone --depth=1 git@github.com:deployphp/deployer.git
Cloning into 'deployer'...
remote: Counting objects: 218, done.
remote: Compressing objects: 100% (199/199), done.
remote: Total 218 (delta 36), reused 50 (delta 4), pack-reused 0
Receiving objects: 100% (218/218), 119.99 KiB | 8.57 MiB/s, done.
Resolving deltas: 100% (36/36), done.

$ git log --graph

* commit 006e0b16f3a92025759fc1da30481000e00c0320 (grafted, HEAD -> master, origin/master, origin/HEAD)
  Author: Martin Supiot <martin@webaaz.com>
  Date:   Tue Apr 10 09:49:32 2018 +0200

      fix typo (#1585)

--depth=1とオプションを指定したのでログにはコミットが1つだけとなりHEADgraftedがつきました。

そして普通にcloneした場合と比較してオブジェクト数が1/40まで減っています!!

先程と同様に.git配下も確認してみます。

$ ls .git
HEAD        description index       logs        packed-refs shallow
config      hooks       info        objects     refs

直下にshallowというファイルが増えましたね。

中身を確認してみます。

$ cat .git/shallow
006e0b16f3a92025759fc1da30481000e00c0320

graftedなポジションのコミットのハッシュ値が記録されているようです。

ローカルのHomebrewのコアリポジトリhomebrew-coreを確認してみたら同じようになっていました。

f:id:tukaelu:20180410002444p:plain

あまり意識して--depthオプションをつけることがないですが、 大規模なリポジトリを扱う際(かつ過去を振り返らない場合)は有効なオプションですね。

git replace --graft

正直、今回のことがなければgit replaceを使うことはなかったかなと。。

初めて見たのでマニュアルを確認して先程と同様にdeployerリポジトリを使って叩いてみます。

Git - git-replace Documentation

$ git log --graph

* commit 006e0b16f3a92025759fc1da30481000e00c0320 (HEAD -> master, origin/master, origin/HEAD)
| Author: Martin Supiot <martin@webaaz.com>
| Date:   Tue Apr 10 09:49:32 2018 +0200
|
|     fix typo (#1585)
|
* commit 35027de18ba2eadd6205336961910f0f400e557c
| Author: Keith Bremner <kmbremner@gmail.com>
| Date:   Sat Mar 17 17:39:37 2018 +0000
|
|     Update shared.php (#1571)
|
|     fix typos
|
:

コマンドがgit replace --graft {commit}となるのでHEADを指定してみます。

$ git replace --graft HEAD
$ git log

* commit 006e0b16f3a92025759fc1da30481000e00c0320 (HEAD -> master, replaced, origin/master, origin/HEAD)
  Author: Martin Supiot <martin@webaaz.com>
  Date:   Tue Apr 10 09:49:32 2018 +0200

      fix typo (#1585)

今度はgraftedではなくreplacedとマークされましたね。

マニュアルにAdds a replace reference in refs/replace/ namespace.と記載があったので確認してみます。

$ ls .git
HEAD        description index       logs        packed-refs
config      hooks       info        objects     refs

$ ls .git/refs/replace/
006e0b16f3a92025759fc1da30481000e00c0320

git clone --depthの時と違って.git/shallowではなく.git/refs/replace/{commit}が作成されていました。

まとめ

正直ちょっとよくわからずw

graftedのマークはcloneする時点で対象のオブジェクトが絞られているのでリポジトリ容量はだいぶ軽くなるので、 別リポジトリを参照のためにネストする場合は最新のコミットだけ知っておけば事足りるので活用出来る気がしますがreplacedの使いみちがピンと来ず。。

業務でgitを使っている間にgraftedreplacedリポジトリを作ることはなさそうだなーというのが正直なところ。

最近ポッドキャストsoussuneを一ヶ月遅れで聴いていてその中でgitの話があったのですが、内部構造とかもっと詳しくなるとこの辺ピンとくるのかな。

soussune.com

入門Git買って読むかー(´•ω•`)

入門Git

入門Git

またわかったら別記事として書こうと思います。眠いので寝るー(ヽ´ω`)

追記

寝ぼけててまとめの文章が超絶おかしいけど、git clone --depth自体はわりと普通にやる行為なんですよね。

ただその際にgraftedとマークが付くことを初めて知ったのとそこがイマイチしっくり来ていません。故にわからず。。

graftを直訳すると接ぎ木という意味なので別リポジトリとジョイントする際にそちら側の履歴を知っておく必要はないのでgraftedになるのかなと思うのですが、 その辺のGitの運用フローがピンときていないのでclone --depthreplace --graftがふわっとしている感じなのかな。

もう少し調べてわかったら纏めたいと思います。