git-next/src/server/actors/repo/branch.rs

251 lines
7.5 KiB
Rust
Raw Normal View History

2024-04-11 14:20:59 +01:00
use std::fmt::Display;
use actix::prelude::*;
use kxio::network;
use tracing::{error, info, warn};
2024-04-11 14:20:59 +01:00
use crate::server::{
config::{self, BranchName},
forge,
};
use super::{RepoActor, StartMonitoring, StartRepo};
2024-04-11 07:46:30 +01:00
#[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;
};
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
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>,
) {
info!("Advance Next");
let user = repo_details.forge.user;
let token = repo_details.forge.token;
let hostname = repo_details.forge.hostname;
let path = repo_details.repo;
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;
};
let next = repo_config.branches().next();
let command =
format!("/usr/bin/git push https://{user}:{token}@{hostname}/{path}.git {commit}:{next}");
info!("Running command: {}", command);
match gix::command::prepare(command)
.with_shell_allow_argument_splitting()
.spawn()
{
Ok(mut child) => {
match child.wait() {
Ok(exit_status) => {
info!(%exit_status, "Advance Next Success");
tokio::time::sleep(std::time::Duration::from_secs(10)).await;
addr.do_send(StartRepo);
}
Err(err) => {
warn!(?err, "Advance Next Failed (wait)")
}
};
}
Err(err) => {
warn!(?err, "Advance Next Failed (spawn)")
}
};
}
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'
#[allow(dead_code)]
pub async fn advance_main(
next: forge::Commit,
repo_details: config::RepoDetails,
repo_config: config::RepoConfig,
addr: Addr<RepoActor>,
) {
info!("Advance Main");
let user = repo_details.forge.user;
let token = repo_details.forge.token;
let hostname = repo_details.forge.hostname;
let path = repo_details.repo;
let main = repo_config.branches().main();
let command =
format!("/usr/bin/git push https://{user}:{token}@{hostname}/{path}.git {next}:{main}");
info!("Running command: {}", command);
match gix::command::prepare(command)
.with_shell_allow_argument_splitting()
.spawn()
{
Ok(mut child) => {
match child.wait() {
Ok(exit_status) => {
info!(%exit_status, "Advance Next Success");
addr.do_send(StartRepo);
}
Err(err) => {
warn!(?err, "Advance Next Failed (wait)")
}
};
}
Err(err) => {
warn!(?err, "Advance Next Failed (spawn)")
}
};
}
2024-04-11 14:20:59 +01:00
#[derive(Clone, Debug)]
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)
}
}
#[allow(dead_code)] // TODO: (#21) use to force-update next to main when it isn't a dev ancestor
pub fn reset(
branch: &BranchName,
gitref: impl Into<GitRef>,
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 command =
format!("/usr/bin/git push https://{user}:{token}@{hostname}/{path}.git {gitref}:{branch}");
// 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")));
}
}