diff --git a/src/cli/issues/list.rs b/src/cli/issues/list.rs index 006b123..579592e 100644 --- a/src/cli/issues/list.rs +++ b/src/cli/issues/list.rs @@ -299,12 +299,13 @@ pub async fn cmd_list( db::load_issues_with_filter(&conn, &link.forge_repo, &filter)? }; let comment_counts = db::count_comments_by_issue(&conn, &link.forge_repo)?; + let worktree_issues = db::get_worktree_issue_ids(&conn, &link.forge_repo)?; let elapsed = start.elapsed(); if json_output { println!("{}", serde_json::to_string_pretty(&issues)?); } else if tree { - super::print_issues_tree(&issues, &comment_counts, &child_progress); + super::print_issues_tree(&issues, &comment_counts, &child_progress, &worktree_issues); print_footer( issues.len(), elapsed.as_millis(), @@ -318,6 +319,7 @@ pub async fn cmd_list( &comment_counts, &child_progress, &issues_with_parent, + &worktree_issues, ); let show_tree_hint = has_hierarchy && effective_root_only; let show_flat_hint = has_hierarchy && effective_root_only; diff --git a/src/cli/issues/mod.rs b/src/cli/issues/mod.rs index 1cf2576..d307310 100644 --- a/src/cli/issues/mod.rs +++ b/src/cli/issues/mod.rs @@ -123,6 +123,7 @@ pub(crate) fn print_issues( comment_counts: &HashMap, child_progress: &HashMap, issues_with_parent: &HashSet, + worktree_issues: &HashSet, ) { if issues.is_empty() { println!("No open issues."); @@ -133,7 +134,8 @@ pub(crate) fn print_issues( let comment_count = comment_counts.get(&issue.id).copied(); let progress = child_progress.get(&issue.id).copied(); let has_parent = issues_with_parent.contains(&issue.id); - display::print_issue_row(issue, comment_count, progress, has_parent); + let has_worktree = worktree_issues.contains(&issue.id); + display::print_issue_row(issue, comment_count, progress, has_parent, has_worktree); } } @@ -142,6 +144,7 @@ pub(crate) fn print_issues_tree( issues: &[Issue], comment_counts: &HashMap, child_progress: &HashMap, + worktree_issues: &HashSet, ) { if issues.is_empty() { println!("No open issues."); @@ -169,7 +172,14 @@ pub(crate) fn print_issues_tree( .collect(); for root in roots { - print_issue_tree_node(root, &children_map, comment_counts, child_progress, 0); + print_issue_tree_node( + root, + &children_map, + comment_counts, + child_progress, + worktree_issues, + 0, + ); } } @@ -178,11 +188,13 @@ fn print_issue_tree_node( children_map: &HashMap, Vec<&Issue>>, comment_counts: &HashMap, child_progress: &HashMap, + worktree_issues: &HashSet, depth: usize, ) { let comment_count = comment_counts.get(&issue.id).copied(); let progress = child_progress.get(&issue.id).copied(); - display::print_issue_row_indented(issue, comment_count, progress, depth); + let has_worktree = worktree_issues.contains(&issue.id); + display::print_issue_row_indented(issue, comment_count, progress, depth, has_worktree); // Print children if let Some(children) = children_map.get(&Some(issue.id.clone())) { @@ -192,6 +204,7 @@ fn print_issue_tree_node( children_map, comment_counts, child_progress, + worktree_issues, depth + 1, ); } diff --git a/src/db/mod.rs b/src/db/mod.rs index ffb3831..0cc1fa2 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -41,8 +41,8 @@ pub use rate_limit::{ pub use repos::WatchedRepo; pub use repos::{ RepoLink, add_watched_repo, cleanup_stale_repos, clear_worktree_issues, get_repo_link, - get_worktree_issue, list_watched_repos, remove_repo_link, remove_watched_repo, set_repo_link, - set_worktree_issue, touch_repo, + get_worktree_issue, get_worktree_issue_ids, list_watched_repos, remove_repo_link, + remove_watched_repo, set_repo_link, set_worktree_issue, touch_repo, }; // From sync_state diff --git a/src/db/repos/mod.rs b/src/db/repos/mod.rs index 6a79b25..4588693 100644 --- a/src/db/repos/mod.rs +++ b/src/db/repos/mod.rs @@ -13,4 +13,6 @@ pub use watched::{ WatchedRepo, add_watched_repo, cleanup_stale_repos, list_watched_repos, remove_watched_repo, touch_repo, }; -pub use worktree::{clear_worktree_issues, get_worktree_issue, set_worktree_issue}; +pub use worktree::{ + clear_worktree_issues, get_worktree_issue, get_worktree_issue_ids, set_worktree_issue, +}; diff --git a/src/db/repos/worktree.rs b/src/db/repos/worktree.rs index 0d82bb3..43c805a 100644 --- a/src/db/repos/worktree.rs +++ b/src/db/repos/worktree.rs @@ -1,5 +1,7 @@ //! Worktree issues - associating issues with git worktrees +use std::collections::HashSet; + use anyhow::Result; use rusqlite::{Connection, OptionalExtension, params}; @@ -63,3 +65,12 @@ pub fn clear_worktree_issues(conn: &Connection, git_dir: &str) -> Result<()> { )?; Ok(()) } + +/// Get all issue IDs that have active worktrees for a given repo +pub fn get_worktree_issue_ids(conn: &Connection, repo: &str) -> Result> { + let mut stmt = conn.prepare("SELECT issue_id FROM worktree_issues WHERE repo = ?")?; + let ids = stmt + .query_map(params![repo], |row| row.get::<_, String>(0))? + .collect::, _>>()?; + Ok(ids) +} diff --git a/src/display/issues.rs b/src/display/issues.rs index 7986c4d..9a1a34e 100644 --- a/src/display/issues.rs +++ b/src/display/issues.rs @@ -264,8 +264,16 @@ pub fn print_issue_row( comment_count: Option, child_progress: Option, has_parent: bool, + has_worktree: bool, ) { - print_issue_row_impl(issue, comment_count, child_progress, has_parent, 0); + print_issue_row_impl( + issue, + comment_count, + child_progress, + has_parent, + 0, + has_worktree, + ); } /// Print a compact issue list row with indentation for tree display @@ -274,9 +282,17 @@ pub fn print_issue_row_indented( comment_count: Option, child_progress: Option, depth: usize, + has_worktree: bool, ) { // In tree view, don't show parent indicator (hierarchy is visual) - print_issue_row_impl(issue, comment_count, child_progress, false, depth); + print_issue_row_impl( + issue, + comment_count, + child_progress, + false, + depth, + has_worktree, + ); } /// Internal implementation for issue row printing @@ -286,6 +302,7 @@ fn print_issue_row_impl( child_progress: Option, has_parent: bool, depth: usize, + has_worktree: bool, ) { let tty = is_tty(); @@ -331,6 +348,9 @@ fn print_issue_row_impl( // Format issue ID: "#123" for GitHub, "DEV-123" for Linear/JIRA let issue_id = format_issue_id(&issue.id); + // Format worktree indicator + let worktree_str = if has_worktree { " ⎇" } else { "" }; + // Build indentation prefix for tree display let indent = if depth > 0 { format!("{}└─ ", " ".repeat(depth - 1)) @@ -340,12 +360,17 @@ fn print_issue_row_impl( if tty { println!( - "{}{} {} {:>10} {}{}{}{}{} {}{}", + "{}{} {} {:>10} {}{}{}{}{}{} {}{}", indent, state_char, priority_str, issue_id.dimmed(), issue.title, + if has_worktree { + worktree_str.magenta().to_string() + } else { + String::new() + }, parent_str.dimmed(), progress_str.cyan(), labels_str, @@ -355,12 +380,13 @@ fn print_issue_row_impl( ); } else { println!( - "{}{} {} {:<10} {}{}{}{}{} {}{}", + "{}{} {} {:<10} {}{}{}{}{}{} {}{}", indent, state_char, priority_str, issue_id, issue.title, + worktree_str, parent_str, progress_str, labels_str,