zerogit

Crates.iozerogit
lib.rszerogit
version0.3.7
created_at2026-01-17 16:03:48.171408+00
updated_at2026-01-19 18:03:59.62264+00
descriptionA lightweight, pure Rust Git client library
homepage
repositoryhttps://github.com/siska-tech/zerogit
max_upload_size
id2050716
size864,084
Shion (siska-tech)

documentation

README

zerogit

Pure Rust製の軽量Gitクライアントライブラリ。最小限の依存でGitリポジトリの読み書きを実現します。

Crates.io Documentation License

特徴

  • Pure Rust: Cバインディングなし、クロスコンパイルが容易
  • 最小依存: miniz_oxide(zlib解凍)のみに依存
  • 軽量: 必要な機能だけを実装したシンプルな設計
  • 学習向け: Git内部構造の理解に役立つクリーンな実装

インストール

Cargo.toml に以下を追加:

[dependencies]
zerogit = "0.3"

必要環境

  • Rust 1.70.0 以上
  • Linux / macOS / Windows

クイックスタート

新規リポジトリを初期化

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(())
}

API概要

主要な型

説明
Repository リポジトリ操作のエントリーポイント
Commit コミット情報(author, message, parents等)
Tree ディレクトリ構造
Blob ファイル内容
Oid オブジェクトID(SHA-1ハッシュ)
Branch ブランチ情報
RemoteBranch リモートブランチ情報
Tag タグ情報(軽量/注釈付き)
Head HEAD参照(ブランチまたはdetached)
TreeDiff Tree間の差分
DiffDelta 差分の各エントリ
LogOptions ログ取得オプション

Repository メソッド

// リポジトリを開く・作成する
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(())
}

ロードマップ

Phase 1: 読み取り操作(MVP)✅

ローカルリポジトリの読み取り機能を提供します。

  • オブジェクト読み取り(blob/tree/commit)
  • 参照解決(HEAD/branches/tags)
  • コミット履歴イテレータ
  • ワーキングツリーステータス
  • インデックス読み取り

Phase 2: 書き込み操作 ✅

ローカルリポジトリへの書き込み機能を提供します。

  • add / reset - ステージング操作
  • commit - コミット作成
  • branch - ブランチ作成・削除
  • checkout - ブランチ切り替え

Phase 2.5: 参照拡張・ログフィルタリング・差分機能 ✅

リモートブランチ、タグ、ログフィルタリング、Tree diff機能を提供します。

  • リモートブランチ一覧(remote_branches()
  • タグ一覧(tags())- 軽量タグ・注釈付きタグ両対応
  • ログフィルタリング(log_with_options())- パス、件数、日付、作者
  • Tree diff(diff_trees())- リネーム検出対応
  • コミット変更一覧(commit_diff()
  • ワーキングツリー差分(diff_index_to_workdir(), diff_head_to_index()

Phase 3: Packfile・行単位差分・マージ

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!("@@ -{},{} +{},{} @@", ...);
}

Phase 4: リモート操作(別crate: zerogit-remote

ネットワーク操作は依存関係が増えるため、別crateとして提供予定です。

なぜ別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://... なし(読み取り専用)

想定API

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(())
}

将来の検討事項

  • Worktree対応: 複数のワーキングツリー
  • Submodule対応: サブモジュールの読み取り
  • Sparse checkout: 部分的なチェックアウト
  • Shallow clone: 履歴を限定したクローン

貢献

コントリビューションを歓迎します!

開発環境のセットアップ

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

プルリクエスト

  1. Issueを作成して変更内容を議論
  2. フォークしてfeatureブランチを作成
  3. 変更を実装(テスト必須)
  4. cargo fmtcargo clippy を実行
  5. プルリクエストを送信

コーディング規約

  • cargo fmt でフォーマット
  • cargo clippy の警告をゼロに
  • 公開APIには必ずドキュメントコメント
  • 新機能にはテストを追加

設計ドキュメント

詳細な設計については以下を参照:

関連プロジェクト

  • gitoxide - フル機能のPure Rust Git実装
  • git2-rs - libgit2のRustバインディング

zerogitは学習目的と軽量な用途に特化しています。フル機能が必要な場合は上記のプロジェクトを検討してください。

ライセンス

本プロジェクトはデュアルライセンスです:

お好きな方を選択してください。

謝辞

  • Git - オリジナル実装とドキュメント
  • Pro Git Book - Git内部構造の解説
  • gitoxide - Pure Rust実装の参考
Commit count: 8

cargo fmt