feat: Force push next branch to main when it isn't a dev ancestor
All checks were successful
ci/woodpecker/push/push-next Pipeline was successful
ci/woodpecker/push/cron-docker-builder Pipeline was successful
ci/woodpecker/push/tag-created Pipeline was successful

Closes kemitix/git-next#21
This commit is contained in:
Paul Campbell 2024-04-11 15:43:02 +01:00
parent d2d49f353c
commit 115b353389
3 changed files with 83 additions and 26 deletions

View file

@ -6,6 +6,7 @@ use kxio::network;
use tracing::{error, info, warn}; use tracing::{error, info, warn};
use crate::server::{ use crate::server::{
actors::repo::branch,
config::{self, BranchName}, config::{self, BranchName},
forge, forge,
}; };
@ -31,11 +32,31 @@ pub async fn validate_positions(
return; return;
} }
}; };
let Some(main) = commit_histories.main.first().cloned() else { let Some(main) = commit_histories.main.first().cloned() else {
warn!("No commits on main branch '{}'", config.branches().main()); warn!("No commits on main branch '{}'", config.branches().main());
return; 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 let next_commits = commit_histories
.next .next
.into_iter() .into_iter()
@ -182,7 +203,7 @@ pub async fn advance_main(
}; };
} }
#[derive(Clone, Debug)] #[derive(Clone)]
pub struct GitRef(pub String); pub struct GitRef(pub String);
impl From<forge::Commit> for GitRef { impl From<forge::Commit> for GitRef {
fn from(value: forge::Commit) -> Self { fn from(value: forge::Commit) -> Self {
@ -196,13 +217,19 @@ impl From<BranchName> for GitRef {
} }
impl Display for GitRef { impl Display for GitRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 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( pub fn reset(
branch: &BranchName, branch: &BranchName,
gitref: impl Into<GitRef>, gitref: impl Into<GitRef>,
reset_force: ResetForce,
repo_details: &config::RepoDetails, repo_details: &config::RepoDetails,
) -> Result<(), std::io::Error> { ) -> Result<(), std::io::Error> {
let gitref: GitRef = gitref.into(); let gitref: GitRef = gitref.into();
@ -210,9 +237,13 @@ pub fn reset(
let token = &repo_details.forge.token; let token = &repo_details.forge.token;
let hostname = &repo_details.forge.hostname; let hostname = &repo_details.forge.hostname;
let path = &repo_details.repo; let path = &repo_details.repo;
let command = let origin = format!("https://{user}:{token}@{hostname}/{path}.git");
format!("/usr/bin/git push https://{user}:{token}@{hostname}/{path}.git {gitref}:{branch}"); let force = match reset_force {
// info!("Running command: {}", command); 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) match gix::command::prepare(command)
.with_shell_allow_argument_splitting() .with_shell_allow_argument_splitting()
.spawn() .spawn()

View file

@ -70,13 +70,25 @@ impl Handler<LoadedConfig> for RepoActor {
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 config = msg.0; let config = msg.0;
info!(%self.details, %config, "Config loaded"); info!(%self.details, %config, "Config loaded");
self.config.replace(config.clone()); self.config.replace(config);
let repo_details = self.details.clone(); ctx.address().do_send(ValidateRepo);
let addr = ctx.address(); }
let net = self.net.clone(); }
branch::validate_positions(repo_details, config, addr, net)
.into_actor(self) #[derive(Message)]
.wait(ctx); #[rtype(result = "()")]
pub struct ValidateRepo;
impl Handler<ValidateRepo> 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);
}
} }
} }

View file

@ -14,11 +14,23 @@ pub async fn get_commit_histories(
config: &server::config::RepoConfig, config: &server::config::RepoConfig,
net: &kxio::network::Network, net: &kxio::network::Network,
) -> Result<CommitHistories, OneOf<(network::NetworkError,)>> { ) -> Result<CommitHistories, OneOf<(network::NetworkError,)>> {
let main = (get_commit_history(repo_details, &config.branches().main(), None, net).await)?; let main = (get_commit_history(repo_details, &config.branches().main(), vec![], net).await)?;
let next = let main_head = main[0].clone();
(get_commit_history(repo_details, &config.branches().next(), Some(&main[0]), net).await)?; let next = (get_commit_history(
let dev = repo_details,
(get_commit_history(repo_details, &config.branches().dev(), Some(&next[0]), net).await)?; &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!( info!(
main = main.len(), main = main.len(),
next = next.len(), next = next.len(),
@ -33,16 +45,16 @@ pub async fn get_commit_histories(
async fn get_commit_history( async fn get_commit_history(
repo_details: &server::config::RepoDetails, repo_details: &server::config::RepoDetails,
branch_name: &BranchName, branch_name: &BranchName,
find_commit: Option<&forge::Commit>, find_commits: Vec<forge::Commit>,
net: &kxio::network::Network, net: &kxio::network::Network,
) -> Result<Vec<forge::Commit>, OneOf<(network::NetworkError,)>> { ) -> Result<Vec<forge::Commit>, OneOf<(network::NetworkError,)>> {
let hostname = &repo_details.forge.hostname; let hostname = &repo_details.forge.hostname;
let path = &repo_details.repo; let path = &repo_details.repo;
let token = &repo_details.forge.token; let token = &repo_details.forge.token;
let mut page = 1; let mut page = 1;
let limit = match find_commit { let limit = match find_commits.is_empty() {
Some(_) => 100, true => 1,
None => 1, false => 50,
}; };
let mut all_commits = Vec::new(); let mut all_commits = Vec::new();
loop { loop {
@ -73,9 +85,11 @@ async fn get_commit_history(
.map(|sha| forge::Commit { sha }) .map(|sha| forge::Commit { sha })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let found = find_commit.map_or(true, |find_commit| { let found = find_commits.is_empty()
commits.iter().any(|commit| commit == find_commit) || find_commits
}); .clone()
.into_iter()
.any(|find_commit| commits.iter().any(|commit| commit == &find_commit));
let at_end = commits.len() < limit; let at_end = commits.len() < limit;
all_commits.extend(commits); all_commits.extend(commits);
if found || at_end { if found || at_end {