git_branches/
render.rs

1use crate::branch::{BranchInfo, Node, Topology};
2
3/// Render the branch topology as ASCII art.
4#[must_use]
5pub fn render(topology: &Topology) -> String {
6    let mut lines = Vec::new();
7
8    // Render root node.
9    render_node(&topology.root, &mut lines, "", true);
10
11    lines.join("\n") + "\n"
12}
13
14fn render_node(node: &Node, lines: &mut Vec<String>, prefix: &str, is_root: bool) {
15    // Format this node.
16    let node_text = format_node(node);
17
18    if is_root {
19        lines.push(node_text);
20    } else {
21        // This is handled by the parent's render_children call.
22        unreachable!()
23    }
24
25    // Render children.
26    render_children(&node.children, lines, prefix);
27}
28
29fn render_children(children: &[Node], lines: &mut Vec<String>, prefix: &str) {
30    let count = children.len();
31    for (i, node) in children.iter().enumerate() {
32        let is_last = i == count - 1;
33        let connector = if is_last { "└─ " } else { "├─ " };
34        let child_prefix = if is_last { "   " } else { "│  " };
35
36        lines.push(format!("{prefix}{connector}{}", format_node(node)));
37
38        if !node.children.is_empty() {
39            render_children(&node.children, lines, &format!("{prefix}{child_prefix}"));
40        }
41    }
42}
43
44/// Format a branch name, including PR number if present.
45fn format_branch(info: &BranchInfo) -> String {
46    match info.pr {
47        Some(pr) => format!("{} #{pr}", info.name),
48        None => info.name.clone(),
49    }
50}
51
52/// Format a node for display.
53fn format_node(node: &Node) -> String {
54    if node.branches.is_empty() {
55        // Pure commit node: show hash in brackets.
56        format!("[{}]", node.commit)
57    } else if node.branches.len() == 1 {
58        // Single branch: show name and ahead count.
59        let info = &node.branches[0];
60        if info.ahead > 0 {
61            format!("{} (+{})", format_branch(info), info.ahead)
62        } else {
63            format_branch(info)
64        }
65    } else {
66        // Multiple branches at same commit: show all names, ahead count once at end.
67        let names: Vec<String> = node.branches.iter().map(format_branch).collect();
68        let ahead = node.branches[0].ahead;
69        if ahead > 0 {
70            format!("{} (+{})", names.join(" "), ahead)
71        } else {
72            names.join(" ")
73        }
74    }
75}