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
This commit is contained in:
ZhenYi 2026-04-17 16:30:58 +08:00
parent ee4ff6c752
commit a4dc507b66
2 changed files with 54 additions and 30 deletions

View File

@ -15,10 +15,8 @@ impl GitDomain {
}; };
let mut branches = Vec::with_capacity(16); let mut branches = Vec::with_capacity(16);
let head_name = self.repo.head().ok().and_then(|r| { // Keep head_name as full ref for comparison with branch names
r.name() let head_name = self.repo.head().ok().and_then(|r| r.name().map(String::from));
.map(|name| name.strip_prefix("refs/heads/").unwrap_or(name).to_string())
});
for branch_result in self for branch_result in self
.repo() .repo()
@ -32,6 +30,7 @@ impl GitDomain {
}; };
let name = name.to_string(); let name = name.to_string();
let oid = CommitOid::from_git2(target); 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_head = head_name.as_ref().map_or(false, |h| h == &name);
let is_current = branch.is_head(); let is_current = branch.is_head();
@ -78,24 +77,28 @@ impl GitDomain {
} }
pub fn branch_get(&self, name: &str) -> GitResult<BranchInfo> { pub fn branch_get(&self, name: &str) -> GitResult<BranchInfo> {
// Determine full ref name and branch type // Determine candidates: try full refs as-is, then construct refs/heads/ and refs/remotes/
let full_name = if name.starts_with("refs/heads/") { let candidates: Vec<String> = if name.starts_with("refs/") {
name.to_string() vec![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)
} else { } else {
format!("refs/heads/{}", name) vec![
format!("refs/heads/{}", name),
format!("refs/remotes/{}", name),
]
}; };
let branch = self // Try local branch first, then remote
.repo() let branch = candidates
.find_branch(&full_name, git2::BranchType::Local) .iter()
.or_else(|_| self.repo.find_branch(&full_name, git2::BranchType::Remote)) .find_map(|full_name| {
.map_err(|_e| GitError::RefNotFound(name.to_string()))?; 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 let target = branch
.get() .get()
.target() .target()
@ -125,19 +128,25 @@ impl GitDomain {
} }
pub fn branch_exists(&self, name: &str) -> bool { pub fn branch_exists(&self, name: &str) -> bool {
let full_name = if name.starts_with("refs/heads/") || name.starts_with("refs/remotes/") { // Same candidate logic as branch_get
name.to_string() let candidates: Vec<String> = if name.starts_with("refs/") {
} else if name.contains('/') { vec![name.to_string()]
format!("refs/remotes/{}", name)
} else { } else {
format!("refs/heads/{}", name) vec![
format!("refs/heads/{}", name),
format!("refs/remotes/{}", name),
]
}; };
self.repo.find_branch(&full_name, BranchType::Local).is_ok() candidates.iter().any(|full_name| {
|| self self.repo
.repo() .find_branch(full_name, BranchType::Local)
.find_branch(&full_name, BranchType::Remote)
.is_ok() .is_ok()
|| self
.repo()
.find_branch(full_name, BranchType::Remote)
.is_ok()
})
} }
pub fn branch_is_head(&self, name: &str) -> GitResult<bool> { pub fn branch_is_head(&self, name: &str) -> GitResult<bool> {

View File

@ -89,9 +89,24 @@ impl HookMetaDataSync {
tagger_email: String::new(), tagger_email: String::new(),
}); });
} else if is_branch && !is_remote { } else if is_branch && !is_remote {
let upstream = reference // Try to get upstream branch name from the reference's upstream target
.shorthand() let upstream: Option<String> = if reference.target().is_some() {
.map(|short| format!("refs/remotes/{{}}/{}", short)); 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 { branches.push(BranchTip {
name, name,