From df85ec1881a72cb600007b1c361bb16992f46394 Mon Sep 17 00:00:00 2001 From: Noethix55555 <277300782+Noethix55555@users.noreply.github.com> Date: Wed, 17 Jun 2026 20:15:05 -0400 Subject: [PATCH] fix(rebase): return HEAD when conflict_free_rebase is a no-op When the branch is already up-to-date (zero commits to replay), libgit2 finishes the rebase immediately and the loop body never executes, leaving last_commit as None. The previous code turned that into Err("no commit rebased"), which surfaced in the pull popup as a spurious rebase-failed error even though the operation was successful. Fix: when the loop produces no steps, fall back to get_head_repo() so the caller receives the current HEAD commit id instead of an error. Add a regression test that rebases master onto an ancestor branch and asserts the call succeeds. --- asyncgit/src/sync/rebase.rs | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/asyncgit/src/sync/rebase.rs b/asyncgit/src/sync/rebase.rs index eaf05acdc8..e133cc9bd1 100644 --- a/asyncgit/src/sync/rebase.rs +++ b/asyncgit/src/sync/rebase.rs @@ -3,7 +3,7 @@ use scopetime::scope_time; use crate::{ error::{Error, Result}, - sync::repository::repo, + sync::{repository::repo, utils::get_head_repo}, }; use super::{CommitId, RepoPath}; @@ -63,9 +63,10 @@ pub fn conflict_free_rebase( rebase.finish(Some(&signature))?; - last_commit.ok_or_else(|| { - Error::Generic(String::from("no commit rebased")) - }) + // When the branch is already up-to-date the rebase loop produces zero + // steps. That is a successful no-op: return the current HEAD rather + // than an error. + last_commit.map_or_else(|| get_head_repo(repo), Ok) } /// @@ -254,6 +255,30 @@ mod test_conflict_free_rebase { assert_eq!(parent_ids(&repo, r), vec![c3]); } + #[test] + fn test_noop_already_uptodate() { + // Rebasing a branch onto a commit that is already an ancestor + // (i.e. nothing to replay) must succeed and return the current HEAD. + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path: &RepoPath = + &root.as_os_str().to_str().unwrap().into(); + + let _c1 = + write_commit_file(&repo, "test1.txt", "test", "commit1"); + + // Create branch "foo" at c1, then add a commit on master. + create_branch(repo_path, "foo").unwrap(); + let c2 = + write_commit_file(&repo, "test2.txt", "test", "commit2"); + + // Rebase master onto foo (foo is strictly behind master, so there + // is nothing to replay — master already contains all of foo's history). + // This should be a no-op and return c2 (current HEAD). + let r = test_rebase_branch_repo(repo_path, "foo"); + assert_eq!(r, c2); + } + #[test] fn test_conflict() { let (_td, repo) = repo_init().unwrap();