jj_lib/
tree.rs

1// Copyright 2020 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![expect(missing_docs)]
16
17use std::fmt::Debug;
18use std::fmt::Error;
19use std::fmt::Formatter;
20use std::hash::Hash;
21use std::hash::Hasher;
22use std::sync::Arc;
23
24use itertools::Itertools as _;
25
26use crate::backend;
27use crate::backend::BackendResult;
28use crate::backend::TreeEntriesNonRecursiveIterator;
29use crate::backend::TreeId;
30use crate::backend::TreeValue;
31use crate::matchers::Matcher;
32use crate::repo_path::RepoPath;
33use crate::repo_path::RepoPathBuf;
34use crate::repo_path::RepoPathComponent;
35use crate::store::Store;
36
37#[derive(Clone)]
38pub struct Tree {
39    store: Arc<Store>,
40    dir: RepoPathBuf,
41    id: TreeId,
42    data: Arc<backend::Tree>,
43}
44
45impl Debug for Tree {
46    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
47        f.debug_struct("Tree")
48            .field("dir", &self.dir)
49            .field("id", &self.id)
50            .finish()
51    }
52}
53
54impl PartialEq for Tree {
55    fn eq(&self, other: &Self) -> bool {
56        self.id == other.id && self.dir == other.dir
57    }
58}
59
60impl Eq for Tree {}
61
62impl Hash for Tree {
63    fn hash<H: Hasher>(&self, state: &mut H) {
64        self.dir.hash(state);
65        self.id.hash(state);
66    }
67}
68
69impl Tree {
70    pub fn new(store: Arc<Store>, dir: RepoPathBuf, id: TreeId, data: Arc<backend::Tree>) -> Self {
71        Self {
72            store,
73            dir,
74            id,
75            data,
76        }
77    }
78
79    pub fn empty(store: Arc<Store>, dir: RepoPathBuf) -> Self {
80        let id = store.empty_tree_id().clone();
81        Self {
82            store,
83            dir,
84            id,
85            data: Arc::new(backend::Tree::default()),
86        }
87    }
88
89    pub fn store(&self) -> &Arc<Store> {
90        &self.store
91    }
92
93    pub fn dir(&self) -> &RepoPath {
94        &self.dir
95    }
96
97    pub fn id(&self) -> &TreeId {
98        &self.id
99    }
100
101    pub fn data(&self) -> &backend::Tree {
102        &self.data
103    }
104
105    pub fn entries_non_recursive(&self) -> TreeEntriesNonRecursiveIterator<'_> {
106        self.data.entries()
107    }
108
109    pub fn entries_matching<'matcher>(
110        &self,
111        matcher: &'matcher dyn Matcher,
112    ) -> TreeEntriesIterator<'matcher> {
113        TreeEntriesIterator::new(self.clone(), matcher)
114    }
115
116    pub fn value(&self, basename: &RepoPathComponent) -> Option<&TreeValue> {
117        self.data.value(basename)
118    }
119
120    pub fn path_value(&self, path: &RepoPath) -> BackendResult<Option<TreeValue>> {
121        assert_eq!(self.dir(), RepoPath::root());
122        match path.split() {
123            Some((dir, basename)) => {
124                let tree = self.sub_tree_recursive(dir)?;
125                Ok(tree.and_then(|tree| tree.data.value(basename).cloned()))
126            }
127            None => Ok(Some(TreeValue::Tree(self.id.clone()))),
128        }
129    }
130
131    pub fn sub_tree(&self, name: &RepoPathComponent) -> BackendResult<Option<Self>> {
132        if let Some(sub_tree) = self.data.value(name) {
133            match sub_tree {
134                TreeValue::Tree(sub_tree_id) => {
135                    let subdir = self.dir.join(name);
136                    let sub_tree = self.store.get_tree(subdir, sub_tree_id)?;
137                    Ok(Some(sub_tree))
138                }
139                _ => Ok(None),
140            }
141        } else {
142            Ok(None)
143        }
144    }
145
146    fn known_sub_tree(&self, subdir: RepoPathBuf, id: &TreeId) -> Self {
147        self.store.get_tree(subdir, id).unwrap()
148    }
149
150    /// Look up the tree at the given path.
151    pub fn sub_tree_recursive(&self, path: &RepoPath) -> BackendResult<Option<Self>> {
152        let mut current_tree = self.clone();
153        for name in path.components() {
154            match current_tree.sub_tree(name)? {
155                None => {
156                    return Ok(None);
157                }
158                Some(sub_tree) => {
159                    current_tree = sub_tree;
160                }
161            }
162        }
163        // TODO: It would be nice to be able to return a reference here, but
164        // then we would have to figure out how to share Tree instances
165        // across threads.
166        Ok(Some(current_tree))
167    }
168}
169
170pub struct TreeEntriesIterator<'matcher> {
171    stack: Vec<TreeEntriesDirItem>,
172    matcher: &'matcher dyn Matcher,
173}
174
175struct TreeEntriesDirItem {
176    tree: Tree,
177    entries: Vec<(RepoPathBuf, TreeValue)>,
178}
179
180impl From<Tree> for TreeEntriesDirItem {
181    fn from(tree: Tree) -> Self {
182        let mut entries = tree
183            .entries_non_recursive()
184            .map(|entry| (tree.dir().join(entry.name()), entry.value().clone()))
185            .collect_vec();
186        entries.reverse();
187        Self { tree, entries }
188    }
189}
190
191impl<'matcher> TreeEntriesIterator<'matcher> {
192    fn new(tree: Tree, matcher: &'matcher dyn Matcher) -> Self {
193        // TODO: Restrict walk according to Matcher::visit()
194        Self {
195            stack: vec![TreeEntriesDirItem::from(tree)],
196            matcher,
197        }
198    }
199}
200
201impl Iterator for TreeEntriesIterator<'_> {
202    type Item = (RepoPathBuf, TreeValue);
203
204    fn next(&mut self) -> Option<Self::Item> {
205        while let Some(top) = self.stack.last_mut() {
206            if let Some((path, value)) = top.entries.pop() {
207                match value {
208                    TreeValue::Tree(id) => {
209                        // TODO: Handle the other cases (specific files and trees)
210                        if self.matcher.visit(&path).is_nothing() {
211                            continue;
212                        }
213                        let subtree = top.tree.known_sub_tree(path, &id);
214                        self.stack.push(TreeEntriesDirItem::from(subtree));
215                    }
216                    value => {
217                        if self.matcher.matches(&path) {
218                            return Some((path, value));
219                        }
220                    }
221                };
222            } else {
223                self.stack.pop();
224            }
225        }
226        None
227    }
228}