Compare commits

..

1 commit

Author SHA1 Message Date
0124b8d8c0 WIP: add github crate
All checks were successful
ci/woodpecker/push/cron-docker-builder Pipeline was successful
ci/woodpecker/push/push-next Pipeline was successful
ci/woodpecker/push/tag-created Pipeline was successful
2024-05-26 08:07:12 +01:00
19 changed files with 214 additions and 164 deletions

View file

@ -6,7 +6,7 @@ use kxio::network::{self, Network};
pub async fn get_all( pub async fn get_all(
repo_details: &git::RepoDetails, repo_details: &git::RepoDetails,
net: &Network, net: &Network,
) -> git::branch::Result<Vec<config::BranchName>> { ) -> 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;

View file

@ -1,3 +1,6 @@
mod get_all; mod get_all;
mod validate_positions;
pub use get_all::get_all; pub use get_all::get_all;
pub use validate_positions::validate_positions;

View file

@ -1,24 +1,17 @@
// //
use crate as git; use crate as forgejo;
use git::ForgeLike as _;
use git_next_config as config; use git_next_config as config;
use git_next_git as git;
use tracing::{debug, error, info, warn}; use tracing::{debug, error, info, warn};
pub type Result<T> = core::result::Result<T, Error>;
pub struct Positions {
pub main: git::Commit,
pub next: git::Commit,
pub dev: git::Commit,
pub dev_commit_history: Vec<git::Commit>,
}
#[allow(clippy::cognitive_complexity)] // TODO: (#83) reduce complexity #[allow(clippy::cognitive_complexity)] // TODO: (#83) reduce complexity
pub fn validate_positions( pub fn validate_positions(
forge: &forgejo::ForgeJo,
repository: &git::OpenRepository, repository: &git::OpenRepository,
repo_details: &git::RepoDetails,
repo_config: config::RepoConfig, repo_config: config::RepoConfig,
) -> Result<Positions> { ) -> git::validation::Result {
// Collect Commit Histories for `main`, `next` and `dev` branches // Collect Commit Histories for `main`, `next` and `dev` branches
repository.fetch()?; repository.fetch()?;
let commit_histories = get_commit_histories(repository, &repo_config); let commit_histories = get_commit_histories(repository, &repo_config);
@ -26,7 +19,7 @@ pub 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(git::validation::positions::Error::CommitLog(err)); return Err(git::validation::Error::CommitLog(err));
} }
}; };
// Validations // Validations
@ -35,7 +28,7 @@ pub fn validate_positions(
"No commits on main branch '{}'", "No commits on main branch '{}'",
repo_config.branches().main() repo_config.branches().main()
); );
return Err(git::validation::positions::Error::BranchHasNoCommits( return Err(git::validation::Error::BranchHasNoCommits(
repo_config.branches().main(), repo_config.branches().main(),
)); ));
}; };
@ -48,7 +41,7 @@ pub fn validate_positions(
repo_config.branches().main(), repo_config.branches().main(),
repo_config.branches().main(), repo_config.branches().main(),
); );
return Err(git::validation::positions::Error::DevBranchNotBasedOn( return Err(git::validation::Error::DevBranchNotBasedOn(
repo_config.branches().main(), repo_config.branches().main(),
)); ));
} }
@ -58,27 +51,26 @@ pub fn validate_positions(
"No commits on next branch '{}", "No commits on next branch '{}",
repo_config.branches().next() repo_config.branches().next()
); );
return Err(git::validation::positions::Error::BranchHasNoCommits( return Err(git::validation::Error::BranchHasNoCommits(
repo_config.branches().next(), repo_config.branches().next(),
)); ));
}; };
let next_is_ancestor_of_dev = commit_histories.dev.iter().any(|dev| dev == &next); let next_is_ancestor_of_dev = commit_histories.dev.iter().any(|dev| dev == &next);
if !next_is_ancestor_of_dev { if !next_is_ancestor_of_dev {
info!("Next is not an ancestor of dev - resetting next to main"); info!("Next is not an ancestor of dev - resetting next to main");
if let Err(err) = git::branch::reset( if let Err(err) = forge.branch_reset(
repository, repository,
repo_details,
repo_config.branches().next(), repo_config.branches().next(),
main.into(), main.into(),
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(git::validation::positions::Error::FailedToResetBranch { return Err(git::validation::Error::FailedToResetBranch {
branch: repo_config.branches().next(), branch: repo_config.branches().next(),
commit: next, commit: next,
}); });
} }
return Err(git::validation::positions::Error::BranchReset( return Err(git::validation::Error::BranchReset(
repo_config.branches().next(), repo_config.branches().next(),
)); ));
} }
@ -95,20 +87,19 @@ pub fn validate_positions(
repo_config.branches().main(), repo_config.branches().main(),
repo_config.branches().next() repo_config.branches().next()
); );
if let Err(err) = git::branch::reset( if let Err(err) = forge.branch_reset(
repository, repository,
repo_details,
repo_config.branches().next(), repo_config.branches().next(),
main.into(), main.into(),
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(git::validation::positions::Error::FailedToResetBranch { return Err(git::validation::Error::FailedToResetBranch {
branch: repo_config.branches().next(), branch: repo_config.branches().next(),
commit: next, commit: next,
}); });
} }
return Err(git::validation::positions::Error::BranchReset( return Err(git::validation::Error::BranchReset(
repo_config.branches().next(), repo_config.branches().next(),
)); ));
} }
@ -117,7 +108,7 @@ pub fn validate_positions(
"No commits on next branch '{}'", "No commits on next branch '{}'",
repo_config.branches().next() repo_config.branches().next()
); );
return Err(git::validation::positions::Error::BranchHasNoCommits( return Err(git::validation::Error::BranchHasNoCommits(
repo_config.branches().next(), repo_config.branches().next(),
)); ));
}; };
@ -131,7 +122,7 @@ pub fn validate_positions(
repo_config.branches().dev(), repo_config.branches().dev(),
repo_config.branches().next() repo_config.branches().next()
); );
return Err(git::validation::positions::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
} }
@ -140,11 +131,11 @@ pub fn validate_positions(
"No commits on dev branch '{}'", "No commits on dev branch '{}'",
repo_config.branches().dev() repo_config.branches().dev()
); );
return Err(git::validation::positions::Error::BranchHasNoCommits( return Err(git::validation::Error::BranchHasNoCommits(
repo_config.branches().dev(), repo_config.branches().dev(),
)); ));
}; };
Ok(git::validation::positions::Positions { Ok(git::validation::Positions {
main, main,
next, next,
dev, dev,
@ -155,7 +146,7 @@ pub fn validate_positions(
fn get_commit_histories( fn get_commit_histories(
repository: &git::repository::OpenRepository, repository: &git::repository::OpenRepository,
repo_config: &config::RepoConfig, repo_config: &config::RepoConfig,
) -> git::commit::log::Result<git::commit::Histories> { ) -> Result<git::commit::Histories, git::commit::log::Error> {
let main = (repository.commit_log(&repo_config.branches().main(), &[]))?; let main = (repository.commit_log(&repo_config.branches().main(), &[]))?;
let main_head = [main[0].clone()]; let main_head = [main[0].clone()];
let next = repository.commit_log(&repo_config.branches().next(), &main_head)?; let next = repository.commit_log(&repo_config.branches().next(), &main_head)?;
@ -169,28 +160,3 @@ fn get_commit_histories(
let histories = git::commit::Histories { main, next, dev }; let histories = git::commit::Histories { main, next, dev };
Ok(histories) Ok(histories)
} }
#[derive(Debug, derive_more::Display)]
pub enum Error {
Fetch(git::fetch::Error),
CommitLog(git::commit::log::Error),
#[display("Failed to Reset Branch {branch} to {commit}")]
FailedToResetBranch {
branch: config::BranchName,
commit: git::Commit,
},
BranchReset(config::BranchName),
BranchHasNoCommits(config::BranchName),
DevBranchNotBasedOn(config::BranchName),
}
impl std::error::Error for Error {}
impl From<git::fetch::Error> for Error {
fn from(value: git::fetch::Error) -> Self {
Self::Fetch(value)
}
}

View file

@ -4,7 +4,6 @@ mod file;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
use git::validation::repo::validate_repo;
use git_next_config as config; use git_next_config as config;
use git_next_git as git; use git_next_git as git;
@ -44,6 +43,25 @@ impl git::ForgeLike for ForgeJo {
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(
&self,
repository: git::OpenRepository,
repo_config: config::RepoConfig,
) -> git::validation::Result {
branch::validate_positions(self, &repository, repo_config)
}
fn branch_reset(
&self,
repository: &git::OpenRepository,
branch_name: config::BranchName,
to_commit: git::GitRef,
force: git::push::Force,
) -> git::push::Result {
repository.fetch()?;
repository.push(&self.repo_details, branch_name, to_commit, force)
}
async fn commit_status(&self, commit: &git::Commit) -> git::commit::Status { async fn commit_status(&self, commit: &git::Commit) -> git::commit::Status {
let repo_details = &self.repo_details; let repo_details = &self.repo_details;
let hostname = &repo_details.forge.hostname(); let hostname = &repo_details.forge.hostname();
@ -99,7 +117,7 @@ impl git::ForgeLike for ForgeJo {
self.repo.open(&gitdir)? self.repo.open(&gitdir)?
}; };
info!("Validating..."); info!("Validating...");
validate_repo(&repository, &self.repo_details) git::validate(&repository, &self.repo_details)
.map_err(|e| git::repository::Error::Validation(e.to_string()))?; .map_err(|e| git::repository::Error::Validation(e.to_string()))?;
Ok(repository) Ok(repository)
} }

View file

@ -32,6 +32,27 @@ impl git_next_git::ForgeLike for Github {
todo!(); todo!();
} }
/// 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 {
todo!();
}
/// 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 {
todo!();
}
/// Checks the results of any (e.g. CI) status checks for the commit. /// Checks the results of any (e.g. CI) status checks for the commit.
async fn commit_status(&self, _commit: &git::Commit) -> git::commit::Status { async fn commit_status(&self, _commit: &git::Commit) -> git::commit::Status {
todo!(); todo!();

View file

@ -27,6 +27,24 @@ impl git::ForgeLike for MockForge {
todo!() todo!()
} }
async fn branches_validate_positions(
&self,
_repository: git::OpenRepository,
_repo_config: config::RepoConfig,
) -> git::validation::Result {
todo!()
}
fn branch_reset(
&self,
_repository: &git::OpenRepository,
_branch_name: config::BranchName,
_to_commit: git::GitRef,
_force: git::push::Force,
) -> git::push::Result {
todo!()
}
async fn commit_status(&self, _commit: &git::Commit) -> git::commit::Status { async fn commit_status(&self, _commit: &git::Commit) -> git::commit::Status {
todo!() todo!()
} }

View file

@ -1,26 +1,6 @@
//
use crate as git;
use git_next_config as config;
pub type Result<T> = core::result::Result<T, Error>;
#[derive(Debug, derive_more::Display, derive_more::From)] #[derive(Debug, derive_more::Display, derive_more::From)]
pub enum Error { pub enum Error {
#[display("network: {}", 0)]
Network(kxio::network::NetworkError), Network(kxio::network::NetworkError),
Fetch(git::fetch::Error),
Push(git::push::Error),
} }
impl std::error::Error for Error {} impl std::error::Error for Error {}
pub fn reset(
repository: &git::OpenRepository,
repo_details: &git::RepoDetails,
branch_name: config::BranchName,
to_commit: git::GitRef,
force: git::push::Force,
) -> Result<()> {
repository.fetch()?;
Ok(repository.push(repo_details, branch_name, to_commit, force)?)
}

View file

@ -36,8 +36,6 @@ pub struct Histories {
pub mod log { pub mod log {
use derive_more::{Display, From}; use derive_more::{Display, From};
pub type Result<T> = core::result::Result<T, Error>;
#[derive(Debug, Display, From)] #[derive(Debug, Display, From)]
pub enum Error { pub enum Error {
Gix(String), Gix(String),

View file

@ -15,6 +15,23 @@ pub trait ForgeLike {
file_path: &str, file_path: &str,
) -> Result<String, git::file::Error>; ) -> 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. /// Checks the results of any (e.g. CI) status checks for the commit.
async fn commit_status(&self, commit: &git::Commit) -> git::commit::Status; async fn commit_status(&self, commit: &git::Commit) -> git::commit::Status;

View file

@ -11,6 +11,7 @@ mod git_remote;
pub mod push; pub mod push;
mod repo_details; mod repo_details;
pub mod repository; pub mod repository;
pub mod validate;
pub mod validation; pub mod validation;
#[cfg(test)] #[cfg(test)]
@ -24,3 +25,4 @@ pub use git_remote::GitRemote;
pub use repo_details::RepoDetails; pub use repo_details::RepoDetails;
pub use repository::OpenRepository; pub use repository::OpenRepository;
pub use repository::Repository; pub use repository::Repository;
pub use validate::validate;

View file

@ -17,6 +17,7 @@ impl std::fmt::Display for Force {
#[derive(Debug, derive_more::From, derive_more::Display)] #[derive(Debug, derive_more::From, derive_more::Display)]
pub enum Error { pub enum Error {
Open(Box<gix::open::Error>), Open(Box<gix::open::Error>),
Fetch(super::fetch::Error),
Push, Push,
Lock, Lock,
} }

View file

@ -1,12 +1,10 @@
mod validate { mod validate {
use assert2::let_assert; use assert2::let_assert;
use git_next_config::{ForgeDetails, GitDir, Hostname, RepoPath}; use git_next_config::{ForgeDetails, GitDir, Hostname, RepoPath};
use crate::{ use crate::{
repository, repository::{self, Direction},
validation::{self, repo::validate_repo}, validate, GitRemote, RepoDetails,
GitRemote, RepoDetails,
}; };
#[test] #[test]
@ -29,9 +27,8 @@ mod validate {
|open_repo| { |open_repo| {
let_assert!( let_assert!(
Ok(_) = open_repo.lock().map(|mut open_repo| { Ok(_) = open_repo.lock().map(|mut open_repo| {
open_repo open_repo.has_default_remote(Direction::Push, remote.clone());
.has_default_remote(repository::Direction::Push, remote.clone()); open_repo.has_default_remote(Direction::Fetch, remote);
open_repo.has_default_remote(repository::Direction::Fetch, remote);
}) })
); );
} }
@ -40,7 +37,7 @@ mod validate {
let_assert!(Ok(open_repository) = repository.open(&gitdir)); let_assert!(Ok(open_repository) = repository.open(&gitdir));
let_assert!(Ok(_) = validation::repo::validate_repo(&open_repository, &repo_details)); let_assert!(Ok(_) = validate(&open_repository, &repo_details));
} }
#[test] #[test]
@ -64,7 +61,7 @@ mod validate {
let_assert!( let_assert!(
Ok(_) = open_repo.lock().map(|mut open_repo| { Ok(_) = open_repo.lock().map(|mut open_repo| {
// INFO: open_repo.has_default_remote(Direction::Push, remote.clone()); // INFO: open_repo.has_default_remote(Direction::Push, remote.clone());
open_repo.has_default_remote(repository::Direction::Fetch, remote); open_repo.has_default_remote(Direction::Fetch, remote);
}) })
); );
} }
@ -73,7 +70,7 @@ mod validate {
let_assert!(Ok(open_repository) = repository.open(&gitdir)); let_assert!(Ok(open_repository) = repository.open(&gitdir));
let_assert!(Err(_) = validate_repo(&open_repository, &repo_details)); let_assert!(Err(_) = validate(&open_repository, &repo_details));
} }
#[test] #[test]
fn should_fail_where_no_default_fetch_remote() { fn should_fail_where_no_default_fetch_remote() {
@ -95,7 +92,7 @@ mod validate {
|open_repo| { |open_repo| {
let_assert!( let_assert!(
Ok(_) = open_repo.lock().map(|mut open_repo| { Ok(_) = open_repo.lock().map(|mut open_repo| {
open_repo.has_default_remote(repository::Direction::Push, remote); open_repo.has_default_remote(Direction::Push, remote);
// INFO: open_repo.has_default_remote(Direction::Fetch, remote); // INFO: open_repo.has_default_remote(Direction::Fetch, remote);
}) })
); );
@ -105,7 +102,7 @@ mod validate {
let_assert!(Ok(open_repository) = repository.open(&gitdir)); let_assert!(Ok(open_repository) = repository.open(&gitdir));
let_assert!(Err(_) = validate_repo(&open_repository, &repo_details)); let_assert!(Err(_) = validate(&open_repository, &repo_details));
} }
#[test] #[test]
fn should_fail_where_invalid_default_push_remote() { fn should_fail_where_invalid_default_push_remote() {
@ -131,8 +128,8 @@ mod validate {
|open_repo| { |open_repo| {
let_assert!( let_assert!(
Ok(_) = open_repo.lock().map(|mut open_repo| { Ok(_) = open_repo.lock().map(|mut open_repo| {
open_repo.has_default_remote(repository::Direction::Push, other_remote); open_repo.has_default_remote(Direction::Push, other_remote);
open_repo.has_default_remote(repository::Direction::Fetch, remote); open_repo.has_default_remote(Direction::Fetch, remote);
}) })
); );
} }
@ -141,7 +138,7 @@ mod validate {
let_assert!(Ok(open_repository) = repository.open(&gitdir)); let_assert!(Ok(open_repository) = repository.open(&gitdir));
let_assert!(Err(_) = validate_repo(&open_repository, &repo_details)); let_assert!(Err(_) = validate(&open_repository, &repo_details));
} }
#[test] #[test]
fn should_fail_where_invalid_default_fetch_remote() { fn should_fail_where_invalid_default_fetch_remote() {
@ -167,9 +164,8 @@ mod validate {
|open_repo| { |open_repo| {
let_assert!( let_assert!(
Ok(_) = open_repo.lock().map(|mut open_repo| { Ok(_) = open_repo.lock().map(|mut open_repo| {
open_repo.has_default_remote(repository::Direction::Push, remote); open_repo.has_default_remote(Direction::Push, remote);
open_repo open_repo.has_default_remote(Direction::Fetch, other_remote);
.has_default_remote(repository::Direction::Fetch, other_remote);
}) })
); );
} }
@ -178,7 +174,7 @@ mod validate {
let_assert!(Ok(open_repository) = repository.open(&gitdir)); let_assert!(Ok(open_repository) = repository.open(&gitdir));
let_assert!(Err(_) = validate_repo(&open_repository, &repo_details)); let_assert!(Err(_) = validate(&open_repository, &repo_details));
} }
} }
@ -186,7 +182,7 @@ mod git_clone {
use assert2::let_assert; use assert2::let_assert;
use git_next_config::{ForgeDetails, GitDir, Hostname, RepoPath}; use git_next_config::{ForgeDetails, GitDir, Hostname, RepoPath};
use crate::{repository, GitRemote, RepoDetails}; use crate::{repository::Direction, GitRemote, RepoDetails};
#[test] #[test]
fn should_clone_repo() { fn should_clone_repo() {
@ -199,7 +195,7 @@ mod git_clone {
.with_gitdir(GitDir::new(fs.base())) .with_gitdir(GitDir::new(fs.base()))
.with_repo_path(RepoPath::new("kemitix/git-next".to_string())); .with_repo_path(RepoPath::new("kemitix/git-next".to_string()));
let_assert!(Ok(open_repo) = r.git_clone(&repo_details)); let_assert!(Ok(open_repo) = r.git_clone(&repo_details));
let_assert!(Some(remote) = open_repo.find_default_remote(repository::Direction::Fetch)); let_assert!(Some(remote) = open_repo.find_default_remote(Direction::Fetch));
assert_eq!( assert_eq!(
remote, remote,
GitRemote::new( GitRemote::new(

View file

@ -1,17 +1,15 @@
use tracing::info; use tracing::info;
use crate as git; use crate::repository::{Direction, OpenRepository};
use super::{GitRemote, RepoDetails};
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub fn validate_repo( pub fn validate(repository: &OpenRepository, repo_details: &RepoDetails) -> Result<()> {
repository: &git::OpenRepository, let Some(push_remote) = repository.find_default_remote(Direction::Push) else {
repo_details: &git::RepoDetails,
) -> Result<()> {
let Some(push_remote) = repository.find_default_remote(git::repository::Direction::Push) else {
return Err(Error::NoDefaultPushRemote); return Err(Error::NoDefaultPushRemote);
}; };
let Some(fetch_remote) = repository.find_default_remote(git::repository::Direction::Fetch) let Some(fetch_remote) = repository.find_default_remote(Direction::Fetch) else {
else {
return Err(Error::NoDefaultFetchRemote); return Err(Error::NoDefaultFetchRemote);
}; };
let git_remote = repo_details.git_remote(); let git_remote = repo_details.git_remote();
@ -42,13 +40,13 @@ pub enum Error {
Io(std::io::Error), Io(std::io::Error),
#[display("MismatchDefaultPushRemote(found: {found}, expected: {expected})")] #[display("MismatchDefaultPushRemote(found: {found}, expected: {expected})")]
MismatchDefaultPushRemote { MismatchDefaultPushRemote {
found: git::GitRemote, found: GitRemote,
expected: git::GitRemote, expected: GitRemote,
}, },
#[display("MismatchDefaultFetchRemote(found: {found}, expected: {expected})")] #[display("MismatchDefaultFetchRemote(found: {found}, expected: {expected})")]
MismatchDefaultFetchRemote { MismatchDefaultFetchRemote {
found: git::GitRemote, found: GitRemote,
expected: git::GitRemote, expected: GitRemote,
}, },
} }
impl std::error::Error for Error {} impl std::error::Error for Error {}

View file

@ -0,0 +1,37 @@
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 {
Fetch(git::fetch::Error),
CommitLog(git::commit::log::Error),
#[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 {}
impl From<git::fetch::Error> for Error {
fn from(value: git::fetch::Error) -> Self {
Self::Fetch(value)
}
}

View file

@ -1,2 +0,0 @@
pub mod positions;
pub mod repo;

View file

@ -1,21 +1,21 @@
// use std::time::Duration;
use actix::prelude::*; use actix::prelude::*;
use git_next_config as config; use git_next_config as config;
use git_next_forge as forge;
use git_next_git as git; use git_next_git as git;
use tracing::{info, warn}; use tracing::{info, warn};
use crate::{MessageToken, ValidateRepo}; use crate::{MessageToken, ValidateRepo};
use std::time::Duration;
// advance next to the next commit towards the head of the dev branch // advance next to the next commit towards the head of the dev branch
#[tracing::instrument(fields(next), skip_all)] #[tracing::instrument(fields(next), skip_all)]
pub async fn advance_next( pub async fn advance_next(
next: git::Commit, next: git::Commit,
dev_commit_history: Vec<git::Commit>, dev_commit_history: Vec<git::Commit>,
repo_details: git::RepoDetails,
repo_config: config::RepoConfig, repo_config: config::RepoConfig,
forge: forge::Forge,
repository: git::OpenRepository, repository: git::OpenRepository,
addr: Addr<super::RepoActor>, addr: Addr<super::RepoActor>,
message_token: MessageToken, message_token: MessageToken,
@ -30,9 +30,8 @@ pub async fn advance_next(
return; return;
} }
info!("Advancing next to commit '{}'", commit); info!("Advancing next to commit '{}'", commit);
if let Err(err) = git::branch::reset( if let Err(err) = forge.branch_reset(
&repository, &repository,
&repo_details,
repo_config.branches().next(), repo_config.branches().next(),
commit.into(), commit.into(),
git::push::Force::No, git::push::Force::No,
@ -79,14 +78,13 @@ pub fn find_next_commit_on_dev(
#[tracing::instrument(fields(next), skip_all)] #[tracing::instrument(fields(next), skip_all)]
pub async fn advance_main( pub async fn advance_main(
next: git::Commit, next: git::Commit,
repo_details: &git::RepoDetails,
repo_config: &config::RepoConfig, repo_config: &config::RepoConfig,
forge: &forge::Forge,
repository: &git::OpenRepository, repository: &git::OpenRepository,
) { ) {
info!("Advancing main to next"); info!("Advancing main to next");
if let Err(err) = git::branch::reset( if let Err(err) = forge.branch_reset(
repository, repository,
repo_details,
repo_config.branches().main(), repo_config.branches().main(),
next.into(), next.into(),
git::push::Force::No, git::push::Force::No,

View file

@ -9,7 +9,6 @@ mod tests;
use std::time::Duration; use std::time::Duration;
use actix::prelude::*; use actix::prelude::*;
use git::validation::positions::{validate_positions, Positions};
use crate as repo_actor; use crate as repo_actor;
use git_next_config as config; use git_next_config as config;
@ -20,11 +19,11 @@ use kxio::network::Network;
use tracing::{debug, info, warn, Instrument}; use tracing::{debug, info, warn, Instrument};
#[derive(Debug, derive_more::Display)] #[derive(Debug, derive_more::Display)]
#[display("{}:{}:{}", generation, repo_details.forge.forge_name(), repo_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,
repo_details: git::RepoDetails, details: git::RepoDetails,
webhook: config::server::Webhook, webhook: config::server::Webhook,
webhook_id: Option<webhook::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<webhook::WebhookAuth>, // INFO: if [None] then no webhook is configured webhook_auth: Option<webhook::WebhookAuth>, // INFO: if [None] then no webhook is configured
@ -54,7 +53,7 @@ impl RepoActor {
Self { Self {
generation, generation,
message_token: MessageToken::new(), message_token: MessageToken::new(),
repo_details: details, details,
webhook, webhook,
webhook_id: None, webhook_id: None,
webhook_auth: None, webhook_auth: None,
@ -69,12 +68,12 @@ impl RepoActor {
} }
impl Actor for RepoActor { impl Actor for RepoActor {
type Context = Context<Self>; type Context = Context<Self>;
#[tracing::instrument(name = "RepoActor::stopping", skip_all, fields(repo = %self.repo_details))] #[tracing::instrument(name = "RepoActor::stopping", skip_all, fields(repo = %self.details))]
fn stopping(&mut self, ctx: &mut Self::Context) -> Running { fn stopping(&mut self, ctx: &mut Self::Context) -> Running {
info!("Checking webhook"); info!("Checking webhook");
match self.webhook_id.take() { match self.webhook_id.take() {
Some(webhook_id) => { Some(webhook_id) => {
let repo_details = self.repo_details.clone(); let repo_details = self.details.clone();
let net = self.net.clone(); let net = self.net.clone();
info!(%webhook_id, "Unregistring webhook"); info!(%webhook_id, "Unregistring webhook");
webhook::unregister(webhook_id, repo_details, net) webhook::unregister(webhook_id, repo_details, net)
@ -93,13 +92,13 @@ impl Actor for RepoActor {
pub struct CloneRepo; pub struct CloneRepo;
impl Handler<CloneRepo> for RepoActor { impl Handler<CloneRepo> for RepoActor {
type Result = (); type Result = ();
#[tracing::instrument(name = "RepoActor::CloneRepo", skip_all, fields(repo = %self.repo_details, gitdir = %self.repo_details.gitdir))] #[tracing::instrument(name = "RepoActor::CloneRepo", skip_all, fields(repo = %self.details, gitdir = %self.details.gitdir))]
fn handle(&mut self, _msg: CloneRepo, ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, _msg: CloneRepo, ctx: &mut Self::Context) -> Self::Result {
let gitdir = self.repo_details.gitdir.clone(); let gitdir = self.details.gitdir.clone();
match self.forge.repo_clone(gitdir) { match self.forge.repo_clone(gitdir) {
Ok(repository) => { Ok(repository) => {
self.repository.replace(repository); self.repository.replace(repository);
if self.repo_details.repo_config.is_none() { if self.details.repo_config.is_none() {
ctx.address().do_send(LoadConfigFromRepo); ctx.address().do_send(LoadConfigFromRepo);
} else { } else {
ctx.address().do_send(ValidateRepo { ctx.address().do_send(ValidateRepo {
@ -117,9 +116,9 @@ impl Handler<CloneRepo> for RepoActor {
pub struct LoadConfigFromRepo; pub struct LoadConfigFromRepo;
impl Handler<LoadConfigFromRepo> for RepoActor { impl Handler<LoadConfigFromRepo> for RepoActor {
type Result = (); type Result = ();
#[tracing::instrument(name = "RepoActor::LoadConfigFromRepo", skip_all, fields(repo = %self.repo_details))] #[tracing::instrument(name = "RepoActor::LoadConfigFromRepo", skip_all, fields(repo = %self.details))]
fn handle(&mut self, _msg: LoadConfigFromRepo, ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, _msg: LoadConfigFromRepo, ctx: &mut Self::Context) -> Self::Result {
let details = self.repo_details.clone(); let details = self.details.clone();
let addr = ctx.address(); let addr = ctx.address();
let forge = self.forge.clone(); let forge = self.forge.clone();
repo_actor::load::load_file(details, addr, forge) repo_actor::load::load_file(details, addr, forge)
@ -134,10 +133,10 @@ impl Handler<LoadConfigFromRepo> for RepoActor {
struct LoadedConfig(git_next_config::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.repo_details, branches = %msg.0))] #[tracing::instrument(name = "RepoActor::LoadedConfig", skip_all, fields(repo = %self.details, branches = %msg.0))]
fn handle(&mut self, msg: LoadedConfig, ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: LoadedConfig, ctx: &mut Self::Context) -> Self::Result {
let repo_config = msg.0; let repo_config = msg.0;
self.repo_details.repo_config.replace(repo_config); self.details.repo_config.replace(repo_config);
ctx.address().do_send(ValidateRepo { ctx.address().do_send(ValidateRepo {
message_token: self.message_token, message_token: self.message_token,
@ -152,7 +151,7 @@ pub struct ValidateRepo {
} }
impl Handler<ValidateRepo> for RepoActor { impl Handler<ValidateRepo> for RepoActor {
type Result = (); type Result = ();
#[tracing::instrument(name = "RepoActor::ValidateRepo", skip_all, fields(repo = %self.repo_details, token = %msg.message_token))] #[tracing::instrument(name = "RepoActor::ValidateRepo", skip_all, fields(repo = %self.details, token = %msg.message_token))]
fn handle(&mut self, msg: ValidateRepo, ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: ValidateRepo, ctx: &mut Self::Context) -> Self::Result {
match msg.message_token { match msg.message_token {
message_token if self.message_token < message_token => { message_token if self.message_token < message_token => {
@ -169,7 +168,7 @@ impl Handler<ValidateRepo> for RepoActor {
} }
if self.webhook_id.is_none() { if self.webhook_id.is_none() {
webhook::register( webhook::register(
self.repo_details.clone(), self.details.clone(),
self.webhook.clone(), self.webhook.clone(),
ctx.address(), ctx.address(),
self.net.clone(), self.net.clone(),
@ -178,16 +177,18 @@ impl Handler<ValidateRepo> for RepoActor {
.into_actor(self) .into_actor(self)
.wait(ctx); .wait(ctx);
} }
if let (Some(repository), Some(repo_config)) = ( if let (Some(repository), Some(repo_config)) =
self.repository.clone(), (self.repository.clone(), self.details.repo_config.clone())
self.repo_details.repo_config.clone(), {
) { let forge = self.forge.clone();
let repo_details = self.repo_details.clone();
let addr = ctx.address(); let addr = ctx.address();
let message_token = self.message_token; let message_token = self.message_token;
async move { async move {
match validate_positions(&repository, &repo_details, repo_config) { match forge
Ok(Positions { .branches_validate_positions(repository, repo_config)
.await
{
Ok(git::validation::Positions {
main, main,
next, next,
dev, dev,
@ -220,10 +221,10 @@ pub struct StartMonitoring {
impl Handler<StartMonitoring> for RepoActor { impl Handler<StartMonitoring> for RepoActor {
type Result = (); type Result = ();
#[tracing::instrument(name = "RepoActor::StartMonitoring", skip_all, #[tracing::instrument(name = "RepoActor::StartMonitoring", skip_all,
fields(token = %self.message_token, repo = %self.repo_details, main = %msg.main, next= %msg.next, dev = %msg.dev)) fields(token = %self.message_token, repo = %self.details, main = %msg.main, next= %msg.next, dev = %msg.dev))
] ]
fn handle(&mut self, msg: StartMonitoring, ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: StartMonitoring, ctx: &mut Self::Context) -> Self::Result {
let Some(repo_config) = self.repo_details.repo_config.clone() else { let Some(repo_config) = self.details.repo_config.clone() else {
warn!("No config loaded"); warn!("No config loaded");
return; return;
}; };
@ -245,8 +246,8 @@ impl Handler<StartMonitoring> for RepoActor {
branch::advance_next( branch::advance_next(
msg.next, msg.next,
msg.dev_commit_history, msg.dev_commit_history,
self.repo_details.clone(),
repo_config, repo_config,
forge,
repository, repository,
addr, addr,
self.message_token, self.message_token,
@ -264,7 +265,7 @@ impl Handler<StartMonitoring> for RepoActor {
pub struct WebhookRegistered(webhook::WebhookId, webhook::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.repo_details, webhook_id = %msg.0))] #[tracing::instrument(name = "RepoActor::WebhookRegistered", skip_all, fields(repo = %self.details, webhook_id = %msg.0))]
fn handle(&mut self, msg: WebhookRegistered, _ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: WebhookRegistered, _ctx: &mut Self::Context) -> Self::Result {
self.webhook_id.replace(msg.0); self.webhook_id.replace(msg.0);
self.webhook_auth.replace(msg.1); self.webhook_auth.replace(msg.1);
@ -276,9 +277,9 @@ impl Handler<WebhookRegistered> for RepoActor {
pub struct AdvanceMainTo(git::Commit); pub struct AdvanceMainTo(git::Commit);
impl Handler<AdvanceMainTo> for RepoActor { impl Handler<AdvanceMainTo> for RepoActor {
type Result = (); type Result = ();
#[tracing::instrument(name = "RepoActor::AdvanceMainTo", skip_all, fields(repo = %self.repo_details, commit = %msg.0))] #[tracing::instrument(name = "RepoActor::AdvanceMainTo", skip_all, fields(repo = %self.details, commit = %msg.0))]
fn handle(&mut self, msg: AdvanceMainTo, ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: AdvanceMainTo, ctx: &mut Self::Context) -> Self::Result {
let Some(repo_config) = self.repo_details.repo_config.clone() else { let Some(repo_config) = self.details.repo_config.clone() else {
warn!("No config loaded"); warn!("No config loaded");
return; return;
}; };
@ -286,11 +287,11 @@ impl Handler<AdvanceMainTo> for RepoActor {
warn!("No repository opened"); warn!("No repository opened");
return; return;
}; };
let repo_details = self.repo_details.clone(); let forge = self.forge.clone();
let addr = ctx.address(); let addr = ctx.address();
let message_token = self.message_token; let message_token = self.message_token;
async move { async move {
branch::advance_main(msg.0, &repo_details, &repo_config, &repository).await; branch::advance_main(msg.0, &repo_config, &forge, &repository).await;
match repo_config.source() { match repo_config.source() {
git_next_config::RepoConfigSource::Repo => addr.do_send(LoadConfigFromRepo), git_next_config::RepoConfigSource::Repo => addr.do_send(LoadConfigFromRepo),
git_next_config::RepoConfigSource::Server => { git_next_config::RepoConfigSource::Server => {

View file

@ -182,7 +182,7 @@ impl Handler<WebhookMessage> for RepoActor {
type Result = (); type Result = ();
#[allow(clippy::cognitive_complexity)] // TODO: (#49) reduce complexity #[allow(clippy::cognitive_complexity)] // TODO: (#49) reduce complexity
#[tracing::instrument(name = "RepoActor::WebhookMessage", skip_all, fields(token = %self.message_token, repo = %self.repo_details))] #[tracing::instrument(name = "RepoActor::WebhookMessage", skip_all, fields(token = %self.message_token, repo = %self.details))]
fn handle(&mut self, msg: WebhookMessage, ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: WebhookMessage, ctx: &mut Self::Context) -> Self::Result {
let Some(expected_authorization) = &self.webhook_auth else { let Some(expected_authorization) = &self.webhook_auth else {
warn!("Don't know what authorization to expect"); warn!("Don't know what authorization to expect");
@ -199,7 +199,7 @@ impl Handler<WebhookMessage> for RepoActor {
match serde_json::from_str::<Push>(body.as_str()) { match serde_json::from_str::<Push>(body.as_str()) {
Err(err) => warn!(?err, ?body, "Not a 'push'"), Err(err) => warn!(?err, ?body, "Not a 'push'"),
Ok(push) => { Ok(push) => {
if let Some(config) = &self.repo_details.repo_config { if let Some(config) = &self.details.repo_config {
match push.branch(config.branches()) { match push.branch(config.branches()) {
None => warn!( None => warn!(
?push, ?push,

View file

@ -1,6 +1,6 @@
// //
use assert2::let_assert; use assert2::let_assert;
use git::{repository::Direction, validation::repo::validate_repo}; use git::repository::Direction;
use git_next_config::{ use git_next_config::{
self as config, ForgeType, GitDir, Hostname, RepoBranches, RepoConfig, RepoConfigSource, self as config, ForgeType, GitDir, Hostname, RepoBranches, RepoConfig, RepoConfigSource,
RepoPath, RepoPath,
@ -46,7 +46,7 @@ fn gitdir_should_display_as_pathbuf() {
// git.kemitix.net:kemitix/git-next // git.kemitix.net:kemitix/git-next
// If the default push remote is something else, then this test will fail // If the default push remote is something else, then this test will fail
fn repo_details_find_default_push_remote_finds_correct_remote() -> Result<()> { fn repo_details_find_default_push_remote_finds_correct_remote() -> Result<()> {
let cli_crate_dir = std::env::current_dir().map_err(git::validation::repo::Error::Io)?; let cli_crate_dir = std::env::current_dir().map_err(git::validate::Error::Io)?;
let_assert!(Some(Some(root)) = cli_crate_dir.parent().map(|p| p.parent())); let_assert!(Some(Some(root)) = cli_crate_dir.parent().map(|p| p.parent()));
let mut repo_details = git::common::repo_details( let mut repo_details = git::common::repo_details(
1, 1,
@ -77,7 +77,7 @@ fn repo_details_find_default_push_remote_finds_correct_remote() -> Result<()> {
#[test] #[test]
fn gitdir_validate_should_pass_a_valid_git_repo() -> Result<()> { fn gitdir_validate_should_pass_a_valid_git_repo() -> Result<()> {
let cli_crate_dir = std::env::current_dir().map_err(git::validation::repo::Error::Io)?; let cli_crate_dir = std::env::current_dir().map_err(git::validate::Error::Io)?;
let_assert!(Some(Some(root)) = cli_crate_dir.parent().map(|p| p.parent())); let_assert!(Some(Some(root)) = cli_crate_dir.parent().map(|p| p.parent()));
let mut repo_details = git::common::repo_details( let mut repo_details = git::common::repo_details(
1, 1,
@ -92,16 +92,14 @@ fn gitdir_validate_should_pass_a_valid_git_repo() -> Result<()> {
repo_details.repo_path = RepoPath::new("kemitix/git-next".to_string()); repo_details.repo_path = RepoPath::new("kemitix/git-next".to_string());
let gitdir = &repo_details.gitdir; let gitdir = &repo_details.gitdir;
let repository = git::repository::new().open(gitdir)?; let repository = git::repository::new().open(gitdir)?;
validate_repo(&repository, &repo_details)?; git::validate(&repository, &repo_details)?;
Ok(()) Ok(())
} }
#[test] #[test]
fn gitdir_validate_should_fail_a_git_repo_with_wrong_remote() -> Result<()> { fn gitdir_validate_should_fail_a_git_repo_with_wrong_remote() -> Result<()> {
let_assert!( let_assert!(Ok(cli_crate_dir) = std::env::current_dir().map_err(git::validate::Error::Io));
Ok(cli_crate_dir) = std::env::current_dir().map_err(git::validation::repo::Error::Io)
);
let_assert!(Some(Some(root)) = cli_crate_dir.parent().map(|p| p.parent())); let_assert!(Some(Some(root)) = cli_crate_dir.parent().map(|p| p.parent()));
let mut repo_details = git::common::repo_details( let mut repo_details = git::common::repo_details(
1, 1,
@ -113,7 +111,7 @@ fn gitdir_validate_should_fail_a_git_repo_with_wrong_remote() -> Result<()> {
repo_details.repo_path = RepoPath::new("hello/world".to_string()); repo_details.repo_path = RepoPath::new("hello/world".to_string());
let gitdir = &repo_details.gitdir; let gitdir = &repo_details.gitdir;
let repository = git::repository::new().open(gitdir)?; let repository = git::repository::new().open(gitdir)?;
let_assert!(Err(_) = validate_repo(&repository, &repo_details)); let_assert!(Err(_) = git::validate(&repository, &repo_details));
Ok(()) Ok(())
} }