From a4dc507b669783eb8986fe0632ec8a3f642c58c0 Mon Sep 17 00:00:00 2001 From: ZhenYi <434836402@qq.com> Date: Fri, 17 Apr 2026 16:30:58 +0800 Subject: [PATCH] fix(git): multiple branch and upstream query bugs 1. branch_list: Fix is_head comparison - head_name now keeps full ref (e.g., "refs/heads/main") - Previously stripped to short name causing is_head to always be false 2. branch_get/branch_exists: Fix name resolution for branches with '/' - Previously, "feature/testing" was assumed to be a remote branch - Now tries both refs/heads/ and refs/remotes/ candidates - Correctly handles local branches like "feature/testing" 3. sync_refs: Fix upstream reference format - Was storing "refs/remotes/{}/branch" (broken pattern) - Now properly queries git2 for actual upstream branch name --- libs/git/branch/query.rs | 63 ++++++++++++++++++++---------------- libs/git/hook/sync/commit.rs | 21 ++++++++++-- 2 files changed, 54 insertions(+), 30 deletions(-) diff --git a/libs/git/branch/query.rs b/libs/git/branch/query.rs index 5c5f49f..6b06df6 100644 --- a/libs/git/branch/query.rs +++ b/libs/git/branch/query.rs @@ -15,10 +15,8 @@ impl GitDomain { }; let mut branches = Vec::with_capacity(16); - let head_name = self.repo.head().ok().and_then(|r| { - r.name() - .map(|name| name.strip_prefix("refs/heads/").unwrap_or(name).to_string()) - }); + // Keep head_name as full ref for comparison with branch names + let head_name = self.repo.head().ok().and_then(|r| r.name().map(String::from)); for branch_result in self .repo() @@ -32,6 +30,7 @@ impl GitDomain { }; let name = name.to_string(); let oid = CommitOid::from_git2(target); + // Compare full ref names (e.g., "refs/heads/main" == "refs/heads/main") let is_head = head_name.as_ref().map_or(false, |h| h == &name); let is_current = branch.is_head(); @@ -78,24 +77,28 @@ impl GitDomain { } pub fn branch_get(&self, name: &str) -> GitResult { - // Determine full ref name and branch type - let full_name = if name.starts_with("refs/heads/") { - name.to_string() - } else if name.starts_with("refs/remotes/") { - name.to_string() - } else if name.contains('/') { - // e.g. "origin/main" → remote branch - format!("refs/remotes/{}", name) + // Determine candidates: try full refs as-is, then construct refs/heads/ and refs/remotes/ + let candidates: Vec = if name.starts_with("refs/") { + vec![name.to_string()] } else { - format!("refs/heads/{}", name) + vec![ + format!("refs/heads/{}", name), + format!("refs/remotes/{}", name), + ] }; - let branch = self - .repo() - .find_branch(&full_name, git2::BranchType::Local) - .or_else(|_| self.repo.find_branch(&full_name, git2::BranchType::Remote)) - .map_err(|_e| GitError::RefNotFound(name.to_string()))?; + // Try local branch first, then remote + let branch = candidates + .iter() + .find_map(|full_name| { + self.repo + .find_branch(full_name, git2::BranchType::Local) + .ok() + .or_else(|| self.repo.find_branch(full_name, git2::BranchType::Remote).ok()) + }) + .ok_or_else(|| GitError::RefNotFound(name.to_string()))?; + let full_name = branch.name().ok().flatten().unwrap_or_default().to_string(); let target = branch .get() .target() @@ -125,19 +128,25 @@ impl GitDomain { } pub fn branch_exists(&self, name: &str) -> bool { - let full_name = if name.starts_with("refs/heads/") || name.starts_with("refs/remotes/") { - name.to_string() - } else if name.contains('/') { - format!("refs/remotes/{}", name) + // Same candidate logic as branch_get + let candidates: Vec = if name.starts_with("refs/") { + vec![name.to_string()] } else { - format!("refs/heads/{}", name) + vec![ + format!("refs/heads/{}", name), + format!("refs/remotes/{}", name), + ] }; - self.repo.find_branch(&full_name, BranchType::Local).is_ok() - || self - .repo() - .find_branch(&full_name, BranchType::Remote) + candidates.iter().any(|full_name| { + self.repo + .find_branch(full_name, BranchType::Local) .is_ok() + || self + .repo() + .find_branch(full_name, BranchType::Remote) + .is_ok() + }) } pub fn branch_is_head(&self, name: &str) -> GitResult { diff --git a/libs/git/hook/sync/commit.rs b/libs/git/hook/sync/commit.rs index 2277a6c..2bef47c 100644 --- a/libs/git/hook/sync/commit.rs +++ b/libs/git/hook/sync/commit.rs @@ -89,9 +89,24 @@ impl HookMetaDataSync { tagger_email: String::new(), }); } else if is_branch && !is_remote { - let upstream = reference - .shorthand() - .map(|short| format!("refs/remotes/{{}}/{}", short)); + // Try to get upstream branch name from the reference's upstream target + let upstream: Option = if reference.target().is_some() { + if let Ok(branch) = self.domain.repo().find_branch(&name, git2::BranchType::Local) { + if let Ok(upstream_ref) = branch.upstream() { + if let Some(upstream_name) = upstream_ref.name().ok().flatten() { + Some(upstream_name.to_string()) + } else { + None + } + } else { + None + } + } else { + None + } + } else { + None + }; branches.push(BranchTip { name,