forked from kemitix/git-next
refactor: move validate_positions into git crate
This commit is contained in:
parent
8a35911d00
commit
dd5532d3a3
14 changed files with 229 additions and 267 deletions
|
@ -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,
|
||||||
) -> Result<Vec<config::BranchName>, git::branch::Error> {
|
) -> git::branch::Result<Vec<config::BranchName>> {
|
||||||
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;
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
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;
|
|
||||||
|
|
|
@ -1,162 +0,0 @@
|
||||||
//
|
|
||||||
use crate as forgejo;
|
|
||||||
use git::ForgeLike as _;
|
|
||||||
use git_next_config as config;
|
|
||||||
use git_next_git as git;
|
|
||||||
|
|
||||||
use tracing::{debug, error, info, warn};
|
|
||||||
|
|
||||||
#[allow(clippy::cognitive_complexity)] // TODO: (#83) reduce complexity
|
|
||||||
pub fn validate_positions(
|
|
||||||
forge: &forgejo::ForgeJo,
|
|
||||||
repository: &git::OpenRepository,
|
|
||||||
repo_config: config::RepoConfig,
|
|
||||||
) -> git::validation::Result {
|
|
||||||
// Collect Commit Histories for `main`, `next` and `dev` branches
|
|
||||||
repository.fetch()?;
|
|
||||||
let commit_histories = get_commit_histories(repository, &repo_config);
|
|
||||||
let commit_histories = match commit_histories {
|
|
||||||
Ok(commit_histories) => commit_histories,
|
|
||||||
Err(err) => {
|
|
||||||
error!(?err, "Failed to get commit histories");
|
|
||||||
return Err(git::validation::Error::CommitLog(err));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Validations
|
|
||||||
let Some(main) = commit_histories.main.first().cloned() else {
|
|
||||||
warn!(
|
|
||||||
"No commits on main branch '{}'",
|
|
||||||
repo_config.branches().main()
|
|
||||||
);
|
|
||||||
return Err(git::validation::Error::BranchHasNoCommits(
|
|
||||||
repo_config.branches().main(),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
// Dev must be on main branch, or user must rebase it
|
|
||||||
let dev_has_main = commit_histories.dev.iter().any(|commit| commit == &main);
|
|
||||||
if !dev_has_main {
|
|
||||||
warn!(
|
|
||||||
"Dev branch '{}' is not based on main branch '{}' - user should rebase onto main branch '{}'",
|
|
||||||
repo_config.branches().dev(),
|
|
||||||
repo_config.branches().main(),
|
|
||||||
repo_config.branches().main(),
|
|
||||||
);
|
|
||||||
return Err(git::validation::Error::DevBranchNotBasedOn(
|
|
||||||
repo_config.branches().main(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
// verify that next is an ancestor of dev, and force update to it main if it isn't
|
|
||||||
let Some(next) = commit_histories.next.first().cloned() else {
|
|
||||||
warn!(
|
|
||||||
"No commits on next branch '{}",
|
|
||||||
repo_config.branches().next()
|
|
||||||
);
|
|
||||||
return Err(git::validation::Error::BranchHasNoCommits(
|
|
||||||
repo_config.branches().next(),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
let next_is_ancestor_of_dev = commit_histories.dev.iter().any(|dev| dev == &next);
|
|
||||||
if !next_is_ancestor_of_dev {
|
|
||||||
info!("Next is not an ancestor of dev - resetting next to main");
|
|
||||||
if let Err(err) = forge.branch_reset(
|
|
||||||
repository,
|
|
||||||
repo_config.branches().next(),
|
|
||||||
main.into(),
|
|
||||||
git::push::Force::From(next.clone().into()),
|
|
||||||
) {
|
|
||||||
warn!(?err, "Failed to reset next to main");
|
|
||||||
return Err(git::validation::Error::FailedToResetBranch {
|
|
||||||
branch: repo_config.branches().next(),
|
|
||||||
commit: next,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return Err(git::validation::Error::BranchReset(
|
|
||||||
repo_config.branches().next(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let next_commits = commit_histories
|
|
||||||
.next
|
|
||||||
.into_iter()
|
|
||||||
.take(2) // next should never be more than one commit ahead of main, so this should be
|
|
||||||
// either be next and main on two adjacent commits, or next and main on the same commit,
|
|
||||||
// plus the parent of main.
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
if !next_commits.contains(&main) {
|
|
||||||
warn!(
|
|
||||||
"Main branch '{}' is not on the same commit as next branch '{}', or it's parent - resetting next to main",
|
|
||||||
repo_config.branches().main(),
|
|
||||||
repo_config.branches().next()
|
|
||||||
);
|
|
||||||
if let Err(err) = forge.branch_reset(
|
|
||||||
repository,
|
|
||||||
repo_config.branches().next(),
|
|
||||||
main.into(),
|
|
||||||
git::push::Force::From(next.clone().into()),
|
|
||||||
) {
|
|
||||||
warn!(?err, "Failed to reset next to main");
|
|
||||||
return Err(git::validation::Error::FailedToResetBranch {
|
|
||||||
branch: repo_config.branches().next(),
|
|
||||||
commit: next,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return Err(git::validation::Error::BranchReset(
|
|
||||||
repo_config.branches().next(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let Some(next) = next_commits.first().cloned() else {
|
|
||||||
warn!(
|
|
||||||
"No commits on next branch '{}'",
|
|
||||||
repo_config.branches().next()
|
|
||||||
);
|
|
||||||
return Err(git::validation::Error::BranchHasNoCommits(
|
|
||||||
repo_config.branches().next(),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
let dev_has_next = commit_histories
|
|
||||||
.dev
|
|
||||||
.iter()
|
|
||||||
.any(|commit| commit == &next_commits[0]);
|
|
||||||
if !dev_has_next {
|
|
||||||
warn!(
|
|
||||||
"Dev branch '{}' is not based on next branch '{}' - next branch will be updated shortly",
|
|
||||||
repo_config.branches().dev(),
|
|
||||||
repo_config.branches().next()
|
|
||||||
);
|
|
||||||
return Err(git::validation::Error::DevBranchNotBasedOn(
|
|
||||||
repo_config.branches().next(),
|
|
||||||
)); // dev is not based on next
|
|
||||||
}
|
|
||||||
let Some(dev) = commit_histories.dev.first().cloned() else {
|
|
||||||
warn!(
|
|
||||||
"No commits on dev branch '{}'",
|
|
||||||
repo_config.branches().dev()
|
|
||||||
);
|
|
||||||
return Err(git::validation::Error::BranchHasNoCommits(
|
|
||||||
repo_config.branches().dev(),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
Ok(git::validation::Positions {
|
|
||||||
main,
|
|
||||||
next,
|
|
||||||
dev,
|
|
||||||
dev_commit_history: commit_histories.dev,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_commit_histories(
|
|
||||||
repository: &git::repository::OpenRepository,
|
|
||||||
repo_config: &config::RepoConfig,
|
|
||||||
) -> Result<git::commit::Histories, git::commit::log::Error> {
|
|
||||||
let main = (repository.commit_log(&repo_config.branches().main(), &[]))?;
|
|
||||||
let main_head = [main[0].clone()];
|
|
||||||
let next = repository.commit_log(&repo_config.branches().next(), &main_head)?;
|
|
||||||
let dev = repository.commit_log(&repo_config.branches().dev(), &main_head)?;
|
|
||||||
debug!(
|
|
||||||
main = main.len(),
|
|
||||||
next = next.len(),
|
|
||||||
dev = dev.len(),
|
|
||||||
"Commit histories"
|
|
||||||
);
|
|
||||||
let histories = git::commit::Histories { main, next, dev };
|
|
||||||
Ok(histories)
|
|
||||||
}
|
|
|
@ -43,25 +43,6 @@ 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();
|
||||||
|
|
|
@ -31,24 +31,6 @@ impl git::ForgeLike for MockForgeEnv {
|
||||||
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!()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,26 @@
|
||||||
|
//
|
||||||
|
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)?)
|
||||||
|
}
|
||||||
|
|
|
@ -36,6 +36,8 @@ 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),
|
||||||
|
|
|
@ -15,23 +15,6 @@ 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;
|
||||||
|
|
||||||
|
|
|
@ -26,3 +26,4 @@ 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;
|
pub use validate::validate;
|
||||||
|
pub use validation::validate_positions;
|
||||||
|
|
|
@ -17,7 +17,6 @@ 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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
//
|
||||||
use crate as git;
|
use crate as git;
|
||||||
use git_next_config::BranchName;
|
use git_next_config as config;
|
||||||
|
|
||||||
pub type Result = core::result::Result<Positions, Error>;
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
|
pub type Result<T> = core::result::Result<T, Error>;
|
||||||
|
|
||||||
pub struct Positions {
|
pub struct Positions {
|
||||||
pub main: git::Commit,
|
pub main: git::Commit,
|
||||||
|
@ -10,6 +13,162 @@ pub struct Positions {
|
||||||
pub dev_commit_history: Vec<git::Commit>,
|
pub dev_commit_history: Vec<git::Commit>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::cognitive_complexity)] // TODO: (#83) reduce complexity
|
||||||
|
pub fn validate_positions(
|
||||||
|
repository: &git::OpenRepository,
|
||||||
|
repo_details: &git::RepoDetails,
|
||||||
|
repo_config: config::RepoConfig,
|
||||||
|
) -> Result<Positions> {
|
||||||
|
// Collect Commit Histories for `main`, `next` and `dev` branches
|
||||||
|
repository.fetch()?;
|
||||||
|
let commit_histories = get_commit_histories(repository, &repo_config);
|
||||||
|
let commit_histories = match commit_histories {
|
||||||
|
Ok(commit_histories) => commit_histories,
|
||||||
|
Err(err) => {
|
||||||
|
error!(?err, "Failed to get commit histories");
|
||||||
|
return Err(git::validation::Error::CommitLog(err));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Validations
|
||||||
|
let Some(main) = commit_histories.main.first().cloned() else {
|
||||||
|
warn!(
|
||||||
|
"No commits on main branch '{}'",
|
||||||
|
repo_config.branches().main()
|
||||||
|
);
|
||||||
|
return Err(git::validation::Error::BranchHasNoCommits(
|
||||||
|
repo_config.branches().main(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
// Dev must be on main branch, or user must rebase it
|
||||||
|
let dev_has_main = commit_histories.dev.iter().any(|commit| commit == &main);
|
||||||
|
if !dev_has_main {
|
||||||
|
warn!(
|
||||||
|
"Dev branch '{}' is not based on main branch '{}' - user should rebase onto main branch '{}'",
|
||||||
|
repo_config.branches().dev(),
|
||||||
|
repo_config.branches().main(),
|
||||||
|
repo_config.branches().main(),
|
||||||
|
);
|
||||||
|
return Err(git::validation::Error::DevBranchNotBasedOn(
|
||||||
|
repo_config.branches().main(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
// verify that next is an ancestor of dev, and force update to it main if it isn't
|
||||||
|
let Some(next) = commit_histories.next.first().cloned() else {
|
||||||
|
warn!(
|
||||||
|
"No commits on next branch '{}",
|
||||||
|
repo_config.branches().next()
|
||||||
|
);
|
||||||
|
return Err(git::validation::Error::BranchHasNoCommits(
|
||||||
|
repo_config.branches().next(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
let next_is_ancestor_of_dev = commit_histories.dev.iter().any(|dev| dev == &next);
|
||||||
|
if !next_is_ancestor_of_dev {
|
||||||
|
info!("Next is not an ancestor of dev - resetting next to main");
|
||||||
|
if let Err(err) = git::branch::reset(
|
||||||
|
repository,
|
||||||
|
repo_details,
|
||||||
|
repo_config.branches().next(),
|
||||||
|
main.into(),
|
||||||
|
git::push::Force::From(next.clone().into()),
|
||||||
|
) {
|
||||||
|
warn!(?err, "Failed to reset next to main");
|
||||||
|
return Err(git::validation::Error::FailedToResetBranch {
|
||||||
|
branch: repo_config.branches().next(),
|
||||||
|
commit: next,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Err(git::validation::Error::BranchReset(
|
||||||
|
repo_config.branches().next(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let next_commits = commit_histories
|
||||||
|
.next
|
||||||
|
.into_iter()
|
||||||
|
.take(2) // next should never be more than one commit ahead of main, so this should be
|
||||||
|
// either be next and main on two adjacent commits, or next and main on the same commit,
|
||||||
|
// plus the parent of main.
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if !next_commits.contains(&main) {
|
||||||
|
warn!(
|
||||||
|
"Main branch '{}' is not on the same commit as next branch '{}', or it's parent - resetting next to main",
|
||||||
|
repo_config.branches().main(),
|
||||||
|
repo_config.branches().next()
|
||||||
|
);
|
||||||
|
if let Err(err) = git::branch::reset(
|
||||||
|
repository,
|
||||||
|
repo_details,
|
||||||
|
repo_config.branches().next(),
|
||||||
|
main.into(),
|
||||||
|
git::push::Force::From(next.clone().into()),
|
||||||
|
) {
|
||||||
|
warn!(?err, "Failed to reset next to main");
|
||||||
|
return Err(git::validation::Error::FailedToResetBranch {
|
||||||
|
branch: repo_config.branches().next(),
|
||||||
|
commit: next,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Err(git::validation::Error::BranchReset(
|
||||||
|
repo_config.branches().next(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let Some(next) = next_commits.first().cloned() else {
|
||||||
|
warn!(
|
||||||
|
"No commits on next branch '{}'",
|
||||||
|
repo_config.branches().next()
|
||||||
|
);
|
||||||
|
return Err(git::validation::Error::BranchHasNoCommits(
|
||||||
|
repo_config.branches().next(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
let dev_has_next = commit_histories
|
||||||
|
.dev
|
||||||
|
.iter()
|
||||||
|
.any(|commit| commit == &next_commits[0]);
|
||||||
|
if !dev_has_next {
|
||||||
|
warn!(
|
||||||
|
"Dev branch '{}' is not based on next branch '{}' - next branch will be updated shortly",
|
||||||
|
repo_config.branches().dev(),
|
||||||
|
repo_config.branches().next()
|
||||||
|
);
|
||||||
|
return Err(git::validation::Error::DevBranchNotBasedOn(
|
||||||
|
repo_config.branches().next(),
|
||||||
|
)); // dev is not based on next
|
||||||
|
}
|
||||||
|
let Some(dev) = commit_histories.dev.first().cloned() else {
|
||||||
|
warn!(
|
||||||
|
"No commits on dev branch '{}'",
|
||||||
|
repo_config.branches().dev()
|
||||||
|
);
|
||||||
|
return Err(git::validation::Error::BranchHasNoCommits(
|
||||||
|
repo_config.branches().dev(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
Ok(git::validation::Positions {
|
||||||
|
main,
|
||||||
|
next,
|
||||||
|
dev,
|
||||||
|
dev_commit_history: commit_histories.dev,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_commit_histories(
|
||||||
|
repository: &git::repository::OpenRepository,
|
||||||
|
repo_config: &config::RepoConfig,
|
||||||
|
) -> git::commit::log::Result<git::commit::Histories> {
|
||||||
|
let main = (repository.commit_log(&repo_config.branches().main(), &[]))?;
|
||||||
|
let main_head = [main[0].clone()];
|
||||||
|
let next = repository.commit_log(&repo_config.branches().next(), &main_head)?;
|
||||||
|
let dev = repository.commit_log(&repo_config.branches().dev(), &main_head)?;
|
||||||
|
debug!(
|
||||||
|
main = main.len(),
|
||||||
|
next = next.len(),
|
||||||
|
dev = dev.len(),
|
||||||
|
"Commit histories"
|
||||||
|
);
|
||||||
|
let histories = git::commit::Histories { main, next, dev };
|
||||||
|
Ok(histories)
|
||||||
|
}
|
||||||
#[derive(Debug, derive_more::Display)]
|
#[derive(Debug, derive_more::Display)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
Fetch(git::fetch::Error),
|
Fetch(git::fetch::Error),
|
||||||
|
@ -18,15 +177,15 @@ pub enum Error {
|
||||||
|
|
||||||
#[display("Failed to Reset Branch {branch} to {commit}")]
|
#[display("Failed to Reset Branch {branch} to {commit}")]
|
||||||
FailedToResetBranch {
|
FailedToResetBranch {
|
||||||
branch: BranchName,
|
branch: config::BranchName,
|
||||||
commit: git::Commit,
|
commit: git::Commit,
|
||||||
},
|
},
|
||||||
|
|
||||||
BranchReset(BranchName),
|
BranchReset(config::BranchName),
|
||||||
|
|
||||||
BranchHasNoCommits(BranchName),
|
BranchHasNoCommits(config::BranchName),
|
||||||
|
|
||||||
DevBranchNotBasedOn(BranchName),
|
DevBranchNotBasedOn(config::BranchName),
|
||||||
}
|
}
|
||||||
impl std::error::Error for Error {}
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
|
|
|
@ -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,8 +30,9 @@ pub async fn advance_next(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
info!("Advancing next to commit '{}'", commit);
|
info!("Advancing next to commit '{}'", commit);
|
||||||
if let Err(err) = forge.branch_reset(
|
if let Err(err) = git::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,
|
||||||
|
@ -78,13 +79,14 @@ 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) = forge.branch_reset(
|
if let Err(err) = git::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,
|
||||||
|
|
|
@ -19,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, details.forge.forge_name(), details.repo_alias)]
|
#[display("{}:{}:{}", generation, repo_details.forge.forge_name(), repo_details.repo_alias)]
|
||||||
pub struct RepoActor {
|
pub struct RepoActor {
|
||||||
generation: git::Generation,
|
generation: git::Generation,
|
||||||
message_token: MessageToken,
|
message_token: MessageToken,
|
||||||
details: git::RepoDetails,
|
repo_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
|
||||||
|
@ -53,7 +53,7 @@ impl RepoActor {
|
||||||
Self {
|
Self {
|
||||||
generation,
|
generation,
|
||||||
message_token: MessageToken::new(),
|
message_token: MessageToken::new(),
|
||||||
details,
|
repo_details: details,
|
||||||
webhook,
|
webhook,
|
||||||
webhook_id: None,
|
webhook_id: None,
|
||||||
webhook_auth: None,
|
webhook_auth: None,
|
||||||
|
@ -68,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.details))]
|
#[tracing::instrument(name = "RepoActor::stopping", skip_all, fields(repo = %self.repo_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.details.clone();
|
let repo_details = self.repo_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)
|
||||||
|
@ -92,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.details, gitdir = %self.details.gitdir))]
|
#[tracing::instrument(name = "RepoActor::CloneRepo", skip_all, fields(repo = %self.repo_details, gitdir = %self.repo_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.details.gitdir.clone();
|
let gitdir = self.repo_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.details.repo_config.is_none() {
|
if self.repo_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 {
|
||||||
|
@ -116,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.details))]
|
#[tracing::instrument(name = "RepoActor::LoadConfigFromRepo", skip_all, fields(repo = %self.repo_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.details.clone();
|
let details = self.repo_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)
|
||||||
|
@ -133,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.details, branches = %msg.0))]
|
#[tracing::instrument(name = "RepoActor::LoadedConfig", skip_all, fields(repo = %self.repo_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.details.repo_config.replace(repo_config);
|
self.repo_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,
|
||||||
|
@ -151,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.details, token = %msg.message_token))]
|
#[tracing::instrument(name = "RepoActor::ValidateRepo", skip_all, fields(repo = %self.repo_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 => {
|
||||||
|
@ -168,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.details.clone(),
|
self.repo_details.clone(),
|
||||||
self.webhook.clone(),
|
self.webhook.clone(),
|
||||||
ctx.address(),
|
ctx.address(),
|
||||||
self.net.clone(),
|
self.net.clone(),
|
||||||
|
@ -177,17 +177,15 @@ 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.details.repo_config.clone())
|
self.repository.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 forge
|
match git::validate_positions(&repository, &repo_details, repo_config) {
|
||||||
.branches_validate_positions(repository, repo_config)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(git::validation::Positions {
|
Ok(git::validation::Positions {
|
||||||
main,
|
main,
|
||||||
next,
|
next,
|
||||||
|
@ -221,10 +219,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.details, main = %msg.main, next= %msg.next, dev = %msg.dev))
|
fields(token = %self.message_token, repo = %self.repo_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.details.repo_config.clone() else {
|
let Some(repo_config) = self.repo_details.repo_config.clone() else {
|
||||||
warn!("No config loaded");
|
warn!("No config loaded");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -246,8 +244,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,
|
||||||
|
@ -265,7 +263,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.details, webhook_id = %msg.0))]
|
#[tracing::instrument(name = "RepoActor::WebhookRegistered", skip_all, fields(repo = %self.repo_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);
|
||||||
|
@ -277,9 +275,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.details, commit = %msg.0))]
|
#[tracing::instrument(name = "RepoActor::AdvanceMainTo", skip_all, fields(repo = %self.repo_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.details.repo_config.clone() else {
|
let Some(repo_config) = self.repo_details.repo_config.clone() else {
|
||||||
warn!("No config loaded");
|
warn!("No config loaded");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -287,11 +285,11 @@ impl Handler<AdvanceMainTo> for RepoActor {
|
||||||
warn!("No repository opened");
|
warn!("No repository opened");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
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 {
|
||||||
branch::advance_main(msg.0, &repo_config, &forge, &repository).await;
|
branch::advance_main(msg.0, &repo_details, &repo_config, &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 => {
|
||||||
|
|
|
@ -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.details))]
|
#[tracing::instrument(name = "RepoActor::WebhookMessage", skip_all, fields(token = %self.message_token, repo = %self.repo_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.details.repo_config {
|
if let Some(config) = &self.repo_details.repo_config {
|
||||||
match push.branch(config.branches()) {
|
match push.branch(config.branches()) {
|
||||||
None => warn!(
|
None => warn!(
|
||||||
?push,
|
?push,
|
||||||
|
|
Loading…
Reference in a new issue