fix: treat name-only git remotes as local-only

This commit is contained in:
mehmet turac
2026-05-24 21:58:42 +03:00
parent 8764b6f78e
commit 7516dfe48a
5 changed files with 127 additions and 6 deletions

View File

@@ -18,10 +18,22 @@ pub struct GitPullResult {
pub fn has_remote(vault_path: &str) -> Result<bool, String> {
let vault = Path::new(vault_path);
let output = git_command()
.args(["remote"])
.args(["config", "--get-regexp", r"^remote\..*\.url$"])
.current_dir(vault)
.output()
.map_err(|e| format!("Failed to run git remote: {}", e))?;
.map_err(|e| format!("Failed to inspect git remotes: {}", e))?;
if !output.status.success() {
if output.status.code() == Some(1) {
return Ok(false);
}
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
return Err(if stderr.is_empty() {
"Failed to inspect git remotes".to_string()
} else {
stderr
});
}
Ok(!String::from_utf8_lossy(&output.stdout).trim().is_empty())
}
@@ -181,7 +193,7 @@ fn current_branch(vault: &Path) -> Result<String, String> {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct GitPushResult {
pub status: String, // "ok" | "rejected" | "auth_error" | "network_error" | "error"
pub status: String, // "ok" | "rejected" | "auth_error" | "network_error" | "no_remote" | "error"
pub message: String,
}
@@ -210,6 +222,10 @@ pub fn classify_push_error(stderr: &str) -> GitPushResult {
);
}
if is_no_remote_push_error(&lower) {
return push_error("no_remote", "No remote configured");
}
push_error(
"error",
format!("Push failed: {}", push_error_detail(stderr)),
@@ -259,6 +275,18 @@ fn is_network_push_error(lower: &str) -> bool {
)
}
fn is_no_remote_push_error(lower: &str) -> bool {
contains_any(
lower,
&[
"no configured push destination",
"does not appear to be a git repository",
"no such remote",
"no upstream branch",
],
)
}
fn push_error_detail(stderr: &str) -> String {
let hint_line = stderr
.lines()
@@ -283,6 +311,13 @@ fn push_error_detail(stderr: &str) -> String {
pub fn git_push(vault_path: &str) -> Result<GitPushResult, String> {
let vault = Path::new(vault_path);
if !has_remote(vault_path)? {
return Ok(GitPushResult {
status: "no_remote".to_string(),
message: "No remote configured".to_string(),
});
}
let output = git_command()
.args(["push"])
.current_dir(vault)
@@ -331,6 +366,27 @@ mod tests {
assert!(has_remote(vp).unwrap());
}
#[test]
fn test_has_remote_ignores_name_only_remote_without_url() {
let dir = setup_git_repo();
let vault = dir.path();
let vp = vault.to_str().unwrap();
git_command()
.args(["config", "remote.origin.prune", "true"])
.current_dir(vault)
.output()
.unwrap();
let remote_names = git_command()
.args(["remote"])
.current_dir(vault)
.output()
.unwrap();
assert!(String::from_utf8_lossy(&remote_names.stdout).contains("origin"));
assert!(!has_remote(vp).unwrap());
}
#[test]
fn test_git_pull_no_remote_returns_no_remote() {
let dir = setup_git_repo();
@@ -430,6 +486,14 @@ hint: have locally."#;
assert!(result.message.contains("network"));
}
#[test]
fn test_classify_push_error_no_remote() {
let stderr = "fatal: No configured push destination.";
let result = classify_push_error(stderr);
assert_eq!(result.status, "no_remote");
assert!(result.message.contains("No remote"));
}
#[test]
fn test_classify_push_error_unknown() {
let stderr = "error: something unexpected happened\nhint: Try again later";
@@ -469,6 +533,19 @@ hint: have locally."#;
assert_eq!(result.status, "ok");
}
#[test]
fn test_git_push_no_remote_returns_no_remote() {
let dir = setup_git_repo();
let vault = dir.path();
let vp = vault.to_str().unwrap();
fs::write(vault.join("note.md"), "# Note\n").unwrap();
git_commit(vp, "initial").unwrap();
let result = git_push(vp).unwrap();
assert_eq!(result.status, "no_remote");
}
#[test]
fn test_git_push_rejected_returns_rejected() {
let (_bare, clone_a, clone_b) = setup_remote_pair();