Skip to main content

autom8/gh/
branch.rs

1//! Branch context for PR reviews.
2
3use std::path::PathBuf;
4
5use crate::error::Result;
6use crate::git::{self, CommitInfo};
7use crate::output::{CYAN, GRAY, GREEN, RESET, YELLOW};
8use crate::spec::Spec;
9use crate::state::StateManager;
10
11/// Context about the current branch for PR reviews
12#[derive(Debug, Clone)]
13pub struct BranchContext {
14    /// The branch name
15    pub branch_name: String,
16    /// The spec file if available
17    pub spec: Option<Spec>,
18    /// Path to the spec file if found
19    pub spec_path: Option<PathBuf>,
20    /// Recent commits on this branch
21    pub commits: Vec<CommitInfo>,
22}
23
24/// Result of gathering branch context
25#[derive(Debug, Clone)]
26pub enum BranchContextResult {
27    /// Successfully gathered context with spec
28    SuccessWithSpec(BranchContext),
29    /// Successfully gathered context but no spec found
30    SuccessNoSpec(BranchContext),
31    /// Error occurred during gathering
32    Error(String),
33}
34
35/// Gather context about the current branch for PR review
36pub fn gather_branch_context(show_warning: bool) -> BranchContextResult {
37    let branch_name = match git::current_branch() {
38        Ok(b) => b,
39        Err(e) => {
40            return BranchContextResult::Error(format!("Failed to get current branch: {}", e))
41        }
42    };
43
44    // Try to find spec for this branch
45    let (spec, spec_path) = match find_spec_for_branch(&branch_name) {
46        Ok(Some((s, p))) => (Some(s), Some(p)),
47        Ok(None) => {
48            if show_warning {
49                println!(
50                    "{YELLOW}Warning: No spec file found for branch '{}'{RESET}",
51                    branch_name
52                );
53                println!("{GRAY}PR review will proceed with reduced context.{RESET}");
54                println!();
55            }
56            (None, None)
57        }
58        Err(e) => {
59            if show_warning {
60                println!("{YELLOW}Warning: Failed to load spec: {}{RESET}", e);
61            }
62            (None, None)
63        }
64    };
65
66    // Get recent commits on this branch
67    let commits = git::get_branch_commits(&branch_name).unwrap_or_default();
68
69    let context = BranchContext {
70        branch_name,
71        spec,
72        spec_path,
73        commits,
74    };
75
76    if context.spec.is_some() {
77        BranchContextResult::SuccessWithSpec(context)
78    } else {
79        BranchContextResult::SuccessNoSpec(context)
80    }
81}
82
83/// Find the spec file for a given branch
84pub fn find_spec_for_branch(branch: &str) -> Result<Option<(Spec, PathBuf)>> {
85    let state_manager = StateManager::new()?;
86    let specs = state_manager.list_specs()?;
87
88    // First, try to find a spec that matches the branch name
89    for spec_path in &specs {
90        if let Ok(spec) = Spec::load(spec_path) {
91            if spec.branch_name == branch {
92                return Ok(Some((spec, spec_path.clone())));
93            }
94        }
95    }
96
97    // If no match found, try to infer from branch name
98    // e.g., branch "feature/add-auth" might have spec "spec-add-auth.json"
99    let branch_suffix = branch
100        .strip_prefix("feature/")
101        .or_else(|| branch.strip_prefix("feat/"))
102        .unwrap_or(branch);
103
104    for spec_path in &specs {
105        let filename = spec_path.file_stem().and_then(|s| s.to_str()).unwrap_or("");
106
107        // Check if spec filename matches branch suffix
108        if filename.ends_with(branch_suffix)
109            || filename.replace("spec-", "").contains(branch_suffix)
110        {
111            if let Ok(spec) = Spec::load(spec_path) {
112                return Ok(Some((spec, spec_path.clone())));
113            }
114        }
115    }
116
117    Ok(None)
118}
119
120/// Print a summary of the branch context
121pub fn print_branch_context(context: &BranchContext) {
122    println!("{CYAN}Branch:{RESET} {}", context.branch_name);
123
124    if context.spec.is_some() {
125        println!("{GREEN}  ✓ Spec file loaded{RESET}");
126    } else {
127        println!("{YELLOW}  ⚠ No spec file (reduced context){RESET}");
128    }
129
130    if !context.commits.is_empty() {
131        println!(
132            "{GRAY}  {} commit{} on branch{RESET}",
133            context.commits.len(),
134            if context.commits.len() == 1 { "" } else { "s" }
135        );
136    }
137    println!();
138}