feat(ginit): 新增一键初始化本地 Git 仓库命令
`quicommit ginit`(别名 `gi`)离线完成仓库起步:用 git2 建库并确立初始分支、 写最简 README.md、按根目录标志文件装配 .gitignore、按 --add 添加远程, `--profile` 可选套用已有 Git 配置。在已有仓库上重复执行只补齐缺失项:绝不覆盖 既有的 README.md/.gitignore、绝不改动已有 remote 的地址、不提供 --force、 不创建首个提交。边界取舍见 ADR 0006,命令命名见 ADR 0005。 实现落在新的 src/git/init.rs(9 个 git 层函数)与 src/commands/ginit.rs, 命令层不直接引用 git2,GitRepo 公开接口未改动,其余命令行为不变。 测试:src/git/init.rs 11 条单元测试、tests/ginit_tests.rs 18 条集成测试; 新增 35 个 ginit_* 消息覆盖 7 种语言。 另将 CONTEXT.md 与 docs/adr/ 纳入版本跟踪(.gitignore 放开 adr/ 与 CONTEXT.md 两条忽略规则);RAODMAP.md 除 ginit 条目外还包含此前未提交的 规划条目更新。
This commit is contained in:
5
docs/adr/0001-multi-config-version-resolution.md
Normal file
5
docs/adr/0001-multi-config-version-resolution.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Multi-config version resolution for --auto mode
|
||||
|
||||
When `--auto` detects the project version, it reads from project config files (e.g. Cargo.toml, package.json). We extended this from 2 formats to 16. 11 formats are structured (JSON, TOML, XML), 5 are code-as-config (Ruby, Scala, Clojure, Elixir, CMake/Meson). We use structured parsers for the first group (adding only `roxmltree` for XML — YAML formats use regex to avoid a new dep) and regex for the second. When multiple config files coexist in the project root, we surface all found versions and let the user pick — rather than imposing a fixed priority order. A `go.mod` file causes an immediate skip to commit-analysis fallback (Go defines versions via git tags, not in go.mod). Only the project root is searched; subdirectories are not recursed.
|
||||
|
||||
**Considered Options**: A hardcoded priority list (e.g. Cargo.toml > package.json > …) was rejected because no ordering satisfies every polyglot project. Letting the user choose adds one interaction in the rare multi-config case and avoids surprising wrong picks.
|
||||
7
docs/adr/0002-llm-providers-on-rig.md
Normal file
7
docs/adr/0002-llm-providers-on-rig.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# LLM provider layer built on rig-core instead of hand-written clients
|
||||
|
||||
QuiCommit's LLM layer (commit/tag/changelog generation) moved from six hand-written HTTP clients to `rig-core` 0.41 (locked, `reqwest` + `rustls` features only, umbrella `rig` crate not used). Rationale: the hand-written layer accumulated duplicated SSE parsing, string-matching retry logic, inconsistent per-provider timeouts, and provider-specific bugs (Anthropic silently ignoring configured base_url, Ollama constructor panics); rig-core 0.41 natively supports all six providers and models reasoning/thinking as first-class typed content (streaming `Reasoning`/`ReasoningDelta` events), which matches QuiCommit's thinking-mode display feature. In exchange we accept rig's 0.x breaking-change velocity (upgrades require manual provider-diff review), no application-level retry on non-streaming requests, and the Ollama endpoint moving to `/api/chat`.
|
||||
|
||||
**Considered Options**: keep hand-writing (rejected — every new provider meant a full HTTP/SSE/retry suite; six near-duplicate implementations already); per-provider SDKs like async-openai plus hand-written rest (rejected — multiple API styles, no unified interface); rig-core (accepted — one dependency covers every current provider and normalizes reasoning content across them).
|
||||
|
||||
**Consequences**: user-visible behavior changes recorded in CHANGELOG for 0.6.0 (Ollama `/api/chat`, no non-streaming retry, credential-verify endpoint changes, Anthropic base_url now honored); rig-core upgrades must re-verify provider API changes before bumping the locked version.
|
||||
7
docs/adr/0003-output-channels-and-message-prefixes.md
Normal file
7
docs/adr/0003-output-channels-and-message-prefixes.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Output channels, message prefixes, and the decoration switch
|
||||
|
||||
QuiCommit's user-facing output follows fixed conventions: stdout carries results and interactive content, stderr carries diagnostics; the hidden `credential` subcommand keeps stdout protocol-only (errors on stderr prefixed `[quicommit credential]`). Message text — `Messages` keys and inline command strings alike — never embeds emoji or status symbols; decoration prefixes (✓/✗/⚠/→, spinner frames) are applied only by the shared `utils` print helpers, so they can be toggled centrally. Decoration is configurable via `output.emoji` (bool, default on) with global `--emoji`/`--no-emoji` overrides (precedence: explicit flag > config > default); `--no-color`/`NO_COLOR` is highest priority and disables both color and decoration. When decorations are off, output is plain text with no severity labels — errors stay distinguishable via stderr and anyhow's `Error:` prefix. Emoji is not auto-disabled on non-tty (explicit control only), while color keeps `colored`'s existing tty auto-detection.
|
||||
|
||||
**Considered Options**: emoji embedded in message text per language (rejected — decoration duplicated across 7 languages, half-on states when toggling, and two competing styles already coexisted in the codebase); text severity labels (`Warning:`/`Error:`) substituted when decorations are off (rejected — 7-language label burden for marginal value; stream separation already distinguishes errors); auto-disabling emoji on non-tty (rejected — emoji is a content-level choice, not a terminal capability, and script authors get explicit control via `--no-emoji`).
|
||||
|
||||
**Consequences**: every future command author must route output through the helpers and keep message text decoration-free. Existing tests pinning English output stay valid because the default language is English. `--no-color` now also strips decorations — a behavior change from 0.6.x, where the flag was parsed but ignored (a review finding).
|
||||
7
docs/adr/0004-yes-only-skips-confirmation.md
Normal file
7
docs/adr/0004-yes-only-skips-confirmation.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# --yes only skips confirmation; explicit --no-generate for deterministic output
|
||||
|
||||
`--yes`/`-y` on commit, tag, and changelog now means only "skip interactive prompts" — generation behavior follows config and flags exactly as it would interactively. Previously `commit --yes` still AI-generated, while `tag --yes`/`changelog --yes` silently downgraded to a hardcoded `Release vX` message or template output when auto-generation was enabled — an undocumented three-way inconsistency. Deterministic non-AI output is now explicit: `tag --yes -m <message>` (the existing message flag) or `changelog --no-generate` (new flag, template-based). The tag message fallback (`Release {version}`) moves into `Messages` so it is localized.
|
||||
|
||||
**Considered Options**: keep the silent downgrade and only document it (rejected — a flag that claims to skip prompts must not also change what gets generated; it broke CI differently per command); make `--yes` always generate with no explicit opt-out (rejected — CI users need a deterministic, key-free path, which `--no-generate`/`-m` provide).
|
||||
|
||||
**Consequences**: users who relied on `tag --yes`/`changelog --yes` to avoid LLM calls must switch to `--no-generate` (changelog) or `-m` (tag). Help text and README update accordingly.
|
||||
7
docs/adr/0005-init-and-ginit-stay-separate.md
Normal file
7
docs/adr/0005-init-and-ginit-stay-separate.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# `init` and `ginit` stay two commands, one letter apart
|
||||
|
||||
QuiCommit's existing `init` bootstraps QuiCommit's own configuration (`config.toml`: LLM provider plus a first Git Profile), while the new `ginit` bootstraps a local Git repository in the current directory. The two names differ by a single character and appear next to each other in `quicommit --help`. We kept both rather than unifying them, because the CLI surface is public API: renaming `init` or folding repo creation into it would break existing users, existing alias tests, and ADR 0004's promise that `--yes` behaves identically per command. Disambiguation is carried by help text only — "Initialize quicommit configuration" versus "Initialize a local Git repository" — and by the CONTEXT.md pair **Config init** / **Repo init**.
|
||||
|
||||
**Considered Options**: rename the config flow to `config init` and hand `init` to repo creation (rejected — breaks existing users and their scripts, which is exactly the blast radius this release was scoped to avoid); nest it as `quicommit repo init` (rejected — would introduce a new top-level command group for a single command); pick a longer name like `gitinit` or `git-init` (rejected — no clearer than `ginit`, and less consistent with the existing terse alias set `i`/`c`/`t`/`cl`/`p`/`cfg`).
|
||||
|
||||
**Consequences**: `gi` is added as an alias, and every doc that lists commands must show the two help strings adjacent so the contrast is visible. Anyone later tempted to "clean up" this near-collision should read this ADR first: it is deliberate.
|
||||
7
docs/adr/0006-ginit-prepares-repo-and-stops-there.md
Normal file
7
docs/adr/0006-ginit-prepares-repo-and-stops-there.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# ginit prepares a repository and stops there
|
||||
|
||||
`quicommit ginit` writes `.git`, an Initial branch, `README.md`, `.gitignore` and Remote entries — and deliberately does not: create a first commit, apply a Git Profile unless `--profile` is passed, append anything to a pre-existing `README.md` or `.gitignore`, reuse QuiCommit's own root `.gitignore` as the template, contact a hosting API to create a remote repository, verify remote reachability, or set upstream tracking. Each of these was within reach and would have made the command look more capable, but all of them either destroy the property that makes `ginit` safe to run anywhere ("it only ever adds, never overwrites or deletes") or would silently pull the LLM pipeline, the network, and credential scopes into a command whose whole job is offline repo scaffolding. There is also no `--force`: `git init` is already idempotent on an existing repository, so the only thing `--force` could mean is deleting `.git`.
|
||||
|
||||
**Considered Options**: commit `README.md` + `.gitignore` as `chore: initial commit` (rejected — the user's week-old source files stay untracked, producing a repository that looks complete but is missing files, and it couples a local-only command to commit identity and message generation); apply `default_profile` automatically (rejected — it would rewrite signing keys and `core.sshCommand` in a repo the user did not ask us to configure, so the intent must be explicit); merge missing entries into an existing `.gitignore` (rejected — deciding whether `target/` is already covered means string-matching `/target`, `**/target/`, and directory rules, and a wrong match writes into a file the user maintains by hand); seed the template from the repository's own `.gitignore` (rejected — it carries QuiCommit-specific development artifacts: it ignores `Cargo.lock`, `config.toml`, `CONTEXT.md`, `adr/`, `.claude/`, plus `# OS` and `# IDE` sections that the roadmap entry explicitly rules out).
|
||||
|
||||
**Consequences**: after `ginit`, the first commit is still the user's action via `quicommit commit`, so `ginit` must print next-step guidance and must warn when no `user.name`/`user.email` can be resolved from either the repository or global config — otherwise the very next command fails with an error the user will blame on `ginit`. Users who want `.gitignore` entries added to an existing file must edit it themselves. Repo-creation capability lives in a new `src/git/init.rs` rather than on `GitRepo`, whose `open` diagnostic chain is built for repositories that already exist.
|
||||
Reference in New Issue
Block a user