use anyhow::{Result, bail};
use git2::{BranchType, Repository};
use crate::core::msg;
use crate::core::repo;
use crate::git;
pub fn run(name: Option<String>) -> Result<()> {
let repo = repo::open_repo()?;
let workdir = repo::require_workdir(&repo, "initialize")?;
let name = name.unwrap_or_else(|| "integration".to_string());
let name = name.trim().to_string();
if name.is_empty() {
bail!("Branch name cannot be empty");
}
git::branch_validate_name(&name)?;
repo::ensure_branch_not_exists(&repo, &name)?;
let upstream = detect_upstream(&repo)?;
git::branch_switch_create_tracking(workdir, &name, &upstream)?;
msg::success(&format!(
"Initialized integration branch `{}` tracking `{}`",
name, upstream
));
Ok(())
}
fn detect_upstream(repo: &Repository) -> Result<String> {
if let Some(upstream) = try_github_upstream(repo) {
return Ok(upstream);
}
if let Ok(head) = repo.head()
&& head.is_branch()
&& let Some(branch_name) = head.shorthand()
&& let Ok(local_branch) = repo.find_branch(branch_name, BranchType::Local)
&& let Ok(upstream) = local_branch.upstream()
&& let Ok(Some(upstream_name)) = upstream.name()
{
return Ok(upstream_name.to_string());
}
let candidates = gather_remote_candidates(repo)?;
match candidates.len() {
0 => bail!(
"No remote tracking branches found\n\
Set up a remote with: `git remote add origin <url>`"
),
1 => Ok(candidates[0].clone()),
_ => {
msg::select(
"Which remote branch should this integration track?",
candidates,
)
}
}
}
fn try_github_upstream(repo: &Repository) -> Option<String> {
let remotes = repo.remotes().ok()?;
let is_github = remotes.iter().flatten().any(|name| {
repo.find_remote(name)
.ok()
.and_then(|r| r.url().map(|u| u.contains("github.com")))
.unwrap_or(false)
});
if !is_github {
return None;
}
repo.find_remote("upstream").ok()?;
let head_ref = "refs/remotes/upstream/HEAD";
if let Ok(reference) = repo.find_reference(head_ref)
&& let Ok(resolved) = reference.resolve()
&& let Some(name) = resolved.shorthand()
{
return Some(name.to_string());
}
for branch_name in &["main", "master", "develop"] {
let ref_name = format!("upstream/{}", branch_name);
if repo.find_branch(&ref_name, BranchType::Remote).is_ok() {
return Some(ref_name);
}
}
None
}
fn gather_remote_candidates(repo: &Repository) -> Result<Vec<String>> {
let mut candidates = Vec::new();
let remotes = repo.remotes()?;
for remote_name in remotes.iter() {
let Some(remote_name) = remote_name else {
continue;
};
let head_ref = format!("refs/remotes/{}/HEAD", remote_name);
if let Ok(reference) = repo.find_reference(&head_ref)
&& let Ok(resolved) = reference.resolve()
&& let Some(name) = resolved.shorthand()
{
candidates.push(name.to_string());
continue;
}
for branch_name in &["main", "master", "develop"] {
let ref_name = format!("{}/{}", remote_name, branch_name);
if repo.find_branch(&ref_name, BranchType::Remote).is_ok() {
candidates.push(ref_name);
break; }
}
}
Ok(candidates)
}
#[cfg(test)]
#[path = "init_test.rs"]
mod tests;