History

[traverse-tree] one test to pin implementation down a little

Sebastian Thiel committed 5 years ago

Commit f0aeee11 parent c7633f27 files · +235 −15Browse files

Unified · Split

7 files
  1. git-diff/src/tree/recorder.rs +7 −11
  2. git-traverse/src/tree.rs +103 −0
  3. git-traverse/tests/commit/mod.rs +4 −4
  4. git-traverse/tests/fixtures/make_traversal_repo_for_commits.sh +0 −0
  5. git-traverse/tests/fixtures/make_traversal_repo_for_trees.sh +15 −0
  6. git-traverse/tests/traverse.rs +1 −0
  7. git-traverse/tests/tree/mod.rs +105 −0
git-diff/src/tree/recorder.rs+7−11
@@ -4,7 +4,7 @@ use git_object::{ · 3 unmodified lines
4 bstr::{BStr, BString, ByteSlice, ByteVec},4 bstr::{BStr, BString, ByteSlice, ByteVec},
5 tree,5 tree,
6};6};
7use std::{collections::BTreeMap, ops::Deref, path::PathBuf};7use std::collections::BTreeMap;
8 8
9/// A Change as observed by a call to [`visit(…)`][visit::Visit::visit()], enhanced with the path affected by the change.9/// A Change as observed by a call to [`visit(…)`][visit::Visit::visit()], enhanced with the path affected by the change.
10/// Its similar to [visit::Change] but includes the path that changed.10/// Its similar to [visit::Change] but includes the path that changed.
@@ -14,12 +14,12 @@ pub enum Change { · 3 unmodified lines
14 Addition {14 Addition {
15 entry_mode: tree::EntryMode,15 entry_mode: tree::EntryMode,
16 oid: ObjectId,16 oid: ObjectId,
17 path: PathBuf,17 path: BString,
18 },18 },
19 Deletion {19 Deletion {
20 entry_mode: tree::EntryMode,20 entry_mode: tree::EntryMode,
21 oid: ObjectId,21 oid: ObjectId,
22 path: PathBuf,22 path: BString,
23 },23 },
24 Modification {24 Modification {
25 previous_entry_mode: tree::EntryMode,25 previous_entry_mode: tree::EntryMode,
@@ -28,7 +28,7 @@ pub enum Change { · 2 unmodified lines
28 entry_mode: tree::EntryMode,28 entry_mode: tree::EntryMode,
29 oid: ObjectId,29 oid: ObjectId,
30 30
31 path: PathBuf,31 path: BString,
32 },32 },
33}33}
34 34
@@ -61,10 +61,6 @@ impl Recorder { · 26 unmodified lines
61 fn path_clone(&self) -> BString {61 fn path_clone(&self) -> BString {
62 self.path.clone()62 self.path.clone()
63 }63 }
64
65 fn path_buf(&self) -> PathBuf {
66 self.path.deref().to_owned().into_path_buf_lossy()
67 }
68}64}
69 65
70impl visit::Visit for Recorder {66impl visit::Visit for Recorder {
@@ -96,12 +92,12 @@ impl visit::Visit for Recorder { · 25 unmodified lines
96 Deletion { entry_mode, oid } => Change::Deletion {92 Deletion { entry_mode, oid } => Change::Deletion {
97 entry_mode,93 entry_mode,
98 oid,94 oid,
99 path: self.path_buf(),95 path: self.path_clone(),
100 },96 },
101 Addition { entry_mode, oid } => Change::Addition {97 Addition { entry_mode, oid } => Change::Addition {
102 entry_mode,98 entry_mode,
103 oid,99 oid,
104 path: self.path_buf(),100 path: self.path_clone(),
105 },101 },
106 Modification {102 Modification {
107 previous_entry_mode,103 previous_entry_mode,
@@ -113,7 +109,7 @@ impl visit::Visit for Recorder { · 5 unmodified lines
113 previous_oid,109 previous_oid,
114 entry_mode,110 entry_mode,
115 oid,111 oid,
116 path: self.path_buf(),112 path: self.path_clone(),
117 },113 },
118 });114 });
119 visit::Action::Continue115 visit::Action::Continue
git-traverse/src/tree.rs+103−0
@@ -50,6 +50,109 @@ pub mod visit { · 49 unmodified lines
50 }50 }
51}51}
52 52
53///
54pub mod recorder {
55 use crate::tree::visit;
56 use crate::tree::visit::Action;
57 use git_hash::ObjectId;
58 use git_object::{
59 bstr::{BStr, BString, ByteSlice, ByteVec},
60 immutable, tree,
61 };
62 use std::collections::BTreeMap;
63
64 /// An owned entry as observed by a call to [`visit_(tree|nontree)(…)`][visit::Visit::visit_tree()], enhanced with the full path to it.
65 /// Otherwise similar to [`immutable::tree::Entry`][git_object::immutable::tree::Entry].
66 #[derive(Clone, Debug, PartialEq, Eq)]
67 pub struct Entry {
68 /// The kind of entry, similar to entries in a unix directory tree.
69 pub mode: tree::EntryMode,
70 /// The full path to the entry. A root entry would be `d`, and a file `a` within the directory would be `d/a`.
71 ///
72 /// This is independent of the platform and the path separators actually used there.
73 pub filepath: BString,
74 /// The id of the entry which can be used to locate it in an object database.
75 pub oid: ObjectId,
76 }
77
78 impl Entry {
79 fn new(entry: &immutable::tree::Entry<'_>, filepath: BString) -> Self {
80 Entry {
81 filepath,
82 oid: entry.oid.to_owned(),
83 mode: entry.mode,
84 }
85 }
86 }
87
88 /// A [Visit][visit::Visit] implementation to record every observed change and keep track of the changed paths.
89 #[derive(Clone, Debug, Default)]
90 pub struct Recorder {
91 path_count: usize,
92 path_map: BTreeMap<usize, BString>,
93 path: BString,
94 /// The observed entries.
95 pub records: Vec<Entry>,
96 }
97
98 impl Recorder {
99 fn pop_element(&mut self) {
100 if let Some(pos) = self.path.rfind_byte(b'/') {
101 self.path.resize(pos, 0);
102 } else {
103 self.path.clear();
104 }
105 }
106
107 fn push_element(&mut self, name: &BStr) {
108 if !self.path.is_empty() {
109 self.path.push(b'/');
110 }
111 self.path.push_str(name);
112 }
113
114 fn path_clone(&self) -> BString {
115 self.path.clone()
116 }
117 }
118
119 impl visit::Visit for Recorder {
120 type PathId = usize;
121
122 fn set_current_path(&mut self, path: Self::PathId) {
123 self.path = self.path_map.remove(&path).expect("every parent is set only once");
124 }
125
126 fn push_tracked_path_component(&mut self, component: &BStr) -> Self::PathId {
127 self.push_element(component);
128 self.path_map.insert(self.path_count, self.path_clone());
129 let res = self.path_count;
130 self.path_count += 1;
131 res
132 }
133
134 fn push_path_component(&mut self, component: &BStr) {
135 self.push_element(component);
136 }
137
138 fn pop_path_component(&mut self) {
139 self.pop_element();
140 }
141
142 fn visit_tree(&mut self, entry: &immutable::tree::Entry<'_>) -> Action {
143 self.records.push(Entry::new(entry, self.path_clone()));
144 Action::Continue
145 }
146
147 fn visit_nontree(&mut self, entry: &immutable::tree::Entry<'_>) -> Action {
148 self.records.push(Entry::new(entry, self.path_clone()));
149 Action::Continue
150 }
151 }
152}
153#[doc(inline)]
154pub use recorder::Recorder;
155
53///156///
54pub mod breadthfirst {157pub mod breadthfirst {
55 use crate::tree::visit::Visit;158 use crate::tree::visit::Visit;
git-traverse/tests/commit/mod.rs+4−4
@@ -1,12 +1,12 @@
1mod ancestor {1mod ancestor {
2 use crate::hex_to_id;2 use crate::hex_to_id;
3 use git_hash::ObjectId;3 use git_hash::ObjectId;
4 use git_odb::{linked, linked::Db, pack, Find};4 use git_odb::{linked::Db, pack, Find};
5 use git_traverse::commit;5 use git_traverse::commit;
6 6
7 fn db() -> Result<Db, Box<dyn std::error::Error>> {7 fn db() -> crate::Result<Db> {
8 let dir = git_testtools::scripted_fixture_repo_read_only("make_traversal_repo.sh")?;8 let dir = git_testtools::scripted_fixture_repo_read_only("make_traversal_repo_for_commits.sh")?;
9 let db = linked::Db::at(dir.join(".git").join("objects"))?;9 let db = Db::at(dir.join(".git").join("objects"))?;
10 Ok(db)10 Ok(db)
11 }11 }
12 12
git-traverse/tests/fixtures/make_traversal_repo_for_commits.shrenamed from git-traverse/tests/fixtures/make_traversal_repo.sh+0−0

File renamed without changes.

git-traverse/tests/fixtures/make_traversal_repo_for_trees.sh+15−0
@@ -0,0 +1,15 @@
1#!/bin/bash
2set -eu -o pipefail
3
4git init -q
5git config commit.gpgsign false
6
7git checkout -q -b main
8touch a b c
9mkdir d e f
10touch d/a e/b f/c f/z
11mkdir f/d
12touch f/d/x
13
14git add .
15git commit -q -m c1
git-traverse/tests/traverse.rs+1−0
@@ -5,3 +5,4 @@ pub fn hex_to_id(hex: &str) -> git_hash::ObjectId { · 4 unmodified lines
5}5}
6 6
7mod commit;7mod commit;
8mod tree;
git-traverse/tests/tree/mod.rs+105−0
@@ -0,0 +1,105 @@
1use crate::hex_to_id;
2use git_odb::{linked::Db, pack, Find};
3use git_traverse::tree;
4
5fn db() -> crate::Result<Db> {
6 let dir = git_testtools::scripted_fixture_repo_read_only("make_traversal_repo_for_trees.sh")?;
7 let db = Db::at(dir.join(".git").join("objects"))?;
8 Ok(db)
9}
10
11#[test]
12fn basic_nesting() -> crate::Result<()> {
13 let db = db()?;
14 let mut buf = Vec::new();
15 let mut commit = db
16 .find(
17 hex_to_id("85df34aa34848b8138b2b3dcff5fb5c2b734e0ce"),
18 &mut buf,
19 &mut pack::cache::Never,
20 )?
21 .expect("commit exists")
22 .into_commit_iter()
23 .expect("commit present");
24 let mut recorder = tree::Recorder::default();
25 tree::breadthfirst::traverse(
26 commit.tree_id().expect("a tree is available in a commit"),
27 tree::breadthfirst::State::default(),
28 |oid, buf| {
29 db.find(oid, buf, &mut pack::cache::Never)
30 .ok()
31 .flatten()
32 .and_then(|o| o.into_tree_iter())
33 },
34 &mut recorder,
35 )?;
36
37 use git_object::tree::EntryMode::*;
38 use git_traverse::tree::recorder::Entry;
39 assert_eq!(
40 recorder.records,
41 vec![
42 Entry {
43 mode: Blob,
44 filepath: "a".into(),
45 oid: hex_to_id("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")
46 },
47 Entry {
48 mode: Blob,
49 filepath: "b".into(),
50 oid: hex_to_id("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")
51 },
52 Entry {
53 mode: Blob,
54 filepath: "c".into(),
55 oid: hex_to_id("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")
56 },
57 Entry {
58 mode: Tree,
59 filepath: "d".into(),
60 oid: hex_to_id("496d6428b9cf92981dc9495211e6e1120fb6f2ba")
61 },
62 Entry {
63 mode: Tree,
64 filepath: "e".into(),
65 oid: hex_to_id("4277b6e69d25e5efa77c455340557b384a4c018a")
66 },
67 Entry {
68 mode: Tree,
69 filepath: "f".into(),
70 oid: hex_to_id("70fb16fc77b03e16acb4a5b1a6caf79ba302919a")
71 },
72 Entry {
73 mode: Blob,
74 filepath: "d/a".into(),
75 oid: hex_to_id("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")
76 },
77 Entry {
78 mode: Blob,
79 filepath: "e/b".into(),
80 oid: hex_to_id("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")
81 },
82 Entry {
83 mode: Blob,
84 filepath: "f/c".into(),
85 oid: hex_to_id("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")
86 },
87 Entry {
88 mode: Tree,
89 filepath: "f/d".into(),
90 oid: hex_to_id("5805b676e247eb9a8046ad0c4d249cd2fb2513df")
91 },
92 Entry {
93 mode: Blob,
94 filepath: "f/z".into(),
95 oid: hex_to_id("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")
96 },
97 Entry {
98 mode: Blob,
99 filepath: "f/d/x".into(),
100 oid: hex_to_id("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")
101 }
102 ]
103 );
104 Ok(())
105}