1mod from_tree {
2 use std::collections::VecDeque;
3
4 use bstr::{BStr, BString, ByteSlice, ByteVec};
5 use git_object::{
6 tree::{self, EntryMode},
7 TreeRefIter,
8 };
9 use git_traverse::tree::{breadthfirst, visit::Action, Visit};
10
11 use crate::{
12 entry::{Flags, Mode, Stat},
13 Entry, PathStorage, State, Version,
14 };
15
16 impl State {
18 pub fn new(object_hash: git_hash::Kind) -> Self {
20 State {
21 object_hash,
22 timestamp: filetime::FileTime::now(),
23 version: Version::V2,
24 entries: vec![],
25 path_backing: vec![],
26 is_sparse: false,
27 tree: None,
28 link: None,
29 resolve_undo: None,
30 untracked: None,
31 fs_monitor: None,
32 }
33 }
34 pub fn from_tree<Find>(tree: &git_hash::oid, mut find: Find) -> Result<Self, breadthfirst::Error>
39 where
40 Find: for<'a> FnMut(&git_hash::oid, &'a mut Vec<u8>) -> Option<TreeRefIter<'a>>,
41 {
42 let mut buf = Vec::new();
43 let root = find(tree, &mut buf).ok_or(breadthfirst::Error::NotFound { oid: tree.into() })?;
44
45 let mut delegate = CollectEntries::new();
46 breadthfirst(root, breadthfirst::State::default(), &mut find, &mut delegate)?;
47
48 let CollectEntries {
49 mut entries,
50 path_backing,
51 path: _,
52 path_deque: _,
53 } = delegate;
54
55 entries.sort_by(|a, b| Entry::cmp_filepaths(a.path_in(&path_backing), b.path_in(&path_backing)));
56
57 Ok(State {
58 object_hash: tree.kind(),
59 timestamp: filetime::FileTime::now(),
60 version: Version::V2,
61 entries,
62 path_backing,
63 is_sparse: false,
64 tree: None,
65 link: None,
66 resolve_undo: None,
67 untracked: None,
68 fs_monitor: None,
69 })
70 }
71 }
72
73 struct CollectEntries {
74 entries: Vec<Entry>,
75 path_backing: PathStorage,
76 path: BString,
77 path_deque: VecDeque<BString>,
78 }
79
80 impl CollectEntries {
81 pub fn new() -> CollectEntries {
82 CollectEntries {
83 entries: Vec::new(),
84 path_backing: Vec::new(),
85 path: BString::default(),
86 path_deque: VecDeque::new(),
87 }
88 }
89
90 fn push_element(&mut self, name: &BStr) {
91 if !self.path.is_empty() {
92 self.path.push(b'/');
93 }
94 self.path.push_str(name);
95 }
96
97 pub fn add_entry(&mut self, entry: &tree::EntryRef<'_>) {
98 let mode = match entry.mode {
99 EntryMode::Tree => unreachable!("visit_non_tree() called us"),
100 EntryMode::Blob => Mode::FILE,
101 EntryMode::BlobExecutable => Mode::FILE_EXECUTABLE,
102 EntryMode::Link => Mode::SYMLINK,
103 EntryMode::Commit => Mode::COMMIT,
104 };
105
106 let path_start = self.path_backing.len();
107 self.path_backing.extend_from_slice(&self.path);
108
109 let new_entry = Entry {
110 stat: Stat::default(),
111 id: entry.oid.into(),
112 flags: Flags::empty(),
113 mode,
114 path: path_start..self.path_backing.len(),
115 };
116
117 self.entries.push(new_entry);
118 }
119 }
120
121 impl Visit for CollectEntries {
122 fn pop_front_tracked_path_and_set_current(&mut self) {
123 self.path = self
124 .path_deque
125 .pop_front()
126 .expect("every call is matched with push_tracked_path_component");
127 }
128
129 fn push_back_tracked_path_component(&mut self, component: &bstr::BStr) {
130 self.push_element(component);
131 self.path_deque.push_back(self.path.clone());
132 }
133
134 fn push_path_component(&mut self, component: &bstr::BStr) {
135 self.push_element(component);
136 }
137
138 fn pop_path_component(&mut self) {
139 if let Some(pos) = self.path.rfind_byte(b'/') {
140 self.path.resize(pos, 0);
141 } else {
142 self.path.clear();
143 }
144 }
145
146 fn visit_tree(&mut self, _entry: &git_object::tree::EntryRef<'_>) -> git_traverse::tree::visit::Action {
147 Action::Continue
148 }
149
150 fn visit_nontree(&mut self, entry: &git_object::tree::EntryRef<'_>) -> git_traverse::tree::visit::Action {
151 self.add_entry(entry);
152 Action::Continue
153 }
154 }
155}