diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 89252e4b..d0241e58 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -443,6 +443,8 @@ On first launch, `useOnboarding` checks if the default vault exists. If not, it - **Open an existing folder** → system file picker - **Get started with a template** → pick a parent folder, then call `create_getting_started_vault()` with the derived `.../Getting Started` child path so the cloned vault opens into the populated repo root immediately +When an opened folder is not yet a git repo, `init_git_repo` runs `git init`, ensures Tolaria's default `.gitignore`, stages the vault, and writes the initial `Initial vault setup` commit. That app-managed setup commit explicitly disables commit signing for the single command so inherited global or local `commit.gpgsign` preferences cannot strand onboarding when GPG is missing or misconfigured. + Once a vault is ready, `useAiAgentsOnboarding` can show a one-time `AiAgentsOnboardingPrompt`. That prompt reads `useAiAgentsStatus` so first launch surfaces whether Claude Code and Codex are installed, offers per-agent install links when they are missing, and stores local dismissal so the prompt does not repeat on every launch. `useGettingStartedClone` reuses the same parent-folder semantics for the status-bar / command-palette clone action, and `Toast` is rendered through the AI-agents onboarding gate so the resolved destination path stays visible right after a successful clone. @@ -648,6 +650,7 @@ The vault backend (`src-tauri/src/vault/`) is split into focused submodules: | Command | Description | |---------|-------------| +| `init_git_repo` | Initialize a local repo, add default `.gitignore`, and create the unsigned setup commit | | `git_commit` | Stage all + commit | | `git_pull` | Pull from remote | | `git_push` | Push to remote | diff --git a/src-tauri/src/git/mod.rs b/src-tauri/src/git/mod.rs index 815ee6db..1852637a 100644 --- a/src-tauri/src/git/mod.rs +++ b/src-tauri/src/git/mod.rs @@ -79,18 +79,31 @@ pub fn init_repo(path: &str) -> Result<(), String> { ensure_gitignore(path)?; run_git(dir, &["add", "."])?; - run_git(dir, &["commit", "-m", "Initial vault setup"])?; + commit_initial_vault_setup(dir)?; Ok(()) } +fn commit_initial_vault_setup(dir: &Path) -> Result<(), String> { + run_git( + dir, + &[ + "-c", + "commit.gpgsign=false", + "commit", + "-m", + "Initial vault setup", + ], + ) +} + /// Run a git command in the given directory, returning an error on failure. fn run_git(dir: &Path, args: &[&str]) -> Result<(), String> { let output = Command::new("git") .args(args) .current_dir(dir) .output() - .map_err(|e| format!("Failed to run git {}: {}", args[0], e))?; + .map_err(|e| format!("Failed to run git {}: {e}", git_command_label(args)))?; if output.status.success() { return Ok(()); @@ -98,11 +111,19 @@ fn run_git(dir: &Path, args: &[&str]) -> Result<(), String> { Err(format!( "git {} failed: {}", - args[0], + git_command_label(args), String::from_utf8_lossy(&output.stderr) )) } +fn git_command_label<'a>(args: &'a [&'a str]) -> &'a str { + if args.first() == Some(&"-c") { + return args.get(2).copied().unwrap_or(args[0]); + } + + args[0] +} + /// Set local user.name and user.email if not already configured. fn ensure_author_config(dir: &Path) -> Result<(), String> { for (key, fallback) in [ @@ -289,6 +310,39 @@ mod tests { assert!(log_str.contains("Initial vault setup")); } + #[test] + fn test_init_repo_creates_initial_commit_when_signing_is_misconfigured() { + let dir = TempDir::new().unwrap(); + let vault = dir.path().join("new-vault"); + fs::create_dir_all(&vault).unwrap(); + fs::write(vault.join("note.md"), "# Test\n").unwrap(); + + Command::new("git") + .args(["init"]) + .current_dir(&vault) + .output() + .unwrap(); + Command::new("git") + .args(["config", "commit.gpgsign", "true"]) + .current_dir(&vault) + .output() + .unwrap(); + Command::new("git") + .args(["config", "gpg.program", "/missing/tolaria-test-gpg"]) + .current_dir(&vault) + .output() + .unwrap(); + + init_repo(vault.to_str().unwrap()).unwrap(); + + let log = Command::new("git") + .args(["log", "--oneline"]) + .current_dir(&vault) + .output() + .unwrap(); + assert!(String::from_utf8_lossy(&log.stdout).contains("Initial vault setup")); + } + #[test] fn test_init_repo_stages_all_files() { let dir = TempDir::new().unwrap();