From 2fbdafa6f2c9684429e4a5de83a1cd9103d9c95e Mon Sep 17 00:00:00 2001 From: lucaronin Date: Mon, 27 Apr 2026 18:59:23 +0200 Subject: [PATCH] fix: set git identity before remote connect --- docs/ARCHITECTURE.md | 4 +-- src-tauri/src/git/connect.rs | 46 +++++++++++++++++++++++++- src-tauri/src/git/mod.rs | 16 +++++---- src-tauri/src/vault/getting_started.rs | 34 +------------------ 4 files changed, 57 insertions(+), 43 deletions(-) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index fc36d811..53cb0b86 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -461,7 +461,7 @@ On first launch, `useOnboarding` checks if the default vault exists. If not, it When an opened folder is not yet a git repo, Tolaria shows a dismissible Git setup dialog and a persistent `Git disabled` status-bar warning. Markdown scanning, note browsing, note editing, and search continue normally. Git-dependent surfaces (history, changes, commit, sync, conflict resolution, remotes, AutoGit, and auto-sync) stay unavailable until the user explicitly initializes Git from the dialog, the status-bar warning, or the `Initialize Git for Current Vault` command-palette action. -When the user enables Git later, `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. Later `git_commit` calls honor the user's signing configuration first, then retry the same app-managed commit once with `commit.gpgsign=false` only when Git reports a signing-helper failure, so working GPG/SSH signing setups continue to sign while broken GPG setups do not create repeated opaque commit failures. +When the user enables Git later, `init_git_repo` runs `git init`, ensures Tolaria's default `.gitignore`, stages the vault, and writes the initial `Initial vault setup` commit. Before app-managed setup and remote-connection commits, Tolaria ensures the vault has local `user.name` / `user.email` values, falling back to `Tolaria ` when the vault has no local Git identity yet. 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. Later `git_commit` calls honor the user's signing configuration first, then retry the same app-managed commit once with `commit.gpgsign=false` only when Git reports a signing-helper failure, so working GPG/SSH signing setups continue to sign while broken GPG setups do not create repeated opaque commit failures. 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. @@ -587,7 +587,7 @@ flowchart TD If the current vault is not a Git repository, Tolaria treats Git as disabled instead of degraded. The status bar replaces changes, commit, sync, remote, conflict, and history controls with a `Git disabled` warning that reopens Git setup. Command registration follows the same state: only `Initialize Git for Current Vault` is available in the Git group, while pull, commit, changes, conflict, and remote commands are hidden. `useAutoSync` is disabled for non-git vaults so the app does not run background Git commands against plain folders. -The same local-only state enables the explicit Add Remote flow. `AddRemoteModal` is reachable from the `No remote` chip and the command palette. The backend `git_add_remote` command adds `origin`, fetches it, refuses incompatible histories, and only enables tracking after a safe push or fast-forward-compatible check succeeds. +The same local-only state enables the explicit Add Remote flow. `AddRemoteModal` is reachable from the `No remote` chip and the command palette. The backend `git_add_remote` command ensures the local author identity, adds `origin`, fetches it, refuses incompatible histories, and only enables tracking after a safe push or fast-forward-compatible check succeeds. `useCommitFlow` also exposes `runAutomaticCheckpoint()`, a dialog-free commit path shared by AutoGit and the bottom-bar Commit button. `useAutoGit` watches the last editor activity plus app focus/visibility state, and when the vault is git-backed, all saves are flushed, and no unsaved edits remain, it triggers the same deterministic `Updated N note(s)` / `Updated N file(s)` commit message path after the configured idle or inactive thresholds. The bottom-bar quick action reuses that checkpoint flow after forcing a save first, so manual quick commits and scheduled AutoGit commits stay aligned on message generation and push behavior. diff --git a/src-tauri/src/git/connect.rs b/src-tauri/src/git/connect.rs index 354621bb..d0b3ff0c 100644 --- a/src-tauri/src/git/connect.rs +++ b/src-tauri/src/git/connect.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use std::path::Path; use std::process::Output; -use super::git_command; +use super::{ensure_author_config, git_command}; const DEFAULT_REMOTE_NAME: &str = "origin"; @@ -92,6 +92,8 @@ pub fn git_add_remote(vault_path: &str, remote_url: &str) -> Result bool { + ["user.name", "user.email"].into_iter().all(|key| { + let output = StdCommand::new("git") + .args(["config", "--local", key]) + .current_dir(path) + .output() + .unwrap(); + + output.status.success() && !String::from_utf8_lossy(&output.stdout).trim().is_empty() + }) + } + #[test] fn disconnect_all_remotes_removes_every_remote() { let dir = setup_git_repo(); @@ -489,6 +513,26 @@ mod tests { assert_eq!((status.ahead, status.behind), (0, 0)); } + #[test] + fn git_add_remote_sets_local_identity_when_existing_repo_has_none() { + let local = setup_git_repo(); + create_local_commit(local.path(), "note.md", "Local", "Initial local commit"); + clear_local_author(local.path()); + assert!(!local_author_is_configured(local.path())); + + let bare = TempDir::new().unwrap(); + init_bare_remote(bare.path()); + + let result = git_add_remote( + local.path().to_str().unwrap(), + bare.path().to_str().unwrap(), + ) + .unwrap(); + + assert_eq!(result.status, "connected"); + assert!(local_author_is_configured(local.path())); + } + #[test] fn git_add_remote_pushes_when_remote_is_the_local_branch_ancestor() { let local = setup_git_repo(); diff --git a/src-tauri/src/git/mod.rs b/src-tauri/src/git/mod.rs index 6fefc977..44fc8783 100644 --- a/src-tauri/src/git/mod.rs +++ b/src-tauri/src/git/mod.rs @@ -129,21 +129,23 @@ fn git_command_label<'a>(args: &'a [&'a str]) -> &'a str { } /// Set local user.name and user.email if not already configured. -fn ensure_author_config(dir: &Path) -> Result<(), String> { +pub(crate) fn ensure_author_config(dir: &Path) -> Result<(), String> { for (key, fallback) in [ ("user.name", "Tolaria"), ("user.email", "vault@tolaria.app"), ] { - let check = git_command() - .args(["config", key]) + let local = git_command() + .args(["config", "--local", key]) .current_dir(dir) .output() - .map_err(|e| format!("Failed to check git config: {}", e))?; + .map_err(|e| format!("Failed to check git config {key}: {e}"))?; - let value = String::from_utf8_lossy(&check.stdout); - if !check.status.success() || value.trim().is_empty() { - run_git(dir, &["config", key, fallback])?; + let value = String::from_utf8_lossy(&local.stdout); + if local.status.success() && !value.trim().is_empty() { + continue; } + + run_git(dir, &["config", "--local", key, fallback])?; } Ok(()) } diff --git a/src-tauri/src/vault/getting_started.rs b/src-tauri/src/vault/getting_started.rs index 45fb11f0..636f1eb8 100644 --- a/src-tauri/src/vault/getting_started.rs +++ b/src-tauri/src/vault/getting_started.rs @@ -568,7 +568,7 @@ fn refresh_cloned_vault_config_files(vault_path: &Path) -> Result<(), String> { return Ok(()); } - ensure_commit_identity(vault_path)?; + crate::git::ensure_author_config(vault_path)?; crate::git::git_commit( path_to_utf8(vault_path, "Vault path")?, "Initialize Tolaria config files", @@ -593,38 +593,6 @@ fn vault_has_pending_changes(vault_path: &Path) -> Result { )) } -fn ensure_commit_identity(vault_path: &Path) -> Result<(), String> { - for (key, fallback) in [ - ("user.name", "Tolaria"), - ("user.email", "vault@tolaria.app"), - ] { - let output = crate::hidden_command("git") - .args(["config", key]) - .current_dir(vault_path) - .output() - .map_err(|e| format!("Failed to inspect git config {key}: {e}"))?; - - if output.status.success() && !String::from_utf8_lossy(&output.stdout).trim().is_empty() { - continue; - } - - let set_output = crate::hidden_command("git") - .args(["config", key, fallback]) - .current_dir(vault_path) - .output() - .map_err(|e| format!("Failed to set git config {key}: {e}"))?; - - if !set_output.status.success() { - return Err(format!( - "git config {key} failed: {}", - String::from_utf8_lossy(&set_output.stderr).trim() - )); - } - } - - Ok(()) -} - #[cfg(test)] mod tests { use super::*;