refactor: move ForgeLike to git
All checks were successful
Rust / build (push) Successful in 1m13s
ci/woodpecker/push/push-next Pipeline was successful
ci/woodpecker/push/cron-docker-builder Pipeline was successful
ci/woodpecker/push/tag-created Pipeline was successful

This commit is contained in:
Paul Campbell 2024-05-23 16:50:36 +01:00
parent 639223fcaa
commit 64cbe36dac
16 changed files with 220 additions and 202 deletions

View file

@ -1,12 +1,13 @@
use git_next_config::BranchName; use git_next_config as config;
use git_next_git as git; use git_next_git as git;
use kxio::network::{self, Network}; use kxio::network::{self, Network};
use tracing::error; use tracing::error;
pub async fn get_all( pub async fn get_all(
repo_details: &git::RepoDetails, repo_details: &git::RepoDetails,
net: &Network, net: &Network,
) -> Result<Vec<BranchName>, crate::branch::Error> { ) -> Result<Vec<config::BranchName>, git::branch::Error> {
let hostname = &repo_details.forge.hostname(); let hostname = &repo_details.forge.hostname();
let repo_path = &repo_details.repo_path; let repo_path = &repo_details.repo_path;
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
@ -28,13 +29,13 @@ pub async fn get_all(
let result = net.get::<Vec<Branch>>(request).await; let result = net.get::<Vec<Branch>>(request).await;
let response = result.map_err(|e| { let response = result.map_err(|e| {
error!(?e, "Failed to list branches"); error!(?e, "Failed to list branches");
crate::branch::Error::NoneFound // BranchListNotAvailable git::branch::Error::NoneFound // BranchListNotAvailable
})?; })?;
let branches = response let branches = response
.response_body() .response_body()
.unwrap_or_default() .unwrap_or_default()
.into_iter() .into_iter()
.map(BranchName::from) .map(config::BranchName::from)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
Ok(branches) Ok(branches)
} }
@ -43,7 +44,7 @@ pub async fn get_all(
struct Branch { struct Branch {
name: String, name: String,
} }
impl From<Branch> for BranchName { impl From<Branch> for config::BranchName {
fn from(value: Branch) -> Self { fn from(value: Branch) -> Self {
Self::new(value.name) Self::new(value.name)
} }

View file

@ -1,15 +1,17 @@
use git_next_config::{BranchName, RepoConfig}; //
use crate as forge;
use git::ForgeLike as _;
use git_next_config as config;
use git_next_git as git; use git_next_git as git;
use kxio::network; use kxio::network;
use tracing::{debug, error, info, warn}; use tracing::{debug, error, info, warn};
use crate::{forgejo::ForgeJoEnv, validation, ForgeLike as _};
pub async fn validate_positions( pub async fn validate_positions(
forge: &ForgeJoEnv, forge: &forge::forgejo::ForgeJoEnv,
repository: &git::OpenRepository, repository: &git::OpenRepository,
repo_config: RepoConfig, repo_config: config::RepoConfig,
) -> validation::Result { ) -> git::validation::Result {
let repo_details = &forge.repo_details; let repo_details = &forge.repo_details;
// Collect Commit Histories for `main`, `next` and `dev` branches // Collect Commit Histories for `main`, `next` and `dev` branches
let commit_histories = get_commit_histories(repo_details, &repo_config, &forge.net).await; let commit_histories = get_commit_histories(repo_details, &repo_config, &forge.net).await;
@ -17,7 +19,7 @@ pub async fn validate_positions(
Ok(commit_histories) => commit_histories, Ok(commit_histories) => commit_histories,
Err(err) => { Err(err) => {
error!(?err, "Failed to get commit histories"); error!(?err, "Failed to get commit histories");
return Err(validation::Error::Network(Box::new(err))); return Err(git::validation::Error::Network(Box::new(err)));
} }
}; };
@ -27,7 +29,7 @@ pub async fn validate_positions(
"No commits on main branch '{}'", "No commits on main branch '{}'",
repo_config.branches().main() repo_config.branches().main()
); );
return Err(validation::Error::BranchHasNoCommits( return Err(git::validation::Error::BranchHasNoCommits(
repo_config.branches().main(), repo_config.branches().main(),
)); ));
}; };
@ -40,7 +42,7 @@ pub async fn validate_positions(
repo_config.branches().main(), repo_config.branches().main(),
repo_config.branches().main(), repo_config.branches().main(),
); );
return Err(validation::Error::DevBranchNotBasedOn( return Err(git::validation::Error::DevBranchNotBasedOn(
repo_config.branches().main(), repo_config.branches().main(),
)); ));
} }
@ -50,7 +52,7 @@ pub async fn validate_positions(
"No commits on next branch '{}", "No commits on next branch '{}",
repo_config.branches().next() repo_config.branches().next()
); );
return Err(validation::Error::BranchHasNoCommits( return Err(git::validation::Error::BranchHasNoCommits(
repo_config.branches().next(), repo_config.branches().next(),
)); ));
}; };
@ -65,12 +67,12 @@ pub async fn validate_positions(
git::push::Force::From(next.clone().into()), git::push::Force::From(next.clone().into()),
) { ) {
warn!(?err, "Failed to reset next to main"); warn!(?err, "Failed to reset next to main");
return Err(validation::Error::FailedToResetBranch { return Err(git::validation::Error::FailedToResetBranch {
branch: repo_config.branches().next(), branch: repo_config.branches().next(),
commit: next, commit: next,
}); });
} }
return Err(validation::Error::BranchReset( return Err(git::validation::Error::BranchReset(
repo_config.branches().next(), repo_config.branches().next(),
)); ));
} }
@ -93,12 +95,12 @@ pub async fn validate_positions(
git::push::Force::From(next.clone().into()), git::push::Force::From(next.clone().into()),
) { ) {
warn!(?err, "Failed to reset next to main"); warn!(?err, "Failed to reset next to main");
return Err(validation::Error::FailedToResetBranch { return Err(git::validation::Error::FailedToResetBranch {
branch: repo_config.branches().next(), branch: repo_config.branches().next(),
commit: next, commit: next,
}); });
} }
return Err(validation::Error::BranchReset( return Err(git::validation::Error::BranchReset(
repo_config.branches().next(), repo_config.branches().next(),
)); ));
} }
@ -107,7 +109,7 @@ pub async fn validate_positions(
"No commits on next branch '{}'", "No commits on next branch '{}'",
repo_config.branches().next() repo_config.branches().next()
); );
return Err(validation::Error::BranchHasNoCommits( return Err(git::validation::Error::BranchHasNoCommits(
repo_config.branches().next(), repo_config.branches().next(),
)); ));
}; };
@ -121,7 +123,7 @@ pub async fn validate_positions(
repo_config.branches().dev(), repo_config.branches().dev(),
repo_config.branches().next() repo_config.branches().next()
); );
return Err(validation::Error::DevBranchNotBasedOn( return Err(git::validation::Error::DevBranchNotBasedOn(
repo_config.branches().next(), repo_config.branches().next(),
)); // dev is not based on next )); // dev is not based on next
} }
@ -131,12 +133,12 @@ pub async fn validate_positions(
"No commits on dev branch '{}'", "No commits on dev branch '{}'",
repo_config.branches().dev() repo_config.branches().dev()
); );
return Err(validation::Error::BranchHasNoCommits( return Err(git::validation::Error::BranchHasNoCommits(
repo_config.branches().dev(), repo_config.branches().dev(),
)); ));
}; };
Ok(validation::Positions { Ok(git::validation::Positions {
main, main,
next, next,
dev, dev,
@ -146,7 +148,7 @@ pub async fn validate_positions(
async fn get_commit_histories( async fn get_commit_histories(
repo_details: &git::RepoDetails, repo_details: &git::RepoDetails,
repo_config: &RepoConfig, repo_config: &config::RepoConfig,
net: &network::Network, net: &network::Network,
) -> Result<git::commit::Histories, network::NetworkError> { ) -> Result<git::commit::Histories, network::NetworkError> {
let main = let main =
@ -180,7 +182,7 @@ async fn get_commit_histories(
#[tracing::instrument(fields(%branch_name),skip_all)] #[tracing::instrument(fields(%branch_name),skip_all)]
async fn get_commit_history( async fn get_commit_history(
repo_details: &git::RepoDetails, repo_details: &git::RepoDetails,
branch_name: &BranchName, branch_name: &config::BranchName,
find_commits: Vec<git::Commit>, find_commits: Vec<git::Commit>,
net: &kxio::network::Network, net: &kxio::network::Network,
) -> Result<Vec<git::Commit>, network::NetworkError> { ) -> Result<Vec<git::Commit>, network::NetworkError> {

View file

@ -1,14 +1,15 @@
use git_next_config::BranchName; use git_next_config as config;
use git_next_git as git; use git_next_git as git;
use kxio::network::{self, Network}; use kxio::network::{self, Network};
use tracing::{error, warn}; use tracing::{error, warn};
pub(super) async fn contents_get( pub(super) async fn contents_get(
repo_details: &git::RepoDetails, repo_details: &git::RepoDetails,
net: &Network, net: &Network,
branch: &BranchName, branch: &config::BranchName,
file_path: &str, file_path: &str,
) -> Result<String, crate::file::Error> { ) -> Result<String, git::file::Error> {
let hostname = &repo_details.forge.hostname(); let hostname = &repo_details.forge.hostname();
let repo_path = &repo_details.repo_path; let repo_path = &repo_details.repo_path;
let api_token = &repo_details.forge.token(); let api_token = &repo_details.forge.token();
@ -31,7 +32,7 @@ pub(super) async fn contents_get(
let result = net.get::<ForgeContentsResponse>(request).await; let result = net.get::<ForgeContentsResponse>(request).await;
let response = result.map_err(|e| { let response = result.map_err(|e| {
warn!(?e, ""); warn!(?e, "");
crate::file::Error::NotFound(file_path.to_string()) git::file::Error::NotFound(file_path.to_string())
})?; })?;
let status = response.status_code(); let status = response.status_code();
let contents = match response.response_body() { let contents = match response.response_body() {
@ -39,30 +40,30 @@ pub(super) async fn contents_get(
// we need to decode (see encoding field) the value of 'content' from the response // we need to decode (see encoding field) the value of 'content' from the response
match body.content_type { match body.content_type {
ForgeContentsType::File => decode_config(body), ForgeContentsType::File => decode_config(body),
_ => Err(crate::file::Error::NotFile(file_path.to_string())), _ => Err(git::file::Error::NotFile(file_path.to_string())),
} }
} }
None => { None => {
error!(%status, "Failed to fetch repo config file"); error!(%status, "Failed to fetch repo config file");
Err(crate::file::Error::Unknown(status.to_string())) Err(git::file::Error::Unknown(status.to_string()))
} }
}?; }?;
Ok(contents) Ok(contents)
} }
fn decode_config(body: ForgeContentsResponse) -> Result<String, crate::file::Error> { fn decode_config(body: ForgeContentsResponse) -> Result<String, git::file::Error> {
use base64::Engine; use base64::Engine;
match body.encoding.as_str() { match body.encoding.as_str() {
"base64" => { "base64" => {
let decoded = base64::engine::general_purpose::STANDARD let decoded = base64::engine::general_purpose::STANDARD
.decode(body.content) .decode(body.content)
.map_err(|_| crate::file::Error::DecodeFromBase64)?; .map_err(|_| git::file::Error::DecodeFromBase64)?;
let decoded = let decoded =
String::from_utf8(decoded).map_err(|_| crate::file::Error::DecodeFromUtf8)?; String::from_utf8(decoded).map_err(|_| git::file::Error::DecodeFromUtf8)?;
Ok(decoded) Ok(decoded)
} }
encoding => Err(crate::file::Error::UnknownEncoding(encoding.to_string())), encoding => Err(git::file::Error::UnknownEncoding(encoding.to_string())),
} }
} }

View file

@ -1,14 +1,12 @@
pub mod branch; pub mod branch;
mod file; mod file;
use git::OpenRepository; use git_next_config as config;
use git_next_config::{BranchName, GitDir, RepoConfig};
use git_next_git as git; use git_next_git as git;
use kxio::network::{self, Network}; use kxio::network::{self, Network};
use tracing::{error, info, warn}; use tracing::{error, info, warn};
use crate::validation;
struct ForgeJo; struct ForgeJo;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ForgeJoEnv { pub struct ForgeJoEnv {
@ -30,35 +28,35 @@ impl ForgeJoEnv {
} }
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl super::ForgeLike for ForgeJoEnv { impl git::ForgeLike for ForgeJoEnv {
fn name(&self) -> String { fn name(&self) -> String {
"forgejo".to_string() "forgejo".to_string()
} }
async fn branches_get_all(&self) -> Result<Vec<BranchName>, crate::branch::Error> { async fn branches_get_all(&self) -> Result<Vec<config::BranchName>, git::branch::Error> {
branch::get_all(&self.repo_details, &self.net).await branch::get_all(&self.repo_details, &self.net).await
} }
async fn file_contents_get( async fn file_contents_get(
&self, &self,
branch: &BranchName, branch: &config::BranchName,
file_path: &str, file_path: &str,
) -> Result<String, crate::file::Error> { ) -> Result<String, git::file::Error> {
file::contents_get(&self.repo_details, &self.net, branch, file_path).await file::contents_get(&self.repo_details, &self.net, branch, file_path).await
} }
async fn branches_validate_positions( async fn branches_validate_positions(
&self, &self,
repository: git::OpenRepository, repository: git::OpenRepository,
repo_config: RepoConfig, repo_config: config::RepoConfig,
) -> validation::Result { ) -> git::validation::Result {
branch::validate_positions(self, &repository, repo_config).await branch::validate_positions(self, &repository, repo_config).await
} }
fn branch_reset( fn branch_reset(
&self, &self,
repository: &git::OpenRepository, repository: &git::OpenRepository,
branch_name: BranchName, branch_name: config::BranchName,
to_commit: git::GitRef, to_commit: git::GitRef,
force: git::push::Force, force: git::push::Force,
) -> git::push::Result { ) -> git::push::Result {
@ -110,7 +108,10 @@ impl super::ForgeLike for ForgeJoEnv {
} }
} }
fn repo_clone(&self, gitdir: GitDir) -> Result<OpenRepository, git::repository::Error> { fn repo_clone(
&self,
gitdir: config::GitDir,
) -> Result<git::OpenRepository, git::repository::Error> {
let repository = if !gitdir.exists() { let repository = if !gitdir.exists() {
info!("Local copy not found - cloning..."); info!("Local copy not found - cloning...");
self.repo.git_clone(&self.repo_details)? self.repo.git_clone(&self.repo_details)?

View file

@ -1,7 +1,5 @@
#![allow(dead_code)] #![allow(dead_code)]
use git::OpenRepository;
use git_next_config::{BranchName, GitDir, RepoConfig};
use git_next_git as git; use git_next_git as git;
use kxio::network::Network; use kxio::network::Network;
@ -13,44 +11,6 @@ mod github;
mod mock_forge; mod mock_forge;
#[async_trait::async_trait]
pub trait ForgeLike {
fn name(&self) -> String;
/// Returns a list of all branches in the repo.
async fn branches_get_all(&self) -> Result<Vec<BranchName>, branch::Error>;
/// Returns the contents of the file.
async fn file_contents_get(
&self,
branch: &BranchName,
file_path: &str,
) -> Result<String, file::Error>;
/// Assesses the relative positions of the main, next and dev branch and updates their
/// positions as needed.
async fn branches_validate_positions(
&self,
repository: OpenRepository,
repo_config: RepoConfig,
) -> validation::Result;
/// Moves a branch to a new commit.
fn branch_reset(
&self,
repository: &OpenRepository,
branch_name: BranchName,
to_commit: git::GitRef,
force: git::push::Force,
) -> git::push::Result;
/// Checks the results of any (e.g. CI) status checks for the commit.
async fn commit_status(&self, commit: &git::Commit) -> git::commit::Status;
/// Clones a repo to disk.
fn repo_clone(&self, gitdir: GitDir) -> Result<OpenRepository, git::repository::Error>;
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Forge { pub enum Forge {
Mock(mock_forge::MockForgeEnv), Mock(mock_forge::MockForgeEnv),
@ -78,7 +38,7 @@ impl Forge {
} }
} }
impl std::ops::Deref for Forge { impl std::ops::Deref for Forge {
type Target = dyn ForgeLike; type Target = dyn git::ForgeLike;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
match self { match self {
Self::Mock(env) => env, Self::Mock(env) => env,
@ -90,65 +50,5 @@ impl std::ops::Deref for Forge {
} }
} }
pub mod branch {
#[derive(Debug, derive_more::Display)]
pub enum Error {
#[display("Branch not found: {}", 0)]
NotFound(git_next_config::BranchName),
#[display("Unable to find any branches")]
NoneFound,
}
impl std::error::Error for Error {}
}
pub mod file {
#[derive(Debug, derive_more::Display)]
pub enum Error {
#[display("File not found: {}", 0)]
NotFound(String),
#[display("Unable to parse file contents")]
ParseContent,
#[display("Unable to decode from base64")]
DecodeFromBase64,
#[display("Unable to decoce from UTF-8")]
DecodeFromUtf8,
#[display("Unknown file encoding: {}", 0)]
UnknownEncoding(String),
#[display("Not a file: {}", 0)]
NotFile(String),
#[display("Unknown error (status: {})", 0)]
Unknown(String),
}
impl std::error::Error for Error {}
}
pub mod validation {
use git_next_config::BranchName;
use git_next_git as git;
pub type Result = core::result::Result<Positions, Error>;
pub struct Positions {
pub main: git::Commit,
pub next: git::Commit,
pub dev: git::Commit,
pub dev_commit_history: Vec<git::Commit>,
}
#[derive(Debug, derive_more::Display)]
pub enum Error {
Network(Box<kxio::network::NetworkError>),
#[display("Failed to Reset Branch {branch} to {commit}")]
FailedToResetBranch {
branch: BranchName,
commit: git::Commit,
},
BranchReset(BranchName),
BranchHasNoCommits(BranchName),
DevBranchNotBasedOn(BranchName),
}
impl std::error::Error for Error {}
}
#[cfg(test)] #[cfg(test)]
pub mod tests; pub mod tests;

View file

@ -2,11 +2,9 @@
#![cfg(not(tarpaulin_include))] #![cfg(not(tarpaulin_include))]
use git::OpenRepository; use git::OpenRepository;
use git_next_config::{BranchName, GitDir, RepoConfig}; use git_next_config as config;
use git_next_git as git; use git_next_git as git;
use crate::{branch, file, validation};
struct MockForge; struct MockForge;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct MockForgeEnv; pub struct MockForgeEnv;
@ -16,35 +14,35 @@ impl MockForgeEnv {
} }
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl super::ForgeLike for MockForgeEnv { impl git::ForgeLike for MockForgeEnv {
fn name(&self) -> String { fn name(&self) -> String {
"mock".to_string() "mock".to_string()
} }
async fn branches_get_all(&self) -> Result<Vec<BranchName>, branch::Error> { async fn branches_get_all(&self) -> Result<Vec<config::BranchName>, git::branch::Error> {
todo!() todo!()
} }
async fn file_contents_get( async fn file_contents_get(
&self, &self,
_branch: &BranchName, _branch: &config::BranchName,
_file_path: &str, _file_path: &str,
) -> Result<String, file::Error> { ) -> Result<String, git::file::Error> {
todo!() todo!()
} }
async fn branches_validate_positions( async fn branches_validate_positions(
&self, &self,
_repository: OpenRepository, _repository: git::OpenRepository,
_repo_config: RepoConfig, _repo_config: config::RepoConfig,
) -> validation::Result { ) -> git::validation::Result {
todo!() todo!()
} }
fn branch_reset( fn branch_reset(
&self, &self,
_repository: &OpenRepository, _repository: &git::OpenRepository,
_branch_name: BranchName, _branch_name: config::BranchName,
_to_commit: git::GitRef, _to_commit: git::GitRef,
_force: git::push::Force, _force: git::push::Force,
) -> git::push::Result { ) -> git::push::Result {
@ -55,7 +53,10 @@ impl super::ForgeLike for MockForgeEnv {
todo!() todo!()
} }
fn repo_clone(&self, _gitdir: GitDir) -> Result<OpenRepository, git::repository::Error> { fn repo_clone(
&self,
_gitdir: config::GitDir,
) -> Result<OpenRepository, git::repository::Error> {
todo!() todo!()
} }
} }

View file

@ -1,11 +1,11 @@
// //
use assert2::let_assert; use assert2::let_assert;
use git_next_config::{self as config, ForgeType, RepoConfigSource}; use git_next_config as config;
use kxio::network::{MockNetwork, StatusCode};
use git_next_git as git; use git_next_git as git;
use kxio::network::{MockNetwork, StatusCode};
use super::*; use super::*;
#[test] #[test]
@ -18,9 +18,12 @@ fn test_name() {
let repo_details = git::common::repo_details( let repo_details = git::common::repo_details(
1, 1,
git::Generation::new(), git::Generation::new(),
config::common::forge_details(1, ForgeType::MockForge), config::common::forge_details(1, config::ForgeType::MockForge),
Some(config::common::repo_config(1, RepoConfigSource::Repo)), Some(config::common::repo_config(
GitDir::new(fs.base()), 1,
config::RepoConfigSource::Repo,
)),
config::GitDir::new(fs.base()),
); );
let forge = Forge::new_forgejo(repo_details, net, repo); let forge = Forge::new_forgejo(repo_details, net, repo);
assert_eq!(forge.name(), "forgejo"); assert_eq!(forge.name(), "forgejo");
@ -46,9 +49,12 @@ async fn test_branches_get() {
let repo_details = git::common::repo_details( let repo_details = git::common::repo_details(
1, 1,
git::Generation::new(), git::Generation::new(),
config::common::forge_details(1, ForgeType::MockForge), config::common::forge_details(1, config::ForgeType::MockForge),
Some(config::common::repo_config(1, RepoConfigSource::Repo)), Some(config::common::repo_config(
GitDir::new(fs.base()), 1,
config::RepoConfigSource::Repo,
)),
config::GitDir::new(fs.base()),
); );
let forge = Forge::new_forgejo(repo_details, net.clone(), repo); let forge = Forge::new_forgejo(repo_details, net.clone(), repo);
@ -58,5 +64,5 @@ async fn test_branches_get() {
let_assert!(Some(requests) = net.mocked_requests()); let_assert!(Some(requests) = net.mocked_requests());
assert_eq!(requests.len(), 1); assert_eq!(requests.len(), 1);
assert_eq!(branches, vec![BranchName::new("string")]); assert_eq!(branches, vec![config::BranchName::new("string")]);
} }

View file

@ -17,7 +17,7 @@ tracing = { workspace = true }
# git # git
# # gix = { workspace = true } # # gix = { workspace = true }
gix = { workspace = true } gix = { workspace = true }
# async-trait = { workspace = true } async-trait = { workspace = true }
# fs/network # fs/network
kxio = { workspace = true } kxio = { workspace = true }

8
crates/git/src/branch.rs Normal file
View file

@ -0,0 +1,8 @@
#[derive(Debug, derive_more::Display)]
pub enum Error {
#[display("Branch not found: {}", 0)]
NotFound(git_next_config::BranchName),
#[display("Unable to find any branches")]
NoneFound,
}
impl std::error::Error for Error {}

18
crates/git/src/file.rs Normal file
View file

@ -0,0 +1,18 @@
#[derive(Debug, derive_more::Display)]
pub enum Error {
#[display("File not found: {}", 0)]
NotFound(String),
#[display("Unable to parse file contents")]
ParseContent,
#[display("Unable to decode from base64")]
DecodeFromBase64,
#[display("Unable to decoce from UTF-8")]
DecodeFromUtf8,
#[display("Unknown file encoding: {}", 0)]
UnknownEncoding(String),
#[display("Not a file: {}", 0)]
NotFile(String),
#[display("Unknown error (status: {})", 0)]
Unknown(String),
}
impl std::error::Error for Error {}

View file

@ -0,0 +1,43 @@
use crate as git;
use git_next_config as config;
#[async_trait::async_trait]
pub trait ForgeLike {
fn name(&self) -> String;
/// Returns a list of all branches in the repo.
async fn branches_get_all(&self) -> Result<Vec<config::BranchName>, git::branch::Error>;
/// Returns the contents of the file.
async fn file_contents_get(
&self,
branch: &config::BranchName,
file_path: &str,
) -> Result<String, git::file::Error>;
/// Assesses the relative positions of the main, next and dev branch and updates their
/// positions as needed.
async fn branches_validate_positions(
&self,
repository: git::OpenRepository,
repo_config: config::RepoConfig,
) -> git::validation::Result;
/// Moves a branch to a new commit.
fn branch_reset(
&self,
repository: &git::OpenRepository,
branch_name: config::BranchName,
to_commit: git::GitRef,
force: git::push::Force,
) -> git::push::Result;
/// Checks the results of any (e.g. CI) status checks for the commit.
async fn commit_status(&self, commit: &git::Commit) -> git::commit::Status;
/// Clones a repo to disk.
fn repo_clone(
&self,
gitdir: config::GitDir,
) -> Result<git::OpenRepository, git::repository::Error>;
}

View file

@ -1,7 +1,10 @@
// //
pub mod branch;
pub mod commit; pub mod commit;
pub mod common; pub mod common;
pub mod fetch; pub mod fetch;
pub mod file;
mod forge_like;
mod generation; mod generation;
mod git_ref; mod git_ref;
mod git_remote; mod git_remote;
@ -9,11 +12,13 @@ pub mod push;
mod repo_details; mod repo_details;
pub mod repository; pub mod repository;
pub mod validate; pub mod validate;
pub mod validation;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
pub use commit::Commit; pub use commit::Commit;
pub use forge_like::ForgeLike;
pub use generation::Generation; pub use generation::Generation;
pub use git_ref::GitRef; pub use git_ref::GitRef;
pub use git_remote::GitRemote; pub use git_remote::GitRemote;

View file

@ -0,0 +1,25 @@
use crate as git;
use git_next_config::BranchName;
pub type Result = core::result::Result<Positions, Error>;
pub struct Positions {
pub main: git::Commit,
pub next: git::Commit,
pub dev: git::Commit,
pub dev_commit_history: Vec<git::Commit>,
}
#[derive(Debug, derive_more::Display)]
pub enum Error {
Network(Box<kxio::network::NetworkError>),
#[display("Failed to Reset Branch {branch} to {commit}")]
FailedToResetBranch {
branch: BranchName,
commit: git::Commit,
},
BranchReset(BranchName),
BranchHasNoCommits(BranchName),
DevBranchNotBasedOn(BranchName),
}
impl std::error::Error for Error {}

View file

@ -1,6 +1,8 @@
use actix::prelude::*; use actix::prelude::*;
use git_next_forge as forge; use git_next_forge as forge;
use git_next_git::RepoDetails; use git_next_git as git;
use tracing::{error, info}; use tracing::{error, info};
use crate::load; use crate::load;
@ -9,7 +11,7 @@ use super::{LoadedConfig, RepoActor};
/// Loads the [RepoConfig] from the `.git-next.toml` file in the repository /// Loads the [RepoConfig] from the `.git-next.toml` file in the repository
#[tracing::instrument(skip_all, fields(branch = %repo_details.branch))] #[tracing::instrument(skip_all, fields(branch = %repo_details.branch))]
pub async fn load(repo_details: RepoDetails, addr: Addr<RepoActor>, forge: forge::Forge) { pub async fn load(repo_details: git::RepoDetails, addr: Addr<RepoActor>, forge: forge::Forge) {
info!("Loading .git-next.toml from repo"); info!("Loading .git-next.toml from repo");
let repo_config = match load::load(&repo_details, &forge).await { let repo_config = match load::load(&repo_details, &forge).await {
Ok(repo_config) => repo_config, Ok(repo_config) => repo_config,

View file

@ -10,47 +10,43 @@ mod tests;
use std::time::Duration; use std::time::Duration;
use actix::prelude::*; use actix::prelude::*;
use git::OpenRepository;
use git_next_config::{server::Webhook, ForgeType, RepoConfig, RepoConfigSource};
use git_next_forge as forge; use git_next_forge as forge;
use git_next_git as git; use git_next_git as git;
use kxio::network::Network; use kxio::network::Network;
use tracing::{debug, info, warn, Instrument}; use tracing::{debug, info, warn, Instrument};
// use crate::{actors::repo::webhook::WebhookAuth, config::Webhook, gitforge};
use crate::webhook::WebhookAuth;
use self::webhook::WebhookId;
#[derive(Debug, derive_more::Display)] #[derive(Debug, derive_more::Display)]
#[display("{}:{}:{}", generation, details.forge.forge_name(), details.repo_alias)] #[display("{}:{}:{}", generation, details.forge.forge_name(), details.repo_alias)]
pub struct RepoActor { pub struct RepoActor {
generation: git::Generation, generation: git::Generation,
message_token: MessageToken, message_token: MessageToken,
details: git::RepoDetails, details: git::RepoDetails,
webhook: Webhook, webhook: git_next_config::server::Webhook,
webhook_id: Option<WebhookId>, // INFO: if [None] then no webhook is configured webhook_id: Option<webhook::WebhookId>, // INFO: if [None] then no webhook is configured
webhook_auth: Option<WebhookAuth>, // INFO: if [None] then no webhook is configured webhook_auth: Option<webhook::WebhookAuth>, // INFO: if [None] then no webhook is configured
last_main_commit: Option<git::Commit>, last_main_commit: Option<git::Commit>,
last_next_commit: Option<git::Commit>, last_next_commit: Option<git::Commit>,
last_dev_commit: Option<git::Commit>, last_dev_commit: Option<git::Commit>,
repository: Option<OpenRepository>, repository: Option<git::OpenRepository>,
net: Network, net: Network,
forge: forge::Forge, forge: forge::Forge,
} }
impl RepoActor { impl RepoActor {
pub fn new( pub fn new(
details: git::RepoDetails, details: git::RepoDetails,
webhook: Webhook, webhook: git_next_config::server::Webhook,
generation: git::Generation, generation: git::Generation,
net: Network, net: Network,
repo: git::Repository, repo: git::Repository,
) -> Self { ) -> Self {
let forge = match details.forge.forge_type() { let forge = match details.forge.forge_type() {
#[cfg(feature = "forgejo")] #[cfg(feature = "forgejo")]
ForgeType::ForgeJo => forge::Forge::new_forgejo(details.clone(), net.clone(), repo), git_next_config::ForgeType::ForgeJo => {
ForgeType::MockForge => forge::Forge::new_mock(), forge::Forge::new_forgejo(details.clone(), net.clone(), repo)
}
git_next_config::ForgeType::MockForge => forge::Forge::new_mock(),
}; };
debug!(?forge, "new"); debug!(?forge, "new");
Self { Self {
@ -133,7 +129,7 @@ impl Handler<LoadConfigFromRepo> for RepoActor {
#[derive(Message)] #[derive(Message)]
#[rtype(result = "()")] #[rtype(result = "()")]
struct LoadedConfig(RepoConfig); struct LoadedConfig(git_next_config::RepoConfig);
impl Handler<LoadedConfig> for RepoActor { impl Handler<LoadedConfig> for RepoActor {
type Result = (); type Result = ();
#[tracing::instrument(name = "RepoActor::LoadedConfig", skip_all, fields(repo = %self.details, branches = %msg.0))] #[tracing::instrument(name = "RepoActor::LoadedConfig", skip_all, fields(repo = %self.details, branches = %msg.0))]
@ -191,7 +187,7 @@ impl Handler<ValidateRepo> for RepoActor {
.branches_validate_positions(repository, repo_config) .branches_validate_positions(repository, repo_config)
.await .await
{ {
Ok(forge::validation::Positions { Ok(git::validation::Positions {
main, main,
next, next,
dev, dev,
@ -265,7 +261,7 @@ impl Handler<StartMonitoring> for RepoActor {
#[derive(Message)] #[derive(Message)]
#[rtype(result = "()")] #[rtype(result = "()")]
pub struct WebhookRegistered(WebhookId, WebhookAuth); pub struct WebhookRegistered(webhook::WebhookId, webhook::WebhookAuth);
impl Handler<WebhookRegistered> for RepoActor { impl Handler<WebhookRegistered> for RepoActor {
type Result = (); type Result = ();
#[tracing::instrument(name = "RepoActor::WebhookRegistered", skip_all, fields(repo = %self.details, webhook_id = %msg.0))] #[tracing::instrument(name = "RepoActor::WebhookRegistered", skip_all, fields(repo = %self.details, webhook_id = %msg.0))]
@ -296,8 +292,10 @@ impl Handler<AdvanceMainTo> for RepoActor {
async move { async move {
branch::advance_main(msg.0, &repo_config, &forge, &repository).await; branch::advance_main(msg.0, &repo_config, &forge, &repository).await;
match repo_config.source() { match repo_config.source() {
RepoConfigSource::Repo => addr.do_send(LoadConfigFromRepo), git_next_config::RepoConfigSource::Repo => addr.do_send(LoadConfigFromRepo),
RepoConfigSource::Server => addr.do_send(ValidateRepo { message_token }), git_next_config::RepoConfigSource::Server => {
addr.do_send(ValidateRepo { message_token })
}
} }
} }
.in_current_span() .in_current_span()

View file

@ -1,27 +1,34 @@
use git_next_config::{self as config, BranchName, RepoConfig}; use git_next_config as config;
use git_next_forge as forge; use git_next_forge as forge;
use git_next_git::RepoDetails; use git_next_git as git;
use tracing::error; use tracing::error;
pub async fn load(details: &RepoDetails, forge: &forge::Forge) -> Result<RepoConfig, Error> { pub async fn load(
details: &git::RepoDetails,
forge: &forge::Forge,
) -> Result<config::RepoConfig, Error> {
let contents = forge let contents = forge
.file_contents_get(&details.branch, ".git-next.toml") .file_contents_get(&details.branch, ".git-next.toml")
.await?; .await?;
let config = RepoConfig::load(&contents)?; let config = config::RepoConfig::load(&contents)?;
let config = validate(config, forge).await?; let config = validate(config, forge).await?;
Ok(config) Ok(config)
} }
#[derive(Debug, derive_more::From, derive_more::Display)] #[derive(Debug, derive_more::From, derive_more::Display)]
pub enum Error { pub enum Error {
File(forge::file::Error), File(git::file::Error),
Config(config::server::Error), Config(config::server::Error),
Toml(toml::de::Error), Toml(toml::de::Error),
Forge(forge::branch::Error), Forge(git::branch::Error),
BranchNotFound(BranchName), BranchNotFound(config::BranchName),
} }
pub async fn validate(config: RepoConfig, forge: &forge::Forge) -> Result<RepoConfig, Error> { pub async fn validate(
config: config::RepoConfig,
forge: &forge::Forge,
) -> Result<config::RepoConfig, Error> {
let branches = forge.branches_get_all().await.map_err(|e| { let branches = forge.branches_get_all().await.map_err(|e| {
error!(?e, "Failed to list branches"); error!(?e, "Failed to list branches");
Error::Forge(e) Error::Forge(e)