git daemon を利用したローカルミラー

# git daemon を利用したローカルミラー

git daemon を利用してローカル (同一 LAN 内あるいは同一マシン内) にミラーサーバーを構築する。

git-daemon を提供する docker image があるのでそれを使用する。

https://hub.docker.com/r/sdelrio/git-daemon/

https://github.com/sdelrio/git-daemon

## git daemon の docker image の使い方

ドキュメントを見ると以下のように使うと記載がある。

```
docker run --name git-daemon -d -p 9418:9418 -v $PWD/git:/git sdelrio/git-daemon
```


https://github.com/sdelrio/git-daemon/blob/master/git-daemon.sh のソースコードを見ると以下コマンドを実行するとわかる。

```
git daemon --verbose --enable=receive-pack --base-path=/git --export-all
```

docker container 内の `/git` 以下を公開することがわかる。

ホスト側の `$PWD/git` が docker container 内の `/git` にマッピングされるので `docker run` を実行するときのカレントディレクトリ以下に git フォルダを作成してその下にリポジトリを作成したらいいことがわかる。


## シェルの表記

`$` はキャッシュサーバーがある LINUX でのシェルを表し
`>` はクライアントである WINDOWS のコマンドプロンプトを表します。

## 準備 

docker container と共有するディレクトリをホストに作成する。

```
$ mkdir -p git
$ cd git
```

## Docker container の起動


```
$ docker run --name git-daemon -d -p 9418:9418 -v $PWD/git:/git sdelrio/git-daemon
Unable to find image 'sdelrio/git-daemon:latest' locally
latest: Pulling from sdelrio/git-daemon
128191993b8a: Pull complete
e707abd239ca: Pull complete
434fd9a92a5d: Pull complete
Digest: sha256:53f24a8ec8e9f56d953922f3ff3fda58fc605213d66cc4aa62268d1dc3e0b1ee
Status: Downloaded newer image for sdelrio/git-daemon:latest
6148cf752e9218ce10fb26c791f954bebbe8ee254d3fafa6ce299650b9574efd
```

## リポジトリのミラー

例えば visual studio code のリポジトリをミラーする場合以下のように実行する。

```
~/git  $ git clone --mirror https://github.com/microsoft/vscode.git
Cloning into bare repository 'vscode.git'...
remote: Enumerating objects: 1326819, done.
remote: Counting objects: 100% (2231/2231), done.
remote: Compressing objects: 100% (1647/1647), done.
remote: Total 1326819 (delta 1099), reused 1140 (delta 493), pack-reused 1324588
Receiving objects: 100% (1326819/1326819), 543.47 MiB | 25.87 MiB/s, done.
Resolving deltas: 100% (949929/949929), done.
```

```
~/git  $ ls
vscode.git
```

ミラーを作成したマシンのIP を 192.168.11.19 とすると、以下のような URL で clone するとローカルから取得できる。


```
>git clone --mirror git://192.168.11.19/vscode.git
Cloning into bare repository 'vscode.git'...
remote: Counting objects: 1326819, done.
remote: Compressing objects: 100% (333264/333264), done.
remote: Total 1326819 (delta 949929), reused 1326819 (delta 949929) eceiving objects: 100% (1326819/1326819), 520.70 MiBReceiving objects: 100% (1326819/1326819), 543.11 MiB | 64.52 MiB/s, done.

Resolving deltas: 100% (949929/949929), done.
```

25.87 MiB/s → 64.52 MiB/s になった。

※ 試した環境ではインターネット接続が速いのであまりありがたみがないけど

## リポジトリミラー (複数のサイトのリポジトリをミラーする場合)

前の節では、git ディレクトリ直下にミラーしたが、複数のホスト環境や fork も含めてミラーする場合はディレクトリがかぶって都合が悪い。
幸い git フォルダ以下に階層を掘ったらそのままのディレクトリ構成で公開してくれる。

以下のように clone するディレクトリを指定して clone する

```
$ git clone --mirror https://github.com/microsoft/vscode.git github.com/microsoft/vscode.git
Cloning into bare repository 'github.com/microsoft/vscode.git'...
remote: Enumerating objects: 1326819, done.
remote: Counting objects: 100% (2223/2223), done.
remote: Compressing objects: 100% (1652/1652), done.
remote: Total 1326819 (delta 1092), reused 1125 (delta 480), pack-reused 1324596
Receiving objects: 100% (1326819/1326819), 543.06 MiB | 26.16 MiB/s, done.
Resolving deltas: 100% (949794/949794), done.
```

→ `github.com/microsoft/vscode.git`  というフォルダができる。


このようにすると以下でミラーを取得できる。


```
>git clone --mirror git://192.168.11.19/github.com/microsoft/vscode.git
Cloning into bare repository 'vscode.git'...
remote: Counting objects: 1326819, done.
remote: Compressing objects: 100% (333399/333399), done.
remote: Total 1326819 (delta 949794), reused 1326819 (delta 949794)Receiving objects: 100% (1326819/1326819), 526.06 MiB | 70.85 MiB
Receiving objects: 100% (1326819/1326819), 542.70 MiB | 61.61 MiB/s, done.
Resolving deltas: 100% (949794/949794), done.
```

## insteadof を使った自動切換え

https://qiita.com/m-tmatma/items/2077d64f72ef2d6c3a1d

で紹介した insteadOf を使って以下を実行しておく。(指定の順番としては、ミラー、オリジナルの順番)

```
>git config --global url."git://192.168.11.19/".insteadOf https://
```

### 注意

`"git://192.168.11.19/"` の最後の `/` を忘れるとエラーになる。

単なるテキストの置き換えをするだけなので https://github.com/microsoft/vscode.git という URL があったとき `https://` の部分を `git://192.168.11.19/` に置き換えるだけです。

もし `git config --global url."git://192.168.11.19".insteadOf https://` としていたら `https://github.com/microsoft/vscode.git` は `git://192.168.11.19github.com/microsoft/vscode.git` に置換されてしまうのでエラーになる。

`git://192.168.11.19/` とすることで  `https://github.com/microsoft/vscode.git` は `git://192.168.11.19/github.com/microsoft/vscode.git` に置換される。


## insteadof を使ってミラーから clone

前の節のように insteadof を設定しておくと、以下コマンドを実行すると自動的にミラーに転送される。

```
>git clone --mirror https://github.com/microsoft/vscode.git
Cloning into bare repository 'vscode.git'...
remote: Counting objects: 1326819, done.
remote: Compressing objects: 100% (333399/333399), done.
remote: Total 1326819 (delta 949794), reused 1326819 (delta 949794)Receiving objects: 100% (1326819/1326819), 542.13 MiB | 55.58 MiB/s
Receiving objects: 100% (1326819/1326819), 542.70 MiB | 49.12 MiB/s, done.
Resolving deltas: 100% (949794/949794), done.
```

この速度差では、全然わからないですが、wireshark を起動しながら実行すると gitプロトコルでアクセスしに行っているのを確認できます。

以下のようにまだミラーを作成していない URL に対して clone しようとすると以下のようにエラーになります。

```
>git clone --mirror https://github.com/microsoft/terminal.git
Cloning into bare repository 'terminal.git'...
fatal: remote error: access denied or repository not exported: /github.com/microsoft/terminal.git
```

それでキャッシュサーバー上で以下を実行すると、


```
$ git clone --mirror https://github.com/microsoft/terminal.git  github.com/microsoft/terminal.git
Cloning into bare repository 'github.com/microsoft/terminal.git'...
remote: Enumerating objects: 133120, done.
remote: Counting objects: 100% (6025/6025), done.
remote: Compressing objects: 100% (1908/1908), done.
remote: Total 133120 (delta 4491), reused 5440 (delta 4002), pack-reused 127095
Receiving objects: 100% (133120/133120), 320.75 MiB | 27.22 MiB/s, done.
Resolving deltas: 100% (104775/104775), done.
```

以下の clone が成功するようになります。

```
>git clone --mirror https://github.com/microsoft/terminal.git
Cloning into bare repository 'terminal.git'...
remote: Counting objects: 133120, done.
remote: Compressing objects: 100% (25955/25955), done.
remote: Total 133120 (delta 104775), reused 133120 (delta 104775) eceiving objects: 100% (133120/133120), 301.28 MiB | 8Receiving objects: 100% (133120/133120), 320.70 MiB | 86.99 MiB/s, done.

Resolving deltas: 100% (104775/104775), done.
```

## キャッシュサーバーにアクセスせず直接接続したい場合の設定 (例外指定)

```
>git config --global url."git://192.168.11.19/".insteadOf https://
```

というような設定をするとすべての要求がキャッシュサーバーに飛ぶようになります。

でもこれだと自分が開発に参加している(あるいはウオッチしている)リポジトリにアクセスしたい場合、古いキャッシュを参照することになって都合が悪い。
またリモートリポジトリに push する場合にミラーに push しようとするので都合が悪い。


個別の設定を行ってミラーにアクセスしないURLを設定します。。

例として、 PYTHON の最新の開発状況を知りたいのでキャッシュでは不満な場合(その必要がある人はこの記事読んでないと思うけど)、

以下のように URL をフル指定すると `https://` の条件より詳細な指定になるのでこちらが優先されて、本物にアクセスします。

```
>git config --global url."https://github.com/python/cpython.git".insteadOf https://github.com/python/cpython.git
```

例えば python のアカウント全体を例外にしたい場合は以下でよい。


```
>git config --global url."https://github.com/python/".insteadOf https://github.com/python/
```


## ローカルキャッシュの更新

サーバーの git ディレクトリ以下に移動して以下コマンドを実行することでキャッシュを更新します。

```
$ cd git
$ find -name "*.git" -type d | xargs -I{} git -C {} fetch --all
```

上記コマンドをスクリプト化しておいて、cron で回せば定期的に最新のリポジトリに更新できる。

### 解説

find コマンドで `.git` で終わるディレクトリを検索します。

```
$ find -name "*.git" -type d
./github.com/microsoft/vscode.git
./github.com/microsoft/terminal.git
```

git コマンドで `-C` オプションを指定すると指定した引数のディレクトリに移動したうえで git コマンドを実行します。

xargs コマンドでを使って入力から受け取った行に対してコマンドを実行します。

`-I{}` ではプレースホルダとして指定 するも文字列を指定します。
ここでは `{}` 指定します。

それで git コマンドのオプションで `-C {}` を指定すると xargs は find の結果のディレクトリを指定して git コマンドを実行します。

この例では xargs は以下のコマンドを実行することになる。

```
git -C ./github.com/microsoft/vscode.git fetch --all
git -C ./github.com/microsoft/terminal.git fetch --all
```

## 参考 (フォルダ階層を掘って clone する。すでにフォルダが存在していたら、fetch --all する)

```
function get_local_dir () {
	echo $1 | grep -oP '(?<=://)(.*)$'
}


function mirror () {
	localdir=$(get_local_dir $1)
	
	if [ -e "${localdir}" ]; then
		git -C ${localdir} fetch --all
	else
		git clone --mirror $1 ${localdir}
	fi
}
```

https://github.com/microsoft/vscode.git を clone する例

```
mirror https://github.com/microsoft/vscode.git
```