1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
use std::{collections::HashMap, fmt::Debug, path::PathBuf, sync::RwLock};
use thiserror::Error;
use gix::Repository;
use tracing::error;
use crate::forge::Project;
mod aggregate;
mod git;
mod repostate;
pub use aggregate::*;
pub use repostate::*;
// pub type Repos = Vec<Repo>;
pub type Repos = HashMap<String, RwLock<Repo>>;
pub struct Repo {
pub name: String,
pub path: PathBuf,
pub repo: Option<Repository>,
pub forge: Option<Project>,
pub default_branch: String,
}
impl Repo {
pub fn repo(&self) -> Result<&Repository, RepoError> {
match &self.repo {
Some(repo) => Ok(repo),
None => Err(RepoError::NoLocalRepo),
}
}
pub fn repo_mut(&mut self) -> Result<&mut Repository, RepoError> {
match &mut self.repo {
Some(repo) => Ok(repo),
None => Err(RepoError::NoLocalRepo),
}
}
}
#[derive(Error, Debug)]
pub enum RepoError {
#[error("repo is not cloned locally")]
NoLocalRepo,
#[error("local git repo does not have a remote")]
NoRemoteFound,
#[error("no head found")]
NoHead,
#[error("could not determine default branch based on remote HEAD")]
NoDefaultBranch,
#[error("repo is not checked out")]
NoWorktree,
#[error("repository is dirty: {0}")]
Dirty(LocalRepoState),
#[error("fast-forward merge was not possible")]
NoFF,
#[error("error: {0}")]
Anyhow(#[from] anyhow::Error),
#[error("unknown repo error")]
Unknown,
}
#[derive(Error, Debug, PartialEq)]
pub enum LocalRepoState {
#[error("operation in progress: {0:?}")]
InProgress(gix::state::InProgress),
#[error("currently checked out branch is not default")]
NonDefaultBranch,
#[error("{0} unpushed commits")]
UnpushedCommits(usize),
#[error("head is detached")]
DetachedHead,
#[error("head is unborn")]
UnbornHead,
#[error("repo is clean")]
Clean,
}
impl Ord for Repo {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.name.cmp(&other.name)
}
}
impl Eq for Repo {}
impl PartialOrd for Repo {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.name.partial_cmp(&other.name)
}
}
impl PartialEq for Repo {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
impl From<Project> for Repo {
fn from(project: Project) -> Self {
Self {
name: project.path.clone(),
forge: Some(project),
..Repo::default()
}
}
}
impl From<&Project> for Repo {
fn from(project: &Project) -> Self {
Self {
name: project.path.clone(),
forge: Some(project.to_owned()),
..Repo::default()
}
}
}
impl std::fmt::Display for Repo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{} {}", RepoState::from(self), self.name))
}
}
impl Debug for Repo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Repo").field("path", &self.name).finish()
}
}
impl Default for Repo {
fn default() -> Self {
Self {
name: Default::default(),
path: Default::default(),
repo: Default::default(),
forge: Default::default(),
default_branch: "main".to_string(),
}
}
}
|