Rust製アプリケーションからGit repositoryを扱いたい。
ただし、Gitクライアントを作りたいわけではありません。
今回考えていた用途は、
- ローカルのファイル群について過去との差分を見たい
- repositoryやcommitのidentityを取得したい
- 外部の
gitコマンドに依存したくない - GitHubなどのクラウドサービスを前提にしたくない
- できればPure Rustで完結させたい
というものでした。
そこで候補に上がったのが、Pure RustでGitを実装している gitoxide / gix です。
今回は、
gixをRustアプリケーションへ組み込んだ場合、Git相当の機能をどこまで任せられるのか
を2026年9月時点の公式情報から調べました。
なお、この記事は公式documentationとsourceを読んだ段階の調査です。
実際のrepositoryを使った動作検証や、binary size・依存crate数の実測はまだ行っていません。
調べ始めた時点では、アプリケーション内部の履歴管理そのものにgixを使うことも考えていました。
しかし最終的には、
内部VCSとしては採用しない。
既存Git repositoryを読み取るread-only provenance observerとして使う。
という位置に落ち着きました。
なぜそう判断したのかも含めて整理します。
まずgixの現在地
gixは現時点でも、Gitの低レベル機能をRustから利用するlibraryとしてかなり充実しています。
特に、
- repository open / discovery
- object read / write
- commit
- ref
- index
- status
- blob diff
- tree diff
- rename tracking
- revision traversal
- clone / fetch
などのplumbingは既に提供されています。
一方で、Git CLIで日常的に使う、
- checkout
- switch
- restore
- reset
- merge workflow
- cherry-pick
- revert
- rebase
- stash
- push
などについては、公式のcrate-status.md上で高レベルworkflowとして未完成の領域が残っています。
つまり現在のgixは、
Git CLIを丸ごと置き換える完成済みporcelain
というより、
Gitのrepository構造や履歴をRustから直接扱うための強いplumbing layer
として見るのが適切です。
今回の用途では、むしろこの性質が都合よく働きました。
gixとは
gitoxideはGitをPure Rustで実装するプロジェクトです。
アプリケーションから使う場合は、トップレベルcrateであるgixを使います。
2026年9月時点のdocs.rsではgix 0.87.1が公開されています。
gix::RepositoryがGit repository操作の中心となるabstractionです。
たとえば既存repositoryを開いたあと、
let head = repo.head_commit()?;
let tree = repo.head_tree_id()?;
のように、現在のHEAD commitやtree identityへアクセスできます。
revision featureを有効にしていれば、
let previous = repo.rev_parse_single("HEAD^")?;
のようなrevision解決も可能です。
CLIとしてのgixも存在しますが、組み込み用途ならlibrary APIを直接利用する方が自然です。
Pure Rustである意味
RustからGitを扱う代表的な選択肢にはgit2もあります。
git2はlibgit2のRust bindingです。
一方gitoxideは、
- object database
- refs
- index
- diff
- revision
- protocol
- worktree
などをRustで実装しています。
そのため、
Rust application
↓
gix
↓
Git repository
という構成が取れます。
外部のgit.exeやgitbinaryをsubprocessとして起動する必要がありません。
組み込み用途では、
- PATH
- shell quoting
- インストール済みGitのversion差
- subprocess lifecycle
などへの依存を減らせるのが魅力です。
remote操作に必要なplumbingもgix側に存在するため、必要になればclone / fetchまでRust側で扱えます。ただし今回の用途は、既にローカルに存在するrepositoryを観測することなので、remote機能は採用判断の中心ではありません。
ライセンス
ライセンスも組み込み用途では重要です。
gixは MIT OR Apache-2.0 です。
比較対象となるgit2 crateも MIT OR Apache-2.0 ですが、内部で利用するlibgit2本体は GPLv2 with Linking Exception です。
このLinking Exceptionにより、libgit2をリンクしたアプリケーション全体がGPLになるわけではありません。
つまり両方とも商用アプリケーションへ組み込める選択肢ですが、
gix
→ Pure Rust
→ MIT OR Apache-2.0
git2
→ Rust binding
→ MIT OR Apache-2.0
→ libgit2: GPLv2 + Linking Exception
という違いがあります。
単なる「C依存があるか」だけでなく、配布artifactとライセンス監査の構造も異なります。
repositoryは普通に読める
gix::open()などから既存repositoryを開けます。
Repositoryからは、
- HEAD
- commit
- tree
- object
- refs
- index
などへアクセスできます。
つまり、
このdirectoryはGit repositoryか
を調べたり、
現在どのcommitを指しているか
を取得したり、
HEADが指すtreeは何か
を見る用途に使えます。
read-only provenance用途では、まずここが基本になります。
commitやrefの書き込みも技術的には可能
gix自体はread-only libraryではありません。
commitやref、object、indexのmutation plumbingも存在します。
つまり技術的には、
working tree
↓
index
↓
tree
↓
commit
↓
ref update
というGitの履歴をアプリケーション側から構築できます。
最初にgixを調べ始めた理由もここでした。
アプリ内部の履歴管理をGit repositoryで持てないか?
と考えたからです。
ただ、後述する理由で、この用途には採用しないことにしました。
statusはread-only用途でも重要
status featureを有効にすると、HEAD・index・working treeの関係を調べられます。
これは単に「dirtyか」を表示するためだけではありません。
アプリケーションが実際に読んだfileと、
HEAD commitが表しているfile
が同じ内容なのかを判断するために必要です。
たとえば、
HEAD
document.md = blob A
working tree
document.md = blob B
なら、HEAD commitのidentityだけをsource provenanceとして記録することはできません。
実際に読んだのはblob Bだからです。
この問題は後半の 「Git provenanceはsource identityそのものではない」 で詳しく扱います。
blob / tree diffも使える
gixにはblob diffとtree diffがあります。
そのため、
commit A
↓
tree A
commit B
↓
tree B
を比較して、
- added
- deleted
- modified
- rename候補
などを見ることができます。
テキストblobについてはline-orientedなdiffも扱えます。
source codeやMarkdownなどについて、
このcommit間でどのfileが変わったか
をアプリケーション側から調査する用途には十分魅力的です。
rename trackingもある
diffやstatus周辺にはrename trackingもあります。
たとえば、
docs/design.md
↓
architecture/design.md
へ移動しただけの場合、
deleted docs/design.md
added architecture/design.md
と見るか、
renamed
と見るかで、上位システムの意味が変わります。
Gitのrename detectionはrename情報を履歴として保存しているわけではなく、内容の類似度などから推定する仕組みです。
gixでも、この種のtrackingを利用できます。
Git CLI相当のworkflowは全部完成しているわけではない
ここは重要です。
公式のcrate-status.mdでは、以下のような操作について高レベルworkflowとして未完成の領域が残っています。
- checkout
- switch
- restore
- reset
- merge
- cherry-pick
- revert
- rebase
- bisect
- stash
- am
- apply
- push
低レベルcomponentが既に存在するものもあります。
たとえばworktree mutationやlow-level checkoutに相当する部品があることと、
git restoreと同じsemanticを一つの完成済みworkflowとして提供している
ことは別です。
gixを評価するときは、
plumbingが存在する
ことと、
Git CLI相当の高レベルworkflowが完成している
ことを分けて見る必要があります。
ここで考え直した
ここまで見ると、
ローカル履歴管理にgixを使えばいいのでは?
と思えます。
技術的にはかなり可能です。
しかし、アプリケーション内部にGit repositoryを隠して持つ場合、別の問題が出てきます。
Gitは「歴史を残す」ためのシステム
Gitでは一度commitされたblobは、working treeからfileを削除しただけでは即座に物理消去されません。
object、reflog、packfile、GCなどのlifecycleがあります。
つまりユーザーから見ると、
文書を削除した
のに、
Git objectとして過去の本文は残っている
という状態が起こり得ます。
これはGitとしては正しい動作です。
むしろGitは過去を失いにくくするための仕組みです。
しかしアプリケーション側が、
- 本文を必要以上に保持しない
- 削除要求に明確に応える
ことを重視している場合、Gitの性質そのものが責任モデルと衝突します。
「利用者に意識させない」は責任境界ではない
最初は、
Gitを内部に同梱し、利用者にはGitを意識させない
という案も考えました。
しかしこれは責任境界を守ることにはなりません。
実態としてアプリケーションが、
ドキュメント化されていないversion control systemを内部に持つ
ことになるからです。
UIに表示しなくても、Git repositoryが本文の履歴を保持している事実は変わりません。
この時点で、内部VCSとして採用する案は止めました。
ではgixは使わないのか
逆でした。
採用範囲を変えれば、かなり噛み合います。
内部Git repositoryは作らない。
commitもしない。
historyを書き換えない。
その代わり、
source directoryが既にGit repositoryなら、その履歴をread-onlyで観測する
という使い方です。
既存Git repository
↓
gix
↓
repository / HEAD / tree / blob / status
↓
application provenance
この位置なら、gixの強い部分をかなりそのまま使えます。
Git provenanceはsource identityそのものではない
単純に、
source_git_commit = abc123
と記録するだけでは不十分です。
なぜなら、アプリケーションが実際に読むのはworking treeだからです。
たとえば、
HEAD:
document.md = blob A
working tree:
document.md = blob B
というdirty repositoryで、アプリケーションがblob Bを読んだとします。
この状態で、
source_git_commit = abc123
だけ記録すると、
commit abc123の内容を取り込んだ
ように見えます。
しかし実際に読んだbytesはcommitに存在しません。
provenanceとしては誤りです。
fileごとにGitとの関係を見る
Git情報はfile単位で扱う方が安全です。
たとえば、
repo_scope:
NOT_GIT
SAME_REPO
NESTED_REPO
SUBMODULE
content_relation:
EXACT_HEAD
MODIFIED
UNTRACKED
DELETED_IN_HEAD
ignored:
true / false
のように分離します。
repo_scopeとcontent_relationは別軸です。
たとえばsubmodule配下のfileがHEADと一致していることもありますし、ignored fileがmodifiedであることもあります。
単一enumにすると、こうした組み合わせを正しく表現できません。
ここで重要なのは、
EXACT_HEAD以外では、Git commitは実際に読んだcontentを証明していない
と一目で分かることです。
Git blobとの比較対象はraw bytes
Git blobとの比較対象は抽出後textではなく、実際に読んだraw bytesです。
たとえばdocxなら、Gitが保持しているのはdocx containerそのものです。
したがって、
observed_raw_identity
↓ compare
HEAD blob identity
という比較になります。
一方、parserやextractorによる処理結果には別identityを持たせます。
raw_identity
→ source bytesのidentity
extracted_identity
→ parser / extractor処理後のidentity
この二つを混同しない方が安全です。
HEADのTOCTOUにも注意が要る
もう一つ問題があります。
取り込み開始時に、
HEAD = abc123
だったとしても、処理中にHEADが変わる可能性があります。
特別な競合processを想定しなくても、
- ユーザー自身のIDE
- Git GUI
- editor integration
- background automation
などが普通にcommitやbranch操作を行い得ます。
そこで少なくとも、
- 取り込み前にHEADを読む
- fileを読む
- HEAD blobと比較する
- 取り込み完了後にHEADをもう一度読む
という確認が必要になります。
HEAD before = abc123
HEAD after = abc123
なら、そのrepository observationはstableだったと判断できます。
一方、
HEAD before = abc123
HEAD after = def456
なら、
HEAD_MOVED_DURING_INGEST
のようなrepository × generation単位のstatusとして扱う方が自然です。
この場合、個々のfileについてEXACT_HEAD判定が存在していても、repository observation全体をauthoritativeなGit provenanceとして扱うことはできません。
read-only用途とgixの成熟度がきれいに噛み合う
ここが今回いちばん面白かったところです。
read-only provenance observerとして欲しいのは主に、
- repository discovery
- HEAD resolution
- tree traversal
- blob lookup
- status
- diff
- revision traversal
- ignore / submodule情報
です。
逆に不要なのは、
- checkout
- restore
- reset
- merge workflow
- rebase
- stash
- push
です。
そして後者は、ちょうど公式crate statusで高レベルworkflowとして未完成の領域に寄っています。
つまり、
gixの成熟しているread-side plumbingを中心に使い、未成熟なwrite-side workflowをほぼ踏まずに済む
ことになります。
最初に想定していた「内部VCS」より、read-only observerの方が現在のgixの成熟度分布とも相性がいいわけです。
Trust Modelもread-only用途と相性がいい
gixにはrepositoryのTrust Modelがあります。
repository ownerと現在processのuserなどからtrust levelを決め、Git configurationにもそのtrust informationを反映します。
未信頼sourceから得た、
- executableへのpath
- 外部commandに関連する値
- その他sensitiveなconfiguration
を無条件に利用しない仕組みがあります。
つまり、
repositoryからdataは読みたいが、repository由来のprogram実行までは信用したくない
という用途を明確に意識した設計です。
他人や外部systemが作ったrepositoryをread-onlyで観測する用途では、これは単なる便利機能ではなく採用理由になります。
必要ならuntrusted repository自体を拒否する設定も取れます。
feature flagはread-only用途なら絞れる可能性がある
gix 0.87.1には54個のfeature flagがあり、そのうち32個がdefaultで有効になっています。
defaultでは、
- blob diff
- index
- revision
- status
- merge
- attributes
- worktree mutation
など、かなり広い機能が入ります。
一方、公式のCargo.tomlでは、library developerに必要componentだけを選択してbuild timeなどを最適化することが推奨されています。
read-only provenance用途なら、
blob-diffindexrevisionstatus
などを中心に必要なfeature closureを調べる価値があります。
ただし、
実際に何個のdependencyまで減るのか
binary sizeが何MiB増えるのか
はまだ測定していません。
したがって現時点では、
featureを絞れる可能性がある
までが確認済みであり、
十分小さいbinaryになる
とはまだ言えません。
これは実際にcandidate binaryをbuildして測る必要があります。
公式documentation上の提供状況
以下は実動作を検証した結果ではなく、2026年9月時点の公式documentation / crate statusを読んだ整理です。
| 機能 | ドキュメント上の提供状況 |
|---|---|
| repository open / discovery | 提供あり |
| HEAD / commit / tree lookup | 提供あり |
| blob lookup | 提供あり |
| status | 提供あり |
| blob / tree diff | 提供あり |
| rename tracking | 提供あり |
| revision traversal | 提供あり |
| clone / fetch plumbing | 提供あり |
| commit / ref mutation plumbing | 提供あり |
| low-level worktree mutation | 提供あり |
| restore / reset相当の完成済みporcelain | 未完成領域あり |
| merge / rebase等の高レベルworkflow | 未完成領域あり |
| push workflow | 未完成領域あり |
今回の採用判断
上の機能提供状況とは別に、今回の用途についての判断はこうなりました。
| 用途 | 判断 |
|---|---|
| 既存repositoryのidentity取得 | 採用候補 |
| HEAD / tree / blob provenance取得 | 採用候補 |
| working treeとHEADの関係確認 | 採用候補 |
| 既存historyのread-only参照 | 採用候補 |
| Git diff情報の補助利用 | 採用候補 |
| アプリ内部で新規Git repositoryを作る | 不採用 |
| アプリが自動commitして履歴を所有する | 不採用 |
| restore / merge / rebase等を製品機能として提供 | 今回のscope外 |
| remoteへのpushを製品機能として提供 | 今回のscope外 |
重要なのは、
gixが使えなかったから採用しなかった
のではありません。
むしろ逆です。
gixはかなり使えそうです。
そのうえで、
アプリケーションがGit historyそのものを所有するべきか
を考え直した結果、採用範囲が変わりました。
結論
2026年9月時点のgixは、
Pure RustでGit repositoryを観測・解析するlibrary
としてかなり有力に見えます。
特に、
- repository identity
- HEAD
- tree
- blob
- status
- diff
- revision history
をRustアプリケーション側から直接読みたい場合、外部Git CLIに依存せず実装できる可能性があります。
一方、
アプリ内部の履歴管理そのものをGitへ任せる
場合は、技術的に可能かどうかだけではなく、
- 誰がhistoryを所有するのか
- 削除したcontentがいつ物理的に消えるのか
- 利用者にどのようなversioning contractを約束するのか
まで考える必要があります。
今回の用途では、
内部VCSとしては不採用。
既存Git repositoryを読むread-only provenance observerとして採用候補。
という結論になりました。
偶然ですが、この使い方ならgixで現在まだ弱いwrite-side workflowをほぼ避けながら、read-side plumbingを中心に利用できます。
調べる前より、むしろgixの使いどころが明確になりました。
次は実際に小さなcandidateを作り、
- dirty / clean repository
- nested repository
- submodule
- ignored / untracked file
- HEAD移動中の取り込み
- Windows / Linux / macOS
- binary size
- dependency closure
あたりを実測してみたいところです。
なお、
Git historyそのものを所有せずに、Generation間の差分や削除可能性をどう両立するか
は別の設計問題になります。
これは別記事 「削除できるKnowledge History」 で扱う予定です。
参考資料
-
gitoxide repository
https://github.com/GitoxideLabs/gitoxide -
gix documentation
https://docs.rs/gix/latest/gix/ -
gix
RepositoryAPI
https://docs.rs/gix/latest/gix/struct.Repository.html -
gix feature flags
https://docs.rs/crate/gix/latest/features -
gitoxide
crate-status.md
https://github.com/GitoxideLabs/gitoxide/blob/main/crate-status.md -
git2 / git2-rs documentation
https://docs.rs/crate/git2/latest -
git2-rs repository
https://github.com/rust-lang/git2-rs -
libgit2 repository / license
https://github.com/libgit2/libgit2