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
44 bstr::{BStr, BString, ByteSlice, ByteVec},
55 tree,
66 };
7−use std::{collections::BTreeMap, ops::Deref, path::PathBuf};
7+use std::collections::BTreeMap;
88
99 /// A Change as observed by a call to [`visit(…)`][visit::Visit::visit()], enhanced with the path affected by the change.
1010 /// Its similar to [visit::Change] but includes the path that changed.
@@ -14,12 +14,12 @@ pub enum Change { · 3 unmodified lines
1414 Addition {
1515 entry_mode: tree::EntryMode,
1616 oid: ObjectId,
17− path: PathBuf,
17+ path: BString,
1818 },
1919 Deletion {
2020 entry_mode: tree::EntryMode,
2121 oid: ObjectId,
22− path: PathBuf,
22+ path: BString,
2323 },
2424 Modification {
2525 previous_entry_mode: tree::EntryMode,
@@ -28,7 +28,7 @@ pub enum Change { · 2 unmodified lines
2828 entry_mode: tree::EntryMode,
2929 oid: ObjectId,
3030
31− path: PathBuf,
31+ path: BString,
3232 },
3333 }
3434
@@ -61,10 +61,6 @@ impl Recorder { · 26 unmodified lines
6161 fn path_clone(&self) -> BString {
6262 self.path.clone()
6363 }
64−
65− fn path_buf(&self) -> PathBuf {
66− self.path.deref().to_owned().into_path_buf_lossy()
67− }
6864 }
6965
7066 impl visit::Visit for Recorder {
@@ -96,12 +92,12 @@ impl visit::Visit for Recorder { · 25 unmodified lines
9692 Deletion { entry_mode, oid } => Change::Deletion {
9793 entry_mode,
9894 oid,
99− path: self.path_buf(),
95+ path: self.path_clone(),
10096 },
10197 Addition { entry_mode, oid } => Change::Addition {
10298 entry_mode,
10399 oid,
104− path: self.path_buf(),
100+ path: self.path_clone(),
105101 },
106102 Modification {
107103 previous_entry_mode,
@@ -113,7 +109,7 @@ impl visit::Visit for Recorder { · 5 unmodified lines
113109 previous_oid,
114110 entry_mode,
115111 oid,
116− path: self.path_buf(),
112+ path: self.path_clone(),
117113 },
118114 });
119115 visit::Action::Continue
git-traverse/src/tree.rs+103−0
@@ -50,6 +50,109 @@ pub mod visit { · 49 unmodified lines
5050 }
5151 }
5252
53+///
54+pub 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)]
154+pub use recorder::Recorder;
155+
53156 ///
54157 pub mod breadthfirst {
55158 use crate::tree::visit::Visit;
git-traverse/tests/commit/mod.rs+4−4
@@ -1,12 +1,12 @@
11 mod ancestor {
22 use crate::hex_to_id;
33 use git_hash::ObjectId;
4− use git_odb::{linked, linked::Db, pack, Find};
4+ use git_odb::{linked::Db, pack, Find};
55 use git_traverse::commit;
66
7− fn db() -> Result<Db, Box<dyn std::error::Error>> {
8− let dir = git_testtools::scripted_fixture_repo_read_only("make_traversal_repo.sh")?;
9− let db = linked::Db::at(dir.join(".git").join("objects"))?;
7+ fn db() -> crate::Result<Db> {
8+ let dir = git_testtools::scripted_fixture_repo_read_only("make_traversal_repo_for_commits.sh")?;
9+ let db = Db::at(dir.join(".git").join("objects"))?;
1010 Ok(db)
1111 }
1212
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
2+set -eu -o pipefail
3+
4+git init -q
5+git config commit.gpgsign false
6+
7+git checkout -q -b main
8+touch a b c
9+mkdir d e f
10+touch d/a e/b f/c f/z
11+mkdir f/d
12+touch f/d/x
13+
14+git add .
15+git 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
55 }
66
77 mod commit;
8+mod tree;
git-traverse/tests/tree/mod.rs+105−0
@@ -0,0 +1,105 @@
1+use crate::hex_to_id;
2+use git_odb::{linked::Db, pack, Find};
3+use git_traverse::tree;
4+
5+fn 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]
12+fn 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+}