History

Revert "[pack-gen] remove tree-diff as traversal option."

This reverts commit 8373671fd4f3f7e9d78c480e9f68c0a7ae423c69. Actually that's not correct - diffs can send only changed objects. Diffing is the right tool for that.

Sebastian Thiel committed 5 years ago

Commit 2907a5f1 parent 83736715 files · +177 −18Browse files

Unified · Split

5 files
  1. git-odb/src/pack/data/output/count_objects.rs +160 −5
  2. git-odb/tests/odb/pack/data/output.rs +9 −7
  3. gitoxide-core/src/pack/create.rs +4 −1
  4. src/plumbing/lean/options.rs +1 −1
  5. src/plumbing/pretty/options.rs +3 −4
git-odb/src/pack/data/output/count_objects.rs+160−5
@@ -58,6 +58,7 @@ where · 57 unmodified lines
5858 move |n| {
5959 (
6060 Vec::new(), // object data buffer
61+ Vec::new(), // object data buffer 2 to hold two objects at a time
6162 make_cache(), // cache to speed up pack operations
6263 {
6364 let mut p = progress.lock().add_child(format!("thread {}", n));
@@ -69,17 +70,114 @@ where · 5 unmodified lines
6970 },
7071 {
7172 let seen_objs = Arc::clone(&seen_objs);
72− move |oids: Vec<Oid>, (buf, cache, progress)| {
73+ move |oids: Vec<Oid>, (buf1, buf2, cache, progress)| {
7374 use ObjectExpansion::*;
7475 let mut out = Vec::new();
7576 let mut tree_traversal_state = git_traverse::tree::breadthfirst::State::default();
77+ let mut tree_diff_state = git_diff::tree::State::default();
78+ let mut parent_commit_ids = Vec::new();
7679 let seen_objs = seen_objs.as_ref();
7780 let mut traverse_delegate = tree::traverse::AllUnseen::new(seen_objs);
81+ let mut changes_delegate = tree::changes::AllNew::new(seen_objs);
7882
7983 for id in oids.into_iter() {
8084 let id = id.as_ref();
81− let obj = db.find_existing(id, buf, cache)?;
85+ let obj = db.find_existing(id, buf1, cache)?;
8286 match input_object_expansion {
87+ TreeAdditionsComparedToAncestor => {
88+ use git_object::Kind::*;
89+ let mut obj = obj;
90+ let mut id = id.to_owned();
91+
92+ loop {
93+ push_obj_count_unique(&mut out, seen_objs, &id, &obj, progress);
94+ match obj.kind {
95+ Tree | Blob => break,
96+ Tag => {
97+ id = immutable::TagIter::from_bytes(obj.data)
98+ .target_id()
99+ .expect("every tag has a target");
100+ obj = db.find_existing(id, buf1, cache)?;
101+ continue;
102+ }
103+ Commit => {
104+ let current_tree_iter = {
105+ let mut commit_iter = immutable::CommitIter::from_bytes(obj.data);
106+ let tree_id = commit_iter.tree_id().expect("every commit has a tree");
107+ parent_commit_ids.clear();
108+ for token in commit_iter {
109+ match token {
110+ Ok(immutable::commit::iter::Token::Parent { id }) => {
111+ parent_commit_ids.push(id)
112+ }
113+ Ok(_) => break,
114+ Err(err) => return Err(Error::CommitDecode(err)),
115+ }
116+ }
117+ let obj = db.find_existing(tree_id, buf1, cache)?;
118+ push_obj_count_unique(&mut out, seen_objs, &tree_id, &obj, progress);
119+ immutable::TreeIter::from_bytes(obj.data)
120+ };
121+
122+ let objects = if parent_commit_ids.is_empty() {
123+ traverse_delegate.clear();
124+ git_traverse::tree::breadthfirst(
125+ current_tree_iter,
126+ &mut tree_traversal_state,
127+ |oid, buf| db.find_existing_tree_iter(oid, buf, cache).ok(),
128+ &mut traverse_delegate,
129+ )
130+ .map_err(Error::TreeTraverse)?;
131+ &traverse_delegate.objects
132+ } else {
133+ changes_delegate.clear();
134+ for commit_id in &parent_commit_ids {
135+ let parent_tree_id = {
136+ let parent_commit_obj = db.find_existing(commit_id, buf2, cache)?;
137+
138+ push_obj_count_unique(
139+ &mut out,
140+ seen_objs,
141+ &commit_id,
142+ &parent_commit_obj,
143+ progress,
144+ );
145+ immutable::CommitIter::from_bytes(parent_commit_obj.data)
146+ .tree_id()
147+ .expect("every commit has a tree")
148+ };
149+ let parent_tree = {
150+ let parent_tree_obj =
151+ db.find_existing(parent_tree_id, buf2, cache)?;
152+ push_obj_count_unique(
153+ &mut out,
154+ seen_objs,
155+ &parent_tree_id,
156+ &parent_tree_obj,
157+ progress,
158+ );
159+ immutable::TreeIter::from_bytes(parent_tree_obj.data)
160+ };
161+
162+ git_diff::tree::Changes::from(Some(parent_tree))
163+ .needed_to_obtain(
164+ current_tree_iter.clone(),
165+ &mut tree_diff_state,
166+ |oid, buf| db.find_existing_tree_iter(oid, buf, cache).ok(),
167+ &mut changes_delegate,
168+ )
169+ .map_err(Error::TreeChanges)?;
170+ }
171+ &changes_delegate.objects
172+ };
173+ for id in objects.iter() {
174+ out.push(id_to_count(&db, buf2, id, progress));
175+ }
176+ break;
177+ }
178+ }
179+ }
180+ }
83181 TreeContents => {
84182 use git_object::Kind::*;
85183 let mut id: ObjectId = id.into();
@@ -97,7 +195,7 @@ where · 11 unmodified lines
97195 )
98196 .map_err(Error::TreeTraverse)?;
99197 for id in traverse_delegate.objects.iter() {
100− out.push(id_to_count(&db, buf, id, progress));
198+ out.push(id_to_count(&db, buf1, id, progress));
101199 }
102200 break;
103201 }
@@ -105,7 +203,7 @@ where · 1 unmodified line
105203 id = immutable::CommitIter::from_bytes(obj.data)
106204 .tree_id()
107205 .expect("every commit has a tree");
108− obj = db.find_existing(id, buf, cache)?;
206+ obj = db.find_existing(id, buf1, cache)?;
109207 continue;
110208 }
111209 Blob => break,
@@ -113,7 +211,7 @@ where · 1 unmodified line
113211 id = immutable::TagIter::from_bytes(obj.data)
114212 .target_id()
115213 .expect("every tag has a target");
116− obj = db.find_existing(id, buf, cache)?;
214+ obj = db.find_existing(id, buf1, cache)?;
117215 continue;
118216 }
119217 }
@@ -130,6 +228,56 @@ where · 10 unmodified lines
130228 }
131229
132230 mod tree {
231+ pub mod changes {
232+ use dashmap::DashSet;
233+ use git_diff::tree::{
234+ visit::{Action, Change},
235+ Visit,
236+ };
237+ use git_hash::{bstr::BStr, ObjectId};
238+ use std::collections::HashSet;
239+
240+ pub struct AllNew<'a> {
241+ pub objects: HashSet<ObjectId>,
242+ all_seen: &'a DashSet<ObjectId>,
243+ }
244+
245+ impl<'a> AllNew<'a> {
246+ pub fn new(all_seen: &'a DashSet<ObjectId>) -> Self {
247+ AllNew {
248+ objects: Default::default(),
249+ all_seen,
250+ }
251+ }
252+ pub fn clear(&mut self) {
253+ self.objects.clear();
254+ }
255+ }
256+
257+ impl<'a> Visit for AllNew<'a> {
258+ fn pop_front_tracked_path_and_set_current(&mut self) {}
259+
260+ fn push_back_tracked_path_component(&mut self, _component: &BStr) {}
261+
262+ fn push_path_component(&mut self, _component: &BStr) {}
263+
264+ fn pop_path_component(&mut self) {}
265+
266+ fn visit(&mut self, change: Change) -> Action {
267+ match change {
268+ Change::Addition { oid, .. } | Change::Modification { oid, .. } => {
269+ let inserted = self.all_seen.insert(oid);
270+ if inserted {
271+ self.objects.insert(oid);
272+ }
273+ }
274+ Change::Deletion { .. } => {}
275+ };
276+ Action::Continue
277+ }
278+ }
279+ }
280+
133281 pub mod traverse {
134282 use dashmap::DashSet;
135283 use git_hash::{bstr::BStr, ObjectId};
@@ -255,6 +403,13 @@ mod types { · 119 unmodified lines
255403 ///
256404 /// This mode is useful if all reachable objects should be added, as in cloning a repository.
257405 TreeContents,
406+ /// If the input is a commit, obtain its ancestors and turn them into pack entries. Obtain the ancestor trees along with the commits
407+ /// tree and turn them into pack entries. Finally obtain the added/changed objects when comparing the ancestor trees with the
408+ /// current tree and turn them into entries as well.
409+ /// Otherwise, the same as [`AsIs`][ObjectExpansion::AsIs].
410+ ///
411+ /// This mode is useful to build a pack containing only new objects compared to a previous state.
412+ TreeAdditionsComparedToAncestor,
258413 }
259414
260415 impl Default for ObjectExpansion {
git-odb/tests/odb/pack/data/output.rs+9−7
@@ -67,6 +67,12 @@ mod count_and_entries { · 66 unmodified lines
6767 }
6868 }
6969 }
70+ let whole_pack = Count {
71+ trees: 40,
72+ commits: 16,
73+ blobs: 811,
74+ tags: 1,
75+ };
7076 for (expansion_mode, expected_count) in [
7177 (
7278 output::count_objects::ObjectExpansion::AsIs,
@@ -77,14 +83,10 @@ mod count_and_entries { · 4 unmodified lines
7783 tags: 1,
7884 },
7985 ),
86+ (output::count_objects::ObjectExpansion::TreeContents, whole_pack),
8087 (
81− output::count_objects::ObjectExpansion::TreeContents,
82− Count {
83− trees: 40,
84− commits: 16,
85− blobs: 811,
86− tags: 1,
87− },
88+ output::count_objects::ObjectExpansion::TreeAdditionsComparedToAncestor,
89+ whole_pack,
8890 ),
8991 ]
9092 .iter()
gitoxide-core/src/pack/create.rs+4−1
@@ -12,11 +12,12 @@ pub const PROGRESS_RANGE: std::ops::RangeInclusive<u8> = 1..=2; · 11 unmodified lines
1212 pub enum ObjectExpansion {
1313 None,
1414 TreeTraversal,
15+ TreeDiff,
1516 }
1617
1718 impl ObjectExpansion {
1819 pub fn variants() -> &'static [&'static str] {
19− &["none", "tree-traversal"]
20+ &["none", "tree-traversal", "tree-diff"]
2021 }
2122 }
2223
@@ -35,6 +36,7 @@ impl FromStr for ObjectExpansion { · 12 unmodified lines
3536 Ok(match slc.as_str() {
3637 "none" => None,
3738 "tree-traversal" => TreeTraversal,
39+ "tree-diff" => TreeDiff,
3840 _ => return Err("invalid value".into()),
3941 })
4042 }
@@ -46,6 +48,7 @@ impl From<ObjectExpansion> for pack::data::output::count_objects::ObjectExpansio · 5 unmodified lines
4648 match v {
4749 ObjectExpansion::None => AsIs,
4850 ObjectExpansion::TreeTraversal => TreeContents,
51+ ObjectExpansion::TreeDiff => TreeAdditionsComparedToAncestor,
4952 }
5053 }
5154 }
src/plumbing/lean/options.rs+1−1
@@ -169,7 +169,7 @@ pub struct PackCreate { · 168 unmodified lines
169169 #[argh(option, short = 'e')]
170170 /// the way objects are expanded. They differ in costs.
171171 ///
172− /// Possible values are "none" and "tree-traversal".
172+ /// Possible values are "none" and "tree-traversal". Default is "tree-diff".
173173 pub expansion: Option<core::pack::create::ObjectExpansion>,
174174
175175 /// the tips from which to start the commit graph iteration.
src/plumbing/pretty/options.rs+3−4
@@ -50,11 +50,10 @@ pub enum Subcommands { · 49 unmodified lines
5050 /// the directory containing the '.git' repository from which objects should be read.
5151 repository: Option<PathBuf>,
5252
53− #[clap(long, short = 'e', possible_values(core::pack::create::ObjectExpansion::variants()))]
54− /// the way objects are expanded.
53+ #[clap(long, short = 'e')]
54+ /// the way objects are expanded. They differ in costs.
5555 ///
56− /// 'none' keeps objects as is. 'tree-traversal' expands to objects seen in a tree reachable from the
57− /// input object.
56+ /// Possible values are "none" and "tree-traversal". Default is "tree-diff".
5857 expansion: Option<core::pack::create::ObjectExpansion>,
5958
6059 /// the tips from which to start the commit graph iteration.