| Crates.io | zerogit |
| lib.rs | zerogit |
| version | 0.3.7 |
| created_at | 2026-01-17 16:03:48.171408+00 |
| updated_at | 2026-01-19 18:03:59.62264+00 |
| description | A lightweight, pure Rust Git client library |
| homepage | |
| repository | https://github.com/siska-tech/zerogit |
| max_upload_size | |
| id | 2050716 |
| size | 864,084 |
Pure Rust製の軽量Gitクライアントライブラリ。最小限の依存でGitリポジトリの読み書きを実現します。
miniz_oxide(zlib解凍)のみに依存Cargo.toml に以下を追加:
[dependencies]
zerogit = "0.3"
use zerogit::{Repository, Result};
fn main() -> Result<()> {
// 新しいGitリポジトリを作成
let repo = Repository::init("./my-project")?;
println!("Initialized empty Git repository");
Ok(())
}
use zerogit::{Repository, Result};
fn main() -> Result<()> {
// カレントディレクトリから.gitを探索
let repo = Repository::discover(".")?;
// 最新10件のコミットを表示
for commit in repo.log()?.take(10) {
let commit = commit?;
println!("{} {}", commit.oid().short(), commit.summary());
}
Ok(())
}
use zerogit::{Repository, FileStatus, Result};
fn main() -> Result<()> {
let repo = Repository::open(".")?;
for entry in repo.status()? {
let marker = match entry.status() {
FileStatus::Untracked => "??",
FileStatus::Modified => " M",
FileStatus::Added => "A ",
FileStatus::Deleted => " D",
_ => " ",
};
println!("{} {}", marker, entry.path().display());
}
Ok(())
}
use zerogit::{Repository, Result};
fn main() -> Result<()> {
let repo = Repository::discover(".")?;
// 短縮形式でもOK
let commit = repo.commit("abc1234")?;
println!("Commit: {}", commit.oid());
println!("Author: {} <{}>", commit.author().name(), commit.author().email());
println!("Message: {}", commit.summary());
Ok(())
}
use zerogit::{Repository, Result};
fn main() -> Result<()> {
let repo = Repository::discover(".")?;
let head = repo.head()?;
// ローカルブランチ
for branch in repo.branches()? {
let marker = if head.branch().map(|b| b.name()) == Some(branch.name()) {
"* "
} else {
" "
};
println!("{}{}", marker, branch.name());
}
// リモートブランチ
for rb in repo.remote_branches()? {
println!(" remotes/{}/{}", rb.remote(), rb.name());
}
Ok(())
}
use zerogit::{Repository, Result};
fn main() -> Result<()> {
let repo = Repository::discover(".")?;
for tag in repo.tags()? {
println!("{} -> {}", tag.name(), tag.target().short());
// 注釈付きタグの場合はメッセージも取得可能
if let Some(message) = tag.message() {
println!(" {}", message);
}
}
Ok(())
}
| 型 | 説明 |
|---|---|
Repository |
リポジトリ操作のエントリーポイント |
Commit |
コミット情報(author, message, parents等) |
Tree |
ディレクトリ構造 |
Blob |
ファイル内容 |
Oid |
オブジェクトID(SHA-1ハッシュ) |
Branch |
ブランチ情報 |
RemoteBranch |
リモートブランチ情報 |
Tag |
タグ情報(軽量/注釈付き) |
Head |
HEAD参照(ブランチまたはdetached) |
TreeDiff |
Tree間の差分 |
DiffDelta |
差分の各エントリ |
LogOptions |
ログ取得オプション |
// リポジトリを開く・作成する
Repository::init(path)?; // 新規リポジトリを初期化
Repository::open(path)?; // 指定パス
Repository::discover(path)?; // 親ディレクトリを探索
// 読み取り操作
repo.head()?; // HEAD取得
repo.branches()?; // ローカルブランチ一覧
repo.remote_branches()?; // リモートブランチ一覧
repo.tags()?; // タグ一覧
repo.log()?; // コミット履歴(Iterator)
repo.log_with_options(opts)?; // フィルタリング付きログ
repo.status()?; // ワーキングツリー状態
repo.commit("sha")?; // コミット取得
repo.tree("sha")?; // ツリー取得
repo.blob("sha")?; // Blob取得
repo.index()?; // インデックス取得
// 差分操作
repo.diff_trees(old, new)?; // Tree間の差分
repo.commit_diff(&commit)?; // コミットの変更ファイル一覧
repo.diff_index_to_workdir()?; // git diff 相当
repo.diff_head_to_index()?; // git diff --staged 相当
repo.diff_head_to_workdir()?; // git diff HEAD 相当
// 書き込み操作
repo.add(path)?; // ファイルをステージ
repo.add_all()?; // 全変更をステージ
repo.reset(path)?; // ステージを解除
repo.create_commit(msg, author, email)?; // コミット作成
repo.create_branch(name, target)?; // ブランチ作成
repo.delete_branch(name)?; // ブランチ削除
repo.checkout(target)?; // ブランチ切り替え
詳細は APIドキュメント を参照してください。
let repo = Repository::discover(".")?;
let head = repo.head()?;
let commit = repo.commit(&head.oid().to_hex())?;
let tree = repo.tree(&commit.tree().to_hex())?;
if let Some(entry) = tree.get("README.md") {
let blob = repo.blob(&entry.oid().to_hex())?;
println!("{}", blob.content_str()?);
}
use zerogit::{Repository, Result};
fn main() -> Result<()> {
let repo = Repository::discover(".")?;
// 最新コミットの変更ファイルを表示
for commit in repo.log()?.take(5) {
let commit = commit?;
let diff = repo.commit_diff(&commit)?;
println!("{} {}", commit.oid().short(), commit.summary());
for delta in diff.deltas() {
println!(" {} {}", delta.status_char(), delta.path().display());
}
}
Ok(())
}
use zerogit::{Repository, LogOptions, Result};
fn main() -> Result<()> {
let repo = Repository::discover(".")?;
// 特定ファイルの変更履歴を取得
let log = repo.log_with_options(
LogOptions::new()
.path("src/main.rs")
.max_count(10)
)?;
for commit in log {
let commit = commit?;
println!("{} {}", commit.oid().short(), commit.summary());
}
Ok(())
}
use zerogit::{Repository, Result};
fn main() -> Result<()> {
let repo = Repository::discover(".")?;
// git diff 相当(未ステージの変更)
let unstaged = repo.diff_index_to_workdir()?;
println!("Unstaged changes:");
for delta in unstaged.deltas() {
println!(" {} {}", delta.status_char(), delta.path().display());
}
// git diff --staged 相当(ステージ済みの変更)
let staged = repo.diff_head_to_index()?;
println!("Staged changes:");
for delta in staged.deltas() {
println!(" {} {}", delta.status_char(), delta.path().display());
}
Ok(())
}
use zerogit::{Repository, Result};
fn main() -> Result<()> {
let repo = Repository::discover(".")?;
// ファイルをステージ
repo.add("src/main.rs")?;
// または全変更をステージ
repo.add_all()?;
// コミット作成
let oid = repo.create_commit(
"Add new feature",
"Your Name",
"your@email.com"
)?;
println!("Created commit: {}", oid.short());
Ok(())
}
use zerogit::{Repository, Result};
fn main() -> Result<()> {
let repo = Repository::discover(".")?;
// 新しいブランチを作成
repo.create_branch("feature/new-feature", None)?;
// ブランチに切り替え
repo.checkout("feature/new-feature")?;
// 作業後、mainに戻る
repo.checkout("main")?;
// ブランチを削除
repo.delete_branch("feature/new-feature")?;
Ok(())
}
ローカルリポジトリの読み取り機能を提供します。
ローカルリポジトリへの書き込み機能を提供します。
add / reset - ステージング操作commit - コミット作成branch - ブランチ作成・削除checkout - ブランチ切り替えリモートブランチ、タグ、ログフィルタリング、Tree diff機能を提供します。
remote_branches())tags())- 軽量タグ・注釈付きタグ両対応log_with_options())- パス、件数、日付、作者diff_trees())- リネーム検出対応commit_diff())diff_index_to_workdir(), diff_head_to_index())Packfile対応と行単位差分を提供します。
| 機能 | 説明 | 難易度 |
|---|---|---|
| Blob diff | ファイル内容の行単位差分(Myers算法) | 高 |
| Packfile読み取り | .git/objects/pack/*.pack の読み取り |
高 |
| Packfileインデックス | .idx ファイルによる高速検索 |
中 |
| Delta復元 | ofs_delta / ref_delta の展開 | 高 |
| 3-way merge | 共通祖先ベースのマージ | 高 |
想定API:
// Packfile対応(内部的に自動処理)
let obj = repo.object("abc123")?; // looseまたはpackから透過的に取得
// 行単位差分(将来)
let blob_diff = repo.diff_blobs(&old_blob, &new_blob)?;
for hunk in blob_diff.hunks() {
println!("@@ -{},{} +{},{} @@", ...);
}
zerogit-remote)ネットワーク操作は依存関係が増えるため、別crateとして提供予定です。
| 観点 | zerogit (コア) | zerogit-remote |
|---|---|---|
| 依存 | miniz_oxide のみ |
rustls, russh, ureq 等 |
| ビルド時間 | 高速 | TLS/SSH依存で増加 |
| WASM対応 | ○ | △(制限あり) |
| 組み込み用途 | ○ | △ |
| プロトコル | URL形式 | 認証方式 | 優先度 |
|---|---|---|---|
| HTTPS | https://github.com/... |
Basic / Bearer Token | 高 |
| SSH | git@github.com:... |
SSH鍵 | 中 |
| Git | git://... |
なし(読み取り専用) | 低 |
use zerogit::Repository;
use zerogit_remote::{Remote, Credentials};
// クローン
let repo = Remote::clone(
"https://github.com/user/repo.git",
"./local-repo",
Credentials::token("ghp_xxxx"),
)?;
// フェッチ
let remote = repo.remote("origin")?;
remote.fetch(&Credentials::ssh_key("~/.ssh/id_ed25519"))?;
// プッシュ
remote.push("main", &Credentials::token("ghp_xxxx"))?;
Smart HTTP Protocol:
┌─────────┐ ┌─────────┐
│ Client │ GET /info/refs │ Server │
│ │ ───────────────────────────> │ │
│ │ 200 OK (refs + capabilities)│ │
│ │ <─────────────────────────── │ │
│ │ │ │
│ │ POST /git-upload-pack │ │
│ │ (want/have negotiation) │ │
│ │ ───────────────────────────> │ │
│ │ 200 OK (packfile) │ │
│ │ <─────────────────────────── │ │
└─────────┘ └─────────┘
リモート操作が必要だが zerogit-remote を待てない場合、システムのgitコマンドと連携できます:
use std::process::Command;
fn fetch_with_git(repo_path: &str, remote: &str) -> std::io::Result {
Command::new("git")
.args(["-C", repo_path, "fetch", remote])
.status()?;
Ok(())
}
コントリビューションを歓迎します!
git clone https://github.com/siska-tech/zerogit
cd zerogit
# テスト用フィクスチャの準備
cd tests/fixtures
bash create_fixtures.sh
cd ../..
# テスト実行
cargo test
# フォーマットとLint
cargo fmt
cargo clippy
cargo fmt と cargo clippy を実行cargo fmt でフォーマットcargo clippy の警告をゼロに詳細な設計については以下を参照:
zerogitは学習目的と軽量な用途に特化しています。フル機能が必要な場合は上記のプロジェクトを検討してください。
本プロジェクトはデュアルライセンスです:
お好きな方を選択してください。