use super::*;
const BRANCH_PREFIX: &str = "refs/heads/";
#[derive(clap::Subcommand, Debug)]
pub(crate) enum BranchCmd {
List,
Create {
name: String,
start_point: Option<String>,
#[arg(long, conflicts_with = "start_point")]
from: Option<String>,
},
Delete {
name: String,
},
}
pub(crate) fn run(override_path: Option<&Path>, cmd: BranchCmd) -> Result<()> {
let data_dir = repo::locate_data_dir(override_path)?;
let cfg = config::load(&data_dir)?;
let r = repo::open_repo(Some(data_dir.as_path()))?;
match cmd {
BranchCmd::List => list_branches(&r),
BranchCmd::Create {
name,
start_point,
from,
} => {
let resolved = from.or(start_point);
create_branch(&r, &cfg, &name, resolved.as_deref())
}
BranchCmd::Delete { name } => delete_branch(&r, &cfg, &name),
}
}
fn list_branches(r: &ReadonlyRepo) -> Result<()> {
let refs = &r.view().refs;
let head = r.view().heads.first().cloned();
let mut any = false;
for (name, target) in refs {
let Some(short) = name.strip_prefix(BRANCH_PREFIX) else {
continue;
};
any = true;
let marker = match target {
RefTarget::Normal { target } if Some(target) == head.as_ref() => "*",
_ => " ",
};
let summary = match target {
RefTarget::Normal { target } => format!("-> {target}"),
RefTarget::Conflicted { adds, removes } => {
format!("conflicted(+{} -{})", adds.len(), removes.len())
}
};
println!("{marker} {short} {summary}");
}
if !any {
println!("<no branches>");
}
Ok(())
}
fn create_branch(
r: &ReadonlyRepo,
cfg: &config::Config,
name: &str,
from: Option<&str>,
) -> Result<()> {
if name.is_empty() {
bail!("branch name must not be empty");
}
let full = if name.starts_with(BRANCH_PREFIX) {
name.to_string()
} else {
format!("{BRANCH_PREFIX}{name}")
};
if r.view().refs.contains_key(&full) {
bail!("branch `{name}` already exists");
}
let target_cid = match from {
Some(s) => super::resolve_commitish(r, s).context("resolving start-point")?,
None => r
.view()
.heads
.first()
.cloned()
.ok_or_else(|| anyhow!("repository has no commits yet; pass --from <cid>"))?,
};
let new_r = r.update_ref(
&full,
None,
Some(RefTarget::normal(target_cid.clone())),
&config::author_string(cfg),
)?;
println!("created branch {name} -> {target_cid}");
println!(" op_id {}", new_r.op_id());
Ok(())
}
fn delete_branch(r: &ReadonlyRepo, cfg: &config::Config, name: &str) -> Result<()> {
if name.is_empty() {
bail!("branch name must not be empty");
}
let full = if name.starts_with(BRANCH_PREFIX) {
name.to_string()
} else {
format!("{BRANCH_PREFIX}{name}")
};
let prev = r
.view()
.refs
.get(&full)
.ok_or_else(|| anyhow!("branch `{name}` does not exist"))?;
let new_r = r.update_ref(&full, Some(prev), None, &config::author_string(cfg))?;
println!("deleted branch {name}");
println!(" op_id {}", new_r.op_id());
Ok(())
}