feat: Force push next branch to main when it isn't a dev ancestor
Closes kemitix/git-next#21
This commit is contained in:
parent
d2d49f353c
commit
115b353389
3 changed files with 83 additions and 26 deletions
|
@ -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()
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue