- 仓库未配置任何远程时跳过推送询问并打印说明(--push 显式指定同样跳过) - --remote 指定的目标不存在时回退到第一个可用远程(字母序)并明确提示 - 新增 --select-remote:多远程仓库弹出多选列表依次推送,失败即中止并汇总已推送/未尝试 - 优先级链:--yes > --select-remote > --push - commit/tag 推送段收敛为 commands::run_push_flow 共享流程 - GitRepo 新增 list_remotes/resolve_push_target,i18n 新文案覆盖 7 种语言
1599 lines
48 KiB
Rust
1599 lines
48 KiB
Rust
use assert_cmd::cargo::cargo_bin_cmd;
|
|
use predicates::prelude::*;
|
|
use std::fs;
|
|
use std::path::PathBuf;
|
|
use tempfile::TempDir;
|
|
|
|
fn create_git_repo(dir: &PathBuf) -> std::process::Output {
|
|
std::process::Command::new("git")
|
|
.args(&["init"])
|
|
.current_dir(dir)
|
|
.output()
|
|
.expect("Failed to init git repo")
|
|
}
|
|
|
|
fn configure_git_user(dir: &PathBuf) {
|
|
std::process::Command::new("git")
|
|
.args(&["config", "user.name", "Test User"])
|
|
.current_dir(dir)
|
|
.output()
|
|
.expect("Failed to configure git user name");
|
|
|
|
std::process::Command::new("git")
|
|
.args(&["config", "user.email", "test@example.com"])
|
|
.current_dir(dir)
|
|
.output()
|
|
.expect("Failed to configure git user email");
|
|
}
|
|
|
|
fn create_test_file(dir: &PathBuf, name: &str, content: &str) {
|
|
let file_path = dir.join(name);
|
|
fs::write(&file_path, content).expect("Failed to create test file");
|
|
}
|
|
|
|
fn stage_file(dir: &PathBuf, name: &str) {
|
|
std::process::Command::new("git")
|
|
.args(&["add", name])
|
|
.current_dir(dir)
|
|
.output()
|
|
.expect("Failed to stage file");
|
|
}
|
|
|
|
fn create_commit(dir: &PathBuf, message: &str) {
|
|
std::process::Command::new("git")
|
|
.args(&["commit", "-m", message])
|
|
.current_dir(dir)
|
|
.output()
|
|
.expect("Failed to create commit");
|
|
}
|
|
|
|
fn setup_git_repo(dir: &PathBuf) {
|
|
create_git_repo(dir);
|
|
configure_git_user(dir);
|
|
}
|
|
|
|
fn setup_test_repo_with_file(dir: &PathBuf, file_name: &str, file_content: &str) {
|
|
setup_git_repo(dir);
|
|
create_test_file(dir, file_name, file_content);
|
|
stage_file(dir, file_name);
|
|
}
|
|
|
|
fn init_quicommit(dir: &PathBuf, config_path: &PathBuf) {
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()])
|
|
.current_dir(dir);
|
|
cmd.assert().success();
|
|
}
|
|
|
|
mod cli_basic {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_help() {
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.arg("--help");
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("QuiCommit"))
|
|
.stdout(predicate::str::contains("AI-powered Git assistant"))
|
|
.stdout(predicate::str::contains("Usage:"))
|
|
.stdout(predicate::str::contains("Commands:"))
|
|
.stdout(predicate::str::contains("Options:"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_version() {
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.arg("--version");
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("quicommit"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_no_args_shows_help() {
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.assert()
|
|
.failure()
|
|
.stderr(predicate::str::contains("Usage:"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_verbose_flag() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
let config_path = repo_path.join("config.toml");
|
|
create_git_repo(&repo_path);
|
|
configure_git_user(&repo_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"-vv",
|
|
"init",
|
|
"--yes",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert().success();
|
|
}
|
|
}
|
|
|
|
mod init_command {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_init_quick() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let config_path = temp_dir.path().join("config.toml");
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("initialized successfully"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_init_creates_config_file() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let config_path = temp_dir.path().join("config.toml");
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);
|
|
|
|
cmd.assert().success();
|
|
|
|
assert!(config_path.exists(), "Config file should be created");
|
|
}
|
|
|
|
#[test]
|
|
fn test_init_in_git_repo() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
create_git_repo(&repo_path);
|
|
configure_git_user(&repo_path);
|
|
|
|
let config_path = repo_path.join("test_config.toml");
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert().success();
|
|
}
|
|
|
|
#[test]
|
|
fn test_init_reset() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let config_path = temp_dir.path().join("config.toml");
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);
|
|
cmd.assert().success();
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"init",
|
|
"--yes",
|
|
"--reset",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
]);
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("initialized successfully"));
|
|
}
|
|
}
|
|
|
|
mod profile_command {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_profile_list_empty() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let config_path = temp_dir.path().join("config.toml");
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&["profile", "list", "--config", config_path.to_str().unwrap()]);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("No profiles"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_profile_list_with_profile() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let config_path = temp_dir.path().join("config.toml");
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);
|
|
cmd.assert().success();
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&["profile", "list", "--config", config_path.to_str().unwrap()]);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("default"));
|
|
}
|
|
}
|
|
|
|
mod config_command {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_config_show() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let config_path = temp_dir.path().join("config.toml");
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);
|
|
cmd.assert().success();
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&["config", "show", "--config", config_path.to_str().unwrap()]);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("Configuration"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_path() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let config_path = temp_dir.path().join("config.toml");
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);
|
|
cmd.assert().success();
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&["config", "path", "--config", config_path.to_str().unwrap()]);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("config.toml"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_no_color_disables_ansi() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let config_path = temp_dir.path().join("config.toml");
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);
|
|
cmd.assert().success();
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"config",
|
|
"show",
|
|
"--no-color",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
]);
|
|
|
|
let output = cmd.output().unwrap();
|
|
assert!(output.status.success());
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
assert!(
|
|
!stdout.contains("\x1b["),
|
|
"ANSI color codes present despite --no-color"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_emoji_switch_precedence() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let config_path = temp_dir.path().join("config.toml");
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.env_remove("NO_COLOR");
|
|
cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);
|
|
cmd.assert().success();
|
|
|
|
let set_format = |extra: &[&str], cfg: &std::path::Path| {
|
|
let mut c = cargo_bin_cmd!("quicommit");
|
|
c.env_remove("NO_COLOR");
|
|
c.args(&["config", "set", "commit.format", "conventional"])
|
|
.args(extra)
|
|
.args(&["--config", cfg.to_str().unwrap()]);
|
|
c.output().unwrap()
|
|
};
|
|
|
|
// Default (config true): decorations shown
|
|
let out = set_format(&[], &config_path);
|
|
assert!(out.status.success());
|
|
assert!(String::from_utf8_lossy(&out.stdout).contains("✓"));
|
|
|
|
// Config output.emoji=false: no decorations
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.env_remove("NO_COLOR");
|
|
cmd.args(&[
|
|
"config",
|
|
"set",
|
|
"output.emoji",
|
|
"false",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
]);
|
|
cmd.assert().success();
|
|
|
|
let out = set_format(&[], &config_path);
|
|
assert!(out.status.success());
|
|
assert!(!String::from_utf8_lossy(&out.stdout).contains("✓"));
|
|
|
|
// Explicit --emoji overrides config false
|
|
let out = set_format(&["--emoji"], &config_path);
|
|
assert!(out.status.success());
|
|
assert!(String::from_utf8_lossy(&out.stdout).contains("✓"));
|
|
|
|
// Config back to true, then --no-emoji overrides
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.env_remove("NO_COLOR");
|
|
cmd.args(&[
|
|
"config",
|
|
"set",
|
|
"output.emoji",
|
|
"true",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
]);
|
|
cmd.assert().success();
|
|
|
|
let out = set_format(&["--no-emoji"], &config_path);
|
|
assert!(out.status.success());
|
|
assert!(!String::from_utf8_lossy(&out.stdout).contains("✓"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_chinese_output_smoke() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
setup_test_repo_with_file(&repo_path, "test.txt", "content");
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.env_remove("NO_COLOR");
|
|
cmd.args(&[
|
|
"config",
|
|
"set",
|
|
"language.output_language",
|
|
"zh",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
]);
|
|
cmd.assert().success();
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.env_remove("NO_COLOR");
|
|
cmd.args(&[
|
|
"commit",
|
|
"--manual",
|
|
"-m",
|
|
"feat: 中文冒烟",
|
|
"--dry-run",
|
|
"--yes",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("试运行"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_rust_log_env() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let config_path = temp_dir.path().join("config.toml");
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.env_remove("NO_COLOR");
|
|
cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);
|
|
cmd.assert().success();
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.env_remove("NO_COLOR");
|
|
cmd.env("RUST_LOG", "debug");
|
|
cmd.args(&["config", "path", "--config", config_path.to_str().unwrap()]);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("Starting quicommit"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_auto_generate_defaults_true() {
|
|
// Documents the unified --yes semantics (issue 05): with default
|
|
// config, --yes still AI-generates; deterministic output needs
|
|
// --no-generate (changelog) or -m (tag).
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let config_path = temp_dir.path().join("config.toml");
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.env_remove("NO_COLOR");
|
|
cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);
|
|
cmd.assert().success();
|
|
|
|
for key in [
|
|
"commit.auto_generate",
|
|
"tag.auto_generate",
|
|
"changelog.auto_generate",
|
|
] {
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.env_remove("NO_COLOR");
|
|
cmd.args(&[
|
|
"config",
|
|
"get",
|
|
key,
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
]);
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("true"));
|
|
}
|
|
}
|
|
}
|
|
|
|
mod commit_command {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_commit_no_repo() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let config_path = temp_dir.path().join("config.toml");
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);
|
|
cmd.assert().success();
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"commit",
|
|
"--dry-run",
|
|
"--yes",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(temp_dir.path());
|
|
|
|
cmd.assert()
|
|
.failure()
|
|
.stderr(predicate::str::contains("git").or(predicate::str::contains("repository")));
|
|
}
|
|
|
|
#[test]
|
|
fn test_commit_no_changes() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
setup_git_repo(&repo_path);
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"commit",
|
|
"--manual",
|
|
"-m",
|
|
"test: empty",
|
|
"--dry-run",
|
|
"--yes",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("Dry run"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_commit_with_staged_changes() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
setup_test_repo_with_file(&repo_path, "test.txt", "Hello, World!");
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"commit",
|
|
"--manual",
|
|
"-m",
|
|
"test: add test file",
|
|
"--dry-run",
|
|
"--yes",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("Dry run"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_commit_date_mode() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
setup_test_repo_with_file(&repo_path, "daily.txt", "Daily update");
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"commit",
|
|
"--date",
|
|
"--dry-run",
|
|
"--yes",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("Dry run"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_commit_with_think_flag() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
setup_test_repo_with_file(&repo_path, "test.txt", "Hello, World!");
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"commit",
|
|
"--think",
|
|
"--manual",
|
|
"-m",
|
|
"test: think flag",
|
|
"--dry-run",
|
|
"--yes",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert().success();
|
|
}
|
|
}
|
|
|
|
mod tag_command {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_tag_no_repo() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let config_path = temp_dir.path().join("config.toml");
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);
|
|
cmd.assert().success();
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"tag",
|
|
"--dry-run",
|
|
"--yes",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(temp_dir.path());
|
|
|
|
cmd.assert()
|
|
.failure()
|
|
.stderr(predicate::str::contains("git").or(predicate::str::contains("repository")));
|
|
}
|
|
|
|
#[test]
|
|
fn test_tag_list_empty() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
setup_git_repo(&repo_path);
|
|
|
|
create_test_file(&repo_path, "test.txt", "content");
|
|
stage_file(&repo_path, "test.txt");
|
|
create_commit(&repo_path, "feat: initial commit");
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"tag",
|
|
"--name",
|
|
"v0.1.0",
|
|
"-m",
|
|
"Release v0.1.0",
|
|
"--dry-run",
|
|
"--yes",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("v0.1.0"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_tag_with_think_flag() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
setup_git_repo(&repo_path);
|
|
|
|
create_test_file(&repo_path, "test.txt", "content");
|
|
stage_file(&repo_path, "test.txt");
|
|
create_commit(&repo_path, "feat: initial commit");
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"tag",
|
|
"--think",
|
|
"--name",
|
|
"v0.2.0",
|
|
"-m",
|
|
"Release v0.2.0",
|
|
"--dry-run",
|
|
"--yes",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert().success();
|
|
}
|
|
}
|
|
|
|
mod changelog_command {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_changelog_init() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
setup_git_repo(&repo_path);
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
let changelog_path = repo_path.join("CHANGELOG.md");
|
|
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"changelog",
|
|
"--init",
|
|
"--output",
|
|
changelog_path.to_str().unwrap(),
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert().success();
|
|
|
|
assert!(changelog_path.exists(), "Changelog file should be created");
|
|
}
|
|
|
|
#[test]
|
|
fn test_changelog_dry_run() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
setup_git_repo(&repo_path);
|
|
|
|
create_test_file(&repo_path, "test.txt", "content");
|
|
stage_file(&repo_path, "test.txt");
|
|
create_commit(&repo_path, "feat: add feature");
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"changelog",
|
|
"--dry-run",
|
|
"--yes",
|
|
"--no-generate",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert().success();
|
|
}
|
|
}
|
|
|
|
mod cross_platform {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_path_handling_windows_style() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let config_path = temp_dir.path().join("subdir").join("config.toml");
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);
|
|
|
|
cmd.assert().success();
|
|
assert!(config_path.exists());
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_with_spaces_in_path() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let space_dir = temp_dir.path().join("path with spaces");
|
|
fs::create_dir_all(&space_dir).unwrap();
|
|
let config_path = space_dir.join("config.toml");
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);
|
|
|
|
cmd.assert().success();
|
|
assert!(config_path.exists());
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_with_unicode_path() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let unicode_dir = temp_dir.path().join("路径测试");
|
|
fs::create_dir_all(&unicode_dir).unwrap();
|
|
let config_path = unicode_dir.join("config.toml");
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);
|
|
|
|
cmd.assert().success();
|
|
assert!(config_path.exists());
|
|
}
|
|
}
|
|
|
|
mod git_operations {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_git_repo_detection() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
create_git_repo(&repo_path);
|
|
configure_git_user(&repo_path);
|
|
|
|
let git_dir = repo_path.join(".git");
|
|
assert!(git_dir.exists(), ".git directory should exist");
|
|
}
|
|
|
|
#[test]
|
|
fn test_git_status_clean() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
create_git_repo(&repo_path);
|
|
configure_git_user(&repo_path);
|
|
|
|
let output = std::process::Command::new("git")
|
|
.args(&["status", "--porcelain"])
|
|
.current_dir(&repo_path)
|
|
.output()
|
|
.expect("Failed to run git status");
|
|
|
|
assert!(output.status.success());
|
|
assert!(String::from_utf8_lossy(&output.stdout).is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_git_commit_creation() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
create_git_repo(&repo_path);
|
|
configure_git_user(&repo_path);
|
|
|
|
create_test_file(&repo_path, "test.txt", "content");
|
|
stage_file(&repo_path, "test.txt");
|
|
create_commit(&repo_path, "feat: initial commit");
|
|
|
|
let output = std::process::Command::new("git")
|
|
.args(&["log", "--oneline"])
|
|
.current_dir(&repo_path)
|
|
.output()
|
|
.expect("Failed to run git log");
|
|
|
|
assert!(output.status.success());
|
|
let log = String::from_utf8_lossy(&output.stdout);
|
|
assert!(log.contains("initial commit"));
|
|
}
|
|
}
|
|
|
|
mod validators {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_commit_message_validation() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
setup_test_repo_with_file(&repo_path, "test.txt", "content");
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"commit",
|
|
"--manual",
|
|
"-m",
|
|
"invalid commit message without type",
|
|
"--dry-run",
|
|
"--yes",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert()
|
|
.failure()
|
|
.stderr(predicate::str::contains("Invalid").or(predicate::str::contains("format")));
|
|
}
|
|
|
|
#[test]
|
|
fn test_valid_conventional_commit() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
setup_test_repo_with_file(&repo_path, "test.txt", "content");
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"commit",
|
|
"--manual",
|
|
"-m",
|
|
"feat: add new feature",
|
|
"--dry-run",
|
|
"--yes",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("Dry run"));
|
|
}
|
|
}
|
|
|
|
mod subcommands {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_commit_alias() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
setup_test_repo_with_file(&repo_path, "test.txt", "content");
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"c",
|
|
"--manual",
|
|
"-m",
|
|
"fix: test",
|
|
"--dry-run",
|
|
"--yes",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("Dry run"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_init_alias() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let config_path = temp_dir.path().join("config.toml");
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&["i", "--yes", "--config", config_path.to_str().unwrap()]);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("initialized successfully"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_profile_alias() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let config_path = temp_dir.path().join("config.toml");
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);
|
|
cmd.assert().success();
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&["p", "list", "--config", config_path.to_str().unwrap()]);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("default"));
|
|
}
|
|
}
|
|
|
|
mod edge_cases {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_config_file_not_found() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let non_existent_config = temp_dir.path().join("non_existent_config.toml");
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"config",
|
|
"show",
|
|
"--config",
|
|
non_existent_config.to_str().unwrap(),
|
|
]);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("QuiCommit Configuration"))
|
|
.stdout(predicate::str::contains("Default profile: (none)"))
|
|
.stdout(predicate::str::contains("Profiles: 0"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_invalid_git_repo() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]);
|
|
cmd.assert().success();
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"commit",
|
|
"--dry-run",
|
|
"--yes",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert()
|
|
.failure()
|
|
.stderr(predicate::str::contains("git").or(predicate::str::contains("repository")));
|
|
}
|
|
|
|
#[test]
|
|
fn test_empty_commit_message() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
setup_test_repo_with_file(&repo_path, "test.txt", "content");
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"commit",
|
|
"--manual",
|
|
"-m",
|
|
"",
|
|
"--dry-run",
|
|
"--yes",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert().failure().stderr(predicate::str::contains(
|
|
"Invalid conventional commit format",
|
|
));
|
|
}
|
|
}
|
|
|
|
fn create_bare_remote(path: &std::path::Path) {
|
|
std::fs::create_dir(path).expect("Failed to create remote directory");
|
|
std::process::Command::new("git")
|
|
.args(["init", "--bare"])
|
|
.current_dir(path)
|
|
.output()
|
|
.expect("Failed to init bare remote");
|
|
}
|
|
|
|
fn add_remote(dir: &PathBuf, name: &str, url: &str) {
|
|
std::process::Command::new("git")
|
|
.args(["remote", "add", name, url])
|
|
.current_dir(dir)
|
|
.output()
|
|
.expect("Failed to add remote");
|
|
}
|
|
|
|
fn bare_head_commit(bare_path: &std::path::Path) -> Option<String> {
|
|
let output = std::process::Command::new("git")
|
|
.arg("--git-dir")
|
|
.arg(bare_path)
|
|
.args(["rev-parse", "HEAD"])
|
|
.output()
|
|
.expect("Failed to read bare repo HEAD");
|
|
if !output.status.success() {
|
|
return None;
|
|
}
|
|
Some(String::from_utf8_lossy(&output.stdout).trim().to_string())
|
|
}
|
|
|
|
fn bare_tag_exists(bare_path: &std::path::Path, tag: &str) -> bool {
|
|
std::process::Command::new("git")
|
|
.arg("--git-dir")
|
|
.arg(bare_path)
|
|
.args(["rev-parse", &format!("refs/tags/{}", tag)])
|
|
.output()
|
|
.expect("Failed to read remote tag")
|
|
.status
|
|
.success()
|
|
}
|
|
|
|
fn repo_head_commit(dir: &PathBuf) -> String {
|
|
let output = std::process::Command::new("git")
|
|
.args(["rev-parse", "HEAD"])
|
|
.current_dir(dir)
|
|
.output()
|
|
.expect("Failed to read repo HEAD");
|
|
String::from_utf8_lossy(&output.stdout).trim().to_string()
|
|
}
|
|
|
|
mod remote_aware_push {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_commit_no_remote_skips_push_prompt() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
setup_test_repo_with_file(&repo_path, "test.txt", "Hello, World!");
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"commit",
|
|
"--manual",
|
|
"-m",
|
|
"test: no remote",
|
|
"--yes",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("No remote configured, skipping push."));
|
|
}
|
|
|
|
#[test]
|
|
fn test_commit_no_remote_with_push_flag_skips_instead_of_failing() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
setup_test_repo_with_file(&repo_path, "test.txt", "Hello, World!");
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"commit",
|
|
"--manual",
|
|
"-m",
|
|
"test: no remote with push",
|
|
"--yes",
|
|
"--push",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("No remote configured, skipping push."))
|
|
.stdout(predicate::str::contains("Push failed").not());
|
|
}
|
|
|
|
#[test]
|
|
fn test_commit_with_remote_still_pushes_to_origin() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
let remote_path = temp_dir.path().join("remote.git");
|
|
create_bare_remote(&remote_path);
|
|
setup_test_repo_with_file(&repo_path, "test.txt", "Hello, World!");
|
|
add_remote(&repo_path, "origin", remote_path.to_str().unwrap());
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"commit",
|
|
"--manual",
|
|
"-m",
|
|
"test: push to origin",
|
|
"--yes",
|
|
"--push",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("Pushed branch"));
|
|
|
|
assert_eq!(
|
|
bare_head_commit(&remote_path),
|
|
Some(repo_head_commit(&repo_path)),
|
|
"bare remote should contain the pushed commit"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_tag_no_remote_skips_push_prompt() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
setup_git_repo(&repo_path);
|
|
create_test_file(&repo_path, "test.txt", "content");
|
|
stage_file(&repo_path, "test.txt");
|
|
create_commit(&repo_path, "feat: initial commit");
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"tag",
|
|
"--name",
|
|
"v0.1.0",
|
|
"-m",
|
|
"Release v0.1.0",
|
|
"--yes",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("No remote configured, skipping push."));
|
|
}
|
|
|
|
#[test]
|
|
fn test_tag_with_remote_still_pushes_to_origin() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
let remote_path = temp_dir.path().join("remote.git");
|
|
create_bare_remote(&remote_path);
|
|
setup_git_repo(&repo_path);
|
|
create_test_file(&repo_path, "test.txt", "content");
|
|
stage_file(&repo_path, "test.txt");
|
|
create_commit(&repo_path, "feat: initial commit");
|
|
add_remote(&repo_path, "origin", remote_path.to_str().unwrap());
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"tag",
|
|
"--name",
|
|
"v0.1.0",
|
|
"-m",
|
|
"Release v0.1.0",
|
|
"--yes",
|
|
"--push",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("Pushed tag"));
|
|
|
|
assert!(
|
|
bare_tag_exists(&remote_path, "v0.1.0"),
|
|
"remote should contain the pushed tag"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_commit_falls_back_to_first_available_remote() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
let remote_path = temp_dir.path().join("upstream.git");
|
|
create_bare_remote(&remote_path);
|
|
setup_test_repo_with_file(&repo_path, "test.txt", "Hello, World!");
|
|
add_remote(&repo_path, "upstream", remote_path.to_str().unwrap());
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"commit",
|
|
"--manual",
|
|
"-m",
|
|
"test: fallback push",
|
|
"--yes",
|
|
"--push",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains(
|
|
"Remote 'origin' not found, pushing to 'upstream' instead.",
|
|
))
|
|
.stdout(predicate::str::contains("Pushed branch"));
|
|
|
|
assert_eq!(
|
|
bare_head_commit(&remote_path),
|
|
Some(repo_head_commit(&repo_path)),
|
|
"fallback remote should contain the pushed commit"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_commit_falls_back_when_custom_remote_missing() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
let remote_path = temp_dir.path().join("origin.git");
|
|
create_bare_remote(&remote_path);
|
|
setup_test_repo_with_file(&repo_path, "test.txt", "Hello, World!");
|
|
add_remote(&repo_path, "origin", remote_path.to_str().unwrap());
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"commit",
|
|
"--manual",
|
|
"-m",
|
|
"test: custom remote missing",
|
|
"--yes",
|
|
"--push",
|
|
"--remote",
|
|
"nonexistent",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains(
|
|
"Remote 'nonexistent' not found, pushing to 'origin' instead.",
|
|
))
|
|
.stdout(predicate::str::contains("Pushed branch"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_tag_falls_back_to_first_available_remote() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
let remote_path = temp_dir.path().join("upstream.git");
|
|
create_bare_remote(&remote_path);
|
|
setup_git_repo(&repo_path);
|
|
create_test_file(&repo_path, "test.txt", "content");
|
|
stage_file(&repo_path, "test.txt");
|
|
create_commit(&repo_path, "feat: initial commit");
|
|
add_remote(&repo_path, "upstream", remote_path.to_str().unwrap());
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"tag",
|
|
"--name",
|
|
"v0.1.0",
|
|
"-m",
|
|
"Release v0.1.0",
|
|
"--yes",
|
|
"--push",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains(
|
|
"Remote 'origin' not found, pushing to 'upstream' instead.",
|
|
))
|
|
.stdout(predicate::str::contains("Pushed tag"));
|
|
|
|
assert!(
|
|
bare_tag_exists(&remote_path, "v0.1.0"),
|
|
"fallback remote should contain the pushed tag"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_commit_select_remote_yes_pushes_only_remote_target() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
let origin_path = temp_dir.path().join("origin.git");
|
|
let upstream_path = temp_dir.path().join("upstream.git");
|
|
create_bare_remote(&origin_path);
|
|
create_bare_remote(&upstream_path);
|
|
setup_test_repo_with_file(&repo_path, "test.txt", "Hello, World!");
|
|
add_remote(&repo_path, "origin", origin_path.to_str().unwrap());
|
|
add_remote(&repo_path, "upstream", upstream_path.to_str().unwrap());
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
// --yes outranks --select-remote: no interaction, push only --remote target
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"commit",
|
|
"--manual",
|
|
"-m",
|
|
"test: select remote with yes",
|
|
"--yes",
|
|
"--push",
|
|
"--select-remote",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert().success();
|
|
|
|
assert_eq!(
|
|
bare_head_commit(&origin_path),
|
|
Some(repo_head_commit(&repo_path)),
|
|
"origin should receive the push"
|
|
);
|
|
assert_eq!(
|
|
bare_head_commit(&upstream_path),
|
|
None,
|
|
"upstream should not receive the push under --yes"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_commit_select_remote_no_remote_skips_push() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
setup_test_repo_with_file(&repo_path, "test.txt", "Hello, World!");
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"commit",
|
|
"--manual",
|
|
"-m",
|
|
"test: select remote without remotes",
|
|
"--yes",
|
|
"--select-remote",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("No remote configured, skipping push."));
|
|
}
|
|
|
|
#[test]
|
|
fn test_commit_select_remote_single_remote_with_yes_pushes_directly() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
let origin_path = temp_dir.path().join("origin.git");
|
|
create_bare_remote(&origin_path);
|
|
setup_test_repo_with_file(&repo_path, "test.txt", "Hello, World!");
|
|
add_remote(&repo_path, "origin", origin_path.to_str().unwrap());
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"commit",
|
|
"--manual",
|
|
"-m",
|
|
"test: single remote select",
|
|
"--yes",
|
|
"--push",
|
|
"--select-remote",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("Pushed branch"));
|
|
|
|
assert_eq!(
|
|
bare_head_commit(&origin_path),
|
|
Some(repo_head_commit(&repo_path))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_commit_push_failure_reports_error() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
// Remote points to a path that is not a git repository
|
|
let broken_path = temp_dir.path().join("not-a-repo");
|
|
std::fs::create_dir(&broken_path).unwrap();
|
|
setup_test_repo_with_file(&repo_path, "test.txt", "Hello, World!");
|
|
add_remote(&repo_path, "origin", broken_path.to_str().unwrap());
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"commit",
|
|
"--manual",
|
|
"-m",
|
|
"test: push failure",
|
|
"--yes",
|
|
"--push",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert()
|
|
.failure()
|
|
.stderr(predicate::str::contains("Push failed"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_tag_select_remote_yes_pushes_only_remote_target() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
let origin_path = temp_dir.path().join("origin.git");
|
|
let upstream_path = temp_dir.path().join("upstream.git");
|
|
create_bare_remote(&origin_path);
|
|
create_bare_remote(&upstream_path);
|
|
setup_git_repo(&repo_path);
|
|
create_test_file(&repo_path, "test.txt", "content");
|
|
stage_file(&repo_path, "test.txt");
|
|
create_commit(&repo_path, "feat: initial commit");
|
|
add_remote(&repo_path, "origin", origin_path.to_str().unwrap());
|
|
add_remote(&repo_path, "upstream", upstream_path.to_str().unwrap());
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"tag",
|
|
"--name",
|
|
"v0.1.0",
|
|
"-m",
|
|
"Release v0.1.0",
|
|
"--yes",
|
|
"--push",
|
|
"--select-remote",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert().success();
|
|
|
|
assert!(
|
|
bare_tag_exists(&origin_path, "v0.1.0"),
|
|
"origin should have the tag"
|
|
);
|
|
assert!(
|
|
!bare_tag_exists(&upstream_path, "v0.1.0"),
|
|
"upstream should not receive the tag under --yes"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_tag_no_remote_with_push_flag_skips_instead_of_failing() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
setup_git_repo(&repo_path);
|
|
create_test_file(&repo_path, "test.txt", "content");
|
|
stage_file(&repo_path, "test.txt");
|
|
create_commit(&repo_path, "feat: initial commit");
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"tag",
|
|
"--name",
|
|
"v0.1.0",
|
|
"-m",
|
|
"Release v0.1.0",
|
|
"--yes",
|
|
"--push",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("No remote configured, skipping push."));
|
|
}
|
|
|
|
#[test]
|
|
fn test_tag_push_failure_reports_error() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path().to_path_buf();
|
|
// Remote points to a path that is not a git repository
|
|
let broken_path = temp_dir.path().join("not-a-repo");
|
|
std::fs::create_dir(&broken_path).unwrap();
|
|
setup_git_repo(&repo_path);
|
|
create_test_file(&repo_path, "test.txt", "content");
|
|
stage_file(&repo_path, "test.txt");
|
|
create_commit(&repo_path, "feat: initial commit");
|
|
add_remote(&repo_path, "origin", broken_path.to_str().unwrap());
|
|
|
|
let config_path = repo_path.join("config.toml");
|
|
init_quicommit(&repo_path, &config_path);
|
|
|
|
let mut cmd = cargo_bin_cmd!("quicommit");
|
|
cmd.args(&[
|
|
"tag",
|
|
"--name",
|
|
"v0.1.0",
|
|
"-m",
|
|
"Release v0.1.0",
|
|
"--yes",
|
|
"--push",
|
|
"--config",
|
|
config_path.to_str().unwrap(),
|
|
])
|
|
.current_dir(&repo_path);
|
|
|
|
cmd.assert()
|
|
.failure()
|
|
.stderr(predicate::str::contains("Push failed"));
|
|
}
|
|
}
|