@ -360,7 +360,8 @@ pub fn print_diff(old: &Ticket, new: &Ticket) {
/// e4a781 [todo] New feature (no deps)
/// e4a781 [todo] New feature (no deps)
/// ```
/// ```
///
///
/// Cycles are detected and labelled `[cycle]` rather than looping forever.
/// Nodes that appear in multiple subtrees are marked `*` on subsequent
/// occurrences rather than looping forever.
pub fn format_graph ( graph : & TicketGraph < ' _ > ) -> String {
pub fn format_graph ( graph : & TicketGraph < ' _ > ) -> String {
let mut out = String ::new ( ) ;
let mut out = String ::new ( ) ;
let mut visited : HashSet < String > = HashSet ::new ( ) ;
let mut visited : HashSet < String > = HashSet ::new ( ) ;
@ -408,8 +409,8 @@ pub fn print_subtree(graph: &TicketGraph<'_>, root_id: &str) {
/// becomes their `prefix`). It accumulates one more level of `│ ` or
/// becomes their `prefix`). It accumulates one more level of `│ ` or
/// ` ` with each descent.
/// ` ` with each descent.
///
///
/// When a node ID is already in `visited`, it is rendered as ` [cycle] ` and
/// When a node ID is already in `visited`, it is rendered as ` * ` and
/// the recursion stops, preventing infinite loops in cyclic data.
/// the recursion stops, preventing infinite loops in cyclic or shared data.
fn render_node (
fn render_node (
graph : & TicketGraph < ' _ > ,
graph : & TicketGraph < ' _ > ,
id : & str ,
id : & str ,
@ -419,9 +420,9 @@ fn render_node(
visited : & mut HashSet < String > ,
visited : & mut HashSet < String > ,
out : & mut String ,
out : & mut String ,
) {
) {
// Cycle detection .
// Already-visited detection: this node appears elsewhere in the tree .
if visited . contains ( id ) {
if visited . contains ( id ) {
append_line ( out , & format! ( "{prefix}{connector}{id} [cycle] ") ) ;
append_line ( out , & format! ( "{prefix}{connector}{id} * ") ) ;
return ;
return ;
}
}
visited . insert ( id . to_string ( ) ) ;
visited . insert ( id . to_string ( ) ) ;