Unified · Split
| @@ -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 | }; |
| 7 | use std::{collections::BTreeMap, ops::Deref, path::PathBuf}; | 7 | use 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 | ||
| 70 | impl visit::Visit for Recorder { | 66 | impl 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::Continue | 115 | visit::Action::Continue |
| @@ -50,6 +50,109 @@ pub mod visit { · 49 unmodified lines | |||
| 50 | } | 50 | } |
| 51 | } | 51 | } |
| 52 | 52 | ||
| 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 | |||
| 53 | /// | 156 | /// |
| 54 | pub mod breadthfirst { | 157 | pub mod breadthfirst { |
| 55 | use crate::tree::visit::Visit; | 158 | use crate::tree::visit::Visit; |
| @@ -1,12 +1,12 @@ | |||
| 1 | mod ancestor { | 1 | mod 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 | ||
File renamed without changes.
| @@ -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 | ||
| @@ -5,3 +5,4 @@ pub fn hex_to_id(hex: &str) -> git_hash::ObjectId { · 4 unmodified lines | |||
| 5 | } | 5 | } |
| 6 | 6 | ||
| 7 | mod commit; | 7 | mod commit; |
| 8 | mod tree; | ||
| @@ -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 | } | ||