use crate::cli::UI;
use crate::ops::oplog;
use crate::ops::utils::short_oid;
use anyhow::{bail, Result};
use std::path::Path;
pub fn show_current(path: &Path) -> Result<()> {
let repo = crate::ops::open_repo(path)?;
if let Ok(head) = repo.head() {
if head.is_branch() {
if let Some(name) = head.shorthand() {
println!("{}", name);
}
}
}
Ok(())
}
pub fn list(path: &Path, show_all: bool, ui: &UI) -> Result<()> {
let repo = crate::ops::open_repo(path)?;
let filter = if show_all {
None
} else {
Some(git2::BranchType::Local)
};
let branches = repo.branches(filter)?;
let head = repo.head().ok();
let head_name = head
.as_ref()
.and_then(|h| h.shorthand().map(|s| s.to_string()));
for branch in branches {
let (branch, branch_type) = branch?;
let name = branch.name()?.unwrap_or("").to_string();
let is_current = head_name.as_deref() == Some(&name);
match branch_type {
git2::BranchType::Remote => {
ui.list_item(format!("remotes/{}", name));
}
git2::BranchType::Local => {
let (hash, summary) = match branch.get().peel_to_commit() {
Ok(commit) => (
short_oid(&commit.id()),
commit.summary().unwrap_or("").to_string(),
),
Err(_) => ("???????".to_string(), String::new()),
};
ui.branch_item(&name, &hash, &summary, is_current);
}
}
}
Ok(())
}
pub fn list_compact(path: &Path, show_all: bool) -> Result<String> {
let repo = crate::ops::open_repo(path)?;
let filter = if show_all {
None
} else {
Some(git2::BranchType::Local)
};
let branches = repo.branches(filter)?;
let head = repo.head().ok();
let head_name = head
.as_ref()
.and_then(|h| h.shorthand().map(|s| s.to_string()));
let mut local = Vec::new();
let mut remote_only = Vec::new();
for branch in branches {
let (branch, branch_type) = branch?;
let name = branch.name()?.unwrap_or("").to_string();
let is_current = head_name.as_deref() == Some(&name);
match branch_type {
git2::BranchType::Remote => {
let short = name.split('/').skip(1).collect::<Vec<_>>().join("/");
if !local.iter().any(|(n, _): &(String, bool)| *n == short) {
remote_only.push(name);
}
}
git2::BranchType::Local => {
local.push((name, is_current));
}
}
}
let mut lines = Vec::new();
for (name, current) in &local {
if *current {
lines.push(format!("* {}", name));
} else {
lines.push(format!(" {}", name));
}
}
if !remote_only.is_empty() {
let count = remote_only.len();
let shown: Vec<&str> = remote_only.iter().map(|s| s.as_str()).take(10).collect();
lines.push(format!("remote-only ({}):", count));
for r in &shown {
lines.push(format!(" {}", r));
}
if count > 10 {
lines.push(format!(" ... +{} more", count - 10));
}
}
Ok(lines.join("\n"))
}
pub fn create(path: &Path, name: &str, start_point: Option<&str>, ui: &UI) -> Result<()> {
oplog::with_oplog(path, "branch", &format!("create '{}'", name), || {
create_inner(path, name, start_point, ui)
})
}
fn create_inner(path: &Path, name: &str, start_point: Option<&str>, ui: &UI) -> Result<()> {
let repo = crate::ops::open_repo(path)?;
let commit = if let Some(sp) = start_point {
let obj = repo.revparse_single(sp)?;
obj.peel_to_commit()?
} else {
repo.head()?.peel_to_commit()?
};
repo.branch(name, &commit, false)?;
ui.success(format!("Created branch '{}'", name));
Ok(())
}
pub fn delete(path: &Path, name: &str, force: bool, ui: &UI) -> Result<()> {
oplog::with_oplog(path, "branch", &format!("delete '{}'", name), || {
delete_inner(path, name, force, ui)
})
}
fn delete_inner(path: &Path, name: &str, force: bool, ui: &UI) -> Result<()> {
let repo = crate::ops::open_repo(path)?;
let mut branch = repo.find_branch(name, git2::BranchType::Local)?;
if !force && !branch.is_head() {
let head = repo.head()?.peel_to_commit()?;
let branch_commit = branch.get().peel_to_commit()?;
let merge_base = repo.merge_base(head.id(), branch_commit.id());
if merge_base.ok() != Some(branch_commit.id()) {
bail!(
"Branch '{}' is not fully merged. Use -D to force delete.",
name
);
}
}
branch.delete()?;
ui.success(format!("Deleted branch '{}'", name));
Ok(())
}
pub fn rename(path: &Path, old_name: &str, new_name: &str, force: bool, ui: &UI) -> Result<()> {
oplog::with_oplog(
path,
"branch",
&format!("rename '{}' to '{}'", old_name, new_name),
|| rename_inner(path, old_name, new_name, force, ui),
)
}
fn rename_inner(path: &Path, old_name: &str, new_name: &str, force: bool, ui: &UI) -> Result<()> {
let repo = crate::ops::open_repo(path)?;
let mut branch = repo.find_branch(old_name, git2::BranchType::Local)?;
branch.rename(new_name, force)?;
ui.success(format!("Renamed branch '{}' to '{}'", old_name, new_name));
Ok(())
}