Skip to main content

wip_git/commands/
list.rs

1use crate::git;
2use crate::metadata::WipMetadata;
3use crate::ref_name;
4
5pub struct WipEntry {
6    pub name: String,
7    pub user: String,
8    pub sha: String,
9    pub metadata: WipMetadata,
10    pub timestamp: i64,
11}
12
13pub struct ListResult {
14    pub entries: Vec<WipEntry>,
15}
16
17pub fn run(all: bool, task_filter: Option<String>, remote: String) -> Result<ListResult, String> {
18    let user = ref_name::user()?;
19
20    let pattern = if all {
21        ref_name::list_pattern(None)
22    } else {
23        ref_name::list_pattern(Some(&user))
24    };
25
26    // List remote refs matching pattern
27    let output = git::git_stdout(&["ls-remote", &remote, &format!("{pattern}*")])?;
28
29    if output.is_empty() {
30        return Ok(ListResult {
31            entries: Vec::new(),
32        });
33    }
34
35    let mut entries: Vec<WipEntry> = Vec::new();
36
37    for line in output.lines() {
38        let parts: Vec<&str> = line.split_whitespace().collect();
39        if parts.len() < 2 {
40            continue;
41        }
42        let sha = parts[0].to_string();
43        let refpath = parts[1];
44
45        // Extract user/name from ref path: refs/wip/<user>/<name>
46        let suffix = refpath.strip_prefix("refs/wip/").unwrap_or(refpath);
47        let (entry_user, entry_name) = match suffix.split_once('/') {
48            Some((u, n)) => (u.to_string(), n.to_string()),
49            None => (suffix.to_string(), String::new()),
50        };
51
52        // Fetch to get metadata and timestamp
53        git::git(&["fetch", &remote, refpath])?;
54        let msg = git::git_stdout(&["log", "-1", "--format=%B", "FETCH_HEAD"])?;
55        let meta = WipMetadata::from_commit_message(&msg);
56
57        // Filter by task if requested
58        if let Some(ref task_id) = task_filter
59            && meta.task.as_deref() != Some(task_id)
60        {
61            continue;
62        }
63
64        let timestamp_str = git::git_stdout(&["log", "-1", "--format=%ct", "FETCH_HEAD"])?;
65        let timestamp: i64 = timestamp_str
66            .parse()
67            .map_err(|_| format!("bad timestamp for {refpath}"))?;
68
69        entries.push(WipEntry {
70            name: entry_name,
71            user: entry_user,
72            sha,
73            metadata: meta,
74            timestamp,
75        });
76    }
77
78    Ok(ListResult { entries })
79}
80
81pub fn relative_time(timestamp: i64) -> String {
82    let now = chrono::Utc::now().timestamp();
83    let age = now - timestamp;
84
85    if age < 60 {
86        "just now".into()
87    } else if age < 3600 {
88        format!("{}m ago", age / 60)
89    } else if age < 86400 {
90        format!("{}h ago", age / 3600)
91    } else if age < 86400 * 30 {
92        format!("{}d ago", age / 86400)
93    } else {
94        format!("{}w ago", age / (86400 * 7))
95    }
96}