Skip to main content

wt/git/
worktrees.rs

1//! Worktree enumeration via `git worktree list --porcelain` (spec §4 sanctioned
2//! subprocess read) plus missing-directory detection.
3
4use std::path::Path;
5
6use crate::error::Result;
7use crate::git::cli::GitCli;
8use crate::git::porcelain::{RawWorktree, parse_worktree_list};
9
10/// Enumerates the repository's worktrees from any directory inside it, marking
11/// any whose directory has been deleted externally as missing (spec §3). The
12/// main worktree is listed first.
13pub(crate) fn enumerate(git: &dyn GitCli, dir: &Path) -> Result<Vec<RawWorktree>> {
14    let output = git.run(dir, &["worktree", "list", "--porcelain"])?;
15    let mut worktrees = parse_worktree_list(&output);
16    for wt in &mut worktrees {
17        // A worktree is "missing" when its admin record exists but the directory
18        // is gone. The bare entry has no working directory and is never missing.
19        wt.is_missing = !wt.is_bare && !wt.path.exists();
20    }
21    Ok(worktrees)
22}
23
24#[cfg(test)]
25mod tests {
26    use super::*;
27    use crate::git::cli::RealGit;
28    use crate::testutil::TestRepo;
29
30    #[test]
31    fn enumerates_main_and_linked() {
32        let repo = TestRepo::init();
33        repo.add_worktree("feature/x", "../wt-x");
34        repo.add_worktree("feature/y", "../wt-y");
35        let wts = enumerate(&RealGit, repo.root()).unwrap();
36        assert_eq!(wts.len(), 3);
37        assert!(wts[0].is_main);
38        let branches: Vec<_> = wts.iter().filter_map(|w| w.branch.clone()).collect();
39        assert!(branches.contains(&"feature/x".to_string()));
40        assert!(branches.contains(&"feature/y".to_string()));
41        assert!(wts.iter().all(|w| !w.is_missing));
42    }
43
44    #[test]
45    fn detects_missing_worktree() {
46        let repo = TestRepo::init();
47        repo.add_worktree("gone", "../wt-gone");
48        let linked = repo.root().parent().unwrap().join("wt-gone");
49        std::fs::remove_dir_all(&linked).unwrap();
50        let wts = enumerate(&RealGit, repo.root()).unwrap();
51        let missing = wts
52            .iter()
53            .find(|w| w.branch.as_deref() == Some("gone"))
54            .unwrap();
55        assert!(missing.is_missing);
56    }
57
58    #[test]
59    fn single_worktree_repo() {
60        let repo = TestRepo::init();
61        let wts = enumerate(&RealGit, repo.root()).unwrap();
62        assert_eq!(wts.len(), 1);
63        assert!(wts[0].is_main);
64        assert_eq!(wts[0].branch.as_deref(), Some("main"));
65    }
66}