1use std::fmt;
19
20use serde::{Deserialize, Serialize};
21
22use radicle_surf::vcs::git::{self, Browser, RefScope};
23
24use crate::error::Error;
25
26#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
28pub struct Branch(pub(crate) String);
29
30impl From<String> for Branch {
31 fn from(name: String) -> Self {
32 Self(name)
33 }
34}
35
36impl From<git::Branch> for Branch {
37 fn from(branch: git::Branch) -> Self {
38 Self(branch.name.to_string())
39 }
40}
41
42impl fmt::Display for Branch {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 write!(f, "{}", self.0)
45 }
46}
47
48pub fn branches(browser: &Browser<'_>, filter: RefScope) -> Result<Vec<Branch>, Error> {
55 let mut branches = browser
56 .list_branches(filter)?
57 .into_iter()
58 .map(|b| Branch(b.name.name().to_string()))
59 .collect::<Vec<Branch>>();
60
61 branches.sort();
62
63 Ok(branches)
64}
65
66#[derive(Deserialize, Serialize)]
68pub struct LocalState {
69 branches: Vec<Branch>,
71}
72
73pub fn local_state(repo_path: &str, default_branch: &str) -> Result<LocalState, Error> {
80 let repo = git2::Repository::open(repo_path).map_err(git::error::Error::from)?;
81 let first_branch = repo
82 .branches(Some(git2::BranchType::Local))
83 .map_err(git::error::Error::from)?
84 .filter_map(|branch_result| {
85 let (branch, _) = branch_result.ok()?;
86 let name = branch.name().ok()?;
87 name.map(String::from)
88 })
89 .min()
90 .ok_or(Error::NoBranches)?;
91
92 let repo = git::Repository::new(repo_path)?;
93
94 let browser = match Browser::new(&repo, git::Branch::local(default_branch)) {
95 Ok(browser) => browser,
96 Err(_) => Browser::new(&repo, git::Branch::local(&first_branch))?,
97 };
98
99 let mut branches = browser
100 .list_branches(RefScope::Local)?
101 .into_iter()
102 .map(|b| Branch(b.name.name().to_string()))
103 .collect::<Vec<Branch>>();
104
105 branches.sort();
106
107 Ok(LocalState { branches })
108}