use crate::api::model::{PrDetail, PrRef, RepoId};
use crate::git::local;
use crate::act::Subject;
use crate::keymap::Action;
use crate::nav::{Doable, row};
use crate::state::{Cmd, State};
use crate::work::{self, Cx, Failure, Job};
pub const FEATURE: crate::features::Feature = crate::features::Feature {
owns: &[Action::Checkout],
act: |state, _| checkout(state),
offers: |state, out| out.extend(doable(state)),
..crate::features::Feature::NONE
};
pub(crate) async fn here(cx: &Cx, repo: &RepoId) -> Result<local::Clone, Failure> {
let found = local::clone_of(&cx.git.cwd, &repo.owner, &repo.name).await?;
found.ok_or_else(|| Failure::msg(format!("ghtui isn't running in a clone of {repo}")))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Checkout {
pub repo: RepoId,
pub pr: Option<u64>,
pub name: String,
pub track: Option<String>,
}
impl Job for Checkout {
type Out = local::Checkout;
const WRITES: bool = false;
async fn run(&self, cx: &Cx) -> Result<local::Checkout, Failure> {
let c = here(cx, &self.repo).await?;
let source = match self.pr {
Some(n) => format!("refs/pull/{n}/head"),
None => format!("refs/heads/{}", self.name),
};
let track = self.track.as_deref();
Ok(local::checkout(&c.top, &c.remote, &source, &self.name, track).await?)
}
fn done(self, state: &mut State, result: Result<local::Checkout, Failure>) -> Vec<Cmd> {
use local::Checkout as C;
let text = match (result, self.pr) {
(Ok(C::Created(b)), Some(n)) => {
format!("Checked out #{n}: you're on {b}, a new branch at its head")
}
(Ok(C::Switched(b)), Some(n)) => format!("You're on {b}, at #{n}'s head"),
(Ok(C::Created(b) | C::Switched(b)), None) => format!("You're on {b}"),
(Ok(C::Diverged(b)), pr) => {
let theirs = pr.map_or_else(|| "GitHub's".to_owned(), |n| format!("#{n}"));
format!("You're on {b}, which has commits {theirs} doesn't: it's left as it was")
}
(Ok(C::Uncommitted(files)), _) => {
let first = files.first().cloned().unwrap_or_default();
let files = match files.len() {
1 => first,
n => format!(
"{first} and {} {}",
n - 1,
crate::ui::text::noun(n as u64 - 1, "other")
),
};
format!(
"Not checked out: {files} has changes that aren't committed. Commit or stash them first"
)
}
(Err(err), pr) => {
let what = pr.map_or_else(|| self.name.clone(), |n| format!("#{n}"));
state.error(format!("Couldn't check out {what}: {err}"));
return Vec::new();
}
};
state.just_say(text)
}
}
#[must_use]
fn checkout(state: &mut State) -> Vec<Cmd> {
let Some(Subject::Pr(pr)) = crate::act::subject(state, Action::Checkout) else {
return state.just_say("Checking out works on a pull request (or its row in a list)");
};
if let Some(None) = state.clone_of(&pr.repo) {
return state.just_say(format!("ghtui isn't running in a clone of {}", pr.repo));
}
match state.pr(&pr) {
Some(d) => start_checkout(state, pr.clone(), &d.clone()),
None => crate::act::wait(state, Action::Checkout, Subject::Pr(pr), loaded),
}
}
#[must_use]
fn loaded(state: &mut State, about: Subject, _: Action) -> Vec<Cmd> {
let Subject::Pr(pr) = about else {
return Vec::new();
};
if let Some(d) = state.pr(&pr) {
let d = d.clone();
return start_checkout(state, pr, &d);
}
let err = state.pr_remote(&pr).and_then(|r| r.error.clone());
state.error(format!("Couldn't load {pr}: {}", err.unwrap_or_default()));
Vec::new()
}
#[must_use]
fn start_checkout(state: &mut State, pr: PrRef, d: &PrDetail) -> Vec<Cmd> {
let same =
(d.head_repo.as_deref()).is_some_and(|r| r.eq_ignore_ascii_case(&pr.repo.to_string()));
let fork = d
.head_repo
.as_deref()
.and_then(|r| r.split_once('/'))
.map(|(o, _)| o);
let (name, track) = match fork {
_ if same => (d.head_ref.clone(), Some(d.head_ref.clone())),
Some(owner) => (format!("{owner}/{}", d.head_ref), None),
None => (format!("pr-{}", pr.number), None),
};
state.info(format!("Checking out {pr} on {name}…"));
work::start(Checkout {
repo: pr.repo,
pr: Some(pr.number),
name,
track,
})
}
fn doable(state: &State) -> Option<Doable> {
let Some(crate::act::Subject::Pr(pr)) = crate::act::subject(state, Action::Checkout) else {
return None;
};
let why = matches!(state.clone_of(&pr.repo), Some(None))
.then(|| format!("ghtui isn't running in a clone of {}", pr.repo));
let label = "Check out the pull request here";
Some(row("Change", Action::Checkout, label, "", why))
}