Skip to main content

git_workty/commands/
pick.rs

1use crate::git::GitRepo;
2use crate::status::get_all_statuses;
3use crate::ui::{print_error, UiOptions};
4use crate::worktree::list_worktrees;
5use anyhow::Result;
6use console::Term;
7use dialoguer::FuzzySelect;
8use is_terminal::IsTerminal;
9
10pub fn execute(repo: &GitRepo, _opts: &UiOptions) -> Result<()> {
11    if !std::io::stdin().is_terminal() {
12        print_error(
13            "Cannot run interactive picker in non-TTY environment",
14            Some("Use `git workty go <name>` for non-interactive selection."),
15        );
16        std::process::exit(1);
17    }
18
19    let worktrees = list_worktrees(repo)?;
20    if worktrees.is_empty() {
21        print_error("No worktrees found", None);
22        std::process::exit(1);
23    }
24
25    // Get status for richer display
26    let statuses = get_all_statuses(repo, &worktrees);
27
28    // Find max name length for alignment
29    let max_name_len = statuses
30        .iter()
31        .map(|(wt, _)| wt.name().len())
32        .max()
33        .unwrap_or(10);
34
35    let items: Vec<String> = statuses
36        .iter()
37        .map(|(wt, status)| {
38            let name = format!("{:width$}", wt.name(), width = max_name_len);
39
40            // Dirty indicator
41            let dirty = if status.dirty_count > 0 {
42                format!("*{}", status.dirty_count)
43            } else {
44                " ".to_string()
45            };
46
47            // Time since last commit
48            let time = format_time_short(status.last_commit_time);
49
50            // Needs rebase indicator
51            let rebase = if status.needs_rebase() {
52                format!("R{}", status.behind_main.unwrap_or(0))
53            } else {
54                "  ".to_string()
55            };
56
57            format!("{}  {:>3}  {:>4}  {}", name, dirty, time, rebase)
58        })
59        .collect();
60
61    let selection = FuzzySelect::new()
62        .with_prompt("Select worktree")
63        .items(&items)
64        .default(0)
65        .interact_on_opt(&Term::stderr())?;
66
67    match selection {
68        Some(idx) => {
69            println!("{}", statuses[idx].0.path.display());
70            Ok(())
71        }
72        None => {
73            std::process::exit(130);
74        }
75    }
76}
77
78fn format_time_short(seconds: Option<i64>) -> String {
79    match seconds {
80        Some(s) if s < 60 => "now".to_string(),
81        Some(s) if s < 3600 => format!("{}m", s / 60),
82        Some(s) if s < 86400 => format!("{}h", s / 3600),
83        Some(s) if s < 604800 => format!("{}d", s / 86400),
84        Some(s) => format!("{}w", s / 604800),
85        None => "-".to_string(),
86    }
87}