diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0560b06..399c52c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,8 +93,8 @@ jobs: run: cargo build --release - name: Run smoke tests run: | - chmod +x tests/smoke/linux_smoke.sh - AGIT_BIN="${{ github.workspace }}/target/release/agit" tests/smoke/linux_smoke.sh + chmod +x agit-cli/tests/smoke/linux_smoke.sh + AGIT_BIN="${{ github.workspace }}/target/release/agit" agit-cli/tests/smoke/linux_smoke.sh smoke-windows: name: Smoke Tests (Windows) @@ -109,7 +109,7 @@ jobs: shell: pwsh run: | $env:AGIT_BIN = "${{ github.workspace }}\target\release\agit.exe" - .\tests\smoke\windows_smoke.ps1 + .\agit-cli\tests\smoke\windows_smoke.ps1 # ── Release 构建 ── diff --git a/.gitignore b/.gitignore index 7bbeefa..4a9a718 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,6 @@ target /target release/ + +# Bundle files (git bundle) +*.bundle diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d6c5cc..c3242d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to AdapterGit (agit) will be documented in this file. --- +## [v0.14.0] — 2026-06-22 + +> Workspace 拆分 — Multi-crate Architecture + +### Refactor +- **workspace 拆分**: 单 crate → 3 crate workspace (agit-core + agit-ai + agit-cli) +- **agit-core**: 纯 Rust Git 核心库,可独立复用 +- **agit-ai**: AI 提交信息生成独立 crate,携带 reqwest 依赖 +- **agit-cli**: CLI 二进制,lite/full 双版本分发 +- **双版本**: `cargo build --no-default-features -F tag` = Lite, `--all-features` = Full + ## [v0.13.0] — 2026-06-21 > 双版本分发 + AI 提交 — Dual-Edition Distribution & AI Commit diff --git a/CLAUDE.md b/CLAUDE.md index fb363a5..420b8df 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,50 +7,97 @@ This file provides guidance to Claude Code when working in this repo. **agit (AdapterGit)** — a pure-Rust, zero-external-Git-dependency Git implementation. Objects, refs, index, smart-HTTP protocol all from scratch. Ships as a single static binary. Designed for AI agents, CI/CD, and portable use. Never blocks on interactive prompts. -Version: 0.6.1 | Edition: 2021 | License: Apache-2.0 +Version: 0.14.0 | Edition: 2021 | License: Apache-2.0 + +## Workspace Structure + +``` +D:\AdapterGit\ ← Cargo workspace root +├── agit-core/ (lib) ← Git 核心库 +├── agit-ai/ (lib) ← AI 功能(LLM 客户端) +└── agit-cli/ (bin → agit) ← CLI 二进制 +``` ## Build / Test / Lint ```bash -cargo build # Debug -cargo build --release # Release -cargo test # All 119 tests (87 unit + 32 integration) -cargo clippy # Must pass w/ 0 warnings -cargo fmt # Must pass w/ no diff +cargo build # workspace 全量构建 +cargo build --all-features # Full 版 (tag + tls + ai) +cargo build --no-default-features -F tag # Lite 版 (纯本地, 无 TLS) + +cargo build -p agit-core # 单独构建核心库 +cargo build -p agit-ai # 单独构建 AI 库 +cargo build -p agit-cli # 单独构建 CLI + +cargo test # 全部 178 测试 (109 单元 + 69 集成) +cargo test -p agit-core # 核心库单元测试 +cargo test -p agit-cli # CLI 集成测试 + +cargo clippy # Must pass w/ 0 warnings +cargo fmt # Must pass w/ no diff ``` ## Architecture +### agit-core (lib) — Git 核心库 +``` +agit-core/src/ +├── lib.rs ← 模块声明(扁平化,无嵌套 core::) +├── hash.rs ← SHA-1 (sha1 crate) +├── compression.rs ← zlib via flate2 +├── storage.rs ← Loose object R/W +├── refs.rs ← HEAD, refs/heads/*, refs/tags/*, refs/remotes/*, CRUD +├── reflog.rs ← Reflog 管理 +├── index.rs ← DIRC v2 staging area +├── ignore.rs ← .gitignore parser +├── repo.rs ← find_repo_root(), timestamp helpers +├── checkout.rs ← Branch switch, tree restore, index rebuild +├── merge.rs ← 3-way merge + fast-forward + conflict markers +├── rebase.rs ← Rebase/cherry-pick 核心 +├── bisect.rs ← Bisect 状态管理与算法 +├── protocol.rs ← Git smart-HTTP: pkt-line, packfile, ref discovery +├── ssh_transport.rs ← SSH 传输(子进程 ssh) +├── ssh_url.rs ← SSH URL 解析 + ~/.ssh/config +├── remote_utils.rs ← 网络命令共享工具 +├── objects/ +│ ├── blob.rs, commit.rs, tree.rs, tag.rs (feature-gated) +│ └── mod.rs +├── config/ +│ └── mod.rs ← Config: user.name, user.email, aliases, LLM +└── utils/ + ├── mod.rs ← atomic_write() + └── error.rs ← AgitError enum +``` + +### agit-ai (lib) — AI 功能 +``` +agit-ai/src/ +└── lib.rs ← LlmConfig, chat_completion(), generate_commit_message() + depends on agit-core::config, reqwest +``` + +### agit-cli (bin → agit) — CLI 二进制 ``` -main.rs → Config → alias resolve → CLI parse → dispatch -cli/mod.rs → clap derive, 24+ subcommands, global flags (--ai --json --yaml --no-color) -commands/ → One file per subcommand, each exposes run(…) → Result<(), Box> -core/ - objects/ → blob, tree, commit, tag (feature-gated) - storage.rs → Loose object R/W (.git/objects/XX/XXXXXX), zlib - refs.rs → HEAD, refs/heads/*, refs/tags/*, refs/remotes/*, CRUD - index.rs → DIRC v2 staging area - hash.rs → SHA-1 (sha1 crate) - compression.rs → zlib via flate2 - protocol.rs → Git smart-HTTP: pkt-line, ref discovery, packfile parse, push - remote_utils.rs→ Shared helpers for network commands - checkout.rs → Branch switch, tree restore, index rebuild - merge.rs → 3-way merge + fast-forward + conflict markers - ignore.rs → .gitignore parser (glob, negation, char-class, dir-only) - repo.rs → find_repo_root(), timestamp helpers -ai/mod.rs → AI-mode flag, DANGEROUS_COMMANDS list -output/mod.rs → JSON/YAML/no-color mode flags + output formatting -config/mod.rs → Config: user.name, user.email, aliases (env > .agit/config.toml > ~/.agitconfig.toml > defaults) -utils/error.rs → AgitError enum +agit-cli/src/ +├── main.rs ← Config → alias resolve → CLI parse → dispatch +├── cli/mod.rs ← clap derive, 29 subcommands, global flags +├── commands/ ← One file per subcommand, each run(…) → Result +├── ai/mod.rs ← AI-mode flag, DANGEROUS_COMMANDS, re-exports agit-ai +├── output/mod.rs ← JSON/YAML/no-color output +└── tests/ ← Integration tests (69 tests) + └── common/mod.rs ← Test helpers (agit_binary, setup_repo, run_agit) ``` ## Key Patterns -- **Global state**: `AI_MODE`, `JSON_MODE`, `YAML_MODE`, `NO_COLOR` are `AtomicBool` statics, set once in main before dispatch. -- **Error handling**: Commands return `Box`. Core uses `AgitError` enum. IO errors propagate via `?` — never swallow with `unwrap_or_default()` on file reads. -- **Config cascade**: env vars (`AGIT_USER_NAME`/`AGIT_USER_EMAIL`) > repo `.agit/config.toml` > global `~/.agitconfig.toml` > defaults. -- **Commit flow**: `Index::load()` → build `Tree` → write tree → build `Commit` (+parent from HEAD/MERGE_HEAD) → write commit → update ref. -- **Network flow**: Clone = discover_refs → fetch_objects → parse_packfile → write_objects → checkout. Push = discover_refs → collect_local_objects → generate_pack → push_pack. Pull = fetch → merge/ff. +- **Workspace**: 3 crates — `agit-core` (lib), `agit-ai` (lib), `agit-cli` (bin). Root `Cargo.toml` is workspace-only. +- **Dual edition**: Lite = `--no-default-features -F tag` (no TLS, no AI). Full = `--all-features`. +- **Feature propagation**: `agit-cli/tag → agit-core/tag`, `agit-cli/tls → agit-core/tls`, `agit-cli/ai → agit-ai`. +- **Global state**: `AI_MODE`, `JSON_MODE`, `YAML_MODE`, `NO_COLOR` are `AtomicBool` statics in `agit-cli`. +- **Error handling**: Commands return `Box`. Core uses `AgitError` enum. +- **Config cascade**: env vars > repo `.agit/config.toml` > global `~/.agitconfig.toml` > defaults. +- **Commit flow**: `Index::load()` → build `Tree` → write tree → build `Commit` (+parent) → write commit → update ref. +- **Network flow**: Clone = discover_refs → fetch_objects → parse_packfile → write_objects → checkout. ## Rules @@ -61,21 +108,21 @@ utils/error.rs → AgitError enum ### Commit -3. **Single Logical Change** — every commit must be one atomic, self-contained change. No "Fix stuff" or "Update code". -4. **Multi-commit PR is OK** — splitting across commits is encouraged (e.g. `refactor:` → `feat:` → `test:`), but never squash unrelated changes into one. -4a. **Commit per logical unit** — 每个 commit 只包含一个逻辑单元(如一个模块、一个命令、一个 CLI 注册)。不要把多个不相关的改动堆在一坨提交。多个文件同时修改时,按依赖顺序分批提交。 -4b. **Format per commit** — 每个 commit 前单独运行 `cargo fmt`,确保 fmt 结果归属于当前提交。不要等所有改动写完再一起 fmt。 -5. **Conventional Commits**: `feat:`, `fix:`, `docs:`, `style:`, `refactor:`, `test:`, `chore:`. Scope optional: `feat(core): ...`. -5a. **No `@` in commit messages** — 提交信息前后禁止添加 `@` 符号。使用标准 Conventional Commits 格式,不加前缀或后缀的 `@`。 +3. **Single Logical Change** — every commit must be one atomic, self-contained change. +4. **Multi-commit PR is OK** — splitting across commits is encouraged (e.g. `refactor:` → `feat:` → `test:`). +4a. **Commit per logical unit** — 每个 commit 只包含一个逻辑单元。 +4b. **Format per commit** — 每个 commit 前单独运行 `cargo fmt`。 +5. **Conventional Commits**: `feat:`, `fix:`, `docs:`, `style:`, `refactor:`, `test:`, `chore:`. +5a. **No `@` in commit messages**. ### Quality Gate -6. **Tests required** — new features must have unit + integration tests. `cargo test` must pass with 0 failures before push. +6. **Tests required** — `cargo test` must pass with 0 failures before push. 7. **Clippy clean** — `cargo clippy` must produce 0 warnings before push. -8. **Formatted** — `cargo fmt` must produce no diff before push; 在最终 push 前再做一次全量检查。 +8. **Formatted** — `cargo fmt` must produce no diff before push. ### Code -9. **No silent error swallowing** — use `?` or explicit `map_err` for IO. Never `unwrap_or_default()` on file reads. +9. **No silent error swallowing** — use `?` or explicit `map_err` for IO. 10. **Feature gate tag** — `#[cfg(feature = "tag")]` code must compile with both `--features tag` and `--no-default-features`. 11. **Windows compatibility** — use `std::fs` APIs, normalize paths with `replace('\\', '/')`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fc44571..4c78180 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,8 +49,10 @@ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh **构建项目**: ```bash -cargo build -cargo build --release # Release 构建 +cargo build # Debug 构建 +cargo build --release # Release 构建 +cargo build --release --all-features # Full 版本(含 AI) +cargo build --release --no-default-features -F tag # Lite 版本 ``` #### 4. 运行测试 @@ -124,16 +126,29 @@ git push origin feat/add-clone-command ## 项目结构 ``` -src/ -├── core/ # 核心 Git 算法 -├── cli/ # 命令行 -├── ai/ # AI 模式 -├── output/ # 输出格式化 -├── config/ # 配置 -└── utils/ # 工具函数 +agit/ # Workspace 根目录 +├── Cargo.toml # Workspace 定义 +├── agit-core/ # Rust 原生 Git 核心库 +│ └── src/ +│ ├── objects/ # Blob, Tree, Commit, Tag +│ ├── storage.rs # Loose 对象读写 +│ ├── refs.rs # 引用管理(HEAD, 分支, 标签) +│ ├── index.rs # DIRC v2 暂存区 +│ ├── protocol.rs # Git smart-HTTP 协议 +│ ├── merge.rs # 3 路合并 +│ └── checkout.rs # 分支切换 / 树恢复 +├── agit-ai/ # AI 模式(可选,feature 门控) +│ └── src/ +│ └── lib.rs # AI 自动标记、安全防护 +├── agit-cli/ # CLI 二进制入口 +│ └── src/ +│ ├── main.rs # 入口点 +│ ├── commands/ # 每个子命令一个文件 +│ └── output/ # JSON / YAML / 无颜色输出 +└── tests/ # 集成测试 ``` -详见: [架构设计](docs/ARCHITECTURE.md) +详见: [架构设计](ARCHITECTURE.md) ## 开发阶段 diff --git a/Cargo.lock b/Cargo.lock index 11b67d5..053edca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,16 +9,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] -name = "agit" -version = "0.10.0" +name = "agit-ai" +version = "0.14.0" dependencies = [ - "anyhow", + "agit-core", + "reqwest", + "serde", +] + +[[package]] +name = "agit-cli" +version = "0.14.0" +dependencies = [ + "agit-ai", + "agit-core", "clap", - "flate2", - "native-tls", "serde", "serde_json", "serde_yaml", + "toml", +] + +[[package]] +name = "agit-core" +version = "0.14.0" +dependencies = [ + "anyhow", + "flate2", + "native-tls", + "serde", "sha1", "toml", "url", @@ -60,7 +79,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -71,7 +90,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -80,11 +99,23 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" -version = "2.11.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "block-buffer" @@ -95,11 +126,23 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytes" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" + [[package]] name = "cc" -version = "1.2.62" +version = "1.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ "find-msvc-tools", "shlex", @@ -111,6 +154,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "clap" version = "4.6.1" @@ -157,6 +206,16 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -213,15 +272,24 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -235,7 +303,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -261,10 +329,10 @@ dependencies = [ ] [[package]] -name = "foldhash" -version = "0.1.5" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foreign-types" @@ -290,6 +358,55 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -302,24 +419,59 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.2" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", - "wasip3", + "wasm-bindgen", ] [[package]] -name = "hashbrown" -version = "0.15.5" +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", +] + +[[package]] +name = "h2" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" dependencies = [ - "foldhash", + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", ] [[package]] @@ -334,6 +486,123 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "http" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + [[package]] name = "icu_collections" version = "2.2.0" @@ -416,12 +685,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "idna" version = "1.1.0" @@ -450,11 +713,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.1", - "serde", - "serde_core", + "hashbrown", ] +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -468,10 +735,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] -name = "leb128fmt" -version = "0.1.0" +name = "js-sys" +version = "0.3.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] [[package]] name = "libc" @@ -493,15 +765,27 @@ checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "log" -version = "0.4.29" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "lru-slab" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "mime" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" @@ -513,6 +797,17 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "mio" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + [[package]] name = "native-tls" version = "0.2.18" @@ -544,9 +839,9 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "openssl" -version = "0.10.80" +version = "0.10.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" dependencies = [ "bitflags", "cfg-if", @@ -575,9 +870,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.116" +version = "0.9.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" dependencies = [ "cc", "libc", @@ -591,6 +886,12 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + [[package]] name = "pkg-config" version = "0.3.33" @@ -607,13 +908,12 @@ dependencies = [ ] [[package]] -name = "prettyplease" -version = "0.2.37" +name = "ppv-lite86" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "proc-macro2", - "syn", + "zerocopy", ] [[package]] @@ -625,6 +925,61 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.45" @@ -634,12 +989,113 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "r-efi" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + [[package]] name = "rustix" version = "1.1.4" @@ -650,9 +1106,50 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "ryu" version = "1.0.23" @@ -665,7 +1162,7 @@ version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -675,7 +1172,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -691,12 +1188,6 @@ dependencies = [ "libc", ] -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" - [[package]] name = "serde" version = "1.0.228" @@ -729,9 +1220,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -750,21 +1241,33 @@ dependencies = [ ] [[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" +name = "serde_urlencoded" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ - "indexmap", + "form_urlencoded", "itoa", "ryu", "serde", - "unsafe-libyaml", ] [[package]] -name = "sha1" -version = "0.10.6" +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha1" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ @@ -775,9 +1278,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "simd-adler32" @@ -785,11 +1288,27 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] [[package]] name = "stable_deref_trait" @@ -803,17 +1322,32 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -825,6 +1359,27 @@ dependencies = [ "syn", ] +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempfile" version = "3.27.0" @@ -832,10 +1387,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom", + "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -848,6 +1423,68 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.8.23" @@ -889,11 +1526,81 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typenum" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicode-ident" @@ -901,18 +1608,18 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "unsafe-libyaml" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.8" @@ -949,56 +1656,112 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "wit-bindgen 0.57.1", + "wit-bindgen", ] [[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +name = "wasm-bindgen" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" dependencies = [ - "wit-bindgen 0.51.0", + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] -name = "wasm-encoder" -version = "0.244.0" +name = "wasm-bindgen-futures" +version = "0.4.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +checksum = "503b14d284f2c8dac03b819967e155ea753f573586193b2b2c95990cb5d69280" dependencies = [ - "leb128fmt", - "wasmparser", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "wasm-metadata" -version = "0.244.0" +name = "wasm-bindgen-macro" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", + "quote", + "wasm-bindgen-macro-support", ] [[package]] -name = "wasmparser" -version = "0.244.0" +name = "wasm-bindgen-macro-support" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" dependencies = [ - "bitflags", - "hashbrown 0.15.5", - "indexmap", - "semver", + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6430a72df5eb332242960fe84b3002a241163998241eb596d4f739b9757061d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf85cb06032201fa7c6f829d7db5a7e5aa45bcc0655327713065f6f0576731bf" +dependencies = [ + "rustls-pki-types", ] [[package]] @@ -1008,117 +1771,205 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "windows-sys" -version = "0.61.2" +name = "windows-registry" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ "windows-link", + "windows-result", + "windows-strings", ] [[package]] -name = "winnow" -version = "0.7.15" +name = "windows-result" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "memchr", + "windows-link", ] [[package]] -name = "wit-bindgen" -version = "0.51.0" +name = "windows-strings" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "wit-bindgen-rust-macro", + "windows-link", ] [[package]] -name = "wit-bindgen" -version = "0.57.1" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] [[package]] -name = "wit-bindgen-core" -version = "0.51.0" +name = "windows-sys" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "anyhow", - "heck", - "wit-parser", + "windows-targets 0.53.5", ] [[package]] -name = "wit-bindgen-rust" -version = "0.51.0" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", + "windows-link", ] [[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" +name = "windows-targets" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] -name = "wit-component" -version = "0.244.0" +name = "windows-targets" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "anyhow", - "bitflags", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] -name = "wit-parser" -version = "0.244.0" +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", + "memchr", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "writeable" version = "0.6.3" @@ -1127,9 +1978,9 @@ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "yoke" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -1148,6 +1999,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerofrom" version = "0.1.8" @@ -1169,6 +2040,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + [[package]] name = "zerotrie" version = "0.2.4" diff --git a/Cargo.toml b/Cargo.toml index 27a8830..8ddbba9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,31 +1,6 @@ -[package] -name = "agit" -version = "0.13.1" -edition = "2021" -description = "AI-native Git tool - pure Rust implementation" -license = "Apache-2.0" -repository = "https://github.com/bit-torch/AdapterGit" - -[dependencies] -clap = { version = "4", features = ["derive"] } -serde = { version = "1", features = ["derive"] } -serde_json = "1" -serde_yaml = "0.9" -flate2 = "1" -sha1 = "0.10" -anyhow = "1" -url = "2" -native-tls = { version = "0.2", optional = true } -toml = "0.8" -reqwest = { version = "0.12", features = ["json", "rustls-tls", "blocking"], optional = true } - -[features] -default = ["tag", "tls"] -tag = [] -tls = ["native-tls"] -ai = ["reqwest"] -# lite 版: cargo build --no-default-features -F tag (纯本地操作, 无 TLS) -# full 版: cargo build --all-features (全功能, 含 TLS + AI) +[workspace] +members = ["agit-core", "agit-ai", "agit-cli"] +resolver = "2" [profile.release] opt-level = "z" diff --git a/README-zh_CN.md b/README-zh_CN.md index 8b5cead..8f1db44 100644 --- a/README-zh_CN.md +++ b/README-zh_CN.md @@ -85,13 +85,22 @@ sudo dpkg -i agit_0.1.0_amd64.deb # 安装 Rust curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -# 构建 agit +# 克隆并进入仓库 git clone https://github.com/bit-torch/AdapterGit.git cd agit -cargo build --release -# 静态编译(推荐) -cargo build --release --target x86_64-unknown-linux-musl +# Full 版本(完整功能,含 TLS + AI) +cargo build --release --all-features + +# Lite 版本(不含 TLS 和 AI) +cargo build --release --no-default-features -F tag + +# 构建指定 crate +cargo build -p agit-core +cargo build -p agit-cli + +# 静态编译(推荐用于部署) +cargo build --release --target x86_64-unknown-linux-musl --all-features ``` ## 📖 使用示例 @@ -375,15 +384,26 @@ agit 使用 Conventional Commits: ### 项目结构 ``` -agit/ -├── src/ -│ ├── cli/ # 命令行解析 -│ ├── git/ # Git 核心功能 -│ ├── ai/ # AI 模式实现 -│ ├── output/ # 输出格式化 -│ └── utils/ # 工具函数 -├── tests/ # 集成测试 -└── examples/ # 使用示例 +agit/ # Workspace 根目录 +├── Cargo.toml # Workspace 定义 +├── agit-core/ # Rust 原生 Git 核心库 +│ └── src/ +│ ├── objects/ # Blob, Tree, Commit, Tag +│ ├── storage.rs # Loose 对象读写 +│ ├── refs.rs # 引用管理(HEAD, 分支, 标签) +│ ├── index.rs # DIRC v2 暂存区 +│ ├── protocol.rs # Git smart-HTTP 协议 +│ ├── merge.rs # 3 路合并 +│ └── checkout.rs # 分支切换 / 树恢复 +├── agit-ai/ # AI 模式(可选,feature 门控) +│ └── src/ +│ └── lib.rs # AI 自动标记、安全防护 +├── agit-cli/ # CLI 二进制入口 +│ └── src/ +│ ├── main.rs # 入口点 +│ ├── commands/ # 每个子命令一个文件 +│ └── output/ # JSON / YAML / 无颜色输出 +└── tests/ # 集成测试 ``` ## 📄 许可证 diff --git a/README.md b/README.md index 464b0ce..b3b7c57 100644 --- a/README.md +++ b/README.md @@ -84,13 +84,22 @@ sudo dpkg -i agit_0.1.0_amd64.deb # Install Rust curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -# Build agit +# Clone and enter the repo git clone https://github.com/bit-torch/AdapterGit.git cd agit -cargo build --release -# Static build (recommended) -cargo build --release --target x86_64-unknown-linux-musl +# Full edition (TLS + AI) +cargo build --release --all-features + +# Lite edition (no TLS, no AI) +cargo build --release --no-default-features -F tag + +# Build specific crate +cargo build -p agit-core +cargo build -p agit-cli + +# Static build (recommended for deployment) +cargo build --release --target x86_64-unknown-linux-musl --all-features ``` ## Usage Examples @@ -389,15 +398,26 @@ agit follows [Conventional Commits](https://www.conventionalcommits.org/): ### Project Structure ``` -agit/ -├── src/ -│ ├── cli/ # Command-line parsing -│ ├── git/ # Git core functionality -│ ├── ai/ # AI mode implementation -│ ├── output/ # Output formatting -│ └── utils/ # Utility functions -├── tests/ # Integration tests -└── examples/ # Usage examples +agit/ # Workspace root +├── Cargo.toml # Workspace definition +├── agit-core/ # Pure-Rust Git core library +│ └── src/ +│ ├── objects/ # Blob, Tree, Commit, Tag +│ ├── storage.rs # Loose object R/W +│ ├── refs.rs # References (HEAD, branches, tags) +│ ├── index.rs # DIRC v2 staging area +│ ├── protocol.rs # Git smart-HTTP +│ ├── merge.rs # 3-way merge +│ └── checkout.rs # Branch switch / tree restore +├── agit-ai/ # AI mode (optional, feature-gated) +│ └── src/ +│ └── lib.rs # AI auto-tagging, safety guards +├── agit-cli/ # CLI binary +│ └── src/ +│ ├── main.rs # Entry point +│ ├── commands/ # One file per subcommand +│ └── output/ # JSON / YAML / no-color output +└── tests/ # Integration tests ``` ## License diff --git a/TODO.md b/TODO.md index 12883f7..b5a6c3c 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,18 @@ # TODO - 待办事项 -AdapterGit 项目待办事项清单。最后更新: 2026-06-20 (v0.10.0 开发中)。 +AdapterGit 项目待办事项清单。最后更新: 2026-06-22 (v0.14.0 workspace 拆分完成)。 + +## Phase 10: Workspace 拆分 ✅ + +| ID | 任务 | 优先级 | 状态 | 备注 | +|----|------|--------|------|------| +| 10.1 | 清理 bundle 文件 + .gitignore | P0 | ✅ 已完成 | 删除 2 个 bundle, 添加 *.bundle | +| 10.2 | 创建 workspace 根 Cargo.toml | P0 | ✅ 已完成 | 3 crate workspace | +| 10.3 | 创建 agit-core 库 crate | P0 | ✅ 已完成 | core/ + config/ + utils/, 扁平化 | +| 10.4 | 创建 agit-ai 库 crate | P0 | ✅ 已完成 | ai/llm.rs 独立为 crate | +| 10.5 | 创建 agit-cli 二进制 crate | P0 | ✅ 已完成 | cli/ + commands/ + output/ + ai/ | +| 10.6 | 迁移集成测试 | P0 | ✅ 已完成 | tests/ → agit-cli/tests/ | +| 10.7 | 更新所有文档 | P0 | ✅ 已完成 | CLAUDE.md, README, ARCHITECTURE, CHANGELOG | ## Phase 1: 项目初始化 ✅ diff --git a/agit-ai/Cargo.toml b/agit-ai/Cargo.toml new file mode 100644 index 0000000..dd2c475 --- /dev/null +++ b/agit-ai/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "agit-ai" +version = "0.14.0" +edition = "2021" +description = "AI-powered commit message generation for agit" +license = "Apache-2.0" +repository = "https://github.com/bit-torch/AdapterGit" + +[dependencies] +agit-core = { path = "../agit-core" } +serde = { version = "1", features = ["derive"] } +reqwest = { version = "0.12", features = ["json", "rustls-tls", "blocking"] } diff --git a/src/ai/llm.rs b/agit-ai/src/lib.rs similarity index 98% rename from src/ai/llm.rs rename to agit-ai/src/lib.rs index f8c58eb..bcdb295 100644 --- a/src/ai/llm.rs +++ b/agit-ai/src/lib.rs @@ -1,4 +1,4 @@ -//! LLM API 调用模块(需 `ai` feature)。 +//! agit-ai — AI-powered commit message generation for agit. //! //! 支持 OpenAI-compatible API,用于自动生成 commit message。 //! @@ -20,7 +20,7 @@ //! //! `AGIT_LLM_API_URL` 环境变量可直接覆盖 API 端点(优先级最高)。 -use crate::config; +use agit_core::config; use serde::{Deserialize, Serialize}; /// LLM API 运行时配置(已解析)。 diff --git a/agit-cli/Cargo.toml b/agit-cli/Cargo.toml new file mode 100644 index 0000000..ec5fcd9 --- /dev/null +++ b/agit-cli/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "agit-cli" +version = "0.14.0" +edition = "2021" +description = "AI-native Git tool — CLI binary" +license = "Apache-2.0" +repository = "https://github.com/bit-torch/AdapterGit" + +[[bin]] +name = "agit" +path = "src/main.rs" + +[dependencies] +agit-core = { path = "../agit-core" } +agit-ai = { path = "../agit-ai", optional = true } +clap = { version = "4", features = ["derive"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde_yaml = "0.9" +toml = "0.8" + +[features] +default = ["tag", "tls"] +tag = ["agit-core/tag"] +tls = ["agit-core/tls"] +ai = ["agit-ai"] diff --git a/src/ai/mod.rs b/agit-cli/src/ai/mod.rs similarity index 98% rename from src/ai/mod.rs rename to agit-cli/src/ai/mod.rs index d0cf522..042ae34 100644 --- a/src/ai/mod.rs +++ b/agit-cli/src/ai/mod.rs @@ -1,5 +1,5 @@ #[cfg(feature = "ai")] -pub mod llm; +pub use agit_ai as llm; use std::sync::atomic::{AtomicBool, Ordering}; diff --git a/src/cli/mod.rs b/agit-cli/src/cli/mod.rs similarity index 100% rename from src/cli/mod.rs rename to agit-cli/src/cli/mod.rs diff --git a/src/commands/add.rs b/agit-cli/src/commands/add.rs similarity index 94% rename from src/commands/add.rs rename to agit-cli/src/commands/add.rs index 8e04057..1967074 100644 --- a/src/commands/add.rs +++ b/agit-cli/src/commands/add.rs @@ -1,8 +1,8 @@ -use crate::core::ignore::IgnoreMatcher; -use crate::core::index::Index; -use crate::core::objects::blob::Blob; -use crate::core::repo; -use crate::core::storage; +use agit_core::ignore::IgnoreMatcher; +use agit_core::index::Index; +use agit_core::objects::blob::Blob; +use agit_core::repo; +use agit_core::storage; use std::fs; use std::path::Path; diff --git a/src/commands/bisect.rs b/agit-cli/src/commands/bisect.rs similarity index 93% rename from src/commands/bisect.rs rename to agit-cli/src/commands/bisect.rs index afb277d..a8d8c28 100644 --- a/src/commands/bisect.rs +++ b/agit-cli/src/commands/bisect.rs @@ -11,9 +11,9 @@ //! agit bisect run ... //! ``` -use crate::core::bisect::{self, BisectState}; -use crate::core::checkout; -use crate::core::repo; +use agit_core::bisect::{self, BisectState}; +use agit_core::checkout; +use agit_core::repo; /// bisect 子命令类型(内部使用,与 CLI 枚举独立)。 pub enum BisectSubCmd<'a> { @@ -64,14 +64,14 @@ fn bisect_start( } // 保存原始 HEAD - let original_head = crate::core::refs::read_head(repo)?; + let original_head = agit_core::refs::read_head(repo)?; // 解析 bad 提交 let bad_sha = match bad { Some(rev) => repo::resolve_commit(repo, rev)?, None => { // 默认使用 HEAD - crate::core::refs::read_head(repo)? + agit_core::refs::read_head(repo)? } }; @@ -134,7 +134,7 @@ fn bisect_good( let sha = match rev { Some(r) => repo::resolve_commit(repo, r)?, - None => crate::core::refs::read_head(repo)?, + None => agit_core::refs::read_head(repo)?, }; // 将 good SHA 加入列表 @@ -152,7 +152,7 @@ fn bisect_bad(repo: &std::path::Path, rev: Option<&str>) -> Result<(), Box repo::resolve_commit(repo, r)?, - None => crate::core::refs::read_head(repo)?, + None => agit_core::refs::read_head(repo)?, }; // 更新 bad 为新的 bad 提交 @@ -171,7 +171,7 @@ fn bisect_skip( let sha = match rev { Some(r) => repo::resolve_commit(repo, r)?, - None => crate::core::refs::read_head(repo)?, + None => agit_core::refs::read_head(repo)?, }; if !state.skip.contains(&sha) { @@ -235,16 +235,16 @@ fn bisect_reset(repo: &std::path::Path) -> Result<(), Box && state.original_head.chars().all(|c| c.is_ascii_hexdigit()) { // 分离 HEAD - crate::core::refs::write_head(repo, &state.original_head)?; + agit_core::refs::write_head(repo, &state.original_head)?; } else if let Some(branch) = state.original_head.strip_prefix("ref: refs/heads/") { // 符号引用 - crate::core::refs::write_head(repo, &format!("ref: refs/heads/{}", branch))?; + agit_core::refs::write_head(repo, &format!("ref: refs/heads/{}", branch))?; } else { - crate::core::refs::write_head(repo, &state.original_head)?; + agit_core::refs::write_head(repo, &state.original_head)?; } // 恢复工作树 - let head_sha = crate::core::refs::read_head(repo)?; + let head_sha = agit_core::refs::read_head(repo)?; checkout::restore_from_commit(repo, &head_sha)?; } @@ -352,6 +352,6 @@ fn write_head_detached( repo: &std::path::Path, sha: &str, ) -> Result<(), Box> { - crate::core::refs::write_head(repo, sha)?; + agit_core::refs::write_head(repo, sha)?; Ok(()) } diff --git a/src/commands/blame.rs b/agit-cli/src/commands/blame.rs similarity index 98% rename from src/commands/blame.rs rename to agit-cli/src/commands/blame.rs index 4f7aaef..08f10b6 100644 --- a/src/commands/blame.rs +++ b/agit-cli/src/commands/blame.rs @@ -9,9 +9,9 @@ //! 4. 将变更行归因到对应提交 //! 5. 直到所有行都被归因或到达根提交 -use crate::core::objects::commit::Commit; -use crate::core::objects::format_object_data; -use crate::core::{refs, repo, storage}; +use agit_core::objects::commit::Commit; +use agit_core::objects::format_object_data; +use agit_core::{refs, repo, storage}; use std::collections::HashMap; use std::path::Path; @@ -200,7 +200,7 @@ fn find_in_tree( } let tree_data = format_object_data("tree", &content); - let tree = crate::core::objects::tree::Tree::deserialize(&tree_data)?; + let tree = agit_core::objects::tree::Tree::deserialize(&tree_data)?; // 处理多级路径 let parts: Vec<&str> = file_path.split('/').collect(); diff --git a/src/commands/branch.rs b/agit-cli/src/commands/branch.rs similarity index 98% rename from src/commands/branch.rs rename to agit-cli/src/commands/branch.rs index db00354..6f7f32d 100644 --- a/src/commands/branch.rs +++ b/agit-cli/src/commands/branch.rs @@ -1,4 +1,4 @@ -use crate::core::{refs, repo}; +use agit_core::{refs, repo}; pub fn run( _list: bool, diff --git a/src/commands/cat_file.rs b/agit-cli/src/commands/cat_file.rs similarity index 87% rename from src/commands/cat_file.rs rename to agit-cli/src/commands/cat_file.rs index c7e88cd..a8bc2fd 100644 --- a/src/commands/cat_file.rs +++ b/agit-cli/src/commands/cat_file.rs @@ -1,6 +1,6 @@ -use crate::core::objects::tree::Tree; -use crate::core::repo; -use crate::core::storage; +use agit_core::objects::tree::Tree; +use agit_core::repo; +use agit_core::storage; pub fn run( object: &str, @@ -21,7 +21,7 @@ pub fn run( print!("{}", String::from_utf8_lossy(&content)); } "tree" => { - let tree_data = crate::core::objects::format_object_data("tree", &content); + let tree_data = agit_core::objects::format_object_data("tree", &content); let tree = Tree::deserialize(&tree_data)?; for entry in &tree.entries { let type_str = if entry.mode == "40000" { diff --git a/src/commands/checkout.rs b/agit-cli/src/commands/checkout.rs similarity index 93% rename from src/commands/checkout.rs rename to agit-cli/src/commands/checkout.rs index f258504..c65e7e6 100644 --- a/src/commands/checkout.rs +++ b/agit-cli/src/commands/checkout.rs @@ -1,9 +1,9 @@ -use crate::core::index::Index; -use crate::core::{checkout, reflog, refs, repo}; +use agit_core::index::Index; +use agit_core::{checkout, reflog, refs, repo}; pub fn run(branch: &str, force: bool) -> Result<(), Box> { let repo_root = repo::find_repo_root()?; - let cfg = crate::config::load(); + let cfg = agit_core::config::load(); // 检查分支是否存在 let ref_path = format!("refs/heads/{}", branch); diff --git a/src/commands/cherry_pick.rs b/agit-cli/src/commands/cherry_pick.rs similarity index 93% rename from src/commands/cherry_pick.rs rename to agit-cli/src/commands/cherry_pick.rs index 3e00334..f1c27f5 100644 --- a/src/commands/cherry_pick.rs +++ b/agit-cli/src/commands/cherry_pick.rs @@ -1,6 +1,6 @@ -use crate::core::index::Index; -use crate::core::objects::commit::Commit; -use crate::core::{checkout, rebase as core_rebase, refs, repo, storage}; +use agit_core::index::Index; +use agit_core::objects::commit::Commit; +use agit_core::{checkout, rebase as core_rebase, refs, repo, storage}; use std::fs; use std::path::Path; @@ -74,7 +74,7 @@ fn apply_cherry_todo(repo: &Path) -> Result<(), Box> { }; let (_, body) = storage::read_object(repo, &commit_sha)?; - let commit_data = crate::core::objects::format_object_data("commit", &body); + let commit_data = agit_core::objects::format_object_data("commit", &body); let commit = Commit::deserialize(&commit_data)?; let parent_sha = commit.parents.first().cloned(); @@ -115,10 +115,10 @@ fn run_continue(repo: &Path) -> Result<(), Box> { let commit_sha = fs::read_to_string(&cherry_head_path)?.trim().to_string(); let (_, body) = storage::read_object(repo, &commit_sha)?; - let commit_data = crate::core::objects::format_object_data("commit", &body); + let commit_data = agit_core::objects::format_object_data("commit", &body); let original = Commit::deserialize(&commit_data)?; - let config = crate::config::load(); + let config = agit_core::config::load(); let (timestamp, time_str) = repo::get_current_timestamp(); let head_sha = refs::read_head(repo)?; core_rebase::create_commit_from_index( diff --git a/src/commands/clone.rs b/agit-cli/src/commands/clone.rs similarity index 84% rename from src/commands/clone.rs rename to agit-cli/src/commands/clone.rs index bc79824..8b681e6 100644 --- a/src/commands/clone.rs +++ b/agit-cli/src/commands/clone.rs @@ -1,9 +1,9 @@ -use crate::core::index::Index; -use crate::core::protocol::create_transport; -use crate::core::refs; -use crate::core::remote_utils; -use crate::core::repo; -use crate::core::storage; +use agit_core::index::Index; +use agit_core::protocol::create_transport; +use agit_core::refs; +use agit_core::remote_utils; +use agit_core::repo; +use agit_core::storage; use std::fs; use std::io::Write; use std::path::Path; @@ -76,10 +76,10 @@ pub fn run(url: &str) -> Result<(), Box> { // Build index from the checked-out tree let (_, tree_content) = storage::read_object( &repo_dir, - &crate::core::remote_utils::resolve_commit_to_tree(&repo_dir, &head_ref)?, + &agit_core::remote_utils::resolve_commit_to_tree(&repo_dir, &head_ref)?, )?; - let tree_data = crate::core::objects::format_object_data("tree", &tree_content); - let tree = crate::core::objects::tree::Tree::deserialize(&tree_data)?; + let tree_data = agit_core::objects::format_object_data("tree", &tree_content); + let tree = agit_core::objects::tree::Tree::deserialize(&tree_data)?; build_index_from_tree(&repo_dir, &tree, "")?; println!( @@ -150,12 +150,12 @@ fn checkout_head(repo: &Path, head_sha1: &str) -> Result<(), Box Result<(), Box Result<(), Box> { let mut index = Index::load(repo)?; @@ -175,7 +175,7 @@ fn build_index_from_tree( fn add_tree_to_index( repo: &Path, - tree: &crate::core::objects::tree::Tree, + tree: &agit_core::objects::tree::Tree, prefix: &str, index: &mut Index, ) -> Result<(), Box> { @@ -188,8 +188,8 @@ fn add_tree_to_index( if entry.mode == "40000" { let (_, sub_content) = storage::read_object(repo, &entry.sha1)?; - let sub_data = crate::core::objects::format_object_data("tree", &sub_content); - let sub_tree = crate::core::objects::tree::Tree::deserialize(&sub_data)?; + let sub_data = agit_core::objects::format_object_data("tree", &sub_content); + let sub_tree = agit_core::objects::tree::Tree::deserialize(&sub_data)?; add_tree_to_index(repo, &sub_tree, &path, index)?; } else { index.add_entry(&entry.mode, &entry.sha1, &path); diff --git a/src/commands/commit.rs b/agit-cli/src/commands/commit.rs similarity index 96% rename from src/commands/commit.rs rename to agit-cli/src/commands/commit.rs index 7117d4d..ab8ea13 100644 --- a/src/commands/commit.rs +++ b/agit-cli/src/commands/commit.rs @@ -1,14 +1,14 @@ use crate::ai; -use crate::config; -use crate::core::index::Index; +use agit_core::config; +use agit_core::index::Index; #[cfg(feature = "ai")] -use crate::core::objects::blob::Blob; -use crate::core::objects::commit::Commit; -use crate::core::objects::tree::Tree; -use crate::core::reflog; -use crate::core::refs; -use crate::core::repo; -use crate::core::storage; +use agit_core::objects::blob::Blob; +use agit_core::objects::commit::Commit; +use agit_core::objects::tree::Tree; +use agit_core::reflog; +use agit_core::refs; +use agit_core::repo; +use agit_core::storage; pub fn run(message: Option, ai_flag: bool) -> Result<(), Box> { let repo_root = repo::find_repo_root()?; @@ -175,7 +175,7 @@ pub fn run(message: Option, ai_flag: bool) -> Result<(), Box String { let summary = build_staged_summary(repo_root, index); - let cfg = crate::config::load(); + let cfg = agit_core::config::load(); if let Some(llm_cfg) = ai::llm::LlmConfig::from_config(&cfg) { let provider = cfg.llm.provider.as_deref().unwrap_or("openai"); println!( diff --git a/src/commands/config_cmd.rs b/agit-cli/src/commands/config_cmd.rs similarity index 99% rename from src/commands/config_cmd.rs rename to agit-cli/src/commands/config_cmd.rs index 04c7007..57f5007 100644 --- a/src/commands/config_cmd.rs +++ b/agit-cli/src/commands/config_cmd.rs @@ -1,4 +1,4 @@ -use crate::core::repo; +use agit_core::repo; use std::fs; use std::path::PathBuf; use toml::map::Map; diff --git a/src/commands/diff.rs b/agit-cli/src/commands/diff.rs similarity index 95% rename from src/commands/diff.rs rename to agit-cli/src/commands/diff.rs index fb0de4c..064ec9d 100644 --- a/src/commands/diff.rs +++ b/agit-cli/src/commands/diff.rs @@ -1,9 +1,9 @@ -use crate::core::index::Index; -use crate::core::objects::commit::Commit; -use crate::core::objects::tree::Tree; -use crate::core::refs; -use crate::core::repo; -use crate::core::storage; +use agit_core::index::Index; +use agit_core::objects::commit::Commit; +use agit_core::objects::tree::Tree; +use agit_core::refs; +use agit_core::repo; +use agit_core::storage; use std::collections::BTreeMap; use std::fs; use std::path::Path; @@ -160,7 +160,7 @@ fn get_parent(repo: &Path, sha: &str) -> Result Result Result> { let (_, content) = storage::read_object(repo, commit_sha)?; - let commit_data = crate::core::objects::format_object_data("commit", &content); + let commit_data = agit_core::objects::format_object_data("commit", &content); let commit = Commit::deserialize(&commit_data)?; Ok(commit.tree) } @@ -294,7 +294,7 @@ fn print_tree_vs_working( fn show_untracked_diff(repo: &Path, index: &Index) -> Result<(), Box> { let mut untracked = Vec::new(); - let matcher = crate::core::ignore::IgnoreMatcher::load(repo, Path::new("")); + let matcher = agit_core::ignore::IgnoreMatcher::load(repo, Path::new("")); collect_untracked(repo, repo, index, &matcher, &mut untracked)?; if untracked.is_empty() { return Ok(()); @@ -314,9 +314,9 @@ fn build_head_tree_map(repo: &Path, head_sha1: &str) -> BTreeMap let mut result = BTreeMap::new(); if let Ok((obj_type, content)) = storage::read_object(repo, head_sha1) { if obj_type == "commit" { - if let Ok(commit) = Commit::deserialize(&crate::core::objects::format_object_data( - "commit", &content, - )) { + if let Ok(commit) = + Commit::deserialize(&agit_core::objects::format_object_data("commit", &content)) + { let _ = collect_tree_recursive(repo, &commit.tree, "", &mut result); } } @@ -335,7 +335,7 @@ fn collect_tree_recursive( if obj_type != "tree" { return Ok(()); } - let tree_data = crate::core::objects::format_object_data("tree", &content); + let tree_data = agit_core::objects::format_object_data("tree", &content); let tree = Tree::deserialize(&tree_data)?; for entry in &tree.entries { let path = if prefix.is_empty() { @@ -469,7 +469,7 @@ fn collect_untracked( repo: &Path, current: &Path, index: &Index, - matcher: &crate::core::ignore::IgnoreMatcher, + matcher: &agit_core::ignore::IgnoreMatcher, untracked: &mut Vec, ) -> Result<(), Box> { if !current.exists() { diff --git a/src/commands/fetch.rs b/agit-cli/src/commands/fetch.rs similarity index 94% rename from src/commands/fetch.rs rename to agit-cli/src/commands/fetch.rs index 11990e2..4215534 100644 --- a/src/commands/fetch.rs +++ b/agit-cli/src/commands/fetch.rs @@ -1,7 +1,7 @@ -use crate::core::protocol::create_transport; -use crate::core::refs; -use crate::core::remote_utils; -use crate::core::repo; +use agit_core::protocol::create_transport; +use agit_core::refs; +use agit_core::remote_utils; +use agit_core::repo; pub fn run(url: Option<&str>) -> Result<(), Box> { let repo_root = repo::find_repo_root()?; diff --git a/src/commands/init.rs b/agit-cli/src/commands/init.rs similarity index 99% rename from src/commands/init.rs rename to agit-cli/src/commands/init.rs index a8f72e0..66ac328 100644 --- a/src/commands/init.rs +++ b/agit-cli/src/commands/init.rs @@ -1,4 +1,4 @@ -use crate::core::repo; +use agit_core::repo; use std::fs; use std::path::PathBuf; diff --git a/src/commands/log.rs b/agit-cli/src/commands/log.rs similarity index 95% rename from src/commands/log.rs rename to agit-cli/src/commands/log.rs index c009362..0153062 100644 --- a/src/commands/log.rs +++ b/agit-cli/src/commands/log.rs @@ -1,7 +1,7 @@ -use crate::core::objects::commit::Commit; -use crate::core::refs; -use crate::core::repo; -use crate::core::storage; +use agit_core::objects::commit::Commit; +use agit_core::refs; +use agit_core::repo; +use agit_core::storage; pub fn run( oneline: bool, @@ -58,7 +58,7 @@ pub fn run( break; } - let commit_data = crate::core::objects::format_object_data("commit", &content); + let commit_data = agit_core::objects::format_object_data("commit", &content); let commit = match Commit::deserialize(&commit_data) { Ok(c) => c, Err(_) => break, diff --git a/src/commands/ls_tree.rs b/agit-cli/src/commands/ls_tree.rs similarity index 78% rename from src/commands/ls_tree.rs rename to agit-cli/src/commands/ls_tree.rs index 5d1ae99..26dea79 100644 --- a/src/commands/ls_tree.rs +++ b/agit-cli/src/commands/ls_tree.rs @@ -1,6 +1,6 @@ -use crate::core::objects::tree::Tree; -use crate::core::repo; -use crate::core::storage; +use agit_core::objects::tree::Tree; +use agit_core::repo; +use agit_core::storage; pub fn run(tree_sha1: &str) -> Result<(), Box> { let repo_root = repo::find_repo_root()?; @@ -11,7 +11,7 @@ pub fn run(tree_sha1: &str) -> Result<(), Box> { return Ok(()); } - let tree_data = crate::core::objects::format_object_data("tree", &content); + let tree_data = agit_core::objects::format_object_data("tree", &content); let tree = Tree::deserialize(&tree_data)?; for entry in &tree.entries { diff --git a/src/commands/merge.rs b/agit-cli/src/commands/merge.rs similarity index 97% rename from src/commands/merge.rs rename to agit-cli/src/commands/merge.rs index 792144c..75765e4 100644 --- a/src/commands/merge.rs +++ b/agit-cli/src/commands/merge.rs @@ -1,5 +1,5 @@ -use crate::config; -use crate::core::{checkout, merge, reflog, refs, repo}; +use agit_core::config; +use agit_core::{checkout, merge, reflog, refs, repo}; use std::fs; pub fn run( diff --git a/src/commands/mod.rs b/agit-cli/src/commands/mod.rs similarity index 100% rename from src/commands/mod.rs rename to agit-cli/src/commands/mod.rs diff --git a/src/commands/mv.rs b/agit-cli/src/commands/mv.rs similarity index 94% rename from src/commands/mv.rs rename to agit-cli/src/commands/mv.rs index fd36a63..5f7a152 100644 --- a/src/commands/mv.rs +++ b/agit-cli/src/commands/mv.rs @@ -1,7 +1,7 @@ //! mv 命令:在索引和工作区中移动/重命名文件。 -use crate::core::index::Index; -use crate::core::repo; +use agit_core::index::Index; +use agit_core::repo; use std::fs; pub fn run(source: &str, dest: &str) -> Result<(), Box> { diff --git a/src/commands/pull.rs b/agit-cli/src/commands/pull.rs similarity index 87% rename from src/commands/pull.rs rename to agit-cli/src/commands/pull.rs index 8a27d2e..306e175 100644 --- a/src/commands/pull.rs +++ b/agit-cli/src/commands/pull.rs @@ -1,7 +1,7 @@ -use crate::core::index::Index; -use crate::core::refs; -use crate::core::remote_utils; -use crate::core::repo; +use agit_core::index::Index; +use agit_core::refs; +use agit_core::remote_utils; +use agit_core::repo; use std::collections::VecDeque; use std::fs; @@ -62,7 +62,7 @@ fn check_working_tree_clean( repo: &std::path::Path, index: &Index, ) -> Result> { - use crate::core::objects::blob::Blob; + use agit_core::objects::blob::Blob; for (path, entry) in index.entries.iter() { let full_path = repo.join(path); if full_path.exists() { @@ -155,15 +155,15 @@ fn push_parents( sha1: &str, queue: &mut VecDeque, ) -> Result<(), Box> { - let (obj_type, content) = match crate::core::storage::read_object(repo, sha1) { + let (obj_type, content) = match agit_core::storage::read_object(repo, sha1) { Ok(v) => v, Err(_) => return Ok(()), }; if obj_type != "commit" { return Ok(()); } - let commit_data = crate::core::objects::format_object_data("commit", &content); - let commit = crate::core::objects::commit::Commit::deserialize(&commit_data)?; + let commit_data = agit_core::objects::format_object_data("commit", &content); + let commit = agit_core::objects::commit::Commit::deserialize(&commit_data)?; for parent in commit.parents { queue.push_back(parent); } @@ -195,7 +195,7 @@ fn merge_changes( let base_sha = find_common_ancestor(repo, local_sha1, remote_sha1)? .unwrap_or_else(|| local_sha1.to_string()); - let config = crate::config::load(); + let config = agit_core::config::load(); let (timestamp, time_str) = repo::get_current_timestamp(); let author = format!( "{} <{}> {} {}", @@ -205,16 +205,16 @@ fn merge_changes( // 执行 3-way merge(直接操作 tree),写入工作目录和 index let has_conflicts = - crate::core::merge::three_way_merge(repo, &base_sha, local_sha1, remote_sha1)?; + agit_core::merge::three_way_merge(repo, &base_sha, local_sha1, remote_sha1)?; if has_conflicts { println!("Automatic merge failed; fix conflicts and then commit the result."); let git_dir = repo.join(".git"); - crate::utils::atomic_write( + agit_core::utils::atomic_write( &git_dir.join("MERGE_HEAD"), format!("{}\n", remote_sha1).as_bytes(), )?; - crate::utils::atomic_write( + agit_core::utils::atomic_write( &git_dir.join("MERGE_MSG"), format!("Merge branch '{}' of remote into {}\n", branch, branch).as_bytes(), )?; @@ -222,15 +222,15 @@ fn merge_changes( } // 无冲突:从 index 生成 tree 并创建 merge commit - let index = crate::core::index::Index::load(repo)?; - let mut merge_tree = crate::core::objects::tree::Tree::new(); + let index = agit_core::index::Index::load(repo)?; + let mut merge_tree = agit_core::objects::tree::Tree::new(); for entry in index.entries.values() { merge_tree.add_entry(&entry.mode, &entry.path, &entry.sha1); } let tree_sha = merge_tree.hash(); - crate::core::storage::write_object(repo, "tree", &merge_tree.serialize_raw())?; + agit_core::storage::write_object(repo, "tree", &merge_tree.serialize_raw())?; - let mut merge_commit = crate::core::objects::commit::Commit::new( + let mut merge_commit = agit_core::objects::commit::Commit::new( &tree_sha, &author, &committer, @@ -240,7 +240,7 @@ fn merge_changes( merge_commit.add_parent(remote_sha1); let merge_sha1 = merge_commit.hash(); - crate::core::storage::write_object(repo, "commit", &merge_commit.serialize_raw())?; + agit_core::storage::write_object(repo, "commit", &merge_commit.serialize_raw())?; refs::write_ref(repo, &format!("refs/heads/{}", branch), &merge_sha1)?; diff --git a/src/commands/push.rs b/agit-cli/src/commands/push.rs similarity index 93% rename from src/commands/push.rs rename to agit-cli/src/commands/push.rs index 79b9b9a..d13b826 100644 --- a/src/commands/push.rs +++ b/agit-cli/src/commands/push.rs @@ -1,7 +1,7 @@ -use crate::core::protocol::create_transport; -use crate::core::refs; -use crate::core::remote_utils; -use crate::core::repo; +use agit_core::protocol::create_transport; +use agit_core::refs; +use agit_core::remote_utils; +use agit_core::repo; pub fn run(remote: Option<&str>, branch: Option<&str>) -> Result<(), Box> { let repo_root = repo::find_repo_root()?; @@ -106,13 +106,13 @@ fn generate_pack(objects: &[(String, Vec)]) -> Result, Box>= 7; } - let compressed = crate::core::compression::compress(raw_content)?; + let compressed = agit_core::compression::compress(raw_content)?; pack.extend_from_slice(&size_bytes); pack.extend_from_slice(&compressed); } - let sha1 = crate::core::hash::hash_bytes(&pack); + let sha1 = agit_core::hash::hash_bytes(&pack); let sha1_bytes: Vec = (0..40) .step_by(2) .map(|i| u8::from_str_radix(&sha1[i..i + 2], 16).unwrap_or(0)) diff --git a/src/commands/rebase.rs b/agit-cli/src/commands/rebase.rs similarity index 95% rename from src/commands/rebase.rs rename to agit-cli/src/commands/rebase.rs index 3c1a2ac..96d2837 100644 --- a/src/commands/rebase.rs +++ b/agit-cli/src/commands/rebase.rs @@ -1,6 +1,6 @@ -use crate::core::index::Index; -use crate::core::objects::commit::Commit; -use crate::core::{checkout, rebase as core_rebase, refs, repo, storage}; +use agit_core::index::Index; +use agit_core::objects::commit::Commit; +use agit_core::{checkout, rebase as core_rebase, refs, repo, storage}; use std::fs; use std::path::Path; @@ -59,7 +59,7 @@ fn run_start( let head_sha = refs::read_head(repo)?; // 找到 fork point - let fork_point = crate::core::merge::find_merge_base(repo, &head_sha, &upstream_sha)?; + let fork_point = agit_core::merge::find_merge_base(repo, &head_sha, &upstream_sha)?; // 空提交范围检查 if fork_point == head_sha { @@ -110,7 +110,7 @@ fn apply_todo_until_conflict_or_done(repo: &Path) -> Result<(), Box Result<(), Box> { // 读取原始提交的元数据 let commit_sha = fs::read_to_string(&rebase_head_path)?.trim().to_string(); let (_, body) = storage::read_object(repo, &commit_sha)?; - let commit_data = crate::core::objects::format_object_data("commit", &body); + let commit_data = agit_core::objects::format_object_data("commit", &body); let original = Commit::deserialize(&commit_data)?; // 从当前索引创建新 commit(保留原始 author,当前用户为 committer) - let config = crate::config::load(); + let config = agit_core::config::load(); let (timestamp, time_str) = repo::get_current_timestamp(); let head_sha = refs::read_head(repo)?; let _new_sha = core_rebase::create_commit_from_index( diff --git a/src/commands/reflog.rs b/agit-cli/src/commands/reflog.rs similarity index 97% rename from src/commands/reflog.rs rename to agit-cli/src/commands/reflog.rs index 6f38b3c..7c9b48c 100644 --- a/src/commands/reflog.rs +++ b/agit-cli/src/commands/reflog.rs @@ -4,8 +4,8 @@ //! //! 默认显示 HEAD 的引用日志。可以指定分支名查看特定分支的 reflog。 -use crate::core::reflog::{self, ReflogEntry}; -use crate::core::repo; +use agit_core::reflog::{self, ReflogEntry}; +use agit_core::repo; pub fn run(ref_name: Option<&str>) -> Result<(), Box> { let repo_root = repo::find_repo_root()?; diff --git a/src/commands/remote.rs b/agit-cli/src/commands/remote.rs similarity index 99% rename from src/commands/remote.rs rename to agit-cli/src/commands/remote.rs index 468207b..e8efc47 100644 --- a/src/commands/remote.rs +++ b/agit-cli/src/commands/remote.rs @@ -1,6 +1,6 @@ use std::fs; -use crate::core::repo; +use agit_core::repo; pub fn run_add(name: &str, url: &str) -> Result<(), Box> { let repo_root = repo::find_repo_root()?; diff --git a/src/commands/reset.rs b/agit-cli/src/commands/reset.rs similarity index 93% rename from src/commands/reset.rs rename to agit-cli/src/commands/reset.rs index a8dbe9c..d082245 100644 --- a/src/commands/reset.rs +++ b/agit-cli/src/commands/reset.rs @@ -1,7 +1,7 @@ -use crate::core::index::Index; -use crate::core::objects::commit::Commit; -use crate::core::objects::tree::Tree; -use crate::core::{checkout, reflog, refs, repo, storage}; +use agit_core::index::Index; +use agit_core::objects::commit::Commit; +use agit_core::objects::tree::Tree; +use agit_core::{checkout, reflog, refs, repo, storage}; use std::collections::BTreeMap; use std::path::Path; @@ -49,7 +49,7 @@ pub fn run( reset_mixed(&repo_root, &target_sha)?; } - let cfg = crate::config::load(); + let cfg = agit_core::config::load(); let _ = reflog::append_reflog( &repo_root, "HEAD", @@ -132,7 +132,7 @@ fn read_commit_tree( if obj_type != "commit" { return Ok(result); } - let commit_data = crate::core::objects::format_object_data("commit", &content); + let commit_data = agit_core::objects::format_object_data("commit", &content); let commit = Commit::deserialize(&commit_data)?; collect_tree_recursive(repo, &commit.tree, "", &mut result)?; Ok(result) @@ -148,7 +148,7 @@ fn collect_tree_recursive( if obj_type != "tree" { return Ok(()); } - let tree_data = crate::core::objects::format_object_data("tree", &content); + let tree_data = agit_core::objects::format_object_data("tree", &content); let tree = Tree::deserialize(&tree_data)?; for entry in &tree.entries { let path = if prefix.is_empty() { diff --git a/src/commands/rm.rs b/agit-cli/src/commands/rm.rs similarity index 94% rename from src/commands/rm.rs rename to agit-cli/src/commands/rm.rs index 09a01b1..d1a1b88 100644 --- a/src/commands/rm.rs +++ b/agit-cli/src/commands/rm.rs @@ -1,7 +1,7 @@ //! rm 命令:从索引和工作区删除文件。 -use crate::core::index::Index; -use crate::core::repo; +use agit_core::index::Index; +use agit_core::repo; use std::fs; pub fn run(cached: bool, files: &[String]) -> Result<(), Box> { diff --git a/src/commands/show.rs b/agit-cli/src/commands/show.rs similarity index 86% rename from src/commands/show.rs rename to agit-cli/src/commands/show.rs index 0791ba1..3907ca4 100644 --- a/src/commands/show.rs +++ b/agit-cli/src/commands/show.rs @@ -1,8 +1,8 @@ -use crate::core::objects::commit::Commit; -use crate::core::objects::tree::Tree; -use crate::core::refs; -use crate::core::repo; -use crate::core::storage; +use agit_core::objects::commit::Commit; +use agit_core::objects::tree::Tree; +use agit_core::refs; +use agit_core::repo; +use agit_core::storage; pub fn run(object: &str) -> Result<(), Box> { let repo_root = repo::find_repo_root()?; @@ -33,7 +33,7 @@ fn resolve_ref(repo: &std::path::Path, name: &str) -> Result Result<(), Box> { - let commit_data = crate::core::objects::format_object_data("commit", content); + let commit_data = agit_core::objects::format_object_data("commit", content); let commit = Commit::deserialize(&commit_data)?; println!( @@ -53,7 +53,7 @@ fn show_commit(content: &[u8]) -> Result<(), Box> { } fn show_tree(content: &[u8]) -> Result<(), Box> { - let tree_data = crate::core::objects::format_object_data("tree", content); + let tree_data = agit_core::objects::format_object_data("tree", content); let tree = Tree::deserialize(&tree_data)?; for entry in &tree.entries { diff --git a/src/commands/stash.rs b/agit-cli/src/commands/stash.rs similarity index 93% rename from src/commands/stash.rs rename to agit-cli/src/commands/stash.rs index 54661a7..9819dea 100644 --- a/src/commands/stash.rs +++ b/agit-cli/src/commands/stash.rs @@ -3,11 +3,11 @@ //! stash 结构:每个 stash 是一个 commit,parent 指向 HEAD,树捕获工作区+索引状态。 //! 多个 stash 以线性链存储在 `refs/stash`,最新 stash 在顶端。 -use crate::core::index::Index; -use crate::core::objects::blob::Blob; -use crate::core::objects::commit::Commit; -use crate::core::objects::tree::Tree; -use crate::core::{checkout, refs, repo, storage}; +use agit_core::index::Index; +use agit_core::objects::blob::Blob; +use agit_core::objects::commit::Commit; +use agit_core::objects::tree::Tree; +use agit_core::{checkout, refs, repo, storage}; use std::fs; /// `stash push`:保存当前工作区状态并重置到 HEAD。 @@ -38,7 +38,7 @@ pub fn run_push() -> Result<(), Box> { return Err("No local changes to save".into()); } - let config = crate::config::load(); + let config = agit_core::config::load(); let (timestamp, time_str) = repo::get_current_timestamp(); let author = format!( "{} <{}> {} {}", @@ -96,7 +96,7 @@ pub fn run_pop() -> Result<(), Box> { if obj_type != "commit" { return Err("refs/stash does not point to a commit".into()); } - let commit_data = crate::core::objects::format_object_data("commit", &content); + let commit_data = agit_core::objects::format_object_data("commit", &content); let commit = Commit::deserialize(&commit_data)?; // 恢复 stash 的 tree 到工作区 @@ -136,7 +136,7 @@ pub fn run_list() -> Result<(), Box> { if obj_type != "commit" { break; } - let commit_data = crate::core::objects::format_object_data("commit", &content); + let commit_data = agit_core::objects::format_object_data("commit", &content); let commit = match Commit::deserialize(&commit_data) { Ok(c) => c, Err(_) => break, @@ -172,7 +172,7 @@ pub fn run_drop(stash_ref: Option<&str>) -> Result<(), Box= 2 { diff --git a/src/commands/status.rs b/agit-cli/src/commands/status.rs similarity index 91% rename from src/commands/status.rs rename to agit-cli/src/commands/status.rs index d276fdb..f6cdb0d 100644 --- a/src/commands/status.rs +++ b/agit-cli/src/commands/status.rs @@ -1,9 +1,9 @@ -use crate::core::index::Index; -use crate::core::objects::commit::Commit; -use crate::core::objects::tree::Tree; -use crate::core::refs; -use crate::core::repo; -use crate::core::storage; +use agit_core::index::Index; +use agit_core::objects::commit::Commit; +use agit_core::objects::tree::Tree; +use agit_core::refs; +use agit_core::repo; +use agit_core::storage; use std::collections::{BTreeMap, BTreeSet}; use std::fs; use std::path::Path; @@ -83,9 +83,9 @@ fn get_head_tree(repo: &Path, sha1: &str) -> BTreeMap { let mut result = BTreeMap::new(); if let Ok((obj_type, content)) = storage::read_object(repo, sha1) { if obj_type == "commit" { - if let Ok(commit) = Commit::deserialize(&crate::core::objects::format_object_data( - "commit", &content, - )) { + if let Ok(commit) = + Commit::deserialize(&agit_core::objects::format_object_data("commit", &content)) + { let _ = collect_tree_recursive(repo, &commit.tree, "", &mut result); } } @@ -104,7 +104,7 @@ fn collect_tree_recursive( if obj_type != "tree" { return Ok(()); } - let tree_data = crate::core::objects::format_object_data("tree", &content); + let tree_data = agit_core::objects::format_object_data("tree", &content); let tree = Tree::deserialize(&tree_data)?; for entry in &tree.entries { let path = if prefix.is_empty() { @@ -143,7 +143,7 @@ fn get_modified_changes(repo: &Path, index: &Index) -> Vec { continue; } if let Ok(content) = fs::read(&full_path) { - let blob = crate::core::objects::blob::Blob::new(content); + let blob = agit_core::objects::blob::Blob::new(content); if blob.hash() != entry.sha1 { modified.push(path.clone()); } @@ -175,7 +175,7 @@ fn get_deleted_changes( fn list_untracked(repo: &Path, index: &Index) { let mut untracked = Vec::new(); - let matcher = crate::core::ignore::IgnoreMatcher::load(repo, Path::new("")); + let matcher = agit_core::ignore::IgnoreMatcher::load(repo, Path::new("")); if let Err(e) = collect_untracked(repo, repo, index, &matcher, &mut untracked) { eprintln!("error listing untracked: {}", e); return; @@ -194,7 +194,7 @@ fn collect_untracked( repo: &Path, current: &Path, index: &Index, - matcher: &crate::core::ignore::IgnoreMatcher, + matcher: &agit_core::ignore::IgnoreMatcher, untracked: &mut Vec, ) -> Result<(), Box> { if !current.exists() { diff --git a/src/commands/tag.rs b/agit-cli/src/commands/tag.rs similarity index 95% rename from src/commands/tag.rs rename to agit-cli/src/commands/tag.rs index 0452fc3..3652551 100644 --- a/src/commands/tag.rs +++ b/agit-cli/src/commands/tag.rs @@ -1,7 +1,7 @@ //! tag 命令:创建、列出、删除标签。 -use crate::core::objects::tag::Tag; -use crate::core::{refs, repo, storage}; +use agit_core::objects::tag::Tag; +use agit_core::{refs, repo, storage}; pub fn run_create( name: &str, @@ -27,7 +27,7 @@ pub fn run_create( if let Some(msg) = message { // Annotated tag - let config = crate::config::load(); + let config = agit_core::config::load(); let (timestamp, time_str) = repo::get_current_timestamp(); let tagger = format!( "{} <{}> {} {}", diff --git a/src/main.rs b/agit-cli/src/main.rs similarity index 99% rename from src/main.rs rename to agit-cli/src/main.rs index 67d87fe..3665f11 100644 --- a/src/main.rs +++ b/agit-cli/src/main.rs @@ -1,11 +1,9 @@ mod ai; mod cli; mod commands; -mod config; -mod core; mod output; -mod utils; +use agit_core::config; use clap::Parser; #[cfg(feature = "tag")] use cli::TagAction; diff --git a/src/output/mod.rs b/agit-cli/src/output/mod.rs similarity index 100% rename from src/output/mod.rs rename to agit-cli/src/output/mod.rs diff --git a/tests/cherry_pick_test.rs b/agit-cli/tests/cherry_pick_test.rs similarity index 100% rename from tests/cherry_pick_test.rs rename to agit-cli/tests/cherry_pick_test.rs diff --git a/tests/common/mod.rs b/agit-cli/tests/common/mod.rs similarity index 95% rename from tests/common/mod.rs rename to agit-cli/tests/common/mod.rs index bb01f6d..766badf 100644 --- a/tests/common/mod.rs +++ b/agit-cli/tests/common/mod.rs @@ -10,7 +10,9 @@ pub fn agit_binary() -> PathBuf { if let Ok(path) = std::env::var("CARGO_BIN_EXE_agit") { return PathBuf::from(path); } + // CARGO_MANIFEST_DIR 现在是 agit-cli/,workspace 的 target 在上一级 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.push(".."); path.push("target"); path.push("debug"); path.push("agit"); diff --git a/tests/git_compat_test.rs b/agit-cli/tests/git_compat_test.rs similarity index 100% rename from tests/git_compat_test.rs rename to agit-cli/tests/git_compat_test.rs diff --git a/tests/integration_test.rs b/agit-cli/tests/integration_test.rs similarity index 100% rename from tests/integration_test.rs rename to agit-cli/tests/integration_test.rs diff --git a/tests/p1_integration_test.rs b/agit-cli/tests/p1_integration_test.rs similarity index 100% rename from tests/p1_integration_test.rs rename to agit-cli/tests/p1_integration_test.rs diff --git a/tests/rebase_test.rs b/agit-cli/tests/rebase_test.rs similarity index 100% rename from tests/rebase_test.rs rename to agit-cli/tests/rebase_test.rs diff --git a/tests/smoke/linux_smoke.sh b/agit-cli/tests/smoke/linux_smoke.sh similarity index 100% rename from tests/smoke/linux_smoke.sh rename to agit-cli/tests/smoke/linux_smoke.sh diff --git a/tests/smoke/windows_smoke.ps1 b/agit-cli/tests/smoke/windows_smoke.ps1 similarity index 100% rename from tests/smoke/windows_smoke.ps1 rename to agit-cli/tests/smoke/windows_smoke.ps1 diff --git a/agit-core/Cargo.toml b/agit-core/Cargo.toml new file mode 100644 index 0000000..f803eb2 --- /dev/null +++ b/agit-core/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "agit-core" +version = "0.14.0" +edition = "2021" +description = "Pure-Rust Git core library — objects, refs, storage, protocol" +license = "Apache-2.0" +repository = "https://github.com/bit-torch/AdapterGit" + +[dependencies] +serde = { version = "1", features = ["derive"] } +flate2 = "1" +sha1 = "0.10" +anyhow = "1" +url = "2" +toml = "0.8" +native-tls = { version = "0.2", optional = true } + +[features] +default = ["tag", "tls"] +tag = [] +tls = ["native-tls"] diff --git a/src/core/bisect.rs b/agit-core/src/bisect.rs similarity index 98% rename from src/core/bisect.rs rename to agit-core/src/bisect.rs index e3bf0f7..7ca577a 100644 --- a/src/core/bisect.rs +++ b/agit-core/src/bisect.rs @@ -11,9 +11,9 @@ //! - `.git/BISECT_LOG` — 操作日志 //! - `.git/BISECT_START` — 原始的 HEAD 引用,用于 reset -use crate::core::objects::commit::Commit; -use crate::core::objects::format_object_data; -use crate::core::{refs, storage}; +use crate::objects::commit::Commit; +use crate::objects::format_object_data; +use crate::{refs, storage}; use std::fs; use std::path::Path; @@ -293,7 +293,7 @@ pub fn read_bisect_log(repo: &Path) -> Result #[cfg(test)] mod tests { use super::*; - use crate::core::storage; + use crate::storage; use std::fs; use std::path::PathBuf; diff --git a/src/core/checkout.rs b/agit-core/src/checkout.rs similarity index 95% rename from src/core/checkout.rs rename to agit-core/src/checkout.rs index 10f9895..0a9be39 100644 --- a/src/core/checkout.rs +++ b/agit-core/src/checkout.rs @@ -1,6 +1,6 @@ -use crate::core::objects::blob::Blob; -use crate::core::objects::tree::Tree; -use crate::core::{index, refs, storage}; +use crate::objects::blob::Blob; +use crate::objects::tree::Tree; +use crate::{index, refs, storage}; use std::collections::BTreeSet; use std::fs; use std::path::Path; @@ -16,7 +16,7 @@ pub fn restore_from_commit( return Err(format!("object {} is not a commit", commit_sha).into()); } let commit_data = with_object_header("commit", &body); - let commit = crate::core::objects::commit::Commit::deserialize(&commit_data)?; + let commit = crate::objects::commit::Commit::deserialize(&commit_data)?; let old_index = index::Index::load(repo)?; let old_tracked: BTreeSet = old_index.entries.keys().cloned().collect(); @@ -47,7 +47,7 @@ pub fn rebuild_index_from_commit( return Err(format!("object {} is not a commit", commit_sha).into()); } let commit_data = with_object_header("commit", &body); - let commit = crate::core::objects::commit::Commit::deserialize(&commit_data)?; + let commit = crate::objects::commit::Commit::deserialize(&commit_data)?; let mut new_index = index::Index::new(); rebuild_index_from_tree(repo, &commit.tree, Path::new(""), &mut new_index)?; new_index.save(repo)?; @@ -90,7 +90,7 @@ pub fn switch_branch(repo: &Path, branch_name: &str) -> Result<(), Box Self { + Self::new() + } +} + impl Index { pub fn new() -> Self { Index { @@ -105,7 +111,7 @@ impl Index { } // SHA-1 校验和 - let sha1 = crate::core::hash::hash_bytes(&data); + let sha1 = crate::hash::hash_bytes(&data); data.extend_from_slice(&hex_to_bytes(&sha1)?); Ok(data) diff --git a/src/core/mod.rs b/agit-core/src/lib.rs similarity index 90% rename from src/core/mod.rs rename to agit-core/src/lib.rs index a87fa9d..74f1296 100644 --- a/src/core/mod.rs +++ b/agit-core/src/lib.rs @@ -1,6 +1,7 @@ pub mod bisect; pub mod checkout; pub mod compression; +pub mod config; pub mod hash; pub mod ignore; pub mod index; @@ -15,3 +16,4 @@ pub mod repo; pub mod ssh_transport; pub mod ssh_url; pub mod storage; +pub mod utils; diff --git a/src/core/merge.rs b/agit-core/src/merge.rs similarity index 97% rename from src/core/merge.rs rename to agit-core/src/merge.rs index ea7fc1b..14b6aa0 100644 --- a/src/core/merge.rs +++ b/agit-core/src/merge.rs @@ -1,7 +1,7 @@ -use crate::core::objects::blob::Blob; -use crate::core::objects::commit::Commit; -use crate::core::objects::tree::Tree; -use crate::core::{index, refs, storage}; +use crate::objects::blob::Blob; +use crate::objects::commit::Commit; +use crate::objects::tree::Tree; +use crate::{index, refs, storage}; use std::collections::{BTreeMap, BTreeSet}; use std::fs; use std::path::Path; @@ -113,7 +113,7 @@ pub fn merge_branch( } /// 查找两个 commit 的共同祖先(简易 BFS)。 -pub(crate) fn find_merge_base( +pub fn find_merge_base( repo: &Path, sha1: &str, sha2: &str, @@ -175,7 +175,7 @@ pub(crate) fn find_merge_base( /// 3-way merge: 比较 base/ours/theirs 的 tree,生成合并结果。 /// 返回是否产生了冲突。 -pub(crate) fn three_way_merge( +pub fn three_way_merge( repo: &Path, base_sha: &str, ours_sha: &str, @@ -200,7 +200,7 @@ pub(crate) fn three_way_merge( /// 3-way merge 核心逻辑:接受预计算的文件映射。 /// `custom_theirs_label` — 冲突标记 `>>>>>>>` 后使用的自定义标签(None 则用路径名)。 -pub(crate) fn three_way_merge_with_files( +pub fn three_way_merge_with_files( repo: &Path, base_files: &BTreeMap, ours_files: &BTreeMap, @@ -287,13 +287,13 @@ pub(crate) fn three_way_merge_with_files( /// 文件信息:SHA-1 和 mode。 #[derive(Debug, PartialEq)] -pub(crate) struct FileInfo { +pub struct FileInfo { pub sha1: String, pub mode: String, } /// 从 commit SHA 出发,读取 tree 中的所有文件映射。 -pub(crate) fn read_tree_files( +pub fn read_tree_files( repo: &Path, commit_sha: &str, ) -> Result, Box> { @@ -333,7 +333,7 @@ fn read_tree_files_recursive( } /// 读取 commit 的 tree 文件映射(公开 API,用于 rebase/cherry-pick)。 -pub(crate) fn read_commit_tree_files( +pub fn read_commit_tree_files( repo: &Path, commit_sha: &str, ) -> Result, Box> { diff --git a/src/core/objects/blob.rs b/agit-core/src/objects/blob.rs similarity index 98% rename from src/core/objects/blob.rs rename to agit-core/src/objects/blob.rs index 432f874..d8580f4 100644 --- a/src/core/objects/blob.rs +++ b/agit-core/src/objects/blob.rs @@ -1,4 +1,4 @@ -use crate::core::hash::hash_git_object; +use crate::hash::hash_git_object; pub struct Blob { pub content: Vec, diff --git a/src/core/objects/commit.rs b/agit-core/src/objects/commit.rs similarity index 99% rename from src/core/objects/commit.rs rename to agit-core/src/objects/commit.rs index 6bd8aea..372dc13 100644 --- a/src/core/objects/commit.rs +++ b/agit-core/src/objects/commit.rs @@ -1,4 +1,4 @@ -use crate::core::hash::hash_git_object; +use crate::hash::hash_git_object; pub struct Commit { pub tree: String, diff --git a/src/core/objects/mod.rs b/agit-core/src/objects/mod.rs similarity index 100% rename from src/core/objects/mod.rs rename to agit-core/src/objects/mod.rs diff --git a/src/core/objects/tag.rs b/agit-core/src/objects/tag.rs similarity index 99% rename from src/core/objects/tag.rs rename to agit-core/src/objects/tag.rs index 8993e2b..9ff6224 100644 --- a/src/core/objects/tag.rs +++ b/agit-core/src/objects/tag.rs @@ -1,4 +1,4 @@ -use crate::core::hash::hash_git_object; +use crate::hash::hash_git_object; /// Git 注释标签(annotated tag)对象。 /// diff --git a/src/core/objects/tree.rs b/agit-core/src/objects/tree.rs similarity index 97% rename from src/core/objects/tree.rs rename to agit-core/src/objects/tree.rs index 3571445..2e19a74 100644 --- a/src/core/objects/tree.rs +++ b/agit-core/src/objects/tree.rs @@ -1,4 +1,4 @@ -use crate::core::hash::hash_git_object; +use crate::hash::hash_git_object; pub struct Tree { pub entries: Vec, @@ -10,6 +10,12 @@ pub struct TreeEntry { pub sha1: String, } +impl Default for Tree { + fn default() -> Self { + Self::new() + } +} + impl Tree { pub fn new() -> Self { Tree { @@ -225,7 +231,7 @@ mod tests { let data = tree.serialize_raw(); let deserialized = - Tree::deserialize(&crate::core::objects::format_object_data("tree", &data)).unwrap(); + Tree::deserialize(&crate::objects::format_object_data("tree", &data)).unwrap(); // 期望排序: a.txt, dir-a, dir (目录末尾有 "/",排在 dir-a 之后), z.txt assert_eq!(deserialized.entries[0].name, "a.txt"); diff --git a/src/core/protocol.rs b/agit-core/src/protocol.rs similarity index 98% rename from src/core/protocol.rs rename to agit-core/src/protocol.rs index ffa5a50..ff229e4 100644 --- a/src/core/protocol.rs +++ b/agit-core/src/protocol.rs @@ -1,7 +1,7 @@ use std::io::{Read, Write}; use std::net::TcpStream; -use crate::core::ssh_url::SshUrl; +use crate::ssh_url::SshUrl; // ── Transport trait ────────────────────────────────────────── @@ -30,9 +30,7 @@ pub fn create_transport(url: &str) -> Result, Box Result { - let (content, n) = crate::core::compression::decompress_stream(&data[pos..])?; + let (content, n) = crate::compression::decompress_stream(&data[pos..])?; (content, n) } _ => { @@ -270,7 +268,7 @@ pub fn parse_packfile(data: &[u8]) -> Result Result Result, ) -> Result> { // 读取被 pick 的 commit let (_, body) = storage::read_object(repo, commit_sha)?; - let commit_data = crate::core::objects::format_object_data("commit", &body); + let commit_data = crate::objects::format_object_data("commit", &body); let commit = Commit::deserialize(&commit_data)?; let head_sha = refs::read_head(repo)?; @@ -119,7 +119,7 @@ pub(crate) fn pick_commit( /// 从当前索引创建 commit,直接写入 HEAD(支持分离 HEAD)。 /// /// 返回新 commit SHA。 -pub(crate) fn create_commit_from_index( +pub fn create_commit_from_index( repo: &Path, author: &str, committer: &str, @@ -157,10 +157,7 @@ pub(crate) fn create_commit_from_index( // ── TODO 状态文件管理 ────────────────────────────────────── /// 写入 REBASE_TODO 文件(每行一个 SHA,最旧的在前)。 -pub(crate) fn write_todo( - repo: &Path, - commits: &[String], -) -> Result<(), Box> { +pub fn write_todo(repo: &Path, commits: &[String]) -> Result<(), Box> { let path = repo.join(".git").join("REBASE_TODO"); let content: String = commits.iter().map(|s| format!("{}\n", s)).collect(); fs::write(path, content)?; @@ -168,7 +165,7 @@ pub(crate) fn write_todo( } /// 读取 REBASE_TODO 文件。 -pub(crate) fn read_todo(repo: &Path) -> Result, Box> { +pub fn read_todo(repo: &Path) -> Result, Box> { let path = repo.join(".git").join("REBASE_TODO"); let content = fs::read_to_string(path)?; Ok(content @@ -179,7 +176,7 @@ pub(crate) fn read_todo(repo: &Path) -> Result, Box Result, Box> { +pub fn pop_todo(repo: &Path) -> Result, Box> { let mut commits = read_todo(repo)?; if commits.is_empty() { return Ok(None); @@ -192,7 +189,7 @@ pub(crate) fn pop_todo(repo: &Path) -> Result, Box Result<(), Box> { @@ -203,7 +200,7 @@ pub(crate) fn write_cherry_todo( } /// 读取 CHERRY_PICK_TODO 文件。 -pub(crate) fn read_cherry_todo(repo: &Path) -> Result, Box> { +pub fn read_cherry_todo(repo: &Path) -> Result, Box> { let path = repo.join(".git").join("CHERRY_PICK_TODO"); let content = fs::read_to_string(path)?; Ok(content @@ -214,7 +211,7 @@ pub(crate) fn read_cherry_todo(repo: &Path) -> Result, Box Result, Box> { +pub fn pop_cherry_todo(repo: &Path) -> Result, Box> { let mut commits = read_cherry_todo(repo)?; if commits.is_empty() { return Ok(None); diff --git a/src/core/reflog.rs b/agit-core/src/reflog.rs similarity index 98% rename from src/core/reflog.rs rename to agit-core/src/reflog.rs index 7e1a76e..4e9154c 100644 --- a/src/core/reflog.rs +++ b/agit-core/src/reflog.rs @@ -79,7 +79,7 @@ pub fn append_reflog( author: &str, message: &str, ) -> Result<(), Box> { - let (timestamp, timestamp_str) = crate::core::repo::get_current_timestamp(); + let (timestamp, timestamp_str) = crate::repo::get_current_timestamp(); let path = reflog_path(repo, ref_name); if let Some(parent) = path.parent() { diff --git a/src/core/refs.rs b/agit-core/src/refs.rs similarity index 100% rename from src/core/refs.rs rename to agit-core/src/refs.rs diff --git a/src/core/remote_utils.rs b/agit-core/src/remote_utils.rs similarity index 80% rename from src/core/remote_utils.rs rename to agit-core/src/remote_utils.rs index a22fa7e..3e03055 100644 --- a/src/core/remote_utils.rs +++ b/agit-core/src/remote_utils.rs @@ -1,6 +1,6 @@ -use crate::core::objects::commit::Commit; -use crate::core::objects::tree::Tree; -use crate::core::protocol::ObjectList; +use crate::objects::commit::Commit; +use crate::objects::tree::Tree; +use crate::protocol::ObjectList; use std::fs; use std::path::Path; @@ -18,7 +18,7 @@ pub fn write_objects( if let Some(parent) = obj_path.parent() { fs::create_dir_all(parent)?; } - let compressed = crate::core::compression::compress(data)?; + let compressed = crate::compression::compress(data)?; fs::write(&obj_path, &compressed)?; } } @@ -37,14 +37,14 @@ pub fn apply_tree( format!("{}/{}", prefix, entry.name) }; - let (obj_type, content) = crate::core::storage::read_object(repo, &entry.sha1)?; + let (obj_type, content) = crate::storage::read_object(repo, &entry.sha1)?; if obj_type == "tree" { let full_path = repo.join(&path); if !full_path.exists() { fs::create_dir_all(&full_path)?; } - let tree_data = crate::core::objects::format_object_data("tree", &content); + let tree_data = crate::objects::format_object_data("tree", &content); let subtree = Tree::deserialize(&tree_data)?; apply_tree(repo, &path, &subtree)?; } else if obj_type == "blob" { @@ -67,8 +67,8 @@ pub fn apply_tree_by_sha1( prefix: &str, tree_sha1: &str, ) -> Result<(), Box> { - let (_, tree_content) = crate::core::storage::read_object(repo, tree_sha1)?; - let tree_data = crate::core::objects::format_object_data("tree", &tree_content); + let (_, tree_content) = crate::storage::read_object(repo, tree_sha1)?; + let tree_data = crate::objects::format_object_data("tree", &tree_content); let tree = Tree::deserialize(&tree_data)?; apply_tree(repo, prefix, &tree) } @@ -139,14 +139,14 @@ pub fn collect_recent_commits( let mut result = Vec::new(); let mut current = sha1.to_string(); for _ in 0..max { - let (obj_type, content) = match crate::core::storage::read_object(repo, ¤t) { + let (obj_type, content) = match crate::storage::read_object(repo, ¤t) { Ok(v) => v, Err(_) => break, }; if obj_type != "commit" { break; } - let commit_data = crate::core::objects::format_object_data("commit", &content); + let commit_data = crate::objects::format_object_data("commit", &content); let commit = Commit::deserialize(&commit_data)?; if commit.parents.is_empty() { break; @@ -161,8 +161,8 @@ pub fn resolve_commit_to_tree( repo: &Path, sha1: &str, ) -> Result> { - let (_, content) = crate::core::storage::read_object(repo, sha1)?; - let commit_data = crate::core::objects::format_object_data("commit", &content); + let (_, content) = crate::storage::read_object(repo, sha1)?; + let commit_data = crate::objects::format_object_data("commit", &content); let commit = Commit::deserialize(&commit_data)?; Ok(commit.tree) } @@ -188,7 +188,7 @@ pub fn collect_local_objects_for_push( if remote_sha1s.contains(¤t) { continue; } - let (obj_type, content) = match crate::core::storage::read_object(repo, ¤t) { + let (obj_type, content) = match crate::storage::read_object(repo, ¤t) { Ok(v) => v, Err(_) => continue, }; @@ -196,10 +196,10 @@ pub fn collect_local_objects_for_push( continue; } - let full_object = crate::core::objects::format_object_data("commit", &content); + let full_object = crate::objects::format_object_data("commit", &content); objects.push((current.clone(), full_object)); - let commit_data = crate::core::objects::format_object_data("commit", &content); + let commit_data = crate::objects::format_object_data("commit", &content); let commit = Commit::deserialize(&commit_data)?; collect_tree_objects(repo, &commit.tree, &mut objects)?; @@ -226,14 +226,14 @@ fn collect_all_ancestors( if !seen.insert(current.clone()) { continue; } - let (obj_type, content) = match crate::core::storage::read_object(repo, ¤t) { + let (obj_type, content) = match crate::storage::read_object(repo, ¤t) { Ok(v) => v, Err(_) => continue, }; if obj_type != "commit" { continue; } - let commit_data = crate::core::objects::format_object_data("commit", &content); + let commit_data = crate::objects::format_object_data("commit", &content); let commit = Commit::deserialize(&commit_data)?; result.push(current); for parent in commit.parents { @@ -250,26 +250,26 @@ fn collect_tree_objects( tree_sha1: &str, objects: &mut Vec<(String, Vec)>, ) -> Result<(), Box> { - let (obj_type, content) = crate::core::storage::read_object(repo, tree_sha1)?; + let (obj_type, content) = crate::storage::read_object(repo, tree_sha1)?; if obj_type != "tree" { return Ok(()); } objects.push(( tree_sha1.to_string(), - crate::core::objects::format_object_data("tree", &content), + crate::objects::format_object_data("tree", &content), )); - let tree_data = crate::core::objects::format_object_data("tree", &content); + let tree_data = crate::objects::format_object_data("tree", &content); let tree = Tree::deserialize(&tree_data)?; for entry in &tree.entries { - let (e_type, e_content) = crate::core::storage::read_object(repo, &entry.sha1)?; + let (e_type, e_content) = crate::storage::read_object(repo, &entry.sha1)?; if e_type == "tree" { collect_tree_objects(repo, &entry.sha1, objects)?; } else if e_type == "blob" { objects.push(( entry.sha1.clone(), - crate::core::objects::format_object_data("blob", &e_content), + crate::objects::format_object_data("blob", &e_content), )); } } diff --git a/src/core/repo.rs b/agit-core/src/repo.rs similarity index 95% rename from src/core/repo.rs rename to agit-core/src/repo.rs index b05b439..fc19400 100644 --- a/src/core/repo.rs +++ b/agit-core/src/repo.rs @@ -1,7 +1,7 @@ -use crate::core::index::Index; -use crate::core::objects::blob::Blob; -use crate::core::objects::commit::Commit; -use crate::core::{refs, storage}; +use crate::index::Index; +use crate::objects::blob::Blob; +use crate::objects::commit::Commit; +use crate::{refs, storage}; use std::fs; use std::path::{Path, PathBuf}; @@ -185,10 +185,7 @@ fn windows_tz_offset() -> Option { /// 解析 commit/tree-ish 引用为完整 SHA-1。 /// 支持 HEAD、分支名、标签名、remote ref、完整 SHA、缩写 SHA、~N 后缀。 -pub(crate) fn resolve_commit( - repo: &Path, - spec: &str, -) -> Result> { +pub fn resolve_commit(repo: &Path, spec: &str) -> Result> { // 处理 ~N 后缀:遍历父提交 if let Some(tilde_pos) = spec.find('~') { let base = &spec[..tilde_pos]; @@ -245,12 +242,12 @@ pub(crate) fn resolve_commit( } /// 获取 commit 的第一个 parent SHA。 -pub(crate) fn get_parent(repo: &Path, sha: &str) -> Result> { +pub fn get_parent(repo: &Path, sha: &str) -> Result> { let (obj_type, content) = storage::read_object(repo, sha)?; if obj_type != "commit" { return Err(format!("object {} is not a commit", sha).into()); } - let commit_data = crate::core::objects::format_object_data("commit", &content); + let commit_data = crate::objects::format_object_data("commit", &content); let commit = Commit::deserialize(&commit_data)?; commit .parents @@ -285,7 +282,7 @@ fn find_full_sha(repo: &Path, prefix: &str) -> Option { } /// 检查工作区是否干净(tracked 文件是否被修改或删除)。 -pub(crate) fn is_working_tree_clean( +pub fn is_working_tree_clean( repo: &Path, index: &Index, ) -> Result> { diff --git a/src/core/ssh_transport.rs b/agit-core/src/ssh_transport.rs similarity index 98% rename from src/core/ssh_transport.rs rename to agit-core/src/ssh_transport.rs index e576a17..e387ffa 100644 --- a/src/core/ssh_transport.rs +++ b/agit-core/src/ssh_transport.rs @@ -11,11 +11,11 @@ use std::io::{Read, Write}; use std::process::{Command, Stdio}; -use crate::core::protocol::Transport; -use crate::core::protocol::{ +use crate::protocol::Transport; +use crate::protocol::{ find_pack_start, parse_packfile, parse_refs_data, pkt_line_encode, pkt_line_flush, ObjectList, }; -use crate::core::ssh_url::SshUrl; +use crate::ssh_url::SshUrl; /// SSH 传输实现 pub struct SshTransport { diff --git a/src/core/ssh_url.rs b/agit-core/src/ssh_url.rs similarity index 100% rename from src/core/ssh_url.rs rename to agit-core/src/ssh_url.rs diff --git a/src/core/storage.rs b/agit-core/src/storage.rs similarity index 97% rename from src/core/storage.rs rename to agit-core/src/storage.rs index 6d99264..03fcdf9 100644 --- a/src/core/storage.rs +++ b/agit-core/src/storage.rs @@ -1,4 +1,4 @@ -use crate::core::compression::{compress, decompress}; +use crate::compression::{compress, decompress}; use std::fs; use std::path::{Path, PathBuf}; @@ -35,7 +35,7 @@ pub fn write_object( data.extend_from_slice(header.as_bytes()); data.extend_from_slice(content); - let sha1 = crate::core::hash::hash_bytes(&data); + let sha1 = crate::hash::hash_bytes(&data); let compressed = compress(&data)?; let path = object_path(repo, &sha1); diff --git a/src/utils/error.rs b/agit-core/src/utils/error.rs similarity index 100% rename from src/utils/error.rs rename to agit-core/src/utils/error.rs diff --git a/src/utils/mod.rs b/agit-core/src/utils/mod.rs similarity index 100% rename from src/utils/mod.rs rename to agit-core/src/utils/mod.rs diff --git a/agit.bundle b/agit.bundle deleted file mode 100644 index a6d137e..0000000 Binary files a/agit.bundle and /dev/null differ diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index fb248e3..b4232e4 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -4,7 +4,7 @@ AdapterGit (agit) 是一个从底层原生实现的 Git 工具,完全使用 Rust 语言编写,不依赖任何外部 Git 库或系统 Git 命令。 -版本: **v0.4.1** +版本: **v0.14.0** ## 设计目标 @@ -26,11 +26,32 @@ agit show 显示提交/对象信息 agit cat-file 查看对象内容 (-t/-p) agit ls-tree 列出树对象 ── +本地分支与历史 +agit branch 创建/列出/删除分支 +agit checkout 切换分支或恢复工作树文件 +agit merge 合并分支 (fast-forward + 3-way) +agit rebase 变基操作 +agit stash 暂存/恢复工作进度 +agit reset 重置 HEAD 到指定状态 +── +远程操作 agit clone 克隆仓库 (HTTPS) agit fetch [url] 获取更新 agit pull 获取并合并 agit push [remote] 推送更新 agit remote add/list 远程管理 +── +高级操作 +agit cherry-pick 应用指定提交到当前分支 +agit blame 文件每行归属追踪 +agit reflog 引用日志查询 +agit bisect 二分查找引入 bug 的提交 +── +其他 +agit tag 创建/列出标签 +agit rm 从工作区和索引删除文件 +agit mv 移动或重命名文件 +agit config 读取/写入仓库或全局配置 ``` > 全局参数: `--ai` `--json` `--yaml` `--no-color` @@ -38,46 +59,41 @@ agit remote add/list 远程管理 ## 目录结构 ``` -src/ -├── main.rs 入口 + cat-file dispatch -├── cli/ -│ └── mod.rs clap 命令定义 (15 个子命令) -├── commands/ 命令实现 (每个命令一个文件) -│ ├── init.rs -│ ├── add.rs -│ ├── commit.rs -│ ├── status.rs -│ ├── log.rs -│ ├── diff.rs -│ ├── show.rs -│ ├── cat_file.rs -│ ├── ls_tree.rs -│ ├── clone.rs -│ ├── fetch.rs -│ ├── push.rs -│ ├── pull.rs -│ └── remote.rs -├── core/ 核心 Git 实现 -│ ├── hash.rs SHA-1 哈希 -│ ├── compression.rs zlib 压缩/解压 -│ ├── objects/ Git 对象 -│ │ ├── blob.rs Blob 对象 -│ │ ├── tree.rs Tree 对象 -│ │ └── commit.rs Commit 对象 -│ ├── storage.rs 对象存储 (.git/objects) -│ ├── refs.rs 引用系统 (HEAD, refs/heads/*) -│ ├── index.rs 索引文件 (.git/index) -│ ├── repo.rs 仓库工具 (find_root, ensure_dir, timestamp) -│ ├── protocol.rs Git 智能传输协议 (HTTP + pkt-line + packfile) -│ └── remote_utils.rs 网络命令共享工具 -├── ai/ -│ └── mod.rs AI 模式标志 + 标记 -├── output/ -│ └── mod.rs JSON/YAML/颜色 输出 -├── config/ -│ └── mod.rs 配置 (环境变量) -└── utils/ - └── error.rs 错误类型 (AgitError) +D:\AdapterGit\ ← Cargo workspace root +├── Cargo.toml ← [workspace] members = ["agit-core", "agit-ai", "agit-cli"] +├── agit-core/ (lib) +│ └── src/ +│ ├── lib.rs 库入口,重导出公开 API +│ ├── hash.rs SHA-1 哈希 +│ ├── compression.rs zlib 压缩/解压 +│ ├── storage.rs 对象存储 (.git/objects) +│ ├── refs.rs 引用系统 (HEAD, refs/heads/*) +│ ├── index.rs 索引文件 (.git/index) +│ ├── repo.rs 仓库工具 (find_root, ensure_dir, timestamp) +│ ├── protocol.rs Git 智能传输协议 (HTTP + pkt-line + packfile) +│ ├── remote_utils.rs 网络命令共享工具 +│ ├── checkout.rs 分支切换与树恢复 +│ ├── merge.rs 三方合并 + fast-forward + 冲突标记 +│ ├── ignore.rs .gitignore 解析器 +│ ├── objects/ Git 对象 +│ │ ├── blob.rs Blob 对象 +│ │ ├── tree.rs Tree 对象 +│ │ ├── commit.rs Commit 对象 +│ │ └── tag.rs Tag 对象 (feature-gated) +│ ├── config/mod.rs 配置 (环境变量 > 文件 > 默认值) +│ └── utils/ +│ └── error.rs 错误类型 (AgitError) +├── agit-ai/ (lib) +│ └── src/lib.rs LlmConfig、chat_completion、generate_commit_message +└── agit-cli/ (bin → agit) + ├── Cargo.toml 依赖:agit-core, agit-ai + ├── src/ + │ ├── main.rs 入口点 + cat-file dispatch + │ ├── cli/mod.rs clap 命令定义 (29 个子命令) + │ ├── commands/ 命令实现 (每个命令一个文件) + │ ├── ai/mod.rs AI 模式标志 + DANGEROUS_COMMANDS 列表 + │ └── output/mod.rs JSON/YAML/颜色 模式标志 + 输出格式化 + └── tests/ (集成测试) ``` ## 核心模块详解 @@ -184,15 +200,26 @@ pub enum AgitError { ## 技术栈 -| 依赖 | 用途 | -|------|------| -| sha1 0.10 | SHA-1 哈希 | -| flate2 1 | zlib 压缩 | -| clap 4 | CLI 解析 | -| serde 1 + serde_json + serde_yaml | 结构化输出 | -| anyhow 1 | 错误处理 | -| url 2 | URL 解析 | -| native-tls 0.2 | TLS/HTTPS | +### Crate 分层 + +``` +agit-cli (bin) → agit-ai (lib) AI 功能 + → agit-core (lib) Git 核心逻辑 +agit-ai (lib) → reqwest HTTP 客户端 (LLM API) +agit-core (lib) → (pure Rust, no external Git dep) +``` + +| 依赖 | 用途 | 所属 | +|------|------|------| +| sha1 0.10 | SHA-1 哈希 | agit-core | +| flate2 1 | zlib 压缩 | agit-core | +| clap 4 | CLI 解析 | agit-cli | +| serde 1 + serde_json + serde_yaml | 结构化输出 | agit-cli | +| anyhow 1 | 错误处理 | agit-core / agit-cli | +| url 2 | URL 解析 | agit-core | +| native-tls 0.2 | TLS/HTTPS | agit-core | +| reqwest 0.11 | HTTP 客户端 | agit-ai | +| tokio 1 | 异步运行时 | agit-ai | ## 实现阶段 @@ -201,7 +228,8 @@ pub enum AgitError { | v0.1.0 | Phase 1-2: 项目骨架 + 核心对象 | ✅ | | v0.2.0 | Phase 3-4: 本地命令 + AI 模式 | ✅ | | v0.3.0 | Phase 5: 网络功能 | ✅ | -| v0.4.1 | Tag + 配置文件 + 集成测试 + 分支切换清理 | ✅ 当前 | +| v0.4.1 | Tag + 配置文件 + 集成测试 + 分支切换清理 | ✅ | +| v0.14.0 | Workspace 拆分 (agit-core/agit-ai/agit-cli) + AI 提交信息 + 29 子命令 | ✅ 当前 | ## 参考资料