2024-04-09 22:43:23 +01:00
|
|
|
use actix::prelude::*;
|
|
|
|
|
2024-04-13 15:15:39 +01:00
|
|
|
use kxio::network::{self, Network};
|
2024-04-10 11:46:51 +01:00
|
|
|
use tracing::{error, info, warn};
|
2024-04-09 18:18:19 +01:00
|
|
|
|
2024-04-13 15:15:39 +01:00
|
|
|
use crate::server::{
|
|
|
|
actors::repo::ValidateRepo,
|
|
|
|
config::{self, RepoConfig, RepoDetails},
|
|
|
|
forge::{self, CommitHistories},
|
|
|
|
git::Git,
|
|
|
|
types::ResetForce,
|
|
|
|
};
|
2024-04-09 16:16:01 +01:00
|
|
|
|
2024-04-12 09:55:50 +01:00
|
|
|
use super::{RepoActor, StartMonitoring};
|
2024-04-09 18:18:19 +01:00
|
|
|
|
2024-04-11 07:46:30 +01:00
|
|
|
#[tracing::instrument(fields(forge_name = %repo_details.forge.name, repo_name = %repo_details.name), skip_all)]
|
2024-04-09 16:16:01 +01:00
|
|
|
pub async fn validate_positions(
|
2024-04-09 22:43:23 +01:00
|
|
|
repo_details: config::RepoDetails,
|
|
|
|
config: config::RepoConfig,
|
|
|
|
addr: Addr<RepoActor>,
|
2024-04-12 18:45:32 +01:00
|
|
|
git: Git,
|
2024-04-09 22:43:23 +01:00
|
|
|
net: network::Network,
|
2024-04-09 15:31:59 +01:00
|
|
|
) {
|
2024-04-13 15:15:39 +01:00
|
|
|
// Collect Commit Histories for `main`, `next` and `dev` branches
|
|
|
|
let commit_histories = get_commit_histories(&repo_details, &config, &net).await;
|
2024-04-09 18:18:19 +01:00
|
|
|
let commit_histories = match commit_histories {
|
|
|
|
Ok(commit_histories) => commit_histories,
|
|
|
|
Err(err) => {
|
|
|
|
error!(?err, "Failed to get commit histories");
|
2024-04-13 06:54:19 +01:00
|
|
|
revalidate_positions(addr).await;
|
2024-04-09 18:18:19 +01:00
|
|
|
return;
|
2024-04-09 16:16:01 +01:00
|
|
|
}
|
|
|
|
};
|
2024-04-13 15:15:39 +01:00
|
|
|
|
|
|
|
// Validations
|
2024-04-09 18:18:19 +01:00
|
|
|
let Some(main) = commit_histories.main.first().cloned() else {
|
|
|
|
warn!("No commits on main branch '{}'", config.branches().main());
|
2024-04-13 06:54:19 +01:00
|
|
|
revalidate_positions(addr).await;
|
2024-04-09 18:18:19 +01:00
|
|
|
return;
|
|
|
|
};
|
2024-04-11 15:43:02 +01:00
|
|
|
// 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 '{}", config.branches().next());
|
2024-04-13 06:54:19 +01:00
|
|
|
revalidate_positions(addr).await;
|
2024-04-11 15:43:02 +01:00
|
|
|
return;
|
|
|
|
};
|
|
|
|
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");
|
2024-04-13 15:26:45 +01:00
|
|
|
reset_next_to_main(next, main, &repo_details, &config, &git);
|
2024-04-13 06:54:19 +01:00
|
|
|
revalidate_positions(addr).await;
|
2024-04-11 15:43:02 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-04-09 18:18:19 +01:00
|
|
|
let next_commits = commit_histories
|
|
|
|
.next
|
|
|
|
.into_iter()
|
|
|
|
.take(2)
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
if !next_commits.contains(&main) {
|
|
|
|
warn!(
|
2024-04-13 15:26:45 +01:00
|
|
|
"Main branch '{}' is not on the same commit as next branch '{}', or it's parent - resetting next to main",
|
2024-04-09 18:18:19 +01:00
|
|
|
config.branches().main(),
|
|
|
|
config.branches().next()
|
|
|
|
);
|
2024-04-13 15:26:45 +01:00
|
|
|
reset_next_to_main(next, main, &repo_details, &config, &git);
|
2024-04-13 06:54:19 +01:00
|
|
|
revalidate_positions(addr).await;
|
2024-04-09 18:18:19 +01:00
|
|
|
return;
|
|
|
|
}
|
2024-04-10 18:02:04 +01:00
|
|
|
let Some(next) = next_commits.first().cloned() else {
|
|
|
|
warn!("No commits on next branch '{}'", config.branches().next());
|
2024-04-13 06:54:19 +01:00
|
|
|
revalidate_positions(addr).await;
|
2024-04-10 18:02:04 +01:00
|
|
|
return;
|
|
|
|
};
|
2024-04-09 18:18:19 +01:00
|
|
|
let dev_has_next = commit_histories
|
|
|
|
.dev
|
|
|
|
.iter()
|
|
|
|
.any(|commit| commit == &next_commits[0]);
|
|
|
|
if !dev_has_next {
|
|
|
|
warn!(
|
2024-04-13 15:26:45 +01:00
|
|
|
"Dev branch '{}' is not based on next branch '{}' - next branch will be updated shortly",
|
2024-04-09 18:18:19 +01:00
|
|
|
config.branches().dev(),
|
|
|
|
config.branches().next()
|
|
|
|
);
|
2024-04-13 06:54:19 +01:00
|
|
|
revalidate_positions(addr).await;
|
2024-04-09 18:18:19 +01:00
|
|
|
return; // dev is not based on next
|
|
|
|
}
|
|
|
|
let dev_has_main = commit_histories.dev.iter().any(|commit| commit == &main);
|
|
|
|
if !dev_has_main {
|
|
|
|
warn!(
|
2024-04-13 15:26:45 +01:00
|
|
|
"Dev branch '{}' is not based on main branch '{}' - user should rebase onto main branch '{}'",
|
2024-04-09 18:18:19 +01:00
|
|
|
config.branches().dev(),
|
2024-04-13 15:26:45 +01:00
|
|
|
config.branches().main(),
|
|
|
|
config.branches().main(),
|
2024-04-09 18:18:19 +01:00
|
|
|
);
|
2024-04-13 06:54:19 +01:00
|
|
|
revalidate_positions(addr).await;
|
2024-04-09 18:18:19 +01:00
|
|
|
return;
|
|
|
|
}
|
2024-04-10 18:02:04 +01:00
|
|
|
let Some(dev) = commit_histories.dev.first().cloned() else {
|
|
|
|
warn!("No commits on dev branch '{}'", config.branches().dev());
|
2024-04-13 06:54:19 +01:00
|
|
|
revalidate_positions(addr).await;
|
2024-04-10 18:02:04 +01:00
|
|
|
return;
|
|
|
|
};
|
2024-04-10 15:18:48 +01:00
|
|
|
addr.do_send(StartMonitoring {
|
|
|
|
main,
|
|
|
|
next,
|
|
|
|
dev,
|
|
|
|
dev_commit_history: commit_histories.dev,
|
|
|
|
});
|
2024-04-09 15:31:59 +01:00
|
|
|
}
|
2024-04-09 19:30:05 +01:00
|
|
|
|
2024-04-13 15:15:39 +01:00
|
|
|
async fn get_commit_histories(
|
|
|
|
repo_details: &RepoDetails,
|
|
|
|
config: &RepoConfig,
|
|
|
|
net: &Network,
|
|
|
|
) -> Result<CommitHistories, network::NetworkError> {
|
|
|
|
match repo_details.forge.forge_type {
|
|
|
|
#[cfg(feature = "forgejo")]
|
|
|
|
config::ForgeType::ForgeJo => {
|
|
|
|
forge::forgejo::get_commit_histories(repo_details, config, net).await
|
|
|
|
}
|
|
|
|
#[cfg(test)]
|
|
|
|
config::ForgeType::MockForge => {
|
|
|
|
forge::mock::get_commit_histories(repo_details, config, net).await
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-13 15:26:45 +01:00
|
|
|
fn reset_next_to_main(
|
|
|
|
next: forge::Commit,
|
|
|
|
main: forge::Commit,
|
|
|
|
repo_details: &RepoDetails,
|
|
|
|
repo_config: &RepoConfig,
|
|
|
|
git: &Git,
|
|
|
|
) {
|
|
|
|
let reset = git.reset(
|
|
|
|
&repo_config.branches().next(),
|
|
|
|
main.into(),
|
|
|
|
ResetForce::Force(next.into()),
|
|
|
|
repo_details,
|
|
|
|
);
|
|
|
|
if let Err(err) = reset {
|
|
|
|
warn!(?err, "Failed");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-13 06:54:19 +01:00
|
|
|
async fn revalidate_positions(addr: Addr<RepoActor>) {
|
2024-04-13 16:16:09 +01:00
|
|
|
// TODO : (#43) sleep and restart while we don't have webhooks
|
2024-04-13 06:54:19 +01:00
|
|
|
tokio::time::sleep(std::time::Duration::from_secs(10)).await;
|
|
|
|
addr.do_send(ValidateRepo)
|
|
|
|
}
|
|
|
|
|
2024-04-09 22:43:23 +01:00
|
|
|
// advance next to the next commit towards the head of the dev branch
|
2024-04-11 17:58:30 +01:00
|
|
|
#[tracing::instrument(fields(next), skip_all)]
|
2024-04-09 19:30:05 +01:00
|
|
|
pub async fn advance_next(
|
2024-04-10 15:18:48 +01:00
|
|
|
next: forge::Commit,
|
|
|
|
dev_commit_history: Vec<forge::Commit>,
|
|
|
|
repo_details: config::RepoDetails,
|
|
|
|
repo_config: config::RepoConfig,
|
2024-04-09 22:43:23 +01:00
|
|
|
addr: Addr<RepoActor>,
|
2024-04-12 18:45:32 +01:00
|
|
|
git: Git,
|
2024-04-09 19:30:05 +01:00
|
|
|
) {
|
2024-04-10 15:18:48 +01:00
|
|
|
let next_commit = find_next_commit_on_dev(next, dev_commit_history);
|
|
|
|
let Some(commit) = next_commit else {
|
|
|
|
warn!("No commits to advance next to");
|
2024-04-13 16:22:49 +01:00
|
|
|
revalidate_positions(addr).await;
|
2024-04-10 15:18:48 +01:00
|
|
|
return;
|
|
|
|
};
|
2024-04-12 16:21:45 +01:00
|
|
|
if let Some(problem) = validate_commit_message(commit.message()) {
|
|
|
|
warn!("Can't advance next to commit '{}': {}", commit, problem);
|
2024-04-13 16:22:49 +01:00
|
|
|
revalidate_positions(addr).await;
|
2024-04-12 16:21:45 +01:00
|
|
|
return;
|
|
|
|
}
|
2024-04-12 17:41:09 +01:00
|
|
|
info!("Advancing next to commit '{}'", commit);
|
2024-04-13 16:22:49 +01:00
|
|
|
if let Err(err) = git.reset(
|
2024-04-11 17:58:30 +01:00
|
|
|
&repo_config.branches().next(),
|
2024-04-12 18:45:32 +01:00
|
|
|
commit.into(),
|
2024-04-11 18:09:57 +01:00
|
|
|
ResetForce::None,
|
2024-04-11 17:58:30 +01:00
|
|
|
&repo_details,
|
|
|
|
) {
|
2024-04-13 16:22:49 +01:00
|
|
|
warn!(?err, "Failed")
|
2024-04-10 15:18:48 +01:00
|
|
|
};
|
2024-04-13 16:22:49 +01:00
|
|
|
revalidate_positions(addr).await;
|
2024-04-10 15:18:48 +01:00
|
|
|
}
|
|
|
|
|
2024-04-12 16:21:45 +01:00
|
|
|
#[tracing::instrument]
|
|
|
|
fn validate_commit_message(message: &forge::Message) -> Option<String> {
|
|
|
|
let message = &message.to_string();
|
|
|
|
if message.to_ascii_lowercase().starts_with("wip") {
|
|
|
|
return Some("Is Work-In-Progress".to_string());
|
|
|
|
}
|
|
|
|
match git_conventional::Commit::parse(message) {
|
|
|
|
Ok(commit) => {
|
|
|
|
info!(?commit, "Pass");
|
|
|
|
None
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
warn!(?err, "Fail");
|
|
|
|
Some(err.kind().to_string())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-10 15:18:48 +01:00
|
|
|
fn find_next_commit_on_dev(
|
|
|
|
next: forge::Commit,
|
|
|
|
dev_commit_history: Vec<forge::Commit>,
|
|
|
|
) -> Option<forge::Commit> {
|
|
|
|
let mut next_commit: Option<forge::Commit> = None;
|
|
|
|
for commit in dev_commit_history.into_iter() {
|
|
|
|
if commit == next {
|
|
|
|
break;
|
|
|
|
};
|
|
|
|
next_commit.replace(commit);
|
|
|
|
}
|
|
|
|
next_commit
|
2024-04-09 22:43:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// advance main branch to the commit 'next'
|
2024-04-11 17:58:30 +01:00
|
|
|
#[tracing::instrument(fields(next), skip_all)]
|
2024-04-09 22:43:23 +01:00
|
|
|
pub async fn advance_main(
|
2024-04-10 17:36:08 +01:00
|
|
|
next: forge::Commit,
|
|
|
|
repo_details: config::RepoDetails,
|
|
|
|
repo_config: config::RepoConfig,
|
2024-04-12 18:45:32 +01:00
|
|
|
git: Git,
|
2024-04-09 22:43:23 +01:00
|
|
|
) {
|
2024-04-12 17:41:09 +01:00
|
|
|
info!("Advancing main to next");
|
2024-04-12 19:43:21 +01:00
|
|
|
if let Err(err) = git.reset(
|
2024-04-11 17:45:11 +01:00
|
|
|
&repo_config.branches().main(),
|
2024-04-12 18:45:32 +01:00
|
|
|
next.into(),
|
2024-04-11 18:09:57 +01:00
|
|
|
ResetForce::None,
|
2024-04-11 17:45:11 +01:00
|
|
|
&repo_details,
|
2024-04-11 17:45:11 +01:00
|
|
|
) {
|
2024-04-12 19:43:21 +01:00
|
|
|
warn!(?err, "Failed")
|
2024-04-10 17:36:08 +01:00
|
|
|
};
|
2024-04-09 19:30:05 +01:00
|
|
|
}
|
2024-04-10 15:18:48 +01:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_find_next_commit_on_dev() {
|
2024-04-12 16:21:45 +01:00
|
|
|
let next = forge::Commit::new("current-next", "foo");
|
|
|
|
let expected = forge::Commit::new("dev-next", "next-should-go-here");
|
2024-04-10 15:18:48 +01:00
|
|
|
let dev_commit_history = vec![
|
2024-04-12 16:21:45 +01:00
|
|
|
forge::Commit::new("dev", "future"),
|
|
|
|
expected.clone(),
|
|
|
|
next.clone(),
|
|
|
|
forge::Commit::new("current-main", "history"),
|
2024-04-10 15:18:48 +01:00
|
|
|
];
|
|
|
|
let next_commit = find_next_commit_on_dev(next, dev_commit_history);
|
2024-04-12 16:21:45 +01:00
|
|
|
assert_eq!(next_commit, Some(expected));
|
2024-04-10 15:18:48 +01:00
|
|
|
}
|
|
|
|
}
|