dedc4707
Extract auto-detect merge logic into PatchState::check_auto_merge()
a73x 2026-03-22 10:12
Deduplicate the merge detection code that was inline in both from_ref() (post-cache-hit path) and from_ref_uncached(). Both now call the shared private method check_auto_merge(&mut self, repo). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
diff --git a/src/state.rs b/src/state.rs index 14580cb..5b5cfb2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -368,6 +368,28 @@ impl PatchState { Ok((ahead, behind)) } /// Auto-detect merge: if the patch is still Open and its head is /// reachable from the base branch tip, the user merged it outside of /// git-collab. We compare the current base tip to the base_commit /// recorded at creation time — if it has moved to include the patch /// head, the patch is merged. fn check_auto_merge(&mut self, repo: &Repository) { if self.status != PatchStatus::Open { return; } let Ok(patch_head) = self.resolve_head(repo) else { return }; let base_ref = format!("refs/heads/{}", self.base_ref); let Ok(base_tip) = repo.refname_to_id(&base_ref) else { return }; let base_moved = self.base_commit.as_ref() .map(|bc| bc != &base_tip.to_string()) .unwrap_or(false); let reachable = base_tip == patch_head || repo.graph_descendant_of(base_tip, patch_head).unwrap_or(false); if base_moved && reachable { self.status = PatchStatus::Merged; } } pub fn from_ref( repo: &Repository, ref_name: &str, @@ -376,25 +398,12 @@ impl PatchState { // Check cache first if let Some(mut cached) = cache::get_cached_state::<PatchState>(repo, ref_name) { // The cache may return a stale Open status if the patch was merged // outside of git-collab (e.g. git merge) since the DAG tip hasn't // changed. Re-run the auto-detect merge check for Open patches. if cached.status == PatchStatus::Open { if let Ok(patch_head) = cached.resolve_head(repo) { let base_ref_name = format!("refs/heads/{}", cached.base_ref); if let Ok(base_tip) = repo.refname_to_id(&base_ref_name) { let base_moved = cached.base_commit.as_ref() .map(|bc| bc != &base_tip.to_string()) .unwrap_or(false); let reachable = base_tip == patch_head || repo.graph_descendant_of(base_tip, patch_head).unwrap_or(false); if base_moved && reachable { cached.status = PatchStatus::Merged; // Update the cache with the corrected status if let Ok(tip) = repo.refname_to_id(ref_name) { cache::set_cached_state(repo, ref_name, tip, &cached); } } } // outside of git-collab since the DAG tip hasn't changed. cached.check_auto_merge(repo); if cached.status == PatchStatus::Merged { // Update the cache with the corrected status if let Ok(tip) = repo.refname_to_id(ref_name) { cache::set_cached_state(repo, ref_name, tip, &cached); } } return Ok(cached); @@ -536,28 +545,7 @@ impl PatchState { if let Some(ref mut s) = state { s.last_updated = max_timestamp; // Auto-detect merge: if the patch is still Open and its head // is reachable from the base branch tip, the user merged it // outside of git-collab. We compare the current base tip to // the base_commit recorded at creation time — if it has moved // to include the patch head, the patch is merged. if s.status == PatchStatus::Open { if let Ok(patch_head) = s.resolve_head(repo) { let base_ref = format!("refs/heads/{}", s.base_ref); if let Ok(base_tip) = repo.refname_to_id(&base_ref) { // Base must have moved from where it was at creation let base_moved = s.base_commit.as_ref() .map(|bc| bc != &base_tip.to_string()) .unwrap_or(false); let reachable = base_tip == patch_head || repo.graph_descendant_of(base_tip, patch_head).unwrap_or(false); if base_moved && reachable { s.status = PatchStatus::Merged; } } } } s.check_auto_merge(repo); } state.ok_or_else(|| git2::Error::from_str("no PatchCreate event found in DAG").into()) }