use std::process::Command;
#[derive(Debug, Clone)]
pub struct BranchBehindConfig {
pub remote: String,
pub target_branch: String,
}
impl Default for BranchBehindConfig {
fn default() -> Self {
Self { remote: "origin".to_string(), target_branch: "main".to_string() }
}
}
pub fn check_behind(config: &BranchBehindConfig) -> bool {
let remote_branch = format!("{}/{}", config.remote, config.target_branch);
let fetch_output = Command::new("git").arg("fetch").arg(&config.remote).output();
if fetch_output.is_err() {
return false; }
let output = Command::new("git")
.arg("rev-list")
.arg("--count")
.arg(format!("{}..HEAD", remote_branch))
.output();
match output {
Ok(out) => {
if let Ok(count_str) = String::from_utf8(out.stdout) {
count_str.trim().parse::<u32>().unwrap_or(0) == 0
&& check_commits_behind(&remote_branch)
} else {
false
}
}
Err(_) => false,
}
}
fn check_commits_behind(remote_branch: &str) -> bool {
let output = Command::new("git")
.arg("rev-list")
.arg("--count")
.arg(format!("HEAD..{}", remote_branch))
.output();
match output {
Ok(out) => {
if let Ok(count_str) = String::from_utf8(out.stdout) {
count_str.trim().parse::<u32>().unwrap_or(0) > 0
} else {
false
}
}
Err(_) => false,
}
}
pub fn execute(config: &BranchBehindConfig, apply: bool) -> Result<(), String> {
if !check_behind(config) {
return Ok(()); }
let merge_ref = format!("{}/{}", config.remote, config.target_branch);
if apply {
crate::autonomic::subprocess::run_with_timeout(
"git",
&["merge", "--no-ff", "-m", &format!("merge {}", merge_ref), &merge_ref],
false,
)
.map(|_| ())
} else {
eprintln!(
"[BranchBehindPolicy] Would run: git merge --no-ff -m 'merge {}' {}",
merge_ref, merge_ref
);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_branch_behind_config_default() {
let config = BranchBehindConfig::default();
assert_eq!(config.remote, "origin");
assert_eq!(config.target_branch, "main");
}
#[test]
fn test_check_behind_no_git() {
let config = BranchBehindConfig::default();
let result = check_behind(&config);
let _ = result;
}
#[test]
fn test_execute_dry_run() {
let config = BranchBehindConfig::default();
let result = execute(&config, false);
assert!(result.is_ok());
}
}