fix: merge local-only git remote PR #739

This commit is contained in:
lucaronin
2026-05-28 00:42:44 +02:00
4 changed files with 140 additions and 8 deletions

View File

@@ -3,6 +3,7 @@ use std::path::Path;
use std::process::Output;
use super::credentials::request_remote_credentials;
use super::remote_config::{configure_origin_remote, list_configured_remotes};
use super::{ensure_author_config, git_command};
const DEFAULT_REMOTE_NAME: &str = "origin";
@@ -105,7 +106,7 @@ pub fn git_add_remote(vault_path: &str, remote_url: &str) -> Result<GitAddRemote
let connection = RemoteConnection::new(branch);
let trimmed_url = remote_url.trim();
run_git(vault, &["remote", "add", DEFAULT_REMOTE_NAME, trimmed_url])?;
configure_origin_remote(vault, trimmed_url)?;
request_remote_credentials(vault, trimmed_url);
let result = finish_remote_connection(vault, &connection);
@@ -186,13 +187,7 @@ fn current_branch(vault: &Path) -> Result<String, String> {
}
fn list_remotes(vault: &Path) -> Result<Vec<String>, String> {
let output = git_output(vault, &["remote"])?;
if !output.status.success() {
return Err(command_error("git remote", &output));
}
Ok(stdout_lines(&output))
list_configured_remotes(vault)
}
fn unset_upstream(vault: &Path) {

View File

@@ -7,6 +7,7 @@ mod dates;
mod history;
mod pulse;
mod remote;
mod remote_config;
mod status;
use std::ffi::{OsStr, OsString};

View File

@@ -0,0 +1,77 @@
use std::path::Path;
use std::process::Output;
use super::git_command;
const DEFAULT_FETCH_REFSPEC: &str = "+refs/heads/*:refs/remotes/origin/*";
const REMOTE_URL_CONFIG_PATTERN: &str = r"^remote\..*\.url$";
const ORIGIN_URL_CONFIG_KEY: &str = "remote.origin.url";
const ORIGIN_FETCH_CONFIG_KEY: &str = "remote.origin.fetch";
pub(super) fn list_configured_remotes(vault: &Path) -> Result<Vec<String>, String> {
let output = git_command()
.args(["config", "--get-regexp", REMOTE_URL_CONFIG_PATTERN])
.current_dir(vault)
.output()
.map_err(|e| format!("Failed to inspect git remotes: {e}"))?;
if output.status.code() == Some(1) {
return Ok(Vec::new());
}
if !output.status.success() {
return Err(command_error("git config --get-regexp", &output));
}
Ok(stdout_lines(&output)
.into_iter()
.filter_map(|line| remote_name_from_url_config(&line))
.collect())
}
fn remote_name_from_url_config(line: &str) -> Option<String> {
let (key, value) = line.split_once(' ')?;
if value.trim().is_empty() {
return None;
}
key.strip_prefix("remote.")
.and_then(|name| name.strip_suffix(".url"))
.filter(|name| !name.is_empty())
.map(ToString::to_string)
}
pub(super) fn configure_origin_remote(vault: &Path, remote_url: &str) -> Result<(), String> {
run_git_config(vault, ORIGIN_URL_CONFIG_KEY, remote_url)?;
run_git_config(vault, ORIGIN_FETCH_CONFIG_KEY, DEFAULT_FETCH_REFSPEC)
}
fn run_git_config(vault: &Path, key: &str, value: &str) -> Result<(), String> {
let output = git_command()
.args(["config", "--local", "--replace-all", key, value])
.current_dir(vault)
.output()
.map_err(|e| format!("Failed to run git config: {e}"))?;
if output.status.success() {
return Ok(());
}
Err(command_error("git config", &output))
}
fn stdout_lines(output: &Output) -> Vec<String> {
String::from_utf8_lossy(&output.stdout)
.lines()
.map(str::trim)
.filter(|line| !line.is_empty())
.map(ToString::to_string)
.collect()
}
fn command_error(command: &str, output: &Output) -> String {
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
if stderr.is_empty() {
format!("{command} failed")
} else {
stderr
}
}

View File

@@ -0,0 +1,59 @@
use std::fs;
use std::path::Path;
use std::process::Command;
use tempfile::TempDir;
use tolaria_lib::git::{git_add_remote, git_commit, git_remote_status};
fn run_git(path: &Path, args: &[&str]) {
let output = Command::new("git")
.args(args)
.current_dir(path)
.output()
.unwrap();
assert!(
output.status.success(),
"git {:?} failed: {}",
args,
String::from_utf8_lossy(&output.stderr)
);
}
fn setup_repo() -> TempDir {
let dir = TempDir::new().unwrap();
run_git(dir.path(), &["init", "-b", "main"]);
dir
}
fn setup_bare_repo() -> TempDir {
let dir = TempDir::new().unwrap();
run_git(dir.path(), &["init", "--bare"]);
dir
}
#[test]
fn git_add_remote_ignores_name_only_origin_config() {
let local = setup_repo();
fs::write(local.path().join("note.md"), "# Note\n").unwrap();
git_commit(local.path().to_str().unwrap(), "initial").unwrap();
run_git(local.path(), &["config", "remote.origin.prune", "true"]);
let remote_names = Command::new("git")
.args(["remote"])
.current_dir(local.path())
.output()
.unwrap();
assert!(String::from_utf8_lossy(&remote_names.stdout).contains("origin"));
let bare = setup_bare_repo();
let result = git_add_remote(
local.path().to_str().unwrap(),
bare.path().to_str().unwrap(),
)
.unwrap();
assert_eq!(result.status, "connected");
assert!(git_remote_status(local.path().to_str().unwrap())
.unwrap()
.has_remote);
}