1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use crate::{worktree, Worktree};

/// Worktree iteration
impl crate::Repository {
    /// Return a list of all _linked_ worktrees sorted by private git dir path as a lightweight proxy.
    ///
    /// Note that these need additional processing to become usable, but provide a first glimpse a typical worktree information.
    pub fn worktrees(&self) -> std::io::Result<Vec<worktree::Proxy<'_>>> {
        let mut res = Vec::new();
        let iter = match std::fs::read_dir(self.common_dir().join("worktrees")) {
            Ok(iter) => iter,
            Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(res),
            Err(err) => return Err(err),
        };
        for entry in iter {
            let entry = entry?;
            let worktree_git_dir = entry.path();
            if worktree_git_dir.join("gitdir").is_file() {
                res.push(worktree::Proxy {
                    parent: self,
                    git_dir: worktree_git_dir,
                })
            }
        }
        res.sort_by(|a, b| a.git_dir.cmp(&b.git_dir));
        Ok(res)
    }

    /// Iterate all _linked_ worktrees in sort order and resolve them, ignoring those without an accessible work tree, into repositories
    /// whose [`worktree()`][crate::Repository::worktree()] is the worktree currently being iterated.
    ///
    /// Note that for convenience all io errors are squelched so if there is a chance for IO errors during
    /// traversal of an owned directory, better use `list()` directly. The latter allows to resolve repositories
    /// even if the worktree checkout isn't accessible.
    pub fn worktree_repos(&self) -> ! {
        todo!()
    }
}

/// Interact with individual worktrees and their information.
impl crate::Repository {
    /// Return the repository owning the main worktree.
    ///
    /// Note that it might be the one that is currently open if this repository doesn't point to a linked worktree.
    /// Also note that the main repo might be bare.
    pub fn main_repo(&self) -> Result<crate::Repository, crate::open::Error> {
        crate::ThreadSafeRepository::open_opts(self.common_dir(), self.options.clone()).map(Into::into)
    }

    /// Return the currently set worktree if there is one, acting as platform providing a validated worktree base path.
    ///
    /// Note that there would be `None` if this repository is `bare` and the parent [`Repository`][crate::Repository] was instantiated without
    /// registered worktree in the current working dir.
    pub fn worktree(&self) -> Option<Worktree<'_>> {
        self.work_dir().map(|path| Worktree { parent: self, path })
    }

    /// Return true if this repository is bare, and has no main work tree.
    ///
    /// This is not to be confused with the [`worktree()`][crate::Repository::worktree()] worktree, which may exists if this instance
    /// was opened in a worktree that was created separately.
    pub fn is_bare(&self) -> bool {
        self.config.is_bare
    }

    /// Open a new copy of the index file and decode it entirely.
    ///
    /// It will use the `index.threads` configuration key to learn how many threads to use.
    /// Note that it may fail if there is no index.
    // TODO: test
    pub fn open_index(&self) -> Result<git_index::File, worktree::open_index::Error> {
        use std::convert::{TryFrom, TryInto};
        let thread_limit = self
            .config
            .resolved
            .boolean("index", None, "threads")
            .map(|res| {
                res.map(|value| if value { 0usize } else { 1 }).or_else(|err| {
                    git_config::Integer::try_from(err.input.as_ref())
                        .map_err(|err| worktree::open_index::Error::ConfigIndexThreads {
                            value: err.input.clone(),
                            err,
                        })
                        .map(|value| value.to_decimal().and_then(|v| v.try_into().ok()).unwrap_or(1))
                })
            })
            .transpose()?;
        git_index::File::at(
            self.index_path(),
            git_index::decode::Options {
                object_hash: self.object_hash(),
                thread_limit,
                min_extension_block_in_bytes_for_threading: 0,
            },
        )
        .map_err(Into::into)
    }

    /// Return a shared worktree index which is updated automatically if the in-memory snapshot has become stale as the underlying file
    /// on disk has changed.
    ///
    /// The index file is shared across all clones of this repository.
    pub fn index(&self) -> Result<worktree::Index, worktree::open_index::Error> {
        self.index
            .recent_snapshot(
                || self.index_path().metadata().and_then(|m| m.modified()).ok(),
                || {
                    self.open_index().map(Some).or_else(|err| match err {
                        worktree::open_index::Error::IndexFile(git_index::file::init::Error::Io(err))
                            if err.kind() == std::io::ErrorKind::NotFound =>
                        {
                            Ok(None)
                        }
                        err => Err(err),
                    })
                },
            )
            .and_then(|opt| match opt {
                Some(index) => Ok(index),
                None => Err(worktree::open_index::Error::IndexFile(
                    git_index::file::init::Error::Io(std::io::Error::new(
                        std::io::ErrorKind::NotFound,
                        format!("Could not find index file at {:?} for opening.", self.index_path()),
                    )),
                )),
            })
    }
}