@@ -92,6 +92,22 @@ fn looks_like_ssh_auth_failure(msg: &str) -> bool {
9292 || m. contains ( "authentication failed" )
9393}
9494
95+ /// Heuristically detects fast-forward-only divergence failures.
96+ ///
97+ /// # Parameters
98+ /// - `msg`: Error text.
99+ ///
100+ /// # Returns
101+ /// - `true` when text resembles a diverged ff-only pull.
102+ /// - `false` otherwise.
103+ fn looks_like_ff_only_divergence ( msg : & str ) -> bool {
104+ let m = msg. to_lowercase ( ) ;
105+ m. contains ( "not possible to fast-forward" )
106+ || m. contains ( "can't be fast-forwarded" )
107+ || m. contains ( "cannot be fast-forwarded" )
108+ || ( m. contains ( "fast-forward" ) && m. contains ( "diverg" ) )
109+ }
110+
95111/// Returns remote URL for a named remote.
96112///
97113/// # Parameters
@@ -428,10 +444,10 @@ pub async fn vcs_pull<R: Runtime>(
428444 } ) ;
429445 }
430446
431- info ! ( "Fast-forward pulling '{current}' from {remote}/{upstream_branch}" ) ;
447+ info ! ( "Pulling '{current}' from {remote}/{upstream_branch}" ) ;
432448 match repo. inner ( ) . pull_ff_only ( remote, upstream_branch, on) {
433449 Ok ( ( ) ) => {
434- info ! ( "Pull (ff-only) completed successfully for branch '{current}'" ) ;
450+ info ! ( "Pull completed successfully for branch '{current}'" ) ;
435451 Ok ( PullResult {
436452 pulled : true ,
437453 branch : current,
@@ -450,9 +466,20 @@ pub async fn vcs_pull<R: Runtime>(
450466 }
451467 Err ( e) => {
452468 let msg = e. to_string ( ) ;
469+ if looks_like_ff_only_divergence ( & msg) {
470+ info ! ( "Pull skipped for branch '{current}': {msg}" ) ;
471+ return Ok ( PullResult {
472+ pulled : false ,
473+ branch : current. clone ( ) ,
474+ reason : Some ( format ! (
475+ "Branch '{current}' diverged from {remote}/{upstream_branch}; fast-forward pull skipped"
476+ ) ) ,
477+ } ) ;
478+ }
479+
453480 let url = remote_url_for ( repo. inner ( ) , remote) . unwrap_or_default ( ) ;
454481 emit_ssh_prompt ( & app, remote, & url, & msg) ;
455- error ! ( "Pull (ff-only) failed for branch '{current}': {msg}" ) ;
482+ error ! ( "Pull failed for branch '{current}': {msg}" ) ;
456483 Err ( msg)
457484 }
458485 }
@@ -605,6 +632,27 @@ pub async fn vcs_undo_since_push<R: Runtime>(
605632 . await
606633}
607634
635+ #[ cfg( test) ]
636+ mod tests {
637+ use super :: looks_like_ff_only_divergence;
638+
639+ #[ test]
640+ fn detects_fast_forward_only_divergence ( ) {
641+ assert ! ( looks_like_ff_only_divergence(
642+ "fatal: Not possible to fast-forward, aborting."
643+ ) ) ;
644+ assert ! ( looks_like_ff_only_divergence(
645+ "hint: Diverging branches can't be fast-forwarded, you need to either:"
646+ ) ) ;
647+ }
648+
649+ #[ test]
650+ fn ignores_unrelated_pull_failures ( ) {
651+ assert ! ( !looks_like_ff_only_divergence( "permission denied (publickey)" ) ) ;
652+ assert ! ( !looks_like_ff_only_divergence( "could not resolve hostname origin" ) ) ;
653+ }
654+ }
655+
608656#[ tauri:: command]
609657/// Soft-resets HEAD to a selected commit, constrained to ahead-of-upstream history.
610658///
0 commit comments