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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#![allow(missing_docs)]
use std::path::{Path, PathBuf};

use bstr::{BStr, ByteSlice};
use git_hash::oid;

use super::Cache;
use crate::{fs, fs::PathOidMapping};

#[derive(Clone)]
pub enum State {
    /// Useful for checkout where directories need creation, but we need to access attributes as well.
    CreateDirectoryAndAttributesStack {
        /// If there is a symlink or a file in our path, try to unlink it before creating the directory.
        unlink_on_collision: bool,

        /// just for testing
        #[cfg(debug_assertions)]
        test_mkdir_calls: usize,
        /// State to handle attribute information
        attributes: state::Attributes,
    },
    /// Used when adding files, requiring access to both attributes and ignore information, for example during add operations.
    AttributesAndIgnoreStack {
        /// State to handle attribute information
        attributes: state::Attributes,
        /// State to handle exclusion information
        ignore: state::Ignore,
    },
    /// Used when providing worktree status information.
    IgnoreStack(state::Ignore),
}

#[cfg(debug_assertions)]
impl Cache {
    pub fn set_case(&mut self, case: git_glob::pattern::Case) {
        self.case = case;
    }
    pub fn num_mkdir_calls(&self) -> usize {
        match self.state {
            State::CreateDirectoryAndAttributesStack { test_mkdir_calls, .. } => test_mkdir_calls,
            _ => 0,
        }
    }

    pub fn reset_mkdir_calls(&mut self) {
        if let State::CreateDirectoryAndAttributesStack { test_mkdir_calls, .. } = &mut self.state {
            *test_mkdir_calls = 0;
        }
    }

    pub fn unlink_on_collision(&mut self, value: bool) {
        if let State::CreateDirectoryAndAttributesStack {
            unlink_on_collision, ..
        } = &mut self.state
        {
            *unlink_on_collision = value;
        }
    }
}

#[must_use]
pub struct Platform<'a> {
    parent: &'a Cache,
    is_dir: Option<bool>,
}

impl Cache {
    /// Create a new instance with `worktree_root` being the base for all future paths we handle, assuming it to be valid which includes
    /// symbolic links to be included in it as well.
    /// The `case` configures attribute and exclusion query case sensitivity.
    pub fn new(
        worktree_root: impl Into<PathBuf>,
        state: State,
        case: git_glob::pattern::Case,
        buf: Vec<u8>,
        attribute_files_in_index: Vec<PathOidMapping>,
    ) -> Self {
        let root = worktree_root.into();
        Cache {
            stack: fs::Stack::new(root),
            state,
            case,
            buf,
            attribute_files_in_index,
        }
    }

    /// Append the `relative` path to the root directory the cache contains and efficiently create leading directories
    /// unless `is_dir` is known (`Some(…)`) then `relative` points to a directory itself in which case the entire resulting
    /// path is created as directory. If it's not known it is assumed to be a file.
    ///
    /// Provide access to cached information for that `relative` entry via the platform returned.
    pub fn at_path<Find, E>(
        &mut self,
        relative: impl AsRef<Path>,
        is_dir: Option<bool>,
        find: Find,
    ) -> std::io::Result<Platform<'_>>
    where
        Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Result<git_object::BlobRef<'a>, E>,
        E: std::error::Error + Send + Sync + 'static,
    {
        let mut delegate = platform::StackDelegate {
            state: &mut self.state,
            buf: &mut self.buf,
            is_dir: is_dir.unwrap_or(false),
            attribute_files_in_index: &self.attribute_files_in_index,
            find,
        };
        self.stack.make_relative_path_current(relative, &mut delegate)?;
        Ok(Platform { parent: self, is_dir })
    }

    /// **Panics** on illformed UTF8 in `relative`
    // TODO: more docs
    pub fn at_entry<'r, Find, E>(
        &mut self,
        relative: impl Into<&'r BStr>,
        is_dir: Option<bool>,
        find: Find,
    ) -> std::io::Result<Platform<'_>>
    where
        Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Result<git_object::BlobRef<'a>, E>,
        E: std::error::Error + Send + Sync + 'static,
    {
        let relative = relative.into();
        let relative_path = git_path::from_bstr(relative);

        self.at_path(
            relative_path,
            is_dir.or_else(|| relative.ends_with_str("/").then(|| true)),
            // is_dir,
            find,
        )
    }

    /// Return the base path against which all entries or paths should be relative to when querying.
    ///
    /// Note that this path _may_ not be canonicalized.
    pub fn base(&self) -> &Path {
        self.stack.root()
    }
}

mod platform;
///
pub mod state;