use crate::error::Result;
use crate::shell::{exec_async, exec_silent};
#[derive(Debug, Clone)]
pub struct RemoteBranchInfo {
pub name: String,
pub full_name: String,
pub last_commit_date: String,
pub last_committer_name: String,
pub last_commit_message: String,
}
#[derive(Debug, Clone)]
pub struct RemoteBranchStatus {
pub is_deleted: bool,
pub is_merged: bool,
pub merged_into_branch: Option<String>,
}
pub async fn fetch_and_prune() -> Result<()> {
exec_async("git", &["fetch", "--prune", "origin"], None).await?;
Ok(())
}
pub async fn get_remote_branches_with_info() -> Result<Vec<RemoteBranchInfo>> {
let output = exec_async(
"git",
&[
"for-each-ref",
"refs/remotes",
"--format=%(refname:short)|%(committerdate:iso8601-strict)|%(committername)|%(subject)",
],
None,
)
.await?;
let branches: Vec<RemoteBranchInfo> = output
.lines()
.filter(|line| !line.is_empty() && !line.contains("HEAD"))
.filter_map(|line| {
let parts: Vec<&str> = line.split('|').collect();
if parts.len() >= 4 {
let full_name = parts[0].to_string();
let name = full_name.strip_prefix("origin/")?;
Some(RemoteBranchInfo {
name: name.to_string(),
full_name,
last_commit_date: parts[1].to_string(),
last_committer_name: parts[2].to_string(),
last_commit_message: parts[3].to_string(),
})
} else {
None
}
})
.collect();
Ok(branches)
}
pub fn check_remote_branch_status(branch: &str, main_branches: &[String]) -> RemoteBranchStatus {
let branch_name = branch.strip_prefix("refs/heads/").unwrap_or(branch);
let ref_path = format!("refs/remotes/origin/{}", branch_name);
let is_deleted =
exec_silent("git", &["show-ref", "--verify", "--quiet", &ref_path], None).is_err();
let mut is_merged = false;
let mut merged_into_branch = None;
if !is_deleted {
for main_br in main_branches {
let result = exec_silent(
"git",
&[
"merge-base",
"--is-ancestor",
&format!("origin/{}", branch_name),
&format!("origin/{}", main_br),
],
None,
);
if result.is_ok() {
is_merged = true;
merged_into_branch = Some(main_br.clone());
break;
}
}
}
RemoteBranchStatus {
is_deleted,
is_merged,
merged_into_branch,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_remote_branch_status_default() {
let status = RemoteBranchStatus {
is_deleted: false,
is_merged: false,
merged_into_branch: None,
};
assert!(!status.is_deleted);
assert!(!status.is_merged);
assert!(status.merged_into_branch.is_none());
}
#[test]
fn test_remote_branch_info_construction() {
let info = RemoteBranchInfo {
name: "feature/test".to_string(),
full_name: "origin/feature/test".to_string(),
last_commit_date: "2024-01-15T10:30:00+00:00".to_string(),
last_committer_name: "John Doe".to_string(),
last_commit_message: "Add new feature".to_string(),
};
assert_eq!(info.name, "feature/test");
assert_eq!(info.full_name, "origin/feature/test");
}
#[test]
fn test_remote_branch_status_deleted() {
let status = RemoteBranchStatus {
is_deleted: true,
is_merged: false,
merged_into_branch: None,
};
assert!(status.is_deleted);
assert!(!status.is_merged);
assert!(status.merged_into_branch.is_none());
}
#[test]
fn test_remote_branch_status_merged() {
let status = RemoteBranchStatus {
is_deleted: false,
is_merged: true,
merged_into_branch: Some("main".to_string()),
};
assert!(!status.is_deleted);
assert!(status.is_merged);
assert_eq!(status.merged_into_branch, Some("main".to_string()));
}
#[test]
fn test_remote_branch_status_deleted_and_merged() {
let status = RemoteBranchStatus {
is_deleted: true,
is_merged: true,
merged_into_branch: Some("develop".to_string()),
};
assert!(status.is_deleted);
assert!(status.is_merged);
assert_eq!(status.merged_into_branch, Some("develop".to_string()));
}
#[test]
fn test_remote_branch_info_all_fields() {
let info = RemoteBranchInfo {
name: "feature/auth-system".to_string(),
full_name: "origin/feature/auth-system".to_string(),
last_commit_date: "2025-01-15T14:30:00+09:00".to_string(),
last_committer_name: "Alice Developer".to_string(),
last_commit_message: "Add authentication middleware".to_string(),
};
assert_eq!(info.name, "feature/auth-system");
assert_eq!(info.full_name, "origin/feature/auth-system");
assert_eq!(info.last_commit_date, "2025-01-15T14:30:00+09:00");
assert_eq!(info.last_committer_name, "Alice Developer");
assert_eq!(info.last_commit_message, "Add authentication middleware");
}
#[test]
fn test_remote_branch_info_name_without_origin_prefix() {
let info = RemoteBranchInfo {
name: "main".to_string(),
full_name: "main".to_string(), last_commit_date: "2025-01-01T00:00:00Z".to_string(),
last_committer_name: "Bot".to_string(),
last_commit_message: "Initial commit".to_string(),
};
assert_eq!(info.name, "main");
assert_eq!(info.full_name, "main");
}
}