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
129
130
131
132
133
use gix_odb::FindExt;

use crate::{config::cache::util::ApplyLeniencyDefault, repository::IndexPersistedOrInMemory, worktree};

/// Index access
impl crate::Repository {
    /// 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.
    pub fn open_index(&self) -> Result<gix_index::File, worktree::open_index::Error> {
        let thread_limit = self
            .config
            .resolved
            .string("index", None, "threads")
            .map(|value| crate::config::tree::Index::THREADS.try_into_index_threads(value))
            .transpose()
            .with_lenient_default(self.config.lenient_config)?;
        let skip_hash = self
            .config
            .resolved
            .boolean("index", None, "skipHash")
            .map(|res| crate::config::tree::Index::SKIP_HASH.enrich_error(res))
            .transpose()
            .with_lenient_default(self.config.lenient_config)?
            .unwrap_or_default();

        let index = gix_index::File::at(
            self.index_path(),
            self.object_hash(),
            skip_hash,
            gix_index::decode::Options {
                thread_limit,
                min_extension_block_in_bytes_for_threading: 0,
                expected_checksum: None,
            },
        )?;

        Ok(index)
    }

    /// 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.try_index().and_then(|opt| match opt {
            Some(index) => Ok(index),
            None => Err(worktree::open_index::Error::IndexFile(
                gix_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()),
                )),
            )),
        })
    }

    /// 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, or `None` if no such file exists.
    ///
    /// The index file is shared across all clones of this repository.
    pub fn try_index(&self) -> Result<Option<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(gix_index::file::init::Error::Io(err))
                        if err.kind() == std::io::ErrorKind::NotFound =>
                    {
                        Ok(None)
                    }
                    err => Err(err),
                })
            },
        )
    }

    /// Open the persisted worktree index or generate it from the current `HEAD^{tree}` to live in-memory only.
    ///
    /// Use this method to get an index in any repository, even bare ones that don't have one naturally.
    ///
    /// ### Note
    ///
    /// The locally stored index is not guaranteed to represent `HEAD^{tree}` if this repository is bare - bare repos
    /// don't naturally have an index and if an index is present it must have been generated by hand.
    pub fn index_or_load_from_head(
        &self,
    ) -> Result<IndexPersistedOrInMemory, crate::repository::index_or_load_from_head::Error> {
        Ok(match self.try_index()? {
            Some(index) => IndexPersistedOrInMemory::Persisted(index),
            None => {
                let tree = self.head_commit()?.tree_id()?;
                IndexPersistedOrInMemory::InMemory(self.index_from_tree(&tree)?)
            }
        })
    }

    /// Create new index-file, which would live at the correct location, in memory from the given `tree`.
    ///
    /// Note that this is an expensive operation as it requires recursively traversing the entire tree to unpack it into the index.
    pub fn index_from_tree(
        &self,
        tree: &gix_hash::oid,
    ) -> Result<gix_index::File, gix_traverse::tree::breadthfirst::Error> {
        Ok(gix_index::File::from_state(
            gix_index::State::from_tree(tree, |oid, buf| self.objects.find_tree_iter(oid, buf).ok())?,
            self.git_dir().join("index"),
        ))
    }
}

impl std::ops::Deref for IndexPersistedOrInMemory {
    type Target = gix_index::File;

    fn deref(&self) -> &Self::Target {
        match self {
            IndexPersistedOrInMemory::Persisted(i) => i,
            IndexPersistedOrInMemory::InMemory(i) => i,
        }
    }
}

impl IndexPersistedOrInMemory {
    /// Consume this instance and turn it into an owned index file.
    ///
    /// Note that this will cause the persisted index to be cloned, which would happen whenever the repository has a worktree.
    pub fn into_owned(self) -> gix_index::File {
        match self {
            IndexPersistedOrInMemory::Persisted(i) => gix_index::File::clone(&i),
            IndexPersistedOrInMemory::InMemory(i) => i,
        }
    }
}