Skip to main content

wip_git/commands/
gc.rs

1use crate::git;
2use crate::ref_name;
3
4pub struct ExpiredEntry {
5    pub name: String,
6    pub wip_ref: String,
7    pub age_days: i64,
8}
9
10pub struct GcResult {
11    pub entries: Vec<ExpiredEntry>,
12    pub dry_run: bool,
13}
14
15pub fn run(expire: String, dry_run: bool, remote: String) -> Result<GcResult, String> {
16    let user = ref_name::user()?;
17    let pattern = ref_name::list_pattern(Some(&user));
18    let max_age_secs = parse_duration(&expire)?;
19
20    // List all our WIP refs
21    let output = git::git_stdout(&["ls-remote", &remote, &format!("{pattern}*")])?;
22
23    if output.is_empty() {
24        return Ok(GcResult {
25            entries: Vec::new(),
26            dry_run,
27        });
28    }
29
30    let now = chrono::Utc::now().timestamp();
31    let mut entries = Vec::new();
32
33    for line in output.lines() {
34        let parts: Vec<&str> = line.split_whitespace().collect();
35        if parts.len() < 2 {
36            continue;
37        }
38        let refpath = parts[1];
39
40        // Fetch to inspect commit timestamp
41        git::git(&["fetch", &remote, refpath])?;
42        let timestamp_str = git::git_stdout(&["log", "-1", "--format=%ct", "FETCH_HEAD"])?;
43        let timestamp: i64 = timestamp_str
44            .parse()
45            .map_err(|_| format!("bad timestamp for {refpath}"))?;
46
47        let age = now - timestamp;
48        if age > max_age_secs {
49            let display = refpath.strip_prefix("refs/wip/").unwrap_or(refpath);
50            let days = age / 86400;
51
52            if !dry_run {
53                let delete_refspec = format!(":{refpath}");
54                git::git(&["push", &remote, &delete_refspec])?;
55            }
56
57            entries.push(ExpiredEntry {
58                name: display.to_string(),
59                wip_ref: refpath.to_string(),
60                age_days: days,
61            });
62        }
63    }
64
65    Ok(GcResult { entries, dry_run })
66}
67
68pub fn parse_duration(s: &str) -> Result<i64, String> {
69    let s = s.trim();
70    if let Some(days) = s.strip_suffix('d') {
71        let n: i64 = days.parse().map_err(|_| format!("invalid duration: {s}"))?;
72        Ok(n * 86400)
73    } else if let Some(hours) = s.strip_suffix('h') {
74        let n: i64 = hours
75            .parse()
76            .map_err(|_| format!("invalid duration: {s}"))?;
77        Ok(n * 3600)
78    } else {
79        Err(format!("invalid duration: {s} (use e.g. 7d, 24h)"))
80    }
81}