From adce0a5e0d31c7938fbe630b57159e0857b11a94 Mon Sep 17 00:00:00 2001 From: inx Date: Wed, 28 Jan 2026 21:08:09 +0800 Subject: [PATCH] chore(rename): rename project from 'gosh' to 'naj' This rebrands the CLI tool to 'naj' (Old Chinese reconstruction for "Me/I"). This name was chosen for better typing ergonomics (R-L-R alternation) and availability on crates.io. Changes: - Update `Cargo.toml` package name to `naj`. - Update binary name target to `naj`. - Update documentation and README to reflect the new identity. BREAKING CHANGE: The binary name is now `naj`. Users must update their scripts and usage from `gosh` to `naj`. --- Cargo.toml | 6 ++++- README.md | 48 +++++++++++++++++----------------- scripts/build.sh | 4 +-- scripts/tests/README.md | 10 +++---- scripts/tests/alice.sh | 18 ++++++------- scripts/tests/alice2.sh | 18 ++++++------- scripts/tests/alice3.sh | 12 ++++----- scripts/tests/edge_cases.sh | 8 +++--- scripts/tests/security_edge.sh | 20 +++++++------- src/cli.rs | 2 +- src/config.rs | 24 ++++++++--------- src/git.rs | 22 ++++++++-------- src/manage.rs | 12 ++++----- tests/integration_test.rs | 42 ++++++++++++++--------------- tests/test_force_mocking.rs | 4 +-- 15 files changed, 127 insertions(+), 123 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 83f9c8e..dc73286 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "gosh" +name = "naj" version = "0.1.1" edition = "2021" @@ -14,3 +14,7 @@ anyhow = "1.0" assert_cmd = "2.0" tempfile = "3.10" predicates = "3.1" + +[[bin]] +name = "naj" +path = "src/main.rs" \ No newline at end of file diff --git a/README.md b/README.md index 85ff831..6d95e6a 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ -# Gosh (Git Operations SHell) +# Naj (我 `/*ŋˤajʔ/`) -**Gosh** is a lightweight, secure, and idempotent wrapper for Git, written in Rust. It solves the chaos of managing multiple Git identities (Work vs. Personal) by strictly isolating configurations and preventing accidental identity leaks. +**Naj** is a lightweight, secure, and idempotent wrapper for Git, written in Rust. It solves the chaos of managing multiple Git identities (Work vs. Personal) by strictly isolating configurations and preventing accidental identity leaks. ## 🚀 Features -* **🛡️ Fail-Safe Security**: Uses "Blind Injection" to forcibly wipe global identity keys before applying a profile. If your profile lacks a key, Gosh fails securely rather than falling back to your global `~/.gitconfig`. -* **⚡ Ephemeral Execution**: Run commands like `gosh work commit` without modifying any files on disk. Perfect for one-off fixes. +* **🛡️ Fail-Safe Security**: Uses "Blind Injection" to forcibly wipe global identity keys before applying a profile. If your profile lacks a key, Naj fails securely rather than falling back to your global `~/.gitconfig`. +* **⚡ Ephemeral Execution**: Run commands like `naj work commit` without modifying any files on disk. Perfect for one-off fixes. * **💾 Persistent Switching**: Permanently bind a repository to an identity using Git's native `[include]` directive. * **🛠️ Zero Config Setup**: Automatically handles `clone` and `init` setup, applying the correct identity immediately. -* **📂 Portable Profiles**: Profiles are stored in `~/.config/gosh/profiles/`, designed to be synced via a private Git repository. +* **📂 Portable Profiles**: Profiles are stored in `~/.config/naj/profiles/`, designed to be synced via a private Git repository. ## 📦 Installation @@ -22,28 +22,28 @@ cargo install --path . ### 1. Management: Create Identities -Gosh manages identities as "Profiles". +Naj manages identities as "Profiles". ```bash -# Syntax: gosh -c -gosh -c "Alice Work" "alice@company.com" "work" -gosh -c "Alice Hobby" "alice@gmail.com" "personal" +# Syntax: naj -c +naj -c "Alice Work" "alice@company.com" "work" +naj -c "Alice Hobby" "alice@gmail.com" "personal" # List all profiles -gosh -l +naj -l # Edit a profile (e.g., to add signingkey or sshCommand) -gosh -e work +naj -e work ``` ### 2. Workflow A: Setup New Projects (Recommended) -When you clone or init a repository, Gosh automatically sets up the local config. +When you clone or init a repository, Naj automatically sets up the local config. ```bash # Clones the repo and immediately binds it to the "work" profile -gosh work clone git@github.com:company/backend.git +naj work clone git@github.com:company/backend.git # Inside the repo, you can now just use standard git cd backend @@ -57,10 +57,10 @@ Run a command with a specific identity *without* modifying the repository config ```bash # Temporarily commit as "personal" in a work repo (e.g., fixing a typo) -gosh personal commit -m "Fix typo" +naj personal commit -m "Fix typo" # Verification -gosh personal config user.email +naj personal config user.email ``` @@ -70,21 +70,21 @@ Change the identity bound to an existing repository. ```bash cd my-repo -gosh work +naj work # If the repo has "dirty" config (manually set user.name), force overwrite it: -gosh work -f +naj work -f ``` ## ⚙️ Configuration -Gosh follows the XDG Base Directory specification. +Naj follows the XDG Base Directory specification. -* **Config File**: `~/.config/gosh/gosh.toml` -* **Profiles**: `~/.config/gosh/profiles/*.gitconfig` +* **Config File**: `~/.config/naj/naj.toml` +* **Profiles**: `~/.config/naj/profiles/*.gitconfig` -On the first run, Gosh will automatically create these directories and a default configuration file. +On the first run, Naj will automatically create these directories and a default configuration file. ### Environment Variables @@ -93,9 +93,9 @@ On the first run, Gosh will automatically create these directories and a default ## 🔒 Security Design: Blind Injection -In **Exec Mode**, Gosh does **not** read your local configuration to decide what to override. Instead, it aggressively injects empty values for sensitive keys before applying your profile. +In **Exec Mode**, Naj does **not** read your local configuration to decide what to override. Instead, it aggressively injects empty values for sensitive keys before applying your profile. -**Example command generated by Gosh:** +**Example command generated by Naj:** ```bash git \ @@ -103,7 +103,7 @@ git \ -c user.email="" \ -c user.signingkey="" \ -c commit.gpgsign=false \ - -c include.path=~/.config/gosh/profiles/work.gitconfig \ # 2. Apply Profile + -c include.path=~/.config/naj/profiles/work.gitconfig \ # 2. Apply Profile commit ... ``` diff --git a/scripts/build.sh b/scripts/build.sh index eea3a6f..9e43a04 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -2,7 +2,7 @@ set -e # 遇到错误立即停止 # --- 配置部分 --- -APP_NAME="gosh" +APP_NAME="naj" OUTPUT_DIR="dist" # 新增:获取 dist 目录的绝对物理路径 mkdir -p "$OUTPUT_DIR" @@ -65,7 +65,7 @@ for target in "${TARGETS[@]}"; do exit 1 fi - # 3. 打包文件名格式: gosh-v0.1.0-x86_64-unknown-linux-musl.tar.gz + # 3. 打包文件名格式: naj-v0.1.0-x86_64-unknown-linux-musl.tar.gz ARCHIVE_NAME="${APP_NAME}-v${VERSION}-${target}" # 进入输出目录进行打包操作 diff --git a/scripts/tests/README.md b/scripts/tests/README.md index a45af4c..fa3fa00 100644 --- a/scripts/tests/README.md +++ b/scripts/tests/README.md @@ -1,10 +1,10 @@ -# Gosh Integration & Scenario Tests +# Naj Integration & Scenario Tests -This directory contains the integration test suite and scenario simulations for `gosh`. These scripts are designed to validate the core functionality, security isolation, and developer workflows in a controlled, sandbox environment. +This directory contains the integration test suite and scenario simulations for `naj`. These scripts are designed to validate the core functionality, security isolation, and developer workflows in a controlled, sandbox environment. ## Overview -The tests in this directory focus on end-to-end (E2E) validation, ensuring that `gosh` correctly interacts with Git configurations, SSH keys, and system environments without contaminating the user's global settings. +The tests in this directory focus on end-to-end (E2E) validation, ensuring that `naj` correctly interacts with Git configurations, SSH keys, and system environments without contaminating the user's global settings. ## Test Matrix @@ -21,7 +21,7 @@ The tests in this directory focus on end-to-end (E2E) validation, ensuring that To run these tests locally, ensure you have the following installed: - **Bash**: Most scripts use standard Bash features. - **Git**: Version 2.34+ is required for SSH signing tests. -- **Gosh**: The `gosh` binary must be available in your PATH or accessible via the `GOSH_CMD` environment variable. +- **Naj**: The `naj` binary must be available in your PATH or accessible via the `GOSH_CMD` environment variable. ## Running Tests @@ -42,7 +42,7 @@ You can run individual test scripts directly: bash scripts/tests/alice.sh ``` -Each script initializes a sandbox in `/tmp/gosh_test_*` or similar, ensuring that your `~/.gitconfig` and `~/.ssh` remain untouched. +Each script initializes a sandbox in `/tmp/naj_test_*` or similar, ensuring that your `~/.gitconfig` and `~/.ssh` remain untouched. ## Design Principles diff --git a/scripts/tests/alice.sh b/scripts/tests/alice.sh index ae391aa..abe72e4 100755 --- a/scripts/tests/alice.sh +++ b/scripts/tests/alice.sh @@ -2,10 +2,10 @@ set -e # --- 0. 环境与工具准备 --- -GOSH_CMD="gosh" # 确保已编译或 alias 到 cargo run +GOSH_CMD="naj" # 确保已编译或 alias 到 cargo run BASE_DIR="/tmp/alice_demo_signed" -# 隔离 Gosh 配置 +# 隔离 Naj 配置 export GOSH_CONFIG_PATH="$BASE_DIR/config" # 隔离 SSH 密钥目录 SSH_DIR="$BASE_DIR/ssh_keys" @@ -44,14 +44,14 @@ info "Generated Work Key: $SSH_DIR/id_work" ssh-keygen -t ed25519 -C "alice@alice.com" -f "$SSH_DIR/id_personal" -N "" -q info "Generated Personal Key: $SSH_DIR/id_personal" -# --- 3. 使用 Gosh 创建 Profile 并注入签名配置 --- -log "Creating Gosh Profiles..." +# --- 3. 使用 Naj 创建 Profile 并注入签名配置 --- +log "Creating Naj Profiles..." # 3.1 创建基础 Work Profile $GOSH_CMD -c "Alice Work" "alice@contoso.com" "work" # 3.2 手动追加 SSH 签名配置到 Work Profile -# 这里演示了 Gosh 的灵活性:你可以手动编辑生成的 .gitconfig +# 这里演示了 Naj 的灵活性:你可以手动编辑生成的 .gitconfig WORK_PROFILE="$GOSH_CONFIG_PATH/profiles/work.gitconfig" cat >> "$WORK_PROFILE" < Infer -> Switch) +# 使用 Naj 克隆 (Clone -> Infer -> Switch) # 注意:这里我们 Clone 本地路径,但 core.sshCommand 依然会被配置进去,这是符合预期的 $GOSH_CMD work clone "$REPO_DIR/backend.git" work-backend cd work-backend @@ -148,12 +148,12 @@ fi log "Scenario C: Ephemeral Execution (Security Check)" # 当前在 oss-project (Personal),我们想用 Work 身份签个名 -# 执行 gosh work commit +# 执行 naj work commit $GOSH_CMD work commit --allow-empty -m "Hotfix via Exec" > /dev/null # 验证最后一次提交的签名 -# 注意:Exec 模式下,Gosh 会通过 -c user.signingkey="" 先清空,再注入 work profile -# 如果这一步成功且签名了,说明 Gosh 正确注入了 id_work.pub +# 注意:Exec 模式下,Naj 会通过 -c user.signingkey="" 先清空,再注入 work profile +# 如果这一步成功且签名了,说明 Naj 正确注入了 id_work.pub LATEST_COMMIT_MSG=$(git log -1 --pretty=%B) info "Latest commit: $LATEST_COMMIT_MSG" diff --git a/scripts/tests/alice2.sh b/scripts/tests/alice2.sh index 8e0596f..41a2dcd 100755 --- a/scripts/tests/alice2.sh +++ b/scripts/tests/alice2.sh @@ -2,10 +2,10 @@ set -e # --- 0. 环境与工具准备 --- -GOSH_CMD="gosh" # 确保已编译或 alias 到 cargo run +GOSH_CMD="naj" # 确保已编译或 alias 到 cargo run BASE_DIR="/tmp/alice_demo_debug" -# 隔离 Gosh 配置 +# 隔离 Naj 配置 export GOSH_CONFIG_PATH="$BASE_DIR/config" # 隔离 SSH 密钥目录 SSH_DIR="$BASE_DIR/ssh_keys" @@ -54,8 +54,8 @@ info "Generated Work Key: .../id_work" ssh-keygen -t ed25519 -C "alice@alice.com" -f "$SSH_DIR/id_personal" -N "" -q info "Generated Personal Key: .../id_personal" -# --- 3. 使用 Gosh 创建 Profile --- -log "Creating Gosh Profiles..." +# --- 3. 使用 Naj 创建 Profile --- +log "Creating Naj Profiles..." # 3.1 Work Profile $GOSH_CMD -c "Alice Work" "alice@contoso.com" "work" @@ -94,8 +94,8 @@ log "Scenario A: Setup Mode (Work Repo)" cd "$REPO_DIR" git init --bare --quiet "backend.git" -# 使用 Gosh 克隆 -info "Running: gosh work clone ..." +# 使用 Naj 克隆 +info "Running: naj work clone ..." $GOSH_CMD work clone "$REPO_DIR/backend.git" work-backend cd work-backend @@ -114,7 +114,7 @@ git init --quiet "oss-project" cd oss-project # 切换到 Personal -info "Running: gosh personal (Switching...)" +info "Running: naj personal (Switching...)" $GOSH_CMD personal # 提交 @@ -128,9 +128,9 @@ debug_inspect # === 场景 C: 临时执行与密钥隔离 (Exec Mode) === log "Scenario C: Ephemeral Execution (Security Check)" info "Current Profile is: Personal (oss-project)" -info "Executing 'gosh work commit' (Should use Work Identity temporarily)..." +info "Executing 'naj work commit' (Should use Work Identity temporarily)..." -# 执行 gosh work commit +# 执行 naj work commit # 注意:这里我们不再重定向到 /dev/null,我们要看 git 的原生输出 $GOSH_CMD work commit --allow-empty -m "Hotfix via Exec (Scenario C)" diff --git a/scripts/tests/alice3.sh b/scripts/tests/alice3.sh index f37f462..2bc695e 100755 --- a/scripts/tests/alice3.sh +++ b/scripts/tests/alice3.sh @@ -2,10 +2,10 @@ set -e # --- 0. 全局配置 --- -GOSH_CMD="gosh" -BASE_DIR="/tmp/gosh_collab_demo" +GOSH_CMD="naj" +BASE_DIR="/tmp/naj_collab_demo" -# 隔离 Gosh 配置 +# 隔离 Naj 配置 export GOSH_CONFIG_PATH="$BASE_DIR/config" # 隔离 SSH 密钥目录 SSH_DIR="$BASE_DIR/ssh_keys" @@ -79,8 +79,8 @@ ssh-keygen -t ed25519 -C "bob@partner.org" -f "$SSH_DIR/id_bob" -N "" -q echo "bob@partner.org $(cat $SSH_DIR/id_bob.pub)" >> "$ALLOWED_SIGNERS" ok "Generated Bob's Key & Added to Trust Store" -# --- 3. 配置 Gosh Profiles --- -log "Configuring Gosh Profiles..." +# --- 3. 配置 Naj Profiles --- +log "Configuring Naj Profiles..." # --> Alice Profile $GOSH_CMD -c "Alice Work" "alice@contoso.com" "alice_work" @@ -157,7 +157,7 @@ verify_last_commit "bob@partner.org" "bob_partner" # 2. Alice 临时修复 (不切换 Profile,直接用 Exec) # 当前 Profile 依然是 bob_partner (可以通过 .git/config 验证) -# Alice 用 gosh alice_work exec 临时提交 +# Alice 用 naj alice_work exec 临时提交 echo ">>> [Commit 4] Alice hotfixes via Exec Mode" echo "// Hotfix" >> main.rs git add main.rs diff --git a/scripts/tests/edge_cases.sh b/scripts/tests/edge_cases.sh index 15a9ee8..7903f49 100755 --- a/scripts/tests/edge_cases.sh +++ b/scripts/tests/edge_cases.sh @@ -2,8 +2,8 @@ set -e # --- 配置 --- -GOSH_CMD="gosh" -BASE_DIR="/tmp/gosh_edge_test" +GOSH_CMD="naj" +BASE_DIR="/tmp/naj_edge_test" export GOSH_CONFIG_PATH="$BASE_DIR/config" REPO_DIR="$BASE_DIR/repos" @@ -30,7 +30,7 @@ mkdir -p src/deep/level cd src/deep/level echo "Current dir: $(pwd)" -echo "Executing 'gosh edge' from subdirectory..." +echo "Executing 'naj edge' from subdirectory..." # 执行 switch $GOSH_CMD edge @@ -55,7 +55,7 @@ cd "$DIR_WITH_SPACE" git init --quiet echo "Current dir: $(pwd)" -echo "Executing 'gosh edge'..." +echo "Executing 'naj edge'..." $GOSH_CMD edge diff --git a/scripts/tests/security_edge.sh b/scripts/tests/security_edge.sh index cfb4287..00b4a97 100755 --- a/scripts/tests/security_edge.sh +++ b/scripts/tests/security_edge.sh @@ -1,8 +1,8 @@ #!/bin/bash # --- 准备 --- -GOSH_CMD="gosh" # 确保已编译或 alias -BASE_DIR="/tmp/gosh_security_test" +GOSH_CMD="naj" # 确保已编译或 alias +BASE_DIR="/tmp/naj_security_test" UNSAFE_REPO="$BASE_DIR/root_owned_repo" # 1. 初始化一个归属于 root 的仓库 (对当前用户来说是不安全的) @@ -17,14 +17,14 @@ sudo touch "$UNSAFE_REPO/testfile" # 确保当前用户对目录有读写权限(以便能进入),但 .git 依然属于 root sudo chmod -R 777 "$UNSAFE_REPO" -echo "[TEST] Running 'gosh' in a dubious ownership repo..." +echo "[TEST] Running 'naj' in a dubious ownership repo..." cd "$UNSAFE_REPO" -# 2. 尝试运行 gosh (期望失败) +# 2. 尝试运行 naj (期望失败) if $GOSH_CMD -l > /dev/null 2>&1; then - # 注意:gosh -l 不需要 git 仓库,所以应该成功。 + # 注意:naj -l 不需要 git 仓库,所以应该成功。 # 我们需要测 switch 或 exec,这需要 git 上下文 - echo " (gosh list works, which is fine)" + echo " (naj list works, which is fine)" fi echo "Attempting to switch profile..." @@ -33,13 +33,13 @@ OUTPUT=$($GOSH_CMD testprofile 2>&1 || true) # 3. 验证结果 if echo "$OUTPUT" | grep -q "fatal: detected dubious ownership"; then - echo "✅ PASS: Gosh propagated Git's security error." + echo "✅ PASS: Naj propagated Git's security error." echo " Git said: 'detected dubious ownership'" - echo " Gosh refused to act." + echo " Naj refused to act." elif echo "$OUTPUT" | grep -q "Not a git repository"; then - echo "✅ PASS: Gosh treated it as invalid (Git rev-parse failed)." + echo "✅ PASS: Naj treated it as invalid (Git rev-parse failed)." else - echo "❌ FAIL: Gosh tried to execute! This is dangerous." + echo "❌ FAIL: Naj tried to execute! This is dangerous." echo "Output was: $OUTPUT" exit 1 fi diff --git a/src/cli.rs b/src/cli.rs index 06205e6..a214c1a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,7 +1,7 @@ use clap::{Parser, Args}; #[derive(Parser, Debug)] -#[command(name = "gosh")] +#[command(name = "naj")] #[command(about = "Git Operations Shell", long_about = None)] pub struct Cli { #[command(flatten)] diff --git a/src/config.rs b/src/config.rs index 45bfb91..34af83c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -11,19 +11,19 @@ pub struct Strategies { } #[derive(Serialize, Deserialize, Debug)] -pub struct GoshConfig { +pub struct NajConfig { pub strategies: Strategies, pub profile_dir: String, } -impl Default for GoshConfig { +impl Default for NajConfig { fn default() -> Self { - GoshConfig { + NajConfig { strategies: Strategies { clone: "INCLUDE".to_string(), switch: "include".to_string(), }, - profile_dir: "~/.config/gosh/profiles".to_string(), + profile_dir: "~/.config/naj/profiles".to_string(), } } } @@ -33,23 +33,23 @@ pub fn get_config_root() -> Result { return Ok(PathBuf::from(path)); } let config_dir = dirs::config_dir().ok_or_else(|| anyhow::anyhow!("Could not find config directory"))?; - Ok(config_dir.join("gosh")) + Ok(config_dir.join("naj")) } -pub fn load_config() -> Result { +pub fn load_config() -> Result { let root = get_config_root()?; - let config_path = root.join("gosh.toml"); + let config_path = root.join("naj.toml"); if !config_path.exists() { return initialize_config(&root, &config_path); } let content = fs::read_to_string(&config_path).context("Failed to read config file")?; - let config: GoshConfig = toml::from_str(&content).context("Failed to parse config file")?; + let config: NajConfig = toml::from_str(&content).context("Failed to parse config file")?; Ok(config) } -fn initialize_config(root: &Path, config_path: &Path) -> Result { +fn initialize_config(root: &Path, config_path: &Path) -> Result { // Ensure root exists fs::create_dir_all(root).context("Failed to create config root")?; @@ -62,7 +62,7 @@ fn initialize_config(root: &Path, config_path: &Path) -> Result { // We'll just rely on to_string_lossy but be careful with escaping in the format macro p.to_string_lossy().to_string() } else { - "~/.config/gosh/profiles".to_string() + "~/.config/naj/profiles".to_string() }; // Use toml serialization to ensure string is escaped properly? @@ -70,7 +70,7 @@ fn initialize_config(root: &Path, config_path: &Path) -> Result { // If path contains backslashes (Windows), we need to escape them for the TOML string literal: "C:\\Foo" let escaped_profile_dir = profile_dir_str.replace("\\", "\\\\"); - let generated_toml = format!(r#"# Gosh Configuration + let generated_toml = format!(r#"# Naj Configuration profile_dir = "{}" @@ -85,6 +85,6 @@ switch = "include" # Soft strategy let expanded_profile_dir = expand_path(&profile_dir_str)?; fs::create_dir_all(&expanded_profile_dir).context("Failed to create profiles directory")?; - let config: GoshConfig = toml::from_str(&generated_toml)?; + let config: NajConfig = toml::from_str(&generated_toml)?; Ok(config) } diff --git a/src/git.rs b/src/git.rs index 1bbaa10..35fb770 100644 --- a/src/git.rs +++ b/src/git.rs @@ -1,4 +1,4 @@ -use crate::config::GoshConfig; +use crate::config::NajConfig; use crate::sanitizer; use crate::utils::expand_path; use anyhow::{Result, anyhow, Context}; @@ -11,7 +11,7 @@ enum Action { Switch, } -pub fn run(config: &GoshConfig, profile_id: &str, args: &[String], force: bool) -> Result<()> { +pub fn run(config: &NajConfig, profile_id: &str, args: &[String], force: bool) -> Result<()> { // Determine Action let action = if args.is_empty() { Action::Switch @@ -28,7 +28,7 @@ pub fn run(config: &GoshConfig, profile_id: &str, args: &[String], force: bool) } } -fn get_profile_path(config: &GoshConfig, id: &str) -> Result { +fn get_profile_path(config: &NajConfig, id: &str) -> Result { let profile_dir = expand_path(&config.profile_dir)?; let p = profile_dir.join(format!("{}.gitconfig", id)); if !p.exists() { @@ -70,7 +70,7 @@ fn run_command(cmd: &mut Command) -> Result<()> { Ok(()) } -fn get_profile_dir(config: &GoshConfig) -> Result { +fn get_profile_dir(config: &NajConfig) -> Result { expand_path(&config.profile_dir) } @@ -90,10 +90,10 @@ fn clean_existing_profiles(profile_dir: &Path) -> Result<()> { // Check if the path belongs to our profile directory // We use string containment as a heuristic since paths might be canonicalized differently // but typically we add absolute paths. - let is_gosh_profile = val.contains(&profile_dir.to_string_lossy().to_string()) + let is_naj_profile = val.contains(&profile_dir.to_string_lossy().to_string()) || (val.contains("/profiles/") && val.ends_with(".gitconfig")); - if is_gosh_profile { + if is_naj_profile { let mut cmd = Command::new("git"); cmd.args(&["config", "--local", "--unset", "include.path", val]); // We tolerate failure here (e.g. if key doesn't exist anymore for some reason) @@ -108,7 +108,7 @@ fn clean_existing_profiles(profile_dir: &Path) -> Result<()> { Ok(()) } -fn run_exec(config: &GoshConfig, profile_id: &str, args: &[String]) -> Result<()> { +fn run_exec(config: &NajConfig, profile_id: &str, args: &[String]) -> Result<()> { let profile_path = get_profile_path(config, profile_id)?; let injections = sanitizer::BLIND_INJECTIONS; @@ -132,7 +132,7 @@ fn run_exec(config: &GoshConfig, profile_id: &str, args: &[String]) -> Result<() run_command(&mut cmd) } -fn run_switch(config: &GoshConfig, profile_id: &str, force: bool) -> Result<()> { +fn run_switch(config: &NajConfig, profile_id: &str, force: bool) -> Result<()> { let status = Command::new("git") .args(&["rev-parse", "--is-inside-work-tree"]) .stdout(std::process::Stdio::null()) @@ -189,7 +189,7 @@ fn run_switch(config: &GoshConfig, profile_id: &str, force: bool) -> Result<()> } } - // 0. Clean existing gosh profiles + // 0. Clean existing naj profiles let profiles_dir = get_profile_dir(config)?; clean_existing_profiles(&profiles_dir)?; @@ -205,7 +205,7 @@ fn run_switch(config: &GoshConfig, profile_id: &str, force: bool) -> Result<()> Ok(()) } -fn run_setup(config: &GoshConfig, profile_id: &str, args: &[String]) -> Result<()> { +fn run_setup(config: &NajConfig, profile_id: &str, args: &[String]) -> Result<()> { // 1. Pass raw args to git (no injection) let mut cmd = Command::new("git"); cmd.args(args); @@ -306,7 +306,7 @@ fn warn_if_dirty_config(profile_id: &str) -> Result<()> { println!(" These settings (like signing keys) are merging with your profile"); println!(" and causing a \"Frankenstein Identity\"."); println!(""); - println!(" 👉 Fix it: Run 'gosh {} -f' to force clean.\n", profile_id); + println!(" 👉 Fix it: Run 'naj {} -f' to force clean.\n", profile_id); } } Ok(()) diff --git a/src/manage.rs b/src/manage.rs index 98d187f..fc46e18 100644 --- a/src/manage.rs +++ b/src/manage.rs @@ -2,15 +2,15 @@ use anyhow::{bail, Context, Result}; use std::fs; use std::path::PathBuf; use std::process::Command; -use crate::config::GoshConfig; +use crate::config::NajConfig; use crate::utils::expand_path; -fn get_profile_path(config: &GoshConfig, id: &str) -> Result { +fn get_profile_path(config: &NajConfig, id: &str) -> Result { let profile_dir = expand_path(&config.profile_dir)?; Ok(profile_dir.join(format!("{}.gitconfig", id))) } -pub fn create_profile(config: &GoshConfig, name: &str, email: &str, id: &str) -> Result<()> { +pub fn create_profile(config: &NajConfig, name: &str, email: &str, id: &str) -> Result<()> { let file_path = get_profile_path(config, id)?; if file_path.exists() { @@ -28,7 +28,7 @@ pub fn create_profile(config: &GoshConfig, name: &str, email: &str, id: &str) -> Ok(()) } -pub fn remove_profile(config: &GoshConfig, id: &str) -> Result<()> { +pub fn remove_profile(config: &NajConfig, id: &str) -> Result<()> { let file_path = get_profile_path(config, id)?; if !file_path.exists() { @@ -40,7 +40,7 @@ pub fn remove_profile(config: &GoshConfig, id: &str) -> Result<()> { Ok(()) } -pub fn edit_profile(config: &GoshConfig, id: &str) -> Result<()> { +pub fn edit_profile(config: &NajConfig, id: &str) -> Result<()> { let file_path = get_profile_path(config, id)?; if !file_path.exists() { @@ -60,7 +60,7 @@ pub fn edit_profile(config: &GoshConfig, id: &str) -> Result<()> { Ok(()) } -pub fn list_profiles(config: &GoshConfig) -> Result<()> { +pub fn list_profiles(config: &NajConfig) -> Result<()> { let profile_dir = expand_path(&config.profile_dir)?; if !profile_dir.exists() { diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 21d4291..7dc8f88 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -7,15 +7,15 @@ fn test_config_initialization() -> Result<(), Box> { let temp_dir = TempDir::new()?; let config_path = temp_dir.path().join("config"); - // Run gosh with GOSH_CONFIG_PATH set to temp dir - let mut cmd = Command::new(env!("CARGO_BIN_EXE_gosh")); + // Run naj with GOSH_CONFIG_PATH set to temp dir + let mut cmd = Command::new(env!("CARGO_BIN_EXE_naj")); cmd.env("GOSH_CONFIG_PATH", &config_path) .arg("-l") // Trigger config load using list flag (not positional "list" profile) .assert() .success(); // Verify config file exists - assert!(config_path.join("gosh.toml").exists()); + assert!(config_path.join("naj.toml").exists()); assert!(config_path.join("profiles").exists()); Ok(()) @@ -26,7 +26,7 @@ fn test_profile_creation_and_listing() -> Result<(), Box> let temp_dir = TempDir::new()?; let config_path = temp_dir.path(); - let mut cmd = Command::new(env!("CARGO_BIN_EXE_gosh")); + let mut cmd = Command::new(env!("CARGO_BIN_EXE_naj")); cmd.env("GOSH_CONFIG_PATH", config_path) .args(&["-c", "Test User", "test@example.com", "test_user"]) .assert() @@ -40,7 +40,7 @@ fn test_profile_creation_and_listing() -> Result<(), Box> assert!(content.contains("email = test@example.com")); // Verify list - let mut cmd_list = Command::new(env!("CARGO_BIN_EXE_gosh")); + let mut cmd_list = Command::new(env!("CARGO_BIN_EXE_naj")); cmd_list.env("GOSH_CONFIG_PATH", config_path) .arg("-l") .assert() @@ -56,14 +56,14 @@ fn test_duplicate_creation_failure() -> Result<(), Box> { let config_path = temp_dir.path(); // Create first - Command::new(env!("CARGO_BIN_EXE_gosh")) + Command::new(env!("CARGO_BIN_EXE_naj")) .env("GOSH_CONFIG_PATH", config_path) .args(&["-c", "User", "u@e.com", "dup_test"]) .assert() .success(); // Create duplicate - Command::new(env!("CARGO_BIN_EXE_gosh")) + Command::new(env!("CARGO_BIN_EXE_naj")) .env("GOSH_CONFIG_PATH", config_path) .args(&["-c", "User2", "u2@e.com", "dup_test"]) .assert() @@ -79,7 +79,7 @@ fn test_remove_profile() -> Result<(), Box> { let profile_path = config_path.join("profiles").join("rem_test.gitconfig"); // Create - Command::new(env!("CARGO_BIN_EXE_gosh")) + Command::new(env!("CARGO_BIN_EXE_naj")) .env("GOSH_CONFIG_PATH", config_path) .args(&["-c", "User", "u@e.com", "rem_test"]) .assert() @@ -87,7 +87,7 @@ fn test_remove_profile() -> Result<(), Box> { assert!(profile_path.exists()); // Remove - Command::new(env!("CARGO_BIN_EXE_gosh")) + Command::new(env!("CARGO_BIN_EXE_naj")) .env("GOSH_CONFIG_PATH", config_path) .args(&["-r", "rem_test"]) .assert() @@ -95,7 +95,7 @@ fn test_remove_profile() -> Result<(), Box> { assert!(!profile_path.exists()); // Remove non-existent - Command::new(env!("CARGO_BIN_EXE_gosh")) + Command::new(env!("CARGO_BIN_EXE_naj")) .env("GOSH_CONFIG_PATH", config_path) .args(&["-r", "rem_test"]) .assert() @@ -110,14 +110,14 @@ fn test_exec_dry_run_injection_strict() -> Result<(), Box let config_path = temp_dir.path(); // Create a profile first - Command::new(env!("CARGO_BIN_EXE_gosh")) + Command::new(env!("CARGO_BIN_EXE_naj")) .env("GOSH_CONFIG_PATH", config_path) .args(&["-c", "Test", "test@e.com", "p1"]) .assert() .success(); // Run exec with mocking - let mut cmd = Command::new(env!("CARGO_BIN_EXE_gosh")); + let mut cmd = Command::new(env!("CARGO_BIN_EXE_naj")); cmd.env("GOSH_CONFIG_PATH", config_path) .env("GOSH_MOCKING", "1") .args(&["p1", "commit", "-m", "foo"]) @@ -150,14 +150,14 @@ fn test_switch_mode_persistent() -> Result<(), Box> { .output()?; // Create profile - Command::new(env!("CARGO_BIN_EXE_gosh")) + Command::new(env!("CARGO_BIN_EXE_naj")) .env("GOSH_CONFIG_PATH", &config_path) .args(&["-c", "Switch User", "s@e.com", "switch_test"]) .assert() .success(); // Switch - Command::new(env!("CARGO_BIN_EXE_gosh")) + Command::new(env!("CARGO_BIN_EXE_naj")) .env("GOSH_CONFIG_PATH", &config_path) .current_dir(&repo_dir) .arg("switch_test") @@ -197,14 +197,14 @@ fn test_switch_force_mode_sanitization() -> Result<(), Box Result<(), Box> { // Git allows cloning an empty repo, so this is enough // 2. Create Profile - Command::new(env!("CARGO_BIN_EXE_gosh")) + Command::new(env!("CARGO_BIN_EXE_naj")) .env("GOSH_CONFIG_PATH", &config_path) .args(&["-c", "CloneUser", "c@e.com", "clone_test"]) .assert() .success(); - // 3. Run Gosh Clone (Setup Mode) + // 3. Run Naj Clone (Setup Mode) // We clone from local source to a folder named "dest_repo" - // Gosh should: + // Naj should: // a) Run git clone // b) Infer directory is "dest_repo" // c) Run switch logic on "dest_repo" let dest_repo_name = "dest_repo"; - Command::new(env!("CARGO_BIN_EXE_gosh")) + Command::new(env!("CARGO_BIN_EXE_naj")) .env("GOSH_CONFIG_PATH", &config_path) .current_dir(temp_dir.path()) // Execute in temp root .args(&["clone_test", "clone", source_repo.to_str().unwrap(), dest_repo_name]) @@ -270,7 +270,7 @@ fn test_setup_mode_local_clone() -> Result<(), Box> { assert!(dest_git_config.exists(), "Cloned repo config should exist"); let content = fs::read_to_string(dest_git_config)?; - // Verify that gosh automatically switched the profile after cloning + // Verify that naj automatically switched the profile after cloning assert!(content.contains("[include]")); assert!(content.contains("clone_test.gitconfig")); diff --git a/tests/test_force_mocking.rs b/tests/test_force_mocking.rs index 5de950d..adc8807 100644 --- a/tests/test_force_mocking.rs +++ b/tests/test_force_mocking.rs @@ -15,14 +15,14 @@ fn test_switch_force_mocking() -> Result<(), Box> { .output()?; // Create profile - Command::new(env!("CARGO_BIN_EXE_gosh")) + Command::new(env!("CARGO_BIN_EXE_naj")) .env("GOSH_CONFIG_PATH", &config_path) .args(&["-c", "User", "u@e.com", "mock_test"]) .assert() .success(); // Run force switch with mocking - Command::new(env!("CARGO_BIN_EXE_gosh")) + Command::new(env!("CARGO_BIN_EXE_naj")) .env("GOSH_CONFIG_PATH", &config_path) .env("GOSH_MOCKING", "1") .current_dir(&repo_dir)