追放された portsnap、変わりに git が入ってくはずだったのに、気がつけば got がいる

# はじめに

[Ｇｏｔ](https://gameoftrees.org/) というＯｐｅｎＢＳＤプロジェクト由来の [Ｇｉｔ](https://git-scm.com/) の代替品について紹介します。
いわゆる `git` コマンドの代替品なのですが、`git` コマンドの使用および、そのライセンスについて[疑問が無い人には全く不要](https://gameoftrees.org/faq.html#pointless)なツールです。開発元が開発元なので、そのユースケースは独特なものとなります。

Ｇｉｔのベアリポジトリ互換[^compat]を目指しているとのことです。ベアリポジトリに対する操作に関しては互換性はあります。その代わりいわゆるワークツリー（インデックスも）レベルでは互換性がありません。ワークフローとしてはコマンドの使い分けが必要です。

[^compat]: [Game of Trees Goals](https://gameoftrees.org/goals.html)より

とまぁ一般論的な導入で書き始めたのでありますが、ぶっちゃけると最近のＦｒｅｅＢＳＤから `portsnap` コマンドが無くなったので、Ｇｉｔを使え[^use-git]という案内を見て、`portsnap` の代わりに `git` の導入はちょっと勘弁…という向けの話となります。

ＧｏｔはＯｐｅｎＢＳＤ開発におけるワークフローの実現に絞って、実装することを目標としているため、そのコード量は極めて少なく、依存もほとんどありません。軽量コンパクトと、頭痛が痛いレベルで、謳ってしまって問題無いレベルのツールとなります。ただし動作が軽いか…という評価についてはノーコメントで。
また独特の使用感を要求されるため、ガチガチにＧｉｔしたいケースでは使わない方がいいと思います。

[^use-git]: [Ports Collection のインストール](https://docs.freebsd.org/ja/books/handbook/ports/#ports-using-installation-methods)

以後のユースケースは `portsnap` の代わりを目標として記述します。

# インストールしてみる

```
$ pkg install got
```

この辺りは `git` も変わらないです。依存が無いので他にインストールされるパッケージはありません。

なお動作検証したＧｏｔのバージョンは `0.93` となります。バージョンアップによりここに書かれた内容が合わない可能性があります。

# まずはクローンしてみる

`got clone` と `git clone` との差違は以下の通りとなります。
- ベアリポジトリのクローンのみ（`git clone --bare` 相当）
- オリジンの指定ができない（ベアリポジトリにオリジンの指定は無意味？）
- ブランチの指定ができて、デフォルトでシングルブランチっぽい
- ただシャロークローンはできないみたい
- ミラーモードがある（クローンしたリポジトリへのコミットができないモード）
- 対応しているスキーマが少ない ※[後述](#対応しているスキーマ)
- とにかく遅い（マルチスレッドで Resolving deltas しない） [後述](#gitcloneとgotcloneの実行時間)

## クローン事例

```shell
$ git clone https://git.freebsd.org/ports.git /usr/ports
```

上記コマンド実行に相当する `got` コマンドは下記の通りとなります。いったんベアリポジトリを展開する領域が必要です。
またこの処理が非常に重たい（[後述](#クローンにかかる時間の計測)）ため、余裕があるなら `git` コマンドを使ってしまった方がいいかもしれません。

```shell
$ got clone -am ssh://anongit@git.freebsd.org/ports.git /home/ports.git
$ got checkout /home/ports.git /usr/ports
```

## 対応しているスキーマ
- `git://`
- `git+ssh://` （または `ssh://`）

## 対応してないスキーマ
- `git+http://` （または `http://`） ※エラー（ポリシー的に対応予定無し）
- `https://` ※未実装エラー（ＴＯＤＯ）
- `ftp://` ※なにそれ？（存在を認識してるかどうか）
- `ftps://` ※なにそれ？（存在を認識してるかどうか）

## クローンにかかる時間の計測

### `git clone` した場合のメッセージ

```console
$ git clone --bare --single-branch ssh://anongit@git.freebsd.org/ports.git
Cloning into bare repository 'ports.git'...
remote: Enumerating objects: 5942473, done.
remote: Counting objects: 100% (171767/171767), done.
remote: Compressing objects: 100% (16358/16358), done.
remote: Total 5942473 (delta 167421), reused 155423 (delta 155409), pack-reused 5770706
Receiving objects: 100% (5942473/5942473), 1.12 GiB | 11.01 MiB/s, done.
Resolving deltas: 100% (3577978/3577978), done.
```

### `got clone` した場合のメッセージ

```console
$ got clone ssh://anongit@git.freebsd.org/ports.git
Connecting to ssh://anongit@git.freebsd.org/ports.git
server: Enumerating objects: 5963414, done.
server: Counting objects: 100% (133228/133228), done.
server: Compressing objects: 100% (13237/13237), done.
server: Total 5963414 (delta 129757), reused 119991 (delta 119991), pack-reused 5830186
1157M fetched; indexing 100%; resolving deltas 100%
Fetched 0d39a9d41ecbf5cd111bcc9ae9f2cfcf7e30a616.pack
Created cloned repository 'ports.git'
```

### `git clone` と `got clone` の実行時間

```console
git clone --bare --single-branch ssh://anongit@git.freebsd.org/ports.git
511.72s user 40.21s system 163% cpu 5:36.76 total

got clone ssh://anongit@git.freebsd.org/ports.git
660.88s user 75.82s system 87% cpu 14:00.91 total
```

- 実行環境おおよそ５分３７秒 v.s. １４分１秒の差違と２．５倍の差がある。
- またＧｉｔの方がＣＰＵ使用率が高く、処理効率がＣＰＵバンドに偏っていることがわかる（マルチスレッド処理による効果）。
- 逆にＧｏｔはシステム使用率が高く、ボトルネックがＩ／Ｏバンドに偏りがちなのがわかる。

## クローン時のオプション指定による容量差について

| コマンド | シングルブランチ※ | マルチブランチ※ |
|   :-:   |                 -:|              -:|
| `got`   | 1,306,944KB       | 1,326,872KB    |
| `git`   | 1,322,480KB       | 1,350,344KB    |

※シングルブランチ： `git clone --single-branch` または `got clone`
※マルチブランチ： `git clone` または `got clone -a`

一応オプション指定による容量の変化を確認しています。また `git` と `got` 間での違いも大きく、この差違が何故発生するかは不明です。
何かしらのオーバーヘッドと思われますが…。

# ワークツリーの更新

```console
$ cd /usr/ports && git pull
```

上記コマンド実行に相当する `got` コマンドは下記の通りとなります。

```console
$ cd /usr/ports && got fetch && got update
```

本ケースにおけるおおよその使い方はこんな所です。開発フローで必要になる使い方については[コマンド対応表](https://gameoftrees.org/comparison.html)他、参考文献を参照ください。

# オマケの `tog` コマンド
ncursesベースのログビュワーです。１行ログを表示しつつ、そのログを選択（リターンキー）を押すと、詳細ログと修正内容が表示されるという便利なツールです。
Ｇｉｔのサードパーティツールにある `tig` コマンド相当とのことです。

# インフォメーション

Ｇｏｔではリポジトリとワークツリーとで使うべきコマンドが違います。具体的にはこれらは `gotadmin` と `got` となります。
とは言え安全に使えそうなサブコマンドは `info` くらいでしょうか。リポジトリまたはワークツリーの状態に応じて表示内容が変わります。

```console
$ gotadmin info -r /home/ports.git
repository: /home/ports.git
remote "origin": ssh://anongit@git.freebsd.org/ports.git
pack files: 4
packed objects: 5963321
packed total size: 1318M
loose objects: 0
```

`git clone --bare` したディレクトリ内で `gotadmin info` を実行して比較すると `remote` 行の表示が増えています。
リポジトリ配下のファイルを見ると、`config` ファイルにある `remote "origin"` ではなく、`got.conf` 中の `remote "origin"` 設定を参照しているようでした。実際　`got.conf` ファイルを作成したら `remote` 行が表示されるようになりました。 

またワークツリー上で `gotadmin info` を実行した場合、リポジトリの状態が表示されます。
また `got info` の実行結果はワークツリーに関しての情報になります。

```console
$ cd /usr/ports
$ gotadmin info
repository: /home/ports.git
remote "origin": ssh://anongit@git.freebsd.org/ports.git
pack files: 4
packed objects: 5963321
packed total size: 1318M
loose objects: 0
$ got info
work tree: /usr/ports
work tree base commit: 388fa384c1dab4774d4db755ec1089b57e6f9a97
work tree path prefix: /
work tree branch reference: refs/heads/main
work tree UUID: 0c2bbcf5-8a1d-11ee-8d56-9ca3ba01eed8
repository: /home/ports.git
```

# よくある質問とその答え

## Ｑ．`https` スキーマ使えねぇのかよ。
Ａ．`curl` に依存してないからかも。普通にＨＴＴＰ通信する実装だとパラレル処理できない問題あったりするので…と愚考。
`ssh://` ならそこら中に `ssh` コマンドインストールされてるし安心だね【何が？要出典】。

## Ｑ．`got update`／`got merge`／`got rebase`／`got integrate` の違いは？
Ａ．未調査。たぶんＧｉｔと同じ事情と思われ。

## Ｑ．`got clone ... /usr/ports/.git` すれば `git clone` と全く同じ状況を作れるじゃないか！
Ａ．検証しました。`got clone` は成功しますが、`got checkout` は失敗します。

```console
$ got clone -m ssh://anongit@git.freebsd.org/ports.git /usr/ports/.git
Connecting to ssh://anongit@git.freebsd.org/ports.git
   :
Created mirrored repository '/usr/ports/.git'
$ got checkout /usr/ports/.git /usr/ports/
got: work tree and repository paths may not overlap: /usr/ports/.git: bad path
```

## Ｑ．え？ `/usr/ports/.git` ってどういうこと？
Ａ．つまりベアリポジトリのディレクトリだった！ `git clone` したあとに `gotadmin info` したら驚きの結果に…。 

## Ｑ．`git status` したら驚きの結果に！
Ａ．`.gitignore` ファイルの処理に ~~バグがあります~~ 仕様です。本来 `.gitignore` ファイルで指定されたディレクトリやファイルなどは表示されません。
軽く調べたところ `.gitignore` ファイルを処理してないわけでもありません。というわけで ~~バグ~~ 仕様です。

## Ｑ．エイリアス機能は？
Ａ．無いです。欲しいですねぇ。誰か実装してください。

## Ｑ．これだけ動いてるならベースシステムに入っててもいいのでは？
Ａ．ちゃんと使ってレポートしましょう。ベースシステムに入れても十分な品質かまでは現時点では言及できないはずです。
少なくとも自分が触った範疇ではまだその品質に達してないように見えます。

# 参考文献
- [git](https://git-scm.com/)
- [got](https://gameoftrees.org/)
- [got、cvs、svn、git との比較](https://gameoftrees.org/comparison.html)
- [よくある質問とその答え](https://gameoftrees.org/faq.html)
- [FOSDEM 2023 での発表資料](https://www.openbsd.org/papers/fosdem2023-gotd.pdf)
- [EuroBSDcon 2019 での発表資料](https://www.openbsd.org/papers/eurobsdcon2019-gameoftrees.pdf)
- [ＦｒｅｅＢＳＤの入手方法（Ｇｉｔの利用）](https://docs.freebsd.org/ja/books/handbook/mirrors/#git)