gix-traverse-0.39.1/.cargo_vcs_info.json0000644000000001520000000000100135440ustar { "git": { "sha1": "4f98e94e0e8b79ed2899b35bef40f3c30b3025b0" }, "path_in_vcs": "gix-traverse" }gix-traverse-0.39.1/Cargo.toml0000644000000025350000000000100115510ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.65" name = "gix-traverse" version = "0.39.1" authors = ["Sebastian Thiel "] build = false include = [ "src/**/*", "LICENSE-*", ] autobins = false autoexamples = false autotests = false autobenches = false description = "A crate of the gitoxide project" readme = false license = "MIT OR Apache-2.0" repository = "https://github.com/Byron/gitoxide" [lib] name = "gix_traverse" path = "src/lib.rs" doctest = false [dependencies.bitflags] version = "2" [dependencies.gix-commitgraph] version = "^0.24.2" [dependencies.gix-date] version = "^0.8.6" [dependencies.gix-hash] version = "^0.14.2" [dependencies.gix-hashtable] version = "^0.5.2" [dependencies.gix-object] version = "^0.42.2" [dependencies.gix-revwalk] version = "^0.13.1" [dependencies.smallvec] version = "1.10.0" [dependencies.thiserror] version = "1.0.32" gix-traverse-0.39.1/Cargo.toml.orig000064400000000000000000000014371046102023000152320ustar 00000000000000[package] name = "gix-traverse" version = "0.39.1" repository = "https://github.com/Byron/gitoxide" license = "MIT OR Apache-2.0" description = "A crate of the gitoxide project" authors = ["Sebastian Thiel "] edition = "2021" include = ["src/**/*", "LICENSE-*"] rust-version = "1.65" autotests = false [lib] doctest = false [dependencies] gix-hash = { version = "^0.14.2", path = "../gix-hash" } gix-object = { version = "^0.42.2", path = "../gix-object" } gix-date = { version = "^0.8.6", path = "../gix-date" } gix-hashtable = { version = "^0.5.2", path = "../gix-hashtable" } gix-revwalk = { version = "^0.13.1", path = "../gix-revwalk" } gix-commitgraph = { version = "^0.24.2", path = "../gix-commitgraph" } smallvec = "1.10.0" thiserror = "1.0.32" bitflags = "2" gix-traverse-0.39.1/LICENSE-APACHE000064400000000000000000000247461046102023000142770ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. gix-traverse-0.39.1/LICENSE-MIT000064400000000000000000000017771046102023000140060ustar 00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. gix-traverse-0.39.1/src/commit/mod.rs000064400000000000000000000057311046102023000155500ustar 00000000000000//! Provide multiple traversal implementations with different performance envelopes. //! //! Use [`Simple`] for fast walks that maintain minimal state, or [`Topo`] for a more elaborate traversal. use gix_hash::ObjectId; use gix_object::FindExt; use gix_revwalk::graph::IdMap; use gix_revwalk::PriorityQueue; use smallvec::SmallVec; /// A fast iterator over the ancestors of one or more starting commits. pub struct Simple { objects: Find, cache: Option, predicate: Predicate, state: simple::State, parents: Parents, sorting: simple::Sorting, } /// Simple ancestors traversal, without the need to keep track of graph-state. pub mod simple; /// A commit walker that walks in topographical order, like `git rev-list /// --topo-order` or `--date-order` depending on the chosen [`topo::Sorting`]. /// /// Instantiate with [`topo::Builder`]. pub struct Topo { commit_graph: Option, find: Find, predicate: Predicate, indegrees: IdMap, states: IdMap, explore_queue: PriorityQueue, indegree_queue: PriorityQueue, topo_queue: topo::iter::Queue, parents: Parents, min_gen: u32, buf: Vec, } pub mod topo; /// Specify how to handle commit parents during traversal. #[derive(Default, Copy, Clone)] pub enum Parents { /// Traverse all parents, useful for traversing the entire ancestry. #[default] All, /// Only traverse along the first parent, which commonly ignores all branches. First, } /// The collection of parent ids we saw as part of the iteration. /// /// Note that this list is truncated if [`Parents::First`] was used. pub type ParentIds = SmallVec<[gix_hash::ObjectId; 1]>; /// Information about a commit that we obtained naturally as part of the iteration. #[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)] pub struct Info { /// The id of the commit. pub id: gix_hash::ObjectId, /// All parent ids we have encountered. Note that these will be at most one if [`Parents::First`] is enabled. pub parent_ids: ParentIds, /// The time at which the commit was created. It will only be `Some(_)` if the chosen traversal was /// taking dates into consideration. pub commit_time: Option, } enum Either<'buf, 'cache> { CommitRefIter(gix_object::CommitRefIter<'buf>), CachedCommit(gix_commitgraph::file::Commit<'cache>), } fn find<'cache, 'buf, Find>( cache: Option<&'cache gix_commitgraph::Graph>, objects: Find, id: &gix_hash::oid, buf: &'buf mut Vec, ) -> Result, gix_object::find::existing_iter::Error> where Find: gix_object::Find, { match cache.and_then(|cache| cache.commit_by_id(id).map(Either::CachedCommit)) { Some(c) => Ok(c), None => objects.find_commit_iter(id, buf).map(Either::CommitRefIter), } } gix-traverse-0.39.1/src/commit/simple.rs000064400000000000000000000411361046102023000162610ustar 00000000000000use gix_date::SecondsSinceUnixEpoch; use gix_hash::ObjectId; use gix_hashtable::HashSet; use smallvec::SmallVec; use std::collections::VecDeque; /// Specify how to sort commits during a [simple](super::Simple) traversal. /// /// ### Sample History /// /// The following history will be referred to for explaining how the sort order works, with the number denoting the commit timestamp /// (*their X-alignment doesn't matter*). /// /// ```text /// ---1----2----4----7 <- second parent of 8 /// \ \ /// 3----5----6----8--- /// ``` #[derive(Default, Debug, Copy, Clone)] pub enum Sorting { /// Commits are sorted as they are mentioned in the commit graph. /// /// In the *sample history* the order would be `8, 6, 7, 5, 4, 3, 2, 1` /// /// ### Note /// /// This is not to be confused with `git log/rev-list --topo-order`, which is notably different from /// as it avoids overlapping branches. #[default] BreadthFirst, /// Commits are sorted by their commit time in descending order, that is newest first. /// /// The sorting applies to all currently queued commit ids and thus is full. /// /// In the *sample history* the order would be `8, 7, 6, 5, 4, 3, 2, 1` /// /// # Performance /// /// This mode benefits greatly from having an object_cache in `find()` /// to avoid having to lookup each commit twice. ByCommitTimeNewestFirst, /// This sorting is similar to `ByCommitTimeNewestFirst`, but adds a cutoff to not return commits older than /// a given time, stopping the iteration once no younger commits is queued to be traversed. /// /// As the query is usually repeated with different cutoff dates, this search mode benefits greatly from an object cache. /// /// In the *sample history* and a cut-off date of 4, the returned list of commits would be `8, 7, 6, 4` ByCommitTimeNewestFirstCutoffOlderThan { /// The amount of seconds since unix epoch, the same value obtained by any `gix_date::Time` structure and the way git counts time. seconds: gix_date::SecondsSinceUnixEpoch, }, } /// The error is part of the item returned by the [Ancestors](super::Simple) iterator. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { #[error(transparent)] Find(#[from] gix_object::find::existing_iter::Error), #[error(transparent)] ObjectDecode(#[from] gix_object::decode::Error), } /// The state used and potentially shared by multiple graph traversals. #[derive(Clone)] pub(super) struct State { next: VecDeque, queue: gix_revwalk::PriorityQueue, buf: Vec, seen: HashSet, parents_buf: Vec, parent_ids: SmallVec<[(ObjectId, SecondsSinceUnixEpoch); 2]>, } /// #[allow(clippy::empty_docs)] mod init { use gix_date::SecondsSinceUnixEpoch; use gix_hash::{oid, ObjectId}; use gix_object::{CommitRefIter, FindExt}; use super::{ super::{simple::Sorting, Either, Info, ParentIds, Parents, Simple}, collect_parents, Error, State, }; impl Default for State { fn default() -> Self { State { next: Default::default(), queue: gix_revwalk::PriorityQueue::new(), buf: vec![], seen: Default::default(), parents_buf: vec![], parent_ids: Default::default(), } } } impl State { fn clear(&mut self) { self.next.clear(); self.queue.clear(); self.buf.clear(); self.seen.clear(); } } /// Builder impl Simple where Find: gix_object::Find, { /// Set the `sorting` method. pub fn sorting(mut self, sorting: Sorting) -> Result { self.sorting = sorting; match self.sorting { Sorting::BreadthFirst => { self.queue_to_vecdeque(); } Sorting::ByCommitTimeNewestFirst | Sorting::ByCommitTimeNewestFirstCutoffOlderThan { .. } => { let cutoff_time = self.sorting.cutoff_time(); let state = &mut self.state; for commit_id in state.next.drain(..) { let commit_iter = self.objects.find_commit_iter(&commit_id, &mut state.buf)?; let time = commit_iter.committer()?.time.seconds; match cutoff_time { Some(cutoff_time) if time >= cutoff_time => { state.queue.insert(time, commit_id); } Some(_) => {} None => { state.queue.insert(time, commit_id); } } } } } Ok(self) } /// Change our commit parent handling mode to the given one. pub fn parents(mut self, mode: Parents) -> Self { self.parents = mode; if matches!(self.parents, Parents::First) { self.queue_to_vecdeque(); } self } /// Set the commitgraph as `cache` to greatly accelerate any traversal. /// /// The cache will be used if possible, but we will fall-back without error to using the object /// database for commit lookup. If the cache is corrupt, we will fall back to the object database as well. pub fn commit_graph(mut self, cache: Option) -> Self { self.cache = cache; self } fn queue_to_vecdeque(&mut self) { let state = &mut self.state; state.next.extend( std::mem::replace(&mut state.queue, gix_revwalk::PriorityQueue::new()) .into_iter_unordered() .map(|(_time, id)| id), ); } } /// Lifecycle impl Simple bool> where Find: gix_object::Find, { /// Create a new instance. /// /// * `find` - a way to lookup new object data during traversal by their `ObjectId`, writing their data into buffer and returning /// an iterator over commit tokens if the object is present and is a commit. Caching should be implemented within this function /// as needed. /// * `tips` /// * the starting points of the iteration, usually commits /// * each commit they lead to will only be returned once, including the tip that started it pub fn new(tips: impl IntoIterator>, find: Find) -> Self { Self::filtered(tips, find, |_| true) } } /// Lifecycle impl Simple where Find: gix_object::Find, Predicate: FnMut(&oid) -> bool, { /// Create a new instance with commit filtering enabled. /// /// * `find` - a way to lookup new object data during traversal by their `ObjectId`, writing their data into buffer and returning /// an iterator over commit tokens if the object is present and is a commit. Caching should be implemented within this function /// as needed. /// * `tips` /// * the starting points of the iteration, usually commits /// * each commit they lead to will only be returned once, including the tip that started it /// * `predicate` - indicate whether a given commit should be included in the result as well /// as whether its parent commits should be traversed. pub fn filtered( tips: impl IntoIterator>, find: Find, mut predicate: Predicate, ) -> Self { let tips = tips.into_iter(); let mut state = State::default(); { state.clear(); state.next.reserve(tips.size_hint().0); for tip in tips.map(Into::into) { let was_inserted = state.seen.insert(tip); if was_inserted && predicate(&tip) { state.next.push_back(tip); } } } Self { objects: find, cache: None, predicate, state, parents: Default::default(), sorting: Default::default(), } } } /// Access impl Simple { /// Return an iterator for accessing data of the current commit, parsed lazily. pub fn commit_iter(&self) -> CommitRefIter<'_> { CommitRefIter::from_bytes(&self.state.buf) } /// Return the current commits' raw data, which can be parsed using [`gix_object::CommitRef::from_bytes()`]. pub fn commit_data(&self) -> &[u8] { &self.state.buf } } impl Iterator for Simple where Find: gix_object::Find, Predicate: FnMut(&oid) -> bool, { type Item = Result; fn next(&mut self) -> Option { if matches!(self.parents, Parents::First) { self.next_by_topology() } else { match self.sorting { Sorting::BreadthFirst => self.next_by_topology(), Sorting::ByCommitTimeNewestFirst => self.next_by_commit_date(None), Sorting::ByCommitTimeNewestFirstCutoffOlderThan { seconds } => { self.next_by_commit_date(seconds.into()) } } } } } impl Sorting { /// If not topo sort, provide the cutoff date if present. fn cutoff_time(&self) -> Option { match self { Sorting::ByCommitTimeNewestFirstCutoffOlderThan { seconds } => Some(*seconds), _ => None, } } } /// Utilities impl Simple where Find: gix_object::Find, Predicate: FnMut(&oid) -> bool, { fn next_by_commit_date( &mut self, cutoff_older_than: Option, ) -> Option> { let state = &mut self.state; let (commit_time, oid) = state.queue.pop()?; let mut parents: ParentIds = Default::default(); match super::super::find(self.cache.as_ref(), &self.objects, &oid, &mut state.buf) { Ok(Either::CachedCommit(commit)) => { if !collect_parents(&mut state.parent_ids, self.cache.as_ref(), commit.iter_parents()) { // drop corrupt caches and try again with ODB self.cache = None; return self.next_by_commit_date(cutoff_older_than); } for (id, parent_commit_time) in state.parent_ids.drain(..) { parents.push(id); let was_inserted = state.seen.insert(id); if !(was_inserted && (self.predicate)(&id)) { continue; } match cutoff_older_than { Some(cutoff_older_than) if parent_commit_time < cutoff_older_than => continue, Some(_) | None => state.queue.insert(parent_commit_time, id), } } } Ok(Either::CommitRefIter(commit_iter)) => { for token in commit_iter { match token { Ok(gix_object::commit::ref_iter::Token::Tree { .. }) => continue, Ok(gix_object::commit::ref_iter::Token::Parent { id }) => { parents.push(id); let was_inserted = state.seen.insert(id); if !(was_inserted && (self.predicate)(&id)) { continue; } let parent = self.objects.find_commit_iter(id.as_ref(), &mut state.parents_buf).ok(); let parent_commit_time = parent .and_then(|parent| parent.committer().ok().map(|committer| committer.time.seconds)) .unwrap_or_default(); match cutoff_older_than { Some(cutoff_older_than) if parent_commit_time < cutoff_older_than => continue, Some(_) | None => state.queue.insert(parent_commit_time, id), } } Ok(_unused_token) => break, Err(err) => return Some(Err(err.into())), } } } Err(err) => return Some(Err(err.into())), } Some(Ok(Info { id: oid, parent_ids: parents, commit_time: Some(commit_time), })) } } /// Utilities impl Simple where Find: gix_object::Find, Predicate: FnMut(&oid) -> bool, { fn next_by_topology(&mut self) -> Option> { let state = &mut self.state; let oid = state.next.pop_front()?; let mut parents: ParentIds = Default::default(); match super::super::find(self.cache.as_ref(), &self.objects, &oid, &mut state.buf) { Ok(Either::CachedCommit(commit)) => { if !collect_parents(&mut state.parent_ids, self.cache.as_ref(), commit.iter_parents()) { // drop corrupt caches and try again with ODB self.cache = None; return self.next_by_topology(); } for (id, _commit_time) in state.parent_ids.drain(..) { parents.push(id); let was_inserted = state.seen.insert(id); if was_inserted && (self.predicate)(&id) { state.next.push_back(id); } if matches!(self.parents, Parents::First) { break; } } } Ok(Either::CommitRefIter(commit_iter)) => { for token in commit_iter { match token { Ok(gix_object::commit::ref_iter::Token::Tree { .. }) => continue, Ok(gix_object::commit::ref_iter::Token::Parent { id }) => { parents.push(id); let was_inserted = state.seen.insert(id); if was_inserted && (self.predicate)(&id) { state.next.push_back(id); } if matches!(self.parents, Parents::First) { break; } } Ok(_a_token_past_the_parents) => break, Err(err) => return Some(Err(err.into())), } } } Err(err) => return Some(Err(err.into())), } Some(Ok(Info { id: oid, parent_ids: parents, commit_time: None, })) } } } fn collect_parents( dest: &mut SmallVec<[(gix_hash::ObjectId, gix_date::SecondsSinceUnixEpoch); 2]>, cache: Option<&gix_commitgraph::Graph>, parents: gix_commitgraph::file::commit::Parents<'_>, ) -> bool { dest.clear(); let cache = cache.as_ref().expect("parents iter is available, backed by `cache`"); for parent_id in parents { match parent_id { Ok(pos) => dest.push({ let parent = cache.commit_at(pos); ( parent.id().to_owned(), parent.committer_timestamp() as gix_date::SecondsSinceUnixEpoch, // we can't handle errors here and trying seems overkill ) }), Err(_err) => return false, } } true } gix-traverse-0.39.1/src/commit/topo/init.rs000064400000000000000000000134261046102023000167150ustar 00000000000000use crate::commit::topo::iter::gen_and_commit_time; use crate::commit::topo::{Error, Sorting, WalkFlags}; use crate::commit::{find, Info, Parents, Topo}; use gix_hash::{oid, ObjectId}; use gix_revwalk::graph::IdMap; use gix_revwalk::PriorityQueue; /// Builder for [`Topo`]. pub struct Builder { commit_graph: Option, find: Find, predicate: Predicate, sorting: Sorting, parents: Parents, tips: Vec, ends: Vec, } impl Builder bool> where Find: gix_object::Find, { /// Create a new `Builder` for a [`Topo`] that reads commits from a repository with `find`. /// starting at the `tips` and ending at the `ends`. Like `git rev-list /// --topo-order ^ends tips`. pub fn from_iters( find: Find, tips: impl IntoIterator>, ends: Option>>, ) -> Self { let tips = tips.into_iter().map(Into::into).collect::>(); let ends = ends .map(|e| e.into_iter().map(Into::into).collect::>()) .unwrap_or_default(); Self { commit_graph: Default::default(), find, sorting: Default::default(), parents: Default::default(), tips, ends, predicate: |_| true, } } /// Set a `predicate` to filter out revisions from the walk. Can be used to /// implement e.g. filtering on paths or time. This does *not* exclude the /// parent(s) of a revision that is excluded. Specify a revision as an 'end' /// if you want that behavior. pub fn with_predicate(self, predicate: Predicate) -> Builder where Predicate: FnMut(&oid) -> bool, { Builder { commit_graph: self.commit_graph, find: self.find, sorting: self.sorting, parents: self.parents, tips: self.tips, ends: self.ends, predicate, } } } impl Builder where Find: gix_object::Find, Predicate: FnMut(&oid) -> bool, { /// Set the `sorting` to use for the topological walk. pub fn sorting(mut self, sorting: Sorting) -> Self { self.sorting = sorting; self } /// Specify how to handle commit `parents` during traversal. pub fn parents(mut self, parents: Parents) -> Self { self.parents = parents; self } /// Set or unset the `commit_graph` to use for the iteration. pub fn with_commit_graph(mut self, commit_graph: Option) -> Self { self.commit_graph = commit_graph; self } /// Build a new [`Topo`] instance. /// /// Note that merely building an instance is currently expensive. pub fn build(self) -> Result, Error> { let mut w = Topo { commit_graph: self.commit_graph, find: self.find, predicate: self.predicate, indegrees: IdMap::default(), states: IdMap::default(), explore_queue: PriorityQueue::new(), indegree_queue: PriorityQueue::new(), topo_queue: super::iter::Queue::new(self.sorting), parents: self.parents, min_gen: gix_commitgraph::GENERATION_NUMBER_INFINITY, buf: vec![], }; // Initial flags for the states of the tips and ends. All of them are // seen and added to the explore and indegree queues. The ends are by // definition (?) uninteresting and bottom. let tip_flags = WalkFlags::Seen | WalkFlags::Explored | WalkFlags::InDegree; let end_flags = tip_flags | WalkFlags::Uninteresting | WalkFlags::Bottom; for (id, flags) in self .tips .iter() .map(|id| (id, tip_flags)) .chain(self.ends.iter().map(|id| (id, end_flags))) { *w.indegrees.entry(*id).or_default() = 1; let commit = find(w.commit_graph.as_ref(), &w.find, id, &mut w.buf)?; let (gen, time) = gen_and_commit_time(commit)?; if gen < w.min_gen { w.min_gen = gen; } w.states.insert(*id, flags); w.explore_queue.insert((gen, time), *id); w.indegree_queue.insert((gen, time), *id); } // NOTE: Parents of the ends must also be marked uninteresting for some // reason. See handle_commit() for id in &self.ends { let parents = w.collect_all_parents(id)?; for (id, _) in parents { w.states .entry(id) .and_modify(|s| *s |= WalkFlags::Uninteresting) .or_insert(WalkFlags::Uninteresting | WalkFlags::Seen); } } w.compute_indegrees_to_depth(w.min_gen)?; // NOTE: in Git the ends are also added to the topo_queue in addition to // the tips, but then in simplify_commit() Git is told to ignore it. For // now the tests pass. for id in self.tips.iter() { let i = w.indegrees.get(id).ok_or(Error::MissingIndegreeUnexpected)?; if *i != 1 { continue; } let commit = find(w.commit_graph.as_ref(), &w.find, id, &mut w.buf)?; let (_, time) = gen_and_commit_time(commit)?; let parent_ids = w.collect_all_parents(id)?.into_iter().map(|e| e.0).collect(); w.topo_queue.push( time, Info { id: *id, parent_ids, commit_time: Some(time), }, ); } w.topo_queue.initial_sort(); Ok(w) } } gix-traverse-0.39.1/src/commit/topo/iter.rs000064400000000000000000000241501046102023000167110ustar 00000000000000use crate::commit::topo::{Error, Sorting, WalkFlags}; use crate::commit::{find, Either, Info, Parents, Topo}; use gix_hash::{oid, ObjectId}; use gix_revwalk::PriorityQueue; use smallvec::SmallVec; pub(in crate::commit) type GenAndCommitTime = (u32, i64); // Git's priority queue works as a LIFO stack if no compare function is set, // which is the case for `--topo-order.` However, even in that case the initial // items of the queue are sorted according to the commit time before beginning // the walk. #[derive(Debug)] pub(in crate::commit) enum Queue { Date(PriorityQueue), Topo(Vec<(i64, Info)>), } impl Queue { pub(super) fn new(s: Sorting) -> Self { match s { Sorting::DateOrder => Self::Date(PriorityQueue::new()), Sorting::TopoOrder => Self::Topo(vec![]), } } pub(super) fn push(&mut self, commit_time: i64, info: Info) { match self { Self::Date(q) => q.insert(commit_time, info), Self::Topo(q) => q.push((commit_time, info)), } } fn pop(&mut self) -> Option { match self { Self::Date(q) => q.pop().map(|(_, info)| info), Self::Topo(q) => q.pop().map(|(_, info)| info), } } pub(super) fn initial_sort(&mut self) { if let Self::Topo(ref mut inner_vec) = self { inner_vec.sort_by(|a, b| a.0.cmp(&b.0)); } } } impl Topo where Find: gix_object::Find, { pub(super) fn compute_indegrees_to_depth(&mut self, gen_cutoff: u32) -> Result<(), Error> { while let Some(((gen, _), _)) = self.indegree_queue.peek() { if *gen >= gen_cutoff { self.indegree_walk_step()?; } else { break; } } Ok(()) } fn indegree_walk_step(&mut self) -> Result<(), Error> { if let Some(((gen, _), id)) = self.indegree_queue.pop() { self.explore_to_depth(gen)?; let parents = self.collect_parents(&id)?; for (id, gen_time) in parents { self.indegrees.entry(id).and_modify(|e| *e += 1).or_insert(2); let state = self.states.get_mut(&id).ok_or(Error::MissingStateUnexpected)?; if !state.contains(WalkFlags::InDegree) { *state |= WalkFlags::InDegree; self.indegree_queue.insert(gen_time, id); } } } Ok(()) } fn explore_to_depth(&mut self, gen_cutoff: u32) -> Result<(), Error> { while let Some(((gen, _), _)) = self.explore_queue.peek() { if *gen >= gen_cutoff { self.explore_walk_step()?; } else { break; } } Ok(()) } fn explore_walk_step(&mut self) -> Result<(), Error> { if let Some((_, id)) = self.explore_queue.pop() { let parents = self.collect_parents(&id)?; self.process_parents(&id, &parents)?; for (id, gen_time) in parents { let state = self.states.get_mut(&id).ok_or(Error::MissingStateUnexpected)?; if !state.contains(WalkFlags::Explored) { *state |= WalkFlags::Explored; self.explore_queue.insert(gen_time, id); } } } Ok(()) } fn expand_topo_walk(&mut self, id: &oid) -> Result<(), Error> { let parents = self.collect_parents(id)?; self.process_parents(id, &parents)?; for (pid, (parent_gen, parent_commit_time)) in parents { let parent_state = self.states.get(&pid).ok_or(Error::MissingStateUnexpected)?; if parent_state.contains(WalkFlags::Uninteresting) { continue; } if parent_gen < self.min_gen { self.min_gen = parent_gen; self.compute_indegrees_to_depth(self.min_gen)?; } let i = self.indegrees.get_mut(&pid).ok_or(Error::MissingIndegreeUnexpected)?; *i -= 1; if *i != 1 { continue; } let parent_ids = self.collect_all_parents(&pid)?.into_iter().map(|e| e.0).collect(); self.topo_queue.push( parent_commit_time, Info { id: pid, parent_ids, commit_time: Some(parent_commit_time), }, ); } Ok(()) } fn process_parents(&mut self, id: &oid, parents: &[(ObjectId, GenAndCommitTime)]) -> Result<(), Error> { let state = self.states.get_mut(id).ok_or(Error::MissingStateUnexpected)?; if state.contains(WalkFlags::Added) { return Ok(()); } *state |= WalkFlags::Added; // If the current commit is uninteresting we pass that on to ALL // parents, otherwise we set the Seen flag. let (pass, insert) = if state.contains(WalkFlags::Uninteresting) { let flags = WalkFlags::Uninteresting; for (id, _) in parents { let grand_parents = self.collect_all_parents(id)?; for (id, _) in &grand_parents { self.states .entry(*id) .and_modify(|s| *s |= WalkFlags::Uninteresting) .or_insert(WalkFlags::Uninteresting | WalkFlags::Seen); } } (flags, flags) } else { // NOTE: git sets SEEN like we do but keeps the SYMMETRIC_LEFT and // ANCENSTRY_PATH if they are set, but they have no purpose here. let flags = WalkFlags::empty(); (flags, WalkFlags::Seen) }; for (id, _) in parents { self.states.entry(*id).and_modify(|s| *s |= pass).or_insert(insert); } Ok(()) } fn collect_parents(&mut self, id: &oid) -> Result, Error> { collect_parents( self.commit_graph.as_ref(), &self.find, id, matches!(self.parents, Parents::First), &mut self.buf, ) } // Same as collect_parents but disregards the first_parent flag pub(super) fn collect_all_parents( &mut self, id: &oid, ) -> Result, Error> { collect_parents(self.commit_graph.as_ref(), &self.find, id, false, &mut self.buf) } fn pop_commit(&mut self) -> Option> { let commit = self.topo_queue.pop()?; let i = match self.indegrees.get_mut(&commit.id) { Some(i) => i, None => { return Some(Err(Error::MissingIndegreeUnexpected)); } }; *i = 0; if let Err(e) = self.expand_topo_walk(&commit.id) { return Some(Err(e)); }; Some(Ok(commit)) } } impl Iterator for Topo where Find: gix_object::Find, Predicate: FnMut(&oid) -> bool, { type Item = Result; fn next(&mut self) -> Option { loop { match self.pop_commit()? { Ok(id) => { if (self.predicate)(&id.id) { return Some(Ok(id)); } } Err(e) => return Some(Err(e)), } } } } fn collect_parents( cache: Option<&gix_commitgraph::Graph>, f: Find, id: &oid, first_only: bool, buf: &mut Vec, ) -> Result, Error> where Find: gix_object::Find, { let mut parents = SmallVec::<[(ObjectId, GenAndCommitTime); 1]>::new(); match find(cache, &f, id, buf)? { Either::CommitRefIter(c) => { for token in c { use gix_object::commit::ref_iter::Token as T; match token { Ok(T::Tree { .. }) => continue, Ok(T::Parent { id }) => { parents.push((id, (0, 0))); // Dummy numbers to be filled in if first_only { break; } } Ok(_past_parents) => break, Err(err) => return Err(err.into()), } } // Need to check the cache again. That a commit is not in the cache // doesn't mean a parent is not. for (id, gen_time) in parents.iter_mut() { let commit = find(cache, &f, id, buf)?; *gen_time = gen_and_commit_time(commit)?; } } Either::CachedCommit(c) => { for pos in c.iter_parents() { let parent_commit = cache .expect("cache exists if CachedCommit was returned") .commit_at(pos?); parents.push(( parent_commit.id().into(), (parent_commit.generation(), parent_commit.committer_timestamp() as i64), )); if first_only { break; } } } }; Ok(parents) } pub(super) fn gen_and_commit_time(c: Either<'_, '_>) -> Result { match c { Either::CommitRefIter(c) => { let mut commit_time = 0; for token in c { use gix_object::commit::ref_iter::Token as T; match token { Ok(T::Tree { .. }) => continue, Ok(T::Parent { .. }) => continue, Ok(T::Author { .. }) => continue, Ok(T::Committer { signature }) => { commit_time = signature.time.seconds; break; } Ok(_unused_token) => break, Err(err) => return Err(err.into()), } } Ok((gix_commitgraph::GENERATION_NUMBER_INFINITY, commit_time)) } Either::CachedCommit(c) => Ok((c.generation(), c.committer_timestamp() as i64)), } } gix-traverse-0.39.1/src/commit/topo/mod.rs000064400000000000000000000047441046102023000165340ustar 00000000000000//! Topological commit traversal, similar to `git log --topo-order`, which keeps track of graph state. use bitflags::bitflags; /// The errors that can occur during creation and iteration. #[derive(thiserror::Error, Debug)] #[allow(missing_docs)] pub enum Error { #[error("Indegree information is missing")] MissingIndegreeUnexpected, #[error("Internal state (bitflags) not found")] MissingStateUnexpected, #[error(transparent)] CommitGraphFile(#[from] gix_commitgraph::file::commit::Error), #[error(transparent)] ObjectDecode(#[from] gix_object::decode::Error), #[error(transparent)] Find(#[from] gix_object::find::existing_iter::Error), } bitflags! { /// Set of flags to describe the state of a particular commit while iterating. // NOTE: The names correspond to the names of the flags in revision.h #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub(super) struct WalkFlags: u8 { /// Commit has been seen const Seen = 0b000001; /// Commit has been processed by the Explore walk const Explored = 0b000010; /// Commit has been processed by the Indegree walk const InDegree = 0b000100; /// Commit is deemed uninteresting for whatever reason const Uninteresting = 0b001000; /// Commit marks the end of a walk, like `foo` in `git rev-list foo..bar` const Bottom = 0b010000; /// Parents have been processed const Added = 0b100000; } } /// Sorting to use for the topological walk. /// /// ### Sample History /// /// The following history will be referred to for explaining how the sort order works, with the number denoting the commit timestamp /// (*their X-alignment doesn't matter*). /// /// ```text /// ---1----2----4----7 <- second parent of 8 /// \ \ /// 3----5----6----8--- /// ``` #[derive(Clone, Copy, Debug, Default)] pub enum Sorting { /// Show no parents before all of its children are shown, but otherwise show /// commits in the commit timestamp order. /// /// This is equivalent to `git rev-list --date-order`. #[default] DateOrder, /// Show no parents before all of its children are shown, and avoid /// showing commits on multiple lines of history intermixed. /// /// In the *sample history* the order would be `8, 6, 5, 3, 7, 4, 2, 1`. /// This is equivalent to `git rev-list --topo-order`. TopoOrder, } mod init; pub use init::Builder; pub(super) mod iter; gix-traverse-0.39.1/src/lib.rs000064400000000000000000000003121046102023000142350ustar 00000000000000//! Various ways to traverse commit graphs and trees with implementations as iterator #![deny(missing_docs, rust_2018_idioms)] #![forbid(unsafe_code)] pub mod commit; /// Tree traversal pub mod tree; gix-traverse-0.39.1/src/tree/breadthfirst.rs000064400000000000000000000072311046102023000171160ustar 00000000000000use std::collections::VecDeque; use gix_hash::ObjectId; /// The error is part of the item returned by the [`traverse()`][impl_::traverse()] function. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { #[error(transparent)] Find(#[from] gix_object::find::existing_iter::Error), #[error("The delegate cancelled the operation")] Cancelled, #[error(transparent)] ObjectDecode(#[from] gix_object::decode::Error), } /// The state used and potentially shared by multiple tree traversals. #[derive(Default, Clone)] pub struct State { next: VecDeque, buf: Vec, } impl State { fn clear(&mut self) { self.next.clear(); self.buf.clear(); } } pub(crate) mod impl_ { use std::borrow::BorrowMut; use gix_object::{FindExt, TreeRefIter}; use super::{Error, State}; use crate::tree::Visit; /// Start a breadth-first iteration over the `root` trees entries. /// /// * `root` /// * the tree to iterate in a nested fashion. /// * `state` - all state used for the iteration. If multiple iterations are performed, allocations can be minimized by reusing /// this state. /// * `find` - a way to lookup new object data during traversal by their `ObjectId`, writing their data into buffer and returning /// an iterator over entries if the object is present and is a tree. Caching should be implemented within this function /// as needed. The return value is `Option` which degenerates all error information. Not finding a commit should also /// be considered an errors as all objects in the tree DAG should be present in the database. Hence [`Error::Find`] should /// be escalated into a more specific error if its encountered by the caller. /// * `delegate` - A way to observe entries and control the iteration while allowing the optimizer to let you pay only for what you use. pub fn traverse( root: TreeRefIter<'_>, mut state: StateMut, objects: Find, delegate: &mut V, ) -> Result<(), Error> where Find: gix_object::Find, StateMut: BorrowMut, V: Visit, { let state = state.borrow_mut(); state.clear(); let mut tree = root; loop { for entry in tree { let entry = entry?; if entry.mode.is_tree() { use crate::tree::visit::Action::*; delegate.push_path_component(entry.filename); let action = delegate.visit_tree(&entry); match action { Skip => {} Continue => { delegate.pop_path_component(); delegate.push_back_tracked_path_component(entry.filename); state.next.push_back(entry.oid.to_owned()) } Cancel => { return Err(Error::Cancelled); } } } else { delegate.push_path_component(entry.filename); if delegate.visit_nontree(&entry).cancelled() { return Err(Error::Cancelled); } } delegate.pop_path_component(); } match state.next.pop_front() { Some(oid) => { delegate.pop_front_tracked_path_and_set_current(); tree = objects.find_tree_iter(&oid, &mut state.buf)?; } None => break Ok(()), } } } } gix-traverse-0.39.1/src/tree/mod.rs000064400000000000000000000056311046102023000152160ustar 00000000000000use std::collections::VecDeque; use gix_object::bstr::{BStr, BString}; /// A trait to allow responding to a traversal designed to observe all entries in a tree, recursively while keeping track of /// paths if desired. pub trait Visit { /// Sets the full path path in front of the queue so future calls to push and pop components affect it instead. fn pop_front_tracked_path_and_set_current(&mut self); /// Append a `component` to the end of a path, which may be empty. fn push_back_tracked_path_component(&mut self, component: &BStr); /// Append a `component` to the end of a path, which may be empty. fn push_path_component(&mut self, component: &BStr); /// Removes the last component from the path, which may leave it empty. fn pop_path_component(&mut self); /// Observe a tree entry that is a tree and return an instruction whether to continue or not. /// [`Action::Skip`][visit::Action::Skip] can be used to prevent traversing it, for example if it's known to the caller already. /// /// The implementation may use the current path to learn where in the tree the change is located. fn visit_tree(&mut self, entry: &gix_object::tree::EntryRef<'_>) -> visit::Action; /// Observe a tree entry that is NO tree and return an instruction whether to continue or not. /// [`Action::Skip`][visit::Action::Skip] has no effect here. /// /// The implementation may use the current path to learn where in the tree the change is located. fn visit_nontree(&mut self, entry: &gix_object::tree::EntryRef<'_>) -> visit::Action; } /// A [Visit] implementation to record every observed change and keep track of the changed paths. /// /// Recorders can also be instructed to track the filename only, or no location at all. #[derive(Clone, Debug)] pub struct Recorder { path_deque: VecDeque, path: BString, /// How to track the location. location: Option, /// The observed entries. pub records: Vec, } /// #[allow(clippy::empty_docs)] pub mod visit { /// What to do after an entry was [recorded][super::Visit::visit_tree()]. #[derive(Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)] pub enum Action { /// Continue the traversal of entries. Continue, /// Stop the traversal of entries, making this the last call to [`visit_(tree|nontree)(…)`][super::Visit::visit_nontree()]. Cancel, /// Don't dive into the entry, skipping children effectively. Only useful in [`visit_tree(…)`][super::Visit::visit_tree()]. Skip, } impl Action { /// Returns true if this action means to stop the traversal. pub fn cancelled(&self) -> bool { matches!(self, Action::Cancel) } } } /// #[allow(clippy::empty_docs)] pub mod recorder; /// #[allow(clippy::empty_docs)] pub mod breadthfirst; pub use breadthfirst::impl_::traverse as breadthfirst; gix-traverse-0.39.1/src/tree/recorder.rs000064400000000000000000000076071046102023000162510ustar 00000000000000use gix_hash::ObjectId; use gix_object::{ bstr::{BStr, BString, ByteSlice, ByteVec}, tree, }; use crate::tree::{visit::Action, Recorder, Visit}; /// Describe how to track the location of an entry. #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum Location { /// Track the entire path, relative to the repository. Path, /// Keep only the file-name as location, which may be enough for some calculations. /// /// This is less expensive than tracking the entire `Path`. FileName, } /// An owned entry as observed by a call to [`visit_(tree|nontree)(…)`][Visit::visit_tree()], enhanced with the full path to it. /// Otherwise similar to [`gix_object::tree::EntryRef`]. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Entry { /// The kind of entry, similar to entries in a unix directory tree. pub mode: tree::EntryMode, /// The full path to the entry. A root entry would be `d`, and a file `a` within the directory would be `d/a`. /// /// This is independent of the platform and the path separators actually used there. pub filepath: BString, /// The id of the entry which can be used to locate it in an object database. pub oid: ObjectId, } impl Entry { fn new(entry: &tree::EntryRef<'_>, filepath: BString) -> Self { Entry { filepath, oid: entry.oid.to_owned(), mode: entry.mode, } } } impl Default for Recorder { fn default() -> Self { Recorder { path_deque: Default::default(), path: Default::default(), location: Location::Path.into(), records: vec![], } } } impl Recorder { fn pop_element(&mut self) { if let Some(pos) = self.path.rfind_byte(b'/') { self.path.resize(pos, 0); } else { self.path.clear(); } } fn push_element(&mut self, name: &BStr) { if !self.path.is_empty() { self.path.push(b'/'); } self.path.push_str(name); } } /// Builder impl Recorder { /// Obtain a copy of the currently tracked, full path of the entry. pub fn track_location(mut self, location: Option) -> Self { self.location = location; self } } /// Access impl Recorder { /// Obtain a copy of the currently tracked, full path of the entry. pub fn path_clone(&self) -> BString { self.path.clone() } /// Return the currently set path. pub fn path(&self) -> &BStr { self.path.as_ref() } } impl Visit for Recorder { fn pop_front_tracked_path_and_set_current(&mut self) { if let Some(Location::Path) = self.location { self.path = self .path_deque .pop_front() .expect("every call is matched with push_tracked_path_component"); } } fn push_back_tracked_path_component(&mut self, component: &BStr) { if let Some(Location::Path) = self.location { self.push_element(component); self.path_deque.push_back(self.path.clone()); } } fn push_path_component(&mut self, component: &BStr) { match self.location { None => {} Some(Location::Path) => { self.push_element(component); } Some(Location::FileName) => { self.path.clear(); self.path.extend_from_slice(component); } } } fn pop_path_component(&mut self) { if let Some(Location::Path) = self.location { self.pop_element() } } fn visit_tree(&mut self, entry: &tree::EntryRef<'_>) -> Action { self.records.push(Entry::new(entry, self.path_clone())); Action::Continue } fn visit_nontree(&mut self, entry: &tree::EntryRef<'_>) -> Action { self.records.push(Entry::new(entry, self.path_clone())); Action::Continue } }