git_scanner/
git_file_future.rs1#![warn(clippy::all)]
2use git2::Oid;
3use std::collections::HashMap;
4use std::path::{Path, PathBuf};
5
6#[derive(Debug, Clone)]
8pub struct GitFileFutureRegistry {
9 rev_changes: HashMap<Oid, RevChange>,
10}
11
12#[derive(Debug, Clone)]
13struct RevChange {
14 files: HashMap<PathBuf, FileNameChange>,
15 children: Vec<Oid>,
17}
18
19#[derive(Debug, Clone)]
20pub enum FileNameChange {
21 Renamed(PathBuf),
22 Deleted(),
23}
24
25impl RevChange {
26 pub fn new() -> Self {
27 RevChange {
28 files: HashMap::new(),
29 children: Vec::new(),
30 }
31 }
32}
33
34impl GitFileFutureRegistry {
35 pub fn new() -> Self {
36 GitFileFutureRegistry {
37 rev_changes: HashMap::new(),
38 }
39 }
40
41 pub fn register(
42 &mut self,
43 id: &Oid,
44 parent_ids: &[Oid],
45 file_changes: &[(PathBuf, FileNameChange)],
46 ) {
47 let entry = self.rev_changes.entry(*id).or_insert_with(RevChange::new);
48 (*entry).files.extend(file_changes.iter().cloned());
49 for parent_id in parent_ids {
50 let pentry = self
51 .rev_changes
52 .entry(*parent_id)
53 .or_insert_with(RevChange::new);
54 (*pentry).children.push(*id);
55 }
56 }
57
58 pub fn final_name(&self, ref_id: &Oid, file: &Path) -> Option<PathBuf> {
61 let mut current_name: &PathBuf = &file.to_path_buf();
62 let mut current_ref: Oid = *ref_id;
63 loop {
64 let current_change = self.rev_changes.get(¤t_ref).unwrap();
65 match current_change.files.get(current_name) {
66 Some(FileNameChange::Renamed(new_name)) => {
67 current_name = new_name;
68 }
69 Some(FileNameChange::Deleted()) => return None,
70 None => (),
71 }
72 if let Some(first_child) = current_change.children.get(0) {
73 current_ref = *first_child;
74 } else {
76 return Some(current_name.to_path_buf());
78 }
79 }
80 }
81}
82
83#[cfg(test)]
84mod test {
85 use super::*;
86 use failure::Error;
87 use pretty_assertions::assert_eq;
88
89 fn pb(name: &str) -> PathBuf {
90 PathBuf::from(name)
91 }
92
93 #[test]
94 fn trivial_repo_returns_original_name() -> Result<(), Error> {
95 let mut registry = GitFileFutureRegistry::new();
96 let my_id = Oid::from_str("01")?;
97 registry.register(&my_id, &[], &[]);
98 assert_eq!(
99 registry.final_name(&my_id, &pb("foo.txt")),
100 Some(pb("foo.txt"))
101 );
102 Ok(())
103 }
104
105 #[test]
106 fn simple_rename_returns_old_name() -> Result<(), Error> {
107 let mut registry = GitFileFutureRegistry::new();
108 let my_id = Oid::from_str("01")?;
109
110 registry.register(
111 &my_id,
112 &[],
113 &[(pb("foo.txt"), FileNameChange::Renamed(pb("bar.txt")))],
114 );
115 assert_eq!(
116 registry.final_name(&my_id, &pb("foo.txt")),
117 Some(pb("bar.txt"))
118 );
119 Ok(())
120 }
121
122 #[test]
123 fn renames_and_deletes_applied_across_history() -> Result<(), Error> {
124 let mut registry = GitFileFutureRegistry::new();
127 let id_1 = Oid::from_str("01")?;
152 let id_2 = Oid::from_str("02")?;
153 let id_4 = Oid::from_str("04")?;
154 let id_5 = Oid::from_str("05")?;
155 let id_6 = Oid::from_str("06")?;
156
157 registry.register(
158 &id_6,
159 &[id_4, id_5],
160 &[(pb("c"), FileNameChange::Renamed(pb("afinal")))],
161 );
162 registry.register(
164 &id_4,
165 &[id_2],
166 &[(pb("b"), FileNameChange::Renamed(pb("c")))],
167 );
168 registry.register(
169 &id_5,
170 &[id_2],
171 &[(pb("b"), FileNameChange::Renamed(pb("d")))],
172 );
173 registry.register(
174 &id_2,
175 &[id_1],
176 &[
177 (pb("a"), FileNameChange::Renamed(pb("b"))),
178 (pb("z"), FileNameChange::Deleted()),
179 ],
180 );
181 registry.register(&id_1, &[], &[]);
182
183 assert_eq!(registry.final_name(&id_1, &pb("a")), Some(pb("afinal")));
186 assert_eq!(registry.final_name(&id_1, &pb("z")), None);
187 assert_eq!(registry.final_name(&id_2, &pb("b")), Some(pb("afinal")));
189
190 Ok(())
191 }
192}