use crate::error::{GwError, Result};
use crate::git;
use crate::output;
use crate::state::{RepoType, WorkingDirState};
pub fn run(branch_name: Option<String>, stack: bool, verbose: bool) -> Result<()> {
if !git::is_git_repo() {
return Err(GwError::NotAGitRepository);
}
if git::is_detached_head() {
return Err(GwError::Other(
"Cannot run gw new from detached HEAD. Checkout a branch first.".to_string(),
));
}
let branch_name = branch_name.ok_or(GwError::BranchNameRequired)?;
println!();
output::info(&format!("Creating branch: {}", output::bold(&branch_name)));
if git::branch_exists(&branch_name) {
output::error(&format!("Branch '{}' already exists locally", branch_name));
println!();
output::action(&format!(
"git checkout {} # Switch to existing branch",
branch_name
));
output::action(&format!(
"git branch -d {} # Delete and recreate",
branch_name
));
return Err(GwError::BranchAlreadyExists(branch_name));
}
let current = git::current_branch()?;
let repo_type = RepoType::detect()?;
let home_branch = repo_type.home_branch();
let on_home = current == home_branch;
if stack && on_home {
output::error(&format!(
"--stack requires a non-home branch, but you are on '{}'.",
current
));
output::hints(&[
"gw new feature/your-feature # start fresh from origin/main",
"git checkout <parent> && gw new feature/child --stack # stack on a feature branch",
]);
return Err(GwError::Other(
"--stack requires a non-home current branch".to_string(),
));
}
if !stack && !on_home {
output::error(&format!(
"You are on '{}', not the home branch '{}'.",
current, home_branch
));
output::hints(&[
&format!("gw new {branch_name} --stack # stack on {current}"),
&format!("gw home && gw new {branch_name} # start fresh from {home_branch}"),
]);
return Err(GwError::Other(
"gw new outside the home branch needs --stack (or run gw home first)".to_string(),
));
}
let working_dir = WorkingDirState::detect();
let dirty = !working_dir.is_clean();
let mut behind_count = 0usize;
let (start_point, base_label, pr_base): (String, String, Option<String>) = if stack {
(current.clone(), current.clone(), Some(current.clone()))
} else {
output::info("Fetching from origin...");
git::fetch_prune(verbose)?;
output::success("Fetched");
let default_remote = git::get_default_remote_branch()?;
if dirty {
behind_count = git::commit_count(¤t, &default_remote).unwrap_or(0);
(current.clone(), current.clone(), None)
} else {
(default_remote.clone(), default_remote, None)
}
};
if dirty {
output::warn(&format!(
"Working directory has changes ({}); they will move onto {}",
working_dir.description(),
output::bold(&branch_name)
));
}
git::checkout_new_branch(&branch_name, &start_point, verbose)?;
output::success(&format!(
"Created branch {} from {}",
output::bold(&branch_name),
base_label
));
if let Some(base) = &pr_base {
git::set_branch_base(&branch_name, base, verbose)?;
if let Ok(sha) = git::head_commit() {
git::set_branch_base_sha(&branch_name, &sha, verbose)?;
}
}
if behind_count > 0 {
output::warn(&format!(
"local {} is behind origin/{} ({} commit(s)); rebase after committing",
home_branch, home_branch, behind_count
));
}
let commit_short = git::short_commit()?;
let commit_msg = git::head_commit_message()?;
output::ready("Ready to work", &branch_name);
println!("Base: {commit_short} {commit_msg}");
let mut hint_lines: Vec<String> = vec![
"# Make changes, then:".to_string(),
"git add <files> && git commit -m \"feat: description\"".to_string(),
];
if behind_count > 0 {
hint_lines.push("gw sync # local main was behind; catch up".to_string());
}
hint_lines.push(format!("git push -u origin {branch_name}"));
hint_lines.push(match &pr_base {
Some(base) => format!("gh pr create -a \"@me\" -B {base} -t \"Title\""),
None => "gh pr create -a \"@me\" -t \"Title\"".to_string(),
});
let hint_refs: Vec<&str> = hint_lines.iter().map(String::as_str).collect();
output::hints(&hint_refs);
Ok(())
}