バージョン管理に優しいファイル変換

開発チームがドキュメント、デザイン資産、またはデータファイルをソースコードと同じリポジトリに保存する場合、ファイル形式の選択がバージョン管理システムの使いやすさを左右します。選択を誤るとリポジトリサイズが膨れ上がり、diff の出力が分かりにくくなり、 automated build が壊れやすくなります。本稿では、Git が提供するクリーンな履歴と再現性を損なわずに ファイルを変換するための技術的考慮点を解説します。実務フローに基づいたガイダンスで、convertise.app のようなクラウドベースのコンバータを、プライバシーに配慮した瞬時の変換が必要なときに使うことを想定しています。


従来の変換が Git と衝突する理由

Git はプレーンテキストの行単位の変更を追跡するのが得意です。一方、バイナリブロブは不透明なスナップショットとして保存され、変更があるたびにファイル全体が再アップロードされるため、リポジトリが膨張します。さらに、多くの変換パイプラインは出力が非決定的です—タイムスタンプ、GUID、埋め込みメタデータが実行ごとに変わり、git diff に誤検出を引き起こし、マージコンフリクトの解消が難しくなります。大容量バイナリと非決定性の組み合わせは、単一の真実源(シングルソース・オブ・トゥルース)を持つことのメリットをすぐに失わせます。

バージョン管理に優しい変換ワークフローは次の 3 つの核心問題に対処します。

  1. サイズ肥大 – リポジトリに生成資産のメガバイト単位の保存を回避する。
  2. diff の不透明さ – Git が意味のある差分を表示できる形式で出力を保つ。
  3. 再現性 – 同じソースから常に同一の出力が生成されることを保証し、CI パイプラインを決定的に保つ。

変換対応フォーマットは早めに選定する

最も効果的な緩和策は、Git の得意分野に合致したターゲット形式を選ぶことです。以下は代表的な「ソース→ターゲット」ペアとその重要性です。

  • Markdown → HTML / PDF – Markdown はプレーンテキスト、HTML もテキストベースなので diff が機能します。PDF が必要な場合は、タイムスタンプを除去した決定的な LaTeX パイプラインから生成します。
  • SVG → PNG – SVG はベクタで diff 可能。PNG への変換は最終配布時のみ行い、リポジトリには SVG を残してバージョン履歴を保持します。
  • CSV → Parquet – 人間が閲覧できるよう CSV を保存し、解析用に自動ステップで Parquet を生成します。Parquet はバイナリなので、リポジトリではなくデータレイクのバケットに置くべきです。
  • デザインソース(Figma, Sketch) → PNG / PDF – 元のソースファイルはバイナリですが、バージョン管理されたプロジェクトにバンドルして保持します。エクスポートは公開時のみ行い、エクスポート結果は別のアーティファクトストアに保存します。

変換がどうしてもバイナリ(例: コンパイル済み PDF)を生成する場合は、ソース(LaTeX、Markdown、SVG)を Git に保存し、バイナリは派生アーティファクトとして扱う ことがサイズと diff の両課題を解決します。


決定的な変換:隠れた可変要素の排除

バイナリをリポジトリに置く必要がある場合でも、変換を再現可能にできます。以下の手順に従ってください。

  1. タイムスタンプを削除 – 多くのコンバータは実行時の日時を埋め込みます。exiftool -AllDates= … などのポストプロセススクリプトでクリアします。
  2. メタデータの順序を正規化 – ツールによっては辞書エントリが非決定的な順序で書き込まれます。コンバータが提供する固定順序オプションを指定するか、jq -S(JSON)や xsltproc(XML)などの安定シリアライザに通します。
  3. 圧縮設定を固定 – ランダムシードを含む設定は避け、固定シードのロスレス圧縮(例: zlib)を選択します。
  4. 改行コードを統一 – 全体で LF (\n) を強制します。Windows の CRLF (\r\n) は diff を壊します。
  5. 再現可能な環境を使用 – すべてのライブラリバージョンを固定した Docker コンテナ内で変換を実行します。これにより「自分のマシンでは動く」の齟齬が解消されます。

変換パイプラインを純粋関数的にすれば、同一ソースに対して同一ハッシュが毎回生成され、git diff --binary が信頼でき、CI のキャッシュも簡単になります。


Git ワークフローへの変換統合

変換ステップを組み込む代表的パターンは 2 つです。

1. Pre‑commit フックでの生成

Pre‑commit フックは、ステージングされたファイルに対してコンバータを走らせ、派生アーティファクトをインデックスに書き戻すことで、常に最新の変換結果がリポジトリに含まれるようにします。以下は Bash の例です。

#!/usr/bin/env bash
# Pre‑commit hook: generate PDFs from Markdown
files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.md$')
for f in $files; do
  out=${f%.md}.pdf
  curl -X POST -F "file=@$f" https://api.convertise.app/convert -F "target=pdf" -o "$out"
  # Strip timestamps to keep the file deterministic
  exiftool -AllDates= "$out" -overwrite_original
  git add "$out"
done

このフックにより変換は 自動化 され、全コミットが一貫したバイナリを含むことが保証されます。

2. CI のみで生成するビルドアーティファクト

バイナリが大きい場合は、CI サーバ上で生成し、artifact リポジトリ(GitHub Packages、Artifactory など)へプッシュする方が得策です。ソースは Git に残し、リリース時にアーティファクトストアから生成ファイルを取得します。このパターンはリポジトリ肥大化を防ぎつつ、下流の利用者へ即座に利用可能な資産を提供できます。


Git LFS で大容量バイナリを管理する

高解像度画像、書籍用 PDF、3D モデルのプレビューなど、どうしてもバージョン管理が必要な大容量資産は Git LFS(Large File Storage) が標準的な解決策です。成功の鍵は次の通りです。

  • 必須バイナリだけを追跡。変換可能なソースはメインリポジトリに残し、LFS には 最終的な出力 のみを保存する。
  • 命名規則を徹底*.pdf.lfs*.png.lfs など)して、どのファイルが LFS 管理対象か開発者全員が把握できるようにする。
  • サイズ上限を .gitattributes で設定(例: *.pdf filter=lfs diff=lfs merge=lfs -text)し、意図せず巨大ファイルが直接コミットされるのを防ぐ。

決定的な変換と組み合わせれば、Git LFS はバージョンごとに 1 コピーだけを保存し、ブランチ間で同一出力があれば同一 LFS オブジェクトを共有でき、帯域幅を大幅に節約できます。


Pre‑commit と Pre‑push フックでの自動化

基本的な生成フックに加えて、検証ステップ を組み込むことで回帰を早期に捕捉できます。

  • チェックサム検証 – 変換後に SHA‑256 ハッシュを算出し、.checksums ファイルに記載されたハッシュと比較。相違があれば非決定的変換を検出。
  • スキーマ検証 – データファイル(CSV → Parquet)については JSON Schema や Avro 定義を使い、出力が期待通りの列型を保持しているか確認。
  • アクセシビリティチェック – 生成された PDF や HTML に対して自動 a11y ツールを走らせ、alt テキストや見出し階層が保持されているかを検証。

これらはローカルで実行され、コードが中央リポジトリに届く前に即座にフィードバックを提供します。


メタデータと由来情報の保持

バイナリ自体が diff 不可能でも、重要な由来情報はサイドカー(隣接)ファイルに保存できます。生成資産ごとに JSON マニフェストを添付します。

{
  "source": "docs/chapter1.md",
  "converter": "convertise.app",
  "timestamp": "2026-05-24T12:34:56Z",
  "options": {
    "pdfVersion": "1.7",
    "embedFonts": true
  },
  "hash": "a3f5c2..."
}

マニフェストはプレーンテキストで完全にバージョン管理され、CI パイプラインは「バイナリ ⇔ 宣言された出所」が一致しているかを検証できます。


変換精度のテスト

堅牢なワークフローには、回帰テスト が必須です。新たに生成したバイナリを既知のベースラインと比較しますが、バイナリ diff はノイズが多いため次の組み合わせを使用します。

  • ピクセル単位の画像比較(許容閾値付き compare -metric RMSE
  • PDF 構造比較 diff-pdf --output-diff で視覚的差分を抽出
  • テキスト抽出チェック – PDF に OCR を走らせ、抽出されたプレーンテキストを元のソースと比較

これらのチェックは GitHub Actions のジョブとして自動化し、許容閾値を超える差分があれば PR が失敗するようにします。


ミニケーススタディ:技術ドキュメントサイト

あるソフトウェアチームは Hugo で構築された公開ドキュメントサイトを维护しています。ソースは Markdown、サイトはダウンロード可能な PDF ハンドブックも提供しています。最初のワークフローでは PDF を直接リポジトリに保存していました。その結果、リポジトリは 1.5 GB に膨れ上がり、開発者は PDF のマージコンフリクトに苦しんでいました。

解決手順

  1. .md ファイルだけをリポジトリに残す。
  2. Pre‑commit フックで convertise.app を呼び、Markdown から PDF を生成、タイムスタンプを除去し、SHA‑256 ハッシュを同伴する .md5 ファイルに書き込む。
  3. Git LFS に PDF を格納(*.pdf filter=lfs)。
  4. 同じ変換を実行する CI ジョブを作り、コミットされた .md5 とハッシュが一致するか検証、生成 PDF を S3 バケットへ公開。
  5. サイトのビルド時に S3 から PDF を取得。

結果:リポジトリサイズが 78 % 減少し、diff が再び有意味になり、PDF 生成が完全に再現可能になったことでブランチ間の「PDF ドリフト」も解消しました。


ベストプラクティスまとめ

  • ソースに適したテキスト形式(Markdown、SVG、CSV)を Git に保存し、バイナリは派生アーティファクトとして扱う。
  • 変換を決定的に するため、タイムスタンプ除去、圧縮設定固定、コンテナ化環境の利用を徹底。
  • 生成は自動化:小規模資産は pre‑commit フックで、大容量は CI パイプラインで行う。
  • Git LFS は本当に必要なバイナリだけに使用し、命名規則とサイズ制限で管理を明確化。
  • 由来情報はサイドカー JSON に保持し、監査可能かつ軽量に。
  • 定期的な検証:チェックサム、スキーマ、ビジュアル回帰テストで品質を維持。

変換選択をバージョン管理の得意領域に合わせることで、リポジトリは軽量に保たれ、履歴は明快になり、必要なバイナリ資産も確実に提供できます。このアプローチはコード中心のプロジェクトでも、コンテンツが豊富なドキュメントサイトでも同様に有効で、プライバシー重視のクラウドコンバータ convertise.app をオンデマンド変換に組み込むこともスムーズに行えます。