gitレポジトリ内の任意リビジョンのファイルを取り出すピュアJSライブラリとWebサーバ `weboverlay`

開発作業中にローカルの git レポジトリ内の古いファイルを参照したいことがあります。
過去のブランチに `git switch` するのは少し面倒だし、間違えて作業中のファイルを失うことも。
そこで、任意リビジョンの任意ファイルを参照できる Node.js のライブラリや Web サーバを書いてみました。

- https://www.npmjs.com/package/git-cat-file (1) JSライブラリ
- https://www.npmjs.com/package/serve-static-git (2) Expressミドルウェア
- https://www.npmjs.com/package/weboverlay (3) CLI Webサーバ

weboverlay を使えばサーバ１つで、任意のブランチ・コミットにアクセスできるので、たとえば、ブラウザの2つのタブで master ブランチと develop ブランチのウェブサイトを同時に開いて比較することもできます。

# (1) JS ライブラリ `git-cat-file`

標準 `git` コマンドの `git cat-file -p` 等の機能をピュア JavaScript (TypeScript) で実装したのが [git-cat-file](https://www.npmjs.com/package/git-cat-file) になります。

① `.git` ディレクトリのパス
② ブランチ名やコミットID
③ ファイルのパス

の３点を指定すると、そのファイルのバイナリデータを Buffer で取り出せます。

```js
const {openLocalRepo} = require("git-cat-file");

async function catFile(revision, path) {
  const repo = openLocalRepo("path/to/repository/.git");
  const commit = await repo.getCommit(revision);
  const file = await commit.getFile(path);
  process.stdout.write(file.data);
}

catFile("HEAD", "path/to/file.txt");
```

git レポジトリの形式としては、非圧縮の loose と `git gc` で圧縮された packed 形式の両方に対応しています。

# (2) Express ミドルウェア `serve-static-git`

Express.js で静的ファイルを公開するときの `app.use(express.static("htdocs"))` のミドルウェアの実装 [serve-static](https://www.npmjs.com/package/serve-static) と同様に、git レポジトリ内に格納された静的ファイルを提供する Express ミドルウェアが [serve-static-git](https://www.npmjs.com/package/serve-static-git) になります。

ローカルファイルシステム上の静的ファイルを提供するとき：

```js
const express = require("express");
const app = express();

app.use(express.static("htdocs"));

app.listen(3000);
```

git レポジトリ内から静的ファイルを提供するとき：

```js
const express = require("express");
const {serveStaticGit} = require("serve-static-git");
const app = express();

app.use(serveStaticGit({
  repo: "path/to/repository/.git",
  root: "htdocs",
}));

app.listen(3000);
```

[raw.githubusercontent.com](https://raw.githubusercontent.com/kawanet/serve-static-git/main/types/serve-static-git.d.ts) ではパスでリビジョン（git ブランチ名やコミット ID）を指定するのに対して、
serve-static-git ではリクエストヘッダ `Host:` の冒頭（サブドメイン名・ホスト名）でリビジョンを指定するため、
絶対パスで記述しているリンクも参照できます。
いまの Chrome 等のモダンブラウザでは `xxxx.localhost` 形式で任意のホスト名を指定できます。

（例1） main レポジトリ内の htdocs/index.html ファイルを取り出すとき
→ http://main.localhost:3000/index.html

（例2） develop レポジトリ内の htdocs/css/style.css ファイルを取り出すとき
→ http://develop.localhost:3000/css/style.css

ただし、`hotfix/xxx`・`feature/xxx`・`release/xxx` のような記号の入るブランチやタグは、リクエストのホスト名に指定できないため代わりにコミット ID で指定するか、または [210.refs.ts](https://github.com/kawanet/serve-static-git/blob/main/test/210.refs.ts) を参考にして refs オプションでリビジョンを指定する定義を指定する必要があります。

（例3） curl で明示的にリクエストヘッダ `host:` を指定する例：

```sh
curl -s -H 'host: feature/xxx' http://localhost:3000/index.html
```

（例4） リクエストヘッダ `x-refs:` で指定する例：(※要・refsオプション)

```sh
curl -s -H 'x-refs: feature/xxx' http://localhost:3000/index.html
```

指定したブランチが存在しなかった場合は、`HEAD` にフォールバックします。
なお、提供できるのは静的ファイルのみです。動的な API などは提供できません。

# (3) CLI ワンライナー Web サーバ `weboverlay`

最後に紹介するのは、ワンライナーで Web サーバを構築できる [weboverlay](https://www.npmjs.com/package/weboverlay) になります。
自前で `git-cat-file`・`serve-static-git` を使うコードを書かずに、CLI でサーバを起動できます。

```sh
# インストール
npm install weboverlay

# サーバを起動する
node_modules/.bin/weboverlay --port=3000 path/to/repository/.git:htdocs

# Chrome 等でアクセスする
open http://main.localhost:3000/index.html

# curl でアクセスする
curl -s -H 'Host: main' http://localhost:3000/index.html
```