Skip to main content

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 _;
25use pollster::FutureExt as _;
26
27use crate::backend;
28use crate::backend::BackendResult;
29use crate::backend::TreeEntriesNonRecursiveIterator;
30use crate::backend::TreeId;
31use crate::backend::TreeValue;
32use crate::matchers::Matcher;
33use crate::repo_path::RepoPath;
34use crate::repo_path::RepoPathBuf;
35use crate::repo_path::RepoPathComponent;
36use crate::store::Store;
37
38#[derive(Clone)]
39pub struct Tree {
40    store: Arc<Store>,
41    dir: RepoPathBuf,
42    id: TreeId,
43    data: Arc<backend::Tree>,
44}
45
46impl Debug for Tree {
47    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
48        f.debug_struct("Tree")
49            .field("dir", &self.dir)
50            .field("id", &self.id)
51            .finish()
52    }
53}
54
55impl PartialEq for Tree {
56    fn eq(&self, other: &Self) -> bool {
57        self.id == other.id && self.dir == other.dir
58    }
59}
60
61impl Eq for Tree {}
62
63impl Hash for Tree {
64    fn hash<H: Hasher>(&self, state: &mut H) {
65        self.dir.hash(state);
66        self.id.hash(state);
67    }
68}
69
70impl Tree {
71    pub fn new(store: Arc<Store>, dir: RepoPathBuf, id: TreeId, data: Arc<backend::Tree>) -> Self {
72        Self {
73            store,
74            dir,
75            id,
76            data,
77        }
78    }
79
80    pub fn empty(store: Arc<Store>, dir: RepoPathBuf) -> Self {
81        let id = store.empty_tree_id().clone();
82        Self {
83            store,
84            dir,
85            id,
86            data: Arc::new(backend::Tree::default()),
87        }
88    }
89
90    pub fn store(&self) -> &Arc<Store> {
91        &self.store
92    }
93
94    pub fn dir(&self) -> &RepoPath {
95        &self.dir
96    }
97
98    pub fn id(&self) -> &TreeId {
99        &self.id
100    }
101
102    pub fn data(&self) -> &backend::Tree {
103        &self.data
104    }
105
106    pub fn entries_non_recursive(&self) -> TreeEntriesNonRecursiveIterator<'_> {
107        self.data.entries()
108    }
109
110    pub fn entries_matching<'matcher>(
111        &self,
112        matcher: &'matcher dyn Matcher,
113    ) -> TreeEntriesIterator<'matcher> {
114        TreeEntriesIterator::new(self.clone(), matcher)
115    }
116
117    pub fn value(&self, basename: &RepoPathComponent) -> Option<&TreeValue> {
118        self.data.value(basename)
119    }
120
121    pub async fn path_value(&self, path: &RepoPath) -> BackendResult<Option<TreeValue>> {
122        assert_eq!(self.dir(), RepoPath::root());
123        match path.split() {
124            Some((dir, basename)) => {
125                let tree = self.sub_tree_recursive(dir).await?;
126                Ok(tree.and_then(|tree| tree.data.value(basename).cloned()))
127            }
128            None => Ok(Some(TreeValue::Tree(self.id.clone()))),
129        }
130    }
131
132    pub async fn sub_tree(&self, name: &RepoPathComponent) -> BackendResult<Option<Self>> {
133        if let Some(sub_tree) = self.data.value(name) {
134            match sub_tree {
135                TreeValue::Tree(sub_tree_id) => {
136                    let subdir = self.dir.join(name);
137                    let sub_tree = self.store.get_tree(subdir, sub_tree_id).await?;
138                    Ok(Some(sub_tree))
139                }
140                _ => Ok(None),
141            }
142        } else {
143            Ok(None)
144        }
145    }
146
147    async fn known_sub_tree(&self, subdir: RepoPathBuf, id: &TreeId) -> Self {
148        self.store.get_tree(subdir, id).await.unwrap()
149    }
150
151    /// Look up the tree at the given path.
152    pub async fn sub_tree_recursive(&self, path: &RepoPath) -> BackendResult<Option<Self>> {
153        let mut current_tree = self.clone();
154        for name in path.components() {
155            match current_tree.sub_tree(name).await? {
156                None => {
157                    return Ok(None);
158                }
159                Some(sub_tree) => {
160                    current_tree = sub_tree;
161                }
162            }
163        }
164        // TODO: It would be nice to be able to return a reference here, but
165        // then we would have to figure out how to share Tree instances
166        // across threads.
167        Ok(Some(current_tree))
168    }
169}
170
171pub struct TreeEntriesIterator<'matcher> {
172    stack: Vec<TreeEntriesDirItem>,
173    matcher: &'matcher dyn Matcher,
174}
175
176struct TreeEntriesDirItem {
177    tree: Tree,
178    entries: Vec<(RepoPathBuf, TreeValue)>,
179}
180
181impl From<Tree> for TreeEntriesDirItem {
182    fn from(tree: Tree) -> Self {
183        let mut entries = tree
184            .entries_non_recursive()
185            .map(|entry| (tree.dir().join(entry.name()), entry.value().clone()))
186            .collect_vec();
187        entries.reverse();
188        Self { tree, entries }
189    }
190}
191
192impl<'matcher> TreeEntriesIterator<'matcher> {
193    fn new(tree: Tree, matcher: &'matcher dyn Matcher) -> Self {
194        // TODO: Restrict walk according to Matcher::visit()
195        Self {
196            stack: vec![TreeEntriesDirItem::from(tree)],
197            matcher,
198        }
199    }
200}
201
202impl Iterator for TreeEntriesIterator<'_> {
203    type Item = (RepoPathBuf, TreeValue);
204
205    fn next(&mut self) -> Option<Self::Item> {
206        while let Some(top) = self.stack.last_mut() {
207            if let Some((path, value)) = top.entries.pop() {
208                match value {
209                    TreeValue::Tree(id) => {
210                        // TODO: Handle the other cases (specific files and trees)
211                        if self.matcher.visit(&path).is_nothing() {
212                            continue;
213                        }
214                        let subtree = top.tree.known_sub_tree(path, &id).block_on();
215                        self.stack.push(TreeEntriesDirItem::from(subtree));
216                    }
217                    value => {
218                        if self.matcher.matches(&path) {
219                            return Some((path, value));
220                        }
221                    }
222                }
223            } else {
224                self.stack.pop();
225            }
226        }
227        None
228    }
229}