use std::fmt;
use xshell::Shell;
#[derive(Debug, Clone)]
enum Ref {
Branch(String),
Commit(String),
}
impl fmt::Display for Ref {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Branch(name) => write!(f, "{}", name),
Self::Commit(sha) => write!(f, "{}", sha),
}
}
}
impl Ref {
fn current(sh: &Shell) -> Result<Self, Box<dyn std::error::Error>> {
if let Ok(branch) = rbmt_cmd!(sh, "git symbolic-ref -q --short HEAD").read() {
return Ok(Self::Branch(branch.trim().to_string()));
}
let sha = rbmt_cmd!(sh, "git rev-parse HEAD").read()?;
Ok(Self::Commit(sha.trim().to_string()))
}
}
pub struct GitSwitchGuard<'a> {
sh: &'a Shell,
original_ref: Ref,
}
impl<'a> GitSwitchGuard<'a> {
pub fn new(sh: &'a Shell, git_ref: &str) -> Result<Self, Box<dyn std::error::Error>> {
let original_ref = Ref::current(sh)?;
rbmt_eprintln!("Switching from {} to {}", original_ref, git_ref);
rbmt_cmd!(sh, "git switch --detach").arg(git_ref).run()?;
Ok(Self { sh, original_ref })
}
}
impl Drop for GitSwitchGuard<'_> {
fn drop(&mut self) {
rbmt_eprintln!("Returning to original ref {}", self.original_ref);
let git_switch = match &self.original_ref {
Ref::Branch(name) => {
rbmt_cmd!(self.sh, "git switch").arg(name)
}
Ref::Commit(sha) => {
rbmt_cmd!(self.sh, "git switch --detach").arg(sha)
}
};
git_switch.run().expect("Failed to switch back to previous ref");
}
}
pub fn list_commits(sh: &Shell, base: &str) -> Result<Vec<String>, Box<dyn std::error::Error>> {
let range_base = rbmt_cmd!(sh, "git merge-base HEAD {base}").read()?;
let range_base = range_base.trim();
let output = rbmt_cmd!(sh, "git log --reverse --format=%H {range_base}..HEAD").read()?;
let commits = output.lines().map(|s| s.trim().to_owned()).filter(|s| !s.is_empty()).collect();
Ok(commits)
}