252 lines
7.1 KiB
Rust
252 lines
7.1 KiB
Rust
use std::fmt::Display;
|
|
|
|
use actix::prelude::*;
|
|
|
|
use kxio::network;
|
|
use tracing::{error, info, warn};
|
|
|
|
use crate::server::{
|
|
actors::repo::branch,
|
|
config::{self, BranchName},
|
|
forge,
|
|
};
|
|
|
|
use super::{RepoActor, StartMonitoring, StartRepo};
|
|
|
|
#[tracing::instrument(fields(forge_name = %repo_details.forge.name, repo_name = %repo_details.name), skip_all)]
|
|
pub async fn validate_positions(
|
|
repo_details: config::RepoDetails,
|
|
config: config::RepoConfig,
|
|
addr: Addr<RepoActor>,
|
|
net: network::Network,
|
|
) {
|
|
let commit_histories = match repo_details.forge.forge_type {
|
|
config::ForgeType::ForgeJo => {
|
|
forge::forgejo::get_commit_histories(&repo_details, &config, &net).await
|
|
}
|
|
};
|
|
let commit_histories = match commit_histories {
|
|
Ok(commit_histories) => commit_histories,
|
|
Err(err) => {
|
|
error!(?err, "Failed to get commit histories");
|
|
return;
|
|
}
|
|
};
|
|
let Some(main) = commit_histories.main.first().cloned() else {
|
|
warn!("No commits on main branch '{}'", config.branches().main());
|
|
return;
|
|
};
|
|
// 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());
|
|
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");
|
|
if branch::reset(
|
|
&config.branches().next(),
|
|
main,
|
|
ResetForce::Force(next.into()),
|
|
&repo_details,
|
|
)
|
|
.is_ok()
|
|
{
|
|
addr.do_send(super::ValidateRepo)
|
|
}
|
|
return;
|
|
}
|
|
|
|
let next_commits = commit_histories
|
|
.next
|
|
.into_iter()
|
|
.take(2)
|
|
.collect::<Vec<_>>();
|
|
if !next_commits.contains(&main) {
|
|
warn!(
|
|
"Main branch '{}' is not on the same commit as next branch '{}', or it's parent",
|
|
config.branches().main(),
|
|
config.branches().next()
|
|
);
|
|
return;
|
|
}
|
|
let Some(next) = next_commits.first().cloned() else {
|
|
warn!("No commits on next branch '{}'", config.branches().next());
|
|
return;
|
|
};
|
|
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 '{}'",
|
|
config.branches().dev(),
|
|
config.branches().next()
|
|
);
|
|
return; // dev is not based on next
|
|
}
|
|
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 '{}'",
|
|
config.branches().dev(),
|
|
config.branches().main()
|
|
);
|
|
return;
|
|
}
|
|
let Some(dev) = commit_histories.dev.first().cloned() else {
|
|
warn!("No commits on dev branch '{}'", config.branches().dev());
|
|
return;
|
|
};
|
|
addr.do_send(StartMonitoring {
|
|
main,
|
|
next,
|
|
dev,
|
|
dev_commit_history: commit_histories.dev,
|
|
});
|
|
}
|
|
|
|
// advance next to the next commit towards the head of the dev branch
|
|
#[tracing::instrument(fields(next), skip_all)]
|
|
pub async fn advance_next(
|
|
next: forge::Commit,
|
|
dev_commit_history: Vec<forge::Commit>,
|
|
repo_details: config::RepoDetails,
|
|
repo_config: config::RepoConfig,
|
|
addr: Addr<RepoActor>,
|
|
) {
|
|
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");
|
|
return;
|
|
};
|
|
match reset(
|
|
&repo_config.branches().next(),
|
|
commit,
|
|
ResetForce::None,
|
|
&repo_details,
|
|
) {
|
|
Ok(_) => {
|
|
info!("Success");
|
|
addr.do_send(StartRepo);
|
|
}
|
|
Err(err) => {
|
|
warn!(?err, "Failed")
|
|
}
|
|
};
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// advance main branch to the commit 'next'
|
|
#[tracing::instrument(fields(next), skip_all)]
|
|
pub async fn advance_main(
|
|
next: forge::Commit,
|
|
repo_details: config::RepoDetails,
|
|
repo_config: config::RepoConfig,
|
|
addr: Addr<RepoActor>,
|
|
) {
|
|
match reset(
|
|
&repo_config.branches().main(),
|
|
next,
|
|
ResetForce::None,
|
|
&repo_details,
|
|
) {
|
|
Ok(_) => {
|
|
info!("Success");
|
|
addr.do_send(StartRepo);
|
|
}
|
|
Err(err) => {
|
|
warn!(?err, "Failed")
|
|
}
|
|
};
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct GitRef(pub String);
|
|
impl From<forge::Commit> for GitRef {
|
|
fn from(value: forge::Commit) -> Self {
|
|
Self(value.sha)
|
|
}
|
|
}
|
|
impl From<BranchName> for GitRef {
|
|
fn from(value: BranchName) -> Self {
|
|
Self(value.0)
|
|
}
|
|
}
|
|
impl Display for GitRef {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{}", self.0)
|
|
}
|
|
}
|
|
|
|
pub enum ResetForce {
|
|
None,
|
|
Force(GitRef),
|
|
}
|
|
pub fn reset(
|
|
branch: &BranchName,
|
|
gitref: impl Into<GitRef>,
|
|
reset_force: ResetForce,
|
|
repo_details: &config::RepoDetails,
|
|
) -> Result<(), std::io::Error> {
|
|
let gitref: GitRef = gitref.into();
|
|
let user = &repo_details.forge.user;
|
|
let token = &repo_details.forge.token;
|
|
let hostname = &repo_details.forge.hostname;
|
|
let path = &repo_details.repo;
|
|
let origin = format!("https://{user}:{token}@{hostname}/{path}.git");
|
|
let force = match reset_force {
|
|
ResetForce::None => "".to_string(),
|
|
ResetForce::Force(old_ref) => format!("--force-with-lease={branch}:{old_ref}"),
|
|
};
|
|
let command = format!("/usr/bin/git push {origin} {gitref}:{branch} {force}");
|
|
info!("Running command: {}", command);
|
|
match gix::command::prepare(command)
|
|
.with_shell_allow_argument_splitting()
|
|
.spawn()
|
|
{
|
|
Ok(mut child) => match child.wait() {
|
|
Ok(_) => Ok(()),
|
|
Err(err) => {
|
|
warn!(?err, "Advance Next Failed (wait)");
|
|
Err(err)
|
|
}
|
|
},
|
|
Err(err) => {
|
|
warn!(?err, "Advance Next Failed (spawn)");
|
|
Err(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[actix_rt::test]
|
|
async fn test_find_next_commit_on_dev() {
|
|
let next = forge::Commit::new("current-next");
|
|
let dev_commit_history = vec![
|
|
forge::Commit::new("dev"),
|
|
forge::Commit::new("dev-next"),
|
|
forge::Commit::new("current-next"),
|
|
forge::Commit::new("current-main"),
|
|
];
|
|
let next_commit = find_next_commit_on_dev(next, dev_commit_history);
|
|
assert_eq!(next_commit, Some(forge::Commit::new("dev-next")));
|
|
}
|
|
}
|