fix: sanitize appimage git subprocess env
This commit is contained in:
@@ -741,6 +741,7 @@ Tolaria tracks managed vault-level AI guidance separately from normal note conte
|
||||
Tolaria delegates remote auth to the user's system git setup:
|
||||
- `CloneVaultModal` captures a remote URL and local destination
|
||||
- `clone_git_repo` and `create_getting_started_vault` both run system git clone work in blocking Tokio tasks so clone UIs stay responsive
|
||||
- On Linux AppImage launches, every system-git command removes AppImage loader overrides such as `LD_LIBRARY_PATH`, `LD_PRELOAD`, and `GIT_EXEC_PATH` before spawning `git`, so helpers like `git-remote-https` bind against the host git/library stack instead of Tolaria's bundled WebKit/AppImage libraries
|
||||
- `git_add_remote` uses the same system git path and refuses remotes whose history is unrelated or ahead of the local vault
|
||||
- Existing `git_pull` / `git_push` commands keep surfacing raw git errors, and clone commands fail fast when git wants interactive terminal input
|
||||
- No provider-specific token or username is stored in app settings
|
||||
|
||||
@@ -510,8 +510,9 @@ Tolaria no longer implements provider-specific OAuth or remote-repository APIs.
|
||||
1. User opens `CloneVaultModal` from onboarding or the vault menu
|
||||
2. User pastes any git URL and chooses a local destination
|
||||
3. The `clone_git_repo()` Tauri command runs `git clone` inside a blocking Tokio task so the Tauri window stays responsive during slow or failing clones
|
||||
4. `git_push()` / `git_pull()` continue to use the same system git path
|
||||
5. Clone commands disable interactive terminal / askpass prompts and surface the git failure back to the UI instead of freezing the app waiting for input
|
||||
4. Linux AppImage builds strip AppImage loader variables from system-git subprocesses before spawning `git`, keeping `git-remote-https` on the host git/library stack
|
||||
5. `git_push()` / `git_pull()` continue to use the same system git path
|
||||
6. Clone commands disable interactive terminal / askpass prompts and surface the git failure back to the UI instead of freezing the app waiting for input
|
||||
|
||||
**Auth model:**
|
||||
- SSH keys, Git Credential Manager, macOS Keychain helpers, `gh auth`, and other git helpers all work without app-specific setup
|
||||
|
||||
@@ -69,6 +69,8 @@ pnpm playwright:regression # Full Playwright regression suite
|
||||
|
||||
`create_getting_started_vault` clones the public starter repo and then removes every git remote from the new local copy. That means Getting Started vaults open local-only by default. Users connect a compatible remote later through the bottom-bar `No remote` chip or the command palette, both of which feed the same `AddRemoteModal` and `git_add_remote` backend flow.
|
||||
|
||||
Linux AppImage builds still use the user's system `git`. Before Tolaria spawns that `git` process, it removes AppImage loader overrides such as `LD_LIBRARY_PATH`, `LD_PRELOAD`, and `GIT_EXEC_PATH` so HTTPS clone helpers use the host git libraries instead of bundled AppImage libraries.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
|
||||
@@ -214,7 +214,7 @@ fn has_tolaria_vault_marker(path: &std::path::Path) -> bool {
|
||||
pub fn init_git_repo(vault_path: VaultPathArg) -> Result<(), String> {
|
||||
let vault_path = expand_tilde(&vault_path);
|
||||
validate_git_init_target(&vault_path)?;
|
||||
crate::git::init_repo(&vault_path)
|
||||
crate::git::init_repo(std::path::Path::new(vault_path.as_ref()))
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
|
||||
@@ -87,7 +87,7 @@ pub fn repair_vault(vault_path: String) -> Result<String, String> {
|
||||
let vault_path = expand_tilde(&vault_path);
|
||||
vault::migrate_is_a_to_type(&vault_path)?;
|
||||
vault::repair_config_files(&vault_path)?;
|
||||
git::ensure_gitignore(&vault_path)?;
|
||||
git::ensure_gitignore(std::path::Path::new(vault_path.as_ref()))?;
|
||||
Ok("Vault repaired".to_string())
|
||||
}
|
||||
|
||||
|
||||
@@ -57,13 +57,45 @@ const DEFAULT_GITIGNORE: &str = "# Tolaria app files (machine-specific, never co
|
||||
*.swo\n";
|
||||
|
||||
fn git_command() -> Command {
|
||||
crate::hidden_command("git")
|
||||
let mut command = crate::hidden_command("git");
|
||||
sanitize_linux_appimage_git_env(&mut command);
|
||||
command
|
||||
}
|
||||
|
||||
#[cfg(any(test, all(desktop, target_os = "linux")))]
|
||||
const LINUX_APPIMAGE_GIT_ENV_REMOVALS: [&str; 3] =
|
||||
["LD_LIBRARY_PATH", "LD_PRELOAD", "GIT_EXEC_PATH"];
|
||||
|
||||
#[cfg(all(desktop, target_os = "linux"))]
|
||||
fn sanitize_linux_appimage_git_env(command: &mut Command) {
|
||||
sanitize_linux_appimage_git_env_for_launch(command, linux_appimage_env_present());
|
||||
}
|
||||
|
||||
#[cfg(not(all(desktop, target_os = "linux")))]
|
||||
fn sanitize_linux_appimage_git_env(_command: &mut Command) {}
|
||||
|
||||
#[cfg(any(test, all(desktop, target_os = "linux")))]
|
||||
fn sanitize_linux_appimage_git_env_for_launch(command: &mut Command, is_appimage: bool) {
|
||||
if !is_appimage {
|
||||
return;
|
||||
}
|
||||
|
||||
for key in LINUX_APPIMAGE_GIT_ENV_REMOVALS {
|
||||
command.env_remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(desktop, target_os = "linux"))]
|
||||
fn linux_appimage_env_present() -> bool {
|
||||
["APPIMAGE", "APPDIR"]
|
||||
.into_iter()
|
||||
.any(|key| std::env::var(key).is_ok_and(|value| !value.trim().is_empty()))
|
||||
}
|
||||
|
||||
/// Ensure a `.gitignore` with sensible defaults exists in the vault directory.
|
||||
/// Creates the file if missing; leaves existing `.gitignore` files untouched.
|
||||
pub fn ensure_gitignore(path: &str) -> Result<(), String> {
|
||||
let gitignore_path = Path::new(path).join(".gitignore");
|
||||
pub fn ensure_gitignore(path: impl AsRef<Path>) -> Result<(), String> {
|
||||
let gitignore_path = path.as_ref().join(".gitignore");
|
||||
if !gitignore_path.exists() {
|
||||
std::fs::write(&gitignore_path, DEFAULT_GITIGNORE)
|
||||
.map_err(|e| format!("Failed to write .gitignore: {}", e))?;
|
||||
@@ -72,15 +104,15 @@ pub fn ensure_gitignore(path: &str) -> Result<(), String> {
|
||||
}
|
||||
|
||||
/// Initialize a new git repository, stage all files, and create an initial commit.
|
||||
pub fn init_repo(path: &str) -> Result<(), String> {
|
||||
let dir = Path::new(path);
|
||||
pub fn init_repo(path: impl AsRef<Path>) -> Result<(), String> {
|
||||
let dir = path.as_ref();
|
||||
|
||||
run_git(dir, &["init"])?;
|
||||
ensure_author_config(dir)?;
|
||||
|
||||
// Write .gitignore before the first commit so machine-specific and
|
||||
// macOS metadata files are never tracked and don't cause conflicts.
|
||||
ensure_gitignore(path)?;
|
||||
ensure_gitignore(dir)?;
|
||||
|
||||
run_git(dir, &["add", "."])?;
|
||||
commit_initial_vault_setup(dir)?;
|
||||
@@ -176,6 +208,7 @@ fn parse_github_repo_path(url: &str) -> Option<String> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use tempfile::TempDir;
|
||||
|
||||
@@ -259,6 +292,18 @@ mod tests {
|
||||
(bare_dir, clone_a_dir, clone_b_dir)
|
||||
}
|
||||
|
||||
fn command_envs(command: &Command) -> HashMap<String, Option<String>> {
|
||||
command
|
||||
.get_envs()
|
||||
.map(|(key, value)| {
|
||||
(
|
||||
key.to_string_lossy().to_string(),
|
||||
value.map(|entry| entry.to_string_lossy().to_string()),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ensure_gitignore_creates_file() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
@@ -282,6 +327,32 @@ mod tests {
|
||||
assert_eq!(content, "my-rule\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_linux_appimage_git_commands_remove_appimage_loader_env() {
|
||||
let mut command = crate::hidden_command("git");
|
||||
|
||||
sanitize_linux_appimage_git_env_for_launch(&mut command, true);
|
||||
|
||||
let envs = command_envs(&command);
|
||||
|
||||
for key in LINUX_APPIMAGE_GIT_ENV_REMOVALS {
|
||||
assert_eq!(envs.get(key), Some(&None));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_non_appimage_git_commands_keep_parent_env_unmodified() {
|
||||
let mut command = crate::hidden_command("git");
|
||||
|
||||
sanitize_linux_appimage_git_env_for_launch(&mut command, false);
|
||||
|
||||
let envs = command_envs(&command);
|
||||
|
||||
for key in LINUX_APPIMAGE_GIT_ENV_REMOVALS {
|
||||
assert!(!envs.contains_key(key));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_init_repo_creates_git_directory() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user