Don't report a first sync as failed
A repository being pushed for the first time has no upstream, so the pull leg failed with "There is no tracking information for the current branch" and dragged the whole sync's status down — the app said "Sync finished with errors" even though the push had gone through and set the upstream itself. The upstream is now checked up front. Without one there is nothing to rebase onto, so the pull is skipped with a note in the log and the push sets the upstream directly, rather than being run once to fail and then retried. The argument choice moves to push_args so it can be asserted without a repository on disk. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GBWj9TphFMCoh7VHaSRnvQ
This commit is contained in:
+50
-15
@@ -64,6 +64,26 @@ fn has_origin(workspace: &Path) -> bool {
|
|||||||
ok && out.lines().any(|l| l.trim() == "origin")
|
ok && out.lines().any(|l| l.trim() == "origin")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// True if the current branch has an upstream to pull from.
|
||||||
|
///
|
||||||
|
/// A repository being pushed for the first time has none, and `rev-parse`
|
||||||
|
/// exits non-zero rather than printing anything.
|
||||||
|
fn has_upstream(workspace: &Path) -> bool {
|
||||||
|
run_git(workspace, &["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]).0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The push for a branch, which has to set the upstream the first time.
|
||||||
|
///
|
||||||
|
/// Kept separate from running it so it can be asserted in tests, like
|
||||||
|
/// [`origin_args`].
|
||||||
|
pub fn push_args(branch: &str, has_upstream: bool) -> Vec<&str> {
|
||||||
|
if has_upstream {
|
||||||
|
vec!["push"]
|
||||||
|
} else {
|
||||||
|
vec!["push", "--set-upstream", "origin", branch]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The URL `origin` points at, if the remote exists.
|
/// The URL `origin` points at, if the remote exists.
|
||||||
///
|
///
|
||||||
/// Read from the repository rather than from this app's settings: a remote
|
/// Read from the repository rather than from this app's settings: a remote
|
||||||
@@ -140,23 +160,22 @@ pub fn sync(workspace: &Path, message: &str) -> GitOutcome {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if has_origin(workspace) {
|
if has_origin(workspace) {
|
||||||
let (p_ok, p_out) = run_git(workspace, &["pull", "--rebase", "--autostash"]);
|
let upstream = has_upstream(workspace);
|
||||||
log.push_str(&p_out);
|
if upstream {
|
||||||
ok &= p_ok;
|
let (p_ok, p_out) = run_git(workspace, &["pull", "--rebase", "--autostash"]);
|
||||||
|
log.push_str(&p_out);
|
||||||
let (push_ok, push_out) = run_git(workspace, &["push"]);
|
ok &= p_ok;
|
||||||
log.push_str(&push_out);
|
|
||||||
// A failed push often just means no upstream is set yet; surface it but
|
|
||||||
// do not treat a missing upstream as a hard failure of the whole sync.
|
|
||||||
if !push_ok && push_out.contains("no upstream") {
|
|
||||||
let branch = current_branch(workspace);
|
|
||||||
let (u_ok, u_out) =
|
|
||||||
run_git(workspace, &["push", "--set-upstream", "origin", &branch]);
|
|
||||||
log.push_str(&u_out);
|
|
||||||
ok &= u_ok;
|
|
||||||
} else {
|
} else {
|
||||||
ok &= push_ok;
|
// Nothing to rebase onto yet, and pulling anyway fails with "no
|
||||||
|
// tracking information" — which used to make a first sync report
|
||||||
|
// errors even though its push had gone through fine.
|
||||||
|
log.push_str("(no upstream branch yet — nothing to pull)\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let branch = current_branch(workspace);
|
||||||
|
let (push_ok, push_out) = run_git(workspace, &push_args(&branch, upstream));
|
||||||
|
log.push_str(&push_out);
|
||||||
|
ok &= push_ok;
|
||||||
} else {
|
} else {
|
||||||
log.push_str("(no 'origin' remote configured — committed locally only)\n");
|
log.push_str("(no 'origin' remote configured — committed locally only)\n");
|
||||||
}
|
}
|
||||||
@@ -208,6 +227,22 @@ fn strip_command_echo(text: &str) -> String {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_first_push_sets_the_upstream_and_later_ones_do_not() {
|
||||||
|
// Without this the first push fails with "has no upstream branch".
|
||||||
|
assert_eq!(
|
||||||
|
push_args("main", false),
|
||||||
|
vec!["push", "--set-upstream", "origin", "main"]
|
||||||
|
);
|
||||||
|
// Once it is set, a plain push is what respects the tracking config.
|
||||||
|
assert_eq!(push_args("main", true), vec!["push"]);
|
||||||
|
// Whatever the branch is actually called.
|
||||||
|
assert_eq!(
|
||||||
|
push_args("draft-2", false),
|
||||||
|
vec!["push", "--set-upstream", "origin", "draft-2"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn setting_a_remote_adds_it_only_when_it_is_missing() {
|
fn setting_a_remote_adds_it_only_when_it_is_missing() {
|
||||||
// Fresh repository: the remote has to be created.
|
// Fresh repository: the remote has to be created.
|
||||||
|
|||||||
Reference in New Issue
Block a user