chore(release): prepare public source release

This commit is contained in:
MercuryToolbox Release
2026-07-18 15:41:59 +08:00
commit e365e5df4d
508 changed files with 163373 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
[package]
name = "gitshape"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
authors.workspace = true
readme.workspace = true
publish.workspace = true
description = "Summarize Git repository state into compact branch and change counters."
keywords.workspace = true
categories.workspace = true
[lints]
workspace = true
[dependencies]
configsupport = { path = "../configsupport" }
lexopt.workspace = true
serde.workspace = true
[dev-dependencies]
assert_cmd.workspace = true
predicates.workspace = true
tempfile.workspace = true
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
//! Binary entry point for `gitshape`.
fn main() {
std::process::exit(gitshape::main_entry());
}
+288
View File
@@ -0,0 +1,288 @@
//! Integration tests for the `gitshape` command.
use std::fs;
use std::process::Command as ProcessCommand;
use assert_cmd::Command;
use predicates::prelude::*;
use tempfile::tempdir;
fn cargo_command() -> Command {
Command::cargo_bin("gitshape").expect("binary")
}
#[test]
fn status_subcommand_summarizes_dirty_repository_as_json() {
let temp = tempdir().expect("tempdir");
run_git(temp.path(), ["-c", "init.defaultBranch=main", "init"]);
run_git(temp.path(), ["config", "user.name", "Codex"]);
run_git(temp.path(), ["config", "user.email", "codex@example.com"]);
fs::write(temp.path().join("tracked.txt"), "first\n").expect("tracked");
run_git(temp.path(), ["add", "tracked.txt"]);
run_git(temp.path(), ["commit", "-m", "init"]);
fs::write(temp.path().join("tracked.txt"), "second\n").expect("modify");
fs::write(temp.path().join("new.txt"), "new\n").expect("untracked");
let mut command = cargo_command();
command
.arg("--json")
.arg("status")
.arg("--repo")
.arg(temp.path())
.assert()
.success()
.stdout(predicate::str::contains("\"modified\":1"))
.stdout(predicate::str::contains("\"untracked\":1"))
.stdout(predicate::str::contains("\"clean\":false"));
}
#[test]
fn diff_subcommand_reports_staged_change_summary() {
let temp = tempdir().expect("tempdir");
run_git(temp.path(), ["init"]);
run_git(temp.path(), ["config", "user.name", "Codex"]);
run_git(temp.path(), ["config", "user.email", "codex@example.com"]);
fs::write(
temp.path().join("src.rs"),
"fn build() {\n println!(\"old\");\n}\n",
)
.expect("src");
run_git(temp.path(), ["add", "src.rs"]);
run_git(temp.path(), ["commit", "-m", "init"]);
fs::write(
temp.path().join("src.rs"),
"fn build() {\n println!(\"new\");\n}\n",
)
.expect("modify");
run_git(temp.path(), ["add", "src.rs"]);
let mut command = cargo_command();
command
.arg("--json")
.arg("diff")
.arg("--repo")
.arg(temp.path())
.arg("--staged")
.assert()
.success()
.stdout(predicate::str::contains("\"mode\":\"staged\""))
.stdout(predicate::str::contains("\"files_changed\":1"))
.stdout(predicate::str::contains("\"status\":\"modified\""));
}
#[test]
fn status_subcommand_handles_unborn_repository() {
let temp = tempdir().expect("tempdir");
run_git(temp.path(), ["-c", "init.defaultBranch=main", "init"]);
let mut command = cargo_command();
command
.arg("--json")
.arg("status")
.arg("--repo")
.arg(temp.path())
.assert()
.success()
.stdout(predicate::str::contains("\"branch\":\"main\""))
.stdout(predicate::str::contains("\"head\":\"-\""))
.stdout(predicate::str::contains("\"clean\":true"));
}
#[test]
fn diff_subcommand_accepts_explicit_revisions() {
let temp = tempdir().expect("tempdir");
run_git(temp.path(), ["init"]);
run_git(temp.path(), ["config", "user.name", "Codex"]);
run_git(temp.path(), ["config", "user.email", "codex@example.com"]);
fs::write(temp.path().join("demo.txt"), "one\n").expect("demo");
run_git(temp.path(), ["add", "demo.txt"]);
run_git(temp.path(), ["commit", "-m", "first"]);
let first = run_git_capture(temp.path(), ["rev-parse", "--short", "HEAD"]);
fs::write(temp.path().join("demo.txt"), "one\ntwo\n").expect("update");
run_git(temp.path(), ["add", "demo.txt"]);
run_git(temp.path(), ["commit", "-m", "second"]);
let second = run_git_capture(temp.path(), ["rev-parse", "--short", "HEAD"]);
let mut command = cargo_command();
command
.arg("--json")
.arg("diff")
.arg("--repo")
.arg(temp.path())
.arg(first)
.arg(second)
.assert()
.success()
.stdout(predicate::str::contains("\"mode\":\"revisions\""))
.stdout(predicate::str::contains("\"revisions\":["))
.stdout(predicate::str::contains("\"files_changed\":1"));
}
#[test]
fn diff_subcommand_returns_success_for_empty_revision_diff() {
let temp = tempdir().expect("tempdir");
run_git(temp.path(), ["init"]);
run_git(temp.path(), ["config", "user.name", "Codex"]);
run_git(temp.path(), ["config", "user.email", "codex@example.com"]);
fs::write(temp.path().join("demo.txt"), "one\n").expect("demo");
run_git(temp.path(), ["add", "demo.txt"]);
run_git(temp.path(), ["commit", "-m", "first"]);
let mut command = cargo_command();
command
.arg("diff")
.arg("--repo")
.arg(temp.path())
.arg("HEAD")
.arg("HEAD")
.assert()
.success()
.stdout(predicate::str::contains("mode=revisions"))
.stdout(predicate::str::contains("files=0"))
.stdout(predicate::str::contains("line_additions=0"))
.stdout(predicate::str::contains("line_deletions=0"));
}
#[test]
fn help_includes_new_subcommands_and_flags() {
let mut command = cargo_command();
command
.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("gitshape [OPTIONS] status"))
.stdout(predicate::str::contains("gitshape [OPTIONS] diff"))
.stdout(predicate::str::contains("--repo"))
.stdout(predicate::str::contains("--staged"))
.stdout(predicate::str::contains("--ignored"))
.stdout(predicate::str::contains("gitshape"));
}
#[test]
fn bare_path_defaults_to_status_mode() {
let temp = tempdir().expect("tempdir");
run_git(temp.path(), ["-c", "init.defaultBranch=main", "init"]);
let mut command = cargo_command();
command
.arg("--json")
.arg(temp.path())
.assert()
.success()
.stdout(predicate::str::contains("\"repository_present\":true"))
.stdout(predicate::str::contains("\"branch\":\"main\""));
}
#[test]
fn status_reports_missing_repository_without_failing() {
let temp = tempdir().expect("tempdir");
let mut command = cargo_command();
command
.arg("--json")
.arg("status")
.arg("--repo")
.arg(temp.path())
.assert()
.success()
.stdout(predicate::str::contains("\"repository_present\":false"))
.stdout(predicate::str::contains(
"\"error\":\"fatal: not a git repository",
));
}
#[test]
fn diff_subcommand_rejects_staged_with_explicit_revisions() {
let mut command = cargo_command();
command
.arg("diff")
.arg("--staged")
.arg("HEAD")
.assert()
.failure()
.stderr(predicate::str::contains(
"diff --staged does not accept explicit revisions",
));
}
#[test]
fn status_subcommand_rejects_repo_and_positional_path_together() {
let temp = tempdir().expect("tempdir");
let mut command = cargo_command();
command
.arg("status")
.arg("--repo")
.arg(temp.path())
.arg(temp.path())
.assert()
.failure()
.stderr(predicate::str::contains(
"status accepts either --repo <PATH> or positional [PATH], not both",
));
}
#[test]
fn diff_subcommand_text_output_renders_rename_details() {
let temp = tempdir().expect("tempdir");
run_git(temp.path(), ["init"]);
run_git(temp.path(), ["config", "user.name", "Codex"]);
run_git(temp.path(), ["config", "user.email", "codex@example.com"]);
fs::write(
temp.path().join("old_name.rs"),
"fn rename_me() {\n println!(\"old\");\n}\n",
)
.expect("old file");
run_git(temp.path(), ["add", "old_name.rs"]);
run_git(temp.path(), ["commit", "-m", "before rename"]);
let first = run_git_capture(temp.path(), ["rev-parse", "--short", "HEAD"]);
run_git(temp.path(), ["mv", "old_name.rs", "new_name.rs"]);
fs::write(
temp.path().join("new_name.rs"),
"fn rename_me() {\n println!(\"old\");\n}\nfn helper() {}\n",
)
.expect("new file");
run_git(temp.path(), ["add", "new_name.rs"]);
run_git(temp.path(), ["commit", "-m", "after rename"]);
let second = run_git_capture(temp.path(), ["rev-parse", "--short", "HEAD"]);
let mut command = cargo_command();
command
.arg("diff")
.arg("--repo")
.arg(temp.path())
.arg(first)
.arg(second)
.assert()
.success()
.stdout(predicate::str::contains("mode=revisions"))
.stdout(predicate::str::contains("status=renamed"))
.stdout(predicate::str::contains("path=new_name.rs"))
.stdout(predicate::str::contains("previous=old_name.rs"));
}
fn run_git<const N: usize>(cwd: &std::path::Path, args: [&str; N]) {
let status = ProcessCommand::new("git")
.current_dir(cwd)
.args(args)
.status()
.expect("git command");
assert!(status.success(), "git command failed");
}
fn run_git_capture<const N: usize>(cwd: &std::path::Path, args: [&str; N]) -> String {
let output = ProcessCommand::new("git")
.current_dir(cwd)
.args(args)
.output()
.expect("git command output");
assert!(output.status.success(), "git command failed");
String::from_utf8(output.stdout)
.expect("utf8")
.trim()
.to_string()
}