差分ではなくあえて“状態”を保存するという実験
背景
バージョン管理といえば Git。
いまや常識です。差分で履歴を残す仕組みで、
行単位での比較も早いし、容量も節約できるし、マージも賢くて速い。
この発想はデータベースのインクリメンタルバックアップや、
クラウドのスナップショット、動画編集の非破壊編集にも通じています。
「変更だけを記録する」という考え方は、やはり理にかなっています。
…ただ、使っていてふとこう思うことがありました。
- 過去の状態をそのままフォルダで見たい
- 非エンジニアともスムーズにやり取りしたい
- ツールや IDE に頼らず中身を直接扱いたい
- スクリプトや AI から丸ごとフォルダを扱いたい
- 途中の履歴が壊れても中身が見える単純さがほしい
「差分」ではなく「状態そのもの」がほしい場面が確かにある。
そこで、“丸ごと保存してみたらどんな感じか” を試したのが今回の実験です。
アプリケーションの名前は輪廻(RINNE)。
履歴として生まれ、容量の都合でいずれ削除されていく――
そんな「循環」を前提とした運命に、ぴったりの名前です。
設計思想
RINNE のテーマは「可逆・単純・有限」。
過去の状態を、誰でも、どこでも、それ単体で再生できるようにしたい。
- 各履歴はそれ単独で成り立つスナップショット
- ZIP+JSON だけで構成(アプリケーションへの依存なし)
- SHA256 ハッシュで整合性を検証
- 保存件数の上限を設け、古いものは整理する運用ルール
差分型より非効率です。容量も食うし、保存も遅い。
でもそのぶん、誰でもその時点の中身が見えて、どの環境でも簡単に扱える。
たとえ RINNE がなくても「読める状態履歴」です。
RINNE は差分の世界ではなく、「状態の世界」で構成されます。
実装とソースコード
実装したソースコードは下記となります。
→ GitHub: rinne-cli-experiments
構造と仕組み
ディレクトリ構造
.rinne/
 ├─ config/                                       # 設定ファイル
 ├─ data/
 │   ├─ main/                                     # 既定の作業空間(space)
 │   │   ├─ 00000001_20251024T091530123.zip       # 履歴データ
 │   │   ├─ 00000002_20251024T090000000.zip       # 履歴データ 
 │   │   └─ meta/
 │   │      └─ 00000001_20251024T091620223.json  # 履歴メタデータ
 │   │      └─ 00000002_20251024T091000000.json  # 履歴メタデータ
 │   └─ other.../                                 # 他の作業空間(space)
 ├─ logs/                                         # 出力ログ
 ├─ state/                                        # 状態
 │   └─ current                                   # 現在選択中の space 名           
 └─ temp/                                         # 一時ファイル格納
.rinneignore                                      # 無視ルール一覧
実験的な試みですし、複雑なことをすると依存度が上がるので単純な構造にしています。
ルート直下に .rinne/ ディレクトリを作成し、
data/ 配下の任意の space にスナップショットを積み上げていく構造です。
各スナップショットは番号順に保存され、
保存時に破損検証用のメタ情報を作成して
prev, this, hash などのチェーン情報を格納します。
.rinneignore は .gitignore と同じく、保存対象外のパスを記述します。
履歴メタデータ
{
  "schema": 1,
  "id": "00000002_20251024T091000000",
  "seq": 2,
  "utc": "2025-10-30T21:00:15.627Z",
  "space": "main",
  "zip": "../00000002_20251024T090000000.zip",
  "message": "backup test",
  "ignore": {
    "source": ".rinneignore",
    "rules": [
      ".rinne/"
    ]
  },
  "hash": {
    "algo": "SHA256",
    "zip": "e40c048dc99120f85efa8b2b0ffb26450a83f1e8841c38e684ec56a2f588d...",
    "chain": {
      "prevId": "00000001_20251024T091620223",
      "prev": "bbf2ae3d6ccc736f06cb86b67b32016e138928c30a449c236b789b3fd0634...",
      "this": "c43e6920a97a637fb6460a6f1fafb9badf991087d593d840d03ff18d1af46..."
    }
  }
}
クイックスタート
# 1) 初期化(ルートディレクトリ直下に .rinne/ を作成)
rinne init
# 2) スナップショット(space名指定省略のため current スペース)
rinne save -m "1回目スナップショット"
rinne save -m "2回目スナップショット"
rinne save -m "3回目スナップショット"
# 3) 整合性チェック
rinne verify
# 4) 履歴整理(最新2件だけ残す)
rinne log-output off   # ← tidy 実行前は off 推奨(ファイルロック回避)
rinne tidy 2
# 5) 履歴確認
rinne log
ちょい実践例
# space 作成、一覧表示、カレント選択
rinne space create test_space
rinne space select test_space
rinne space list
rinne space select main
# 特定 ID にロールバック
rinne restore 00000001_20251024T091530123
# 差分確認
rinne diff 
rinne textdiff 
# 合成保存(左優先)
rinne recompose main main 00000001_20251024T091530123, feature-x 00000004_20251024T091530123, feature-patch 00000002_20251024T091530123
# 他リポジトリから space ごとインポート
rinne import D:\proj_b\.rinne dev_space
# バックアップ(.rinne を ZIP 出力)
rinne backup D:\rinne_backups
バッチ連携例
@echo off
cd /d C:\Name
rinne space select backup
rinne save -m "backup"
rinne log-output off
rinne tidy backup 30
robocopy "C:\Name" "D:\Name" /MIR /R:0 /W:0 /NFL /NDL /NJH /NJS /NC /NS
おわりに
この実験をとおして、あらためて Git のような仕組みの偉大さを実感しました。
世界中の開発を支えるほどの堅牢さと柔軟さを持ち、
あの小さな .git/ の中に「過去」と「未来」を同時に抱えている。
RINNE は「差分が正しい」世界の外側にある小さな実験でした。
保存効率も容量消費率も良くないが、
フォルダを丸ごと残すという発想は、意外と単純で気持ちがいいものです。
案外、並列処理が可能な凄まじいハードパワーの AI に分析をかけるなら、
丸ごとスナップショットを流し込むのもありなのかもしれません。
(もっとも、AIでも .git の各地点を復元できるので、最終的には Git の効率が勝つと思いますが……)