From 115b353389365795afbc688c45578bf2bb04b4f2 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Thu, 11 Apr 2024 15:43:02 +0100 Subject: [PATCH] feat: Force push next branch to main when it isn't a dev ancestor Closes kemitix/git-next#21 --- src/server/actors/repo/branch.rs | 45 +++++++++++++++++++++++++++----- src/server/actors/repo/mod.rs | 26 +++++++++++++----- src/server/forge/forgejo/mod.rs | 38 ++++++++++++++++++--------- 3 files changed, 83 insertions(+), 26 deletions(-) diff --git a/src/server/actors/repo/branch.rs b/src/server/actors/repo/branch.rs index 179af93..5fb901b 100644 --- a/src/server/actors/repo/branch.rs +++ b/src/server/actors/repo/branch.rs @@ -6,6 +6,7 @@ use kxio::network; use tracing::{error, info, warn}; use crate::server::{ + actors::repo::branch, config::{self, BranchName}, forge, }; @@ -31,11 +32,31 @@ pub async fn validate_positions( 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() @@ -182,7 +203,7 @@ pub async fn advance_main( }; } -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct GitRef(pub String); impl From for GitRef { fn from(value: forge::Commit) -> Self { @@ -196,13 +217,19 @@ impl From for GitRef { } impl Display for GitRef { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) + write!(f, "{}", self.0) } } -#[allow(dead_code)] // TODO: (#21) use to force-update next to main when it isn't a dev ancestor + +pub enum ResetForce { + #[allow(dead_code)] + Normal, + Force(GitRef), +} pub fn reset( branch: &BranchName, gitref: impl Into, + reset_force: ResetForce, repo_details: &config::RepoDetails, ) -> Result<(), std::io::Error> { let gitref: GitRef = gitref.into(); @@ -210,9 +237,13 @@ pub fn reset( 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); + let origin = format!("https://{user}:{token}@{hostname}/{path}.git"); + let force = match reset_force { + ResetForce::Normal => "".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() diff --git a/src/server/actors/repo/mod.rs b/src/server/actors/repo/mod.rs index c9c3b06..b848201 100644 --- a/src/server/actors/repo/mod.rs +++ b/src/server/actors/repo/mod.rs @@ -70,13 +70,25 @@ impl Handler for RepoActor { fn handle(&mut self, msg: LoadedConfig, ctx: &mut Self::Context) -> Self::Result { let config = msg.0; info!(%self.details, %config, "Config loaded"); - self.config.replace(config.clone()); - let repo_details = self.details.clone(); - let addr = ctx.address(); - let net = self.net.clone(); - branch::validate_positions(repo_details, config, addr, net) - .into_actor(self) - .wait(ctx); + self.config.replace(config); + ctx.address().do_send(ValidateRepo); + } +} + +#[derive(Message)] +#[rtype(result = "()")] +pub struct ValidateRepo; +impl Handler for RepoActor { + type Result = (); + fn handle(&mut self, _msg: ValidateRepo, ctx: &mut Self::Context) -> Self::Result { + if let Some(repo_config) = self.config.clone() { + let repo_details = self.details.clone(); + let addr = ctx.address(); + let net = self.net.clone(); + branch::validate_positions(repo_details, repo_config, addr, net) + .into_actor(self) + .wait(ctx); + } } } diff --git a/src/server/forge/forgejo/mod.rs b/src/server/forge/forgejo/mod.rs index a5b45a4..add8eff 100644 --- a/src/server/forge/forgejo/mod.rs +++ b/src/server/forge/forgejo/mod.rs @@ -14,11 +14,23 @@ pub async fn get_commit_histories( config: &server::config::RepoConfig, net: &kxio::network::Network, ) -> Result> { - let main = (get_commit_history(repo_details, &config.branches().main(), None, net).await)?; - let next = - (get_commit_history(repo_details, &config.branches().next(), Some(&main[0]), net).await)?; - let dev = - (get_commit_history(repo_details, &config.branches().dev(), Some(&next[0]), net).await)?; + let main = (get_commit_history(repo_details, &config.branches().main(), vec![], net).await)?; + let main_head = main[0].clone(); + let next = (get_commit_history( + repo_details, + &config.branches().next(), + vec![main_head.clone()], + net, + ) + .await)?; + let next_head = next[0].clone(); + let dev = (get_commit_history( + repo_details, + &config.branches().dev(), + vec![next_head, main_head], + net, + ) + .await)?; info!( main = main.len(), next = next.len(), @@ -33,16 +45,16 @@ pub async fn get_commit_histories( async fn get_commit_history( repo_details: &server::config::RepoDetails, branch_name: &BranchName, - find_commit: Option<&forge::Commit>, + find_commits: Vec, net: &kxio::network::Network, ) -> Result, OneOf<(network::NetworkError,)>> { let hostname = &repo_details.forge.hostname; let path = &repo_details.repo; let token = &repo_details.forge.token; let mut page = 1; - let limit = match find_commit { - Some(_) => 100, - None => 1, + let limit = match find_commits.is_empty() { + true => 1, + false => 50, }; let mut all_commits = Vec::new(); loop { @@ -73,9 +85,11 @@ async fn get_commit_history( .map(|sha| forge::Commit { sha }) .collect::>(); - let found = find_commit.map_or(true, |find_commit| { - commits.iter().any(|commit| commit == find_commit) - }); + let found = find_commits.is_empty() + || find_commits + .clone() + .into_iter() + .any(|find_commit| commits.iter().any(|commit| commit == &find_commit)); let at_end = commits.len() < limit; all_commits.extend(commits); if found || at_end {