History

feat: add first 'debug' version of `gix diff file`

Christoph Rüßler committed 1 year ago

Commit dbb15321 parent 77dbd4b3 files · +222 −1Browse files

Unified · Split

3 files
  1. gitoxide-core/src/repository/diff.rs +195 −1
  2. src/plumbing/main.rs +15 −0
  3. src/plumbing/options/mod.rs +12 −0
gitoxide-core/src/repository/diff.rs+195−1
@@ -1,7 +1,10 @@
1use gix::bstr::{BString, ByteSlice};1use gix::bstr::{BStr, BString, ByteSlice};
2use gix::diff::blob::intern::TokenSource;
3use gix::diff::blob::UnifiedDiffBuilder;
2use gix::objs::tree::EntryMode;4use gix::objs::tree::EntryMode;
3use gix::odb::store::RefreshMode;5use gix::odb::store::RefreshMode;
4use gix::prelude::ObjectIdExt;6use gix::prelude::ObjectIdExt;
7use gix::ObjectId;
5 8
6pub fn tree(9pub fn tree(
7 mut repo: gix::Repository,10 mut repo: gix::Repository,
@@ -111,3 +114,194 @@ fn typed_location(mut location: BString, mode: EntryMode) -> BString { · 103 unmodified lines
111 }114 }
112 location115 location
113}116}
117
118pub fn file(
119 mut repo: gix::Repository,
120 out: &mut dyn std::io::Write,
121 old_treeish: BString,
122 new_treeish: BString,
123 path: BString,
124) -> Result<(), anyhow::Error> {
125 repo.object_cache_size_if_unset(repo.compute_object_cache_size_for_tree_diffs(&**repo.index_or_empty()?));
126 repo.objects.refresh = RefreshMode::Never;
127
128 let old_tree_id = repo.rev_parse_single(old_treeish.as_bstr())?;
129 let new_tree_id = repo.rev_parse_single(new_treeish.as_bstr())?;
130
131 let old_tree = old_tree_id.object()?.peel_to_tree()?;
132 let new_tree = new_tree_id.object()?.peel_to_tree()?;
133
134 let mut old_tree_buf = Vec::new();
135 let mut new_tree_buf = Vec::new();
136
137 use gix::diff::object::FindExt;
138
139 let old_tree_iter = repo.objects.find_tree_iter(&old_tree.id(), &mut old_tree_buf)?;
140 let new_tree_iter = repo.objects.find_tree_iter(&new_tree.id(), &mut new_tree_buf)?;
141
142 use gix::diff::tree::{
143 recorder::{self, Location},
144 Recorder,
145 };
146
147 struct FindChangeToPath {
148 inner: Recorder,
149 interesting_path: BString,
150 change: Option<recorder::Change>,
151 }
152
153 impl FindChangeToPath {
154 fn new(interesting_path: &BStr) -> Self {
155 let inner = Recorder::default().track_location(Some(Location::Path));
156
157 FindChangeToPath {
158 inner,
159 interesting_path: interesting_path.into(),
160 change: None,
161 }
162 }
163 }
164
165 use gix::diff::tree::{visit, Visit};
166
167 impl Visit for FindChangeToPath {
168 fn pop_front_tracked_path_and_set_current(&mut self) {
169 self.inner.pop_front_tracked_path_and_set_current();
170 }
171
172 fn push_back_tracked_path_component(&mut self, component: &BStr) {
173 self.inner.push_back_tracked_path_component(component);
174 }
175
176 fn push_path_component(&mut self, component: &BStr) {
177 self.inner.push_path_component(component);
178 }
179
180 fn pop_path_component(&mut self) {
181 self.inner.pop_path_component();
182 }
183
184 fn visit(&mut self, change: visit::Change) -> visit::Action {
185 if self.inner.path() == self.interesting_path {
186 self.change = Some(match change {
187 visit::Change::Deletion {
188 entry_mode,
189 oid,
190 relation,
191 } => recorder::Change::Deletion {
192 entry_mode,
193 oid,
194 path: self.inner.path_clone(),
195 relation,
196 },
197 visit::Change::Addition {
198 entry_mode,
199 oid,
200 relation,
201 } => recorder::Change::Addition {
202 entry_mode,
203 oid,
204 path: self.inner.path_clone(),
205 relation,
206 },
207 visit::Change::Modification {
208 previous_entry_mode,
209 previous_oid,
210 entry_mode,
211 oid,
212 } => recorder::Change::Modification {
213 previous_entry_mode,
214 previous_oid,
215 entry_mode,
216 oid,
217 path: self.inner.path_clone(),
218 },
219 });
220
221 visit::Action::Cancel
222 } else {
223 visit::Action::Continue
224 }
225 }
226 }
227
228 let mut recorder = FindChangeToPath::new(path.as_ref());
229 let state = gix::diff::tree::State::default();
230 let result = gix::diff::tree(old_tree_iter, new_tree_iter, state, &repo.objects, &mut recorder);
231
232 let change = match result {
233 Ok(_) | Err(gix::diff::tree::Error::Cancelled) => recorder.change,
234 Err(error) => return Err(error.into()),
235 };
236
237 let Some(change) = change else {
238 anyhow::bail!(
239 "There was no change to {} between {} and {}",
240 &path,
241 old_treeish,
242 new_treeish
243 )
244 };
245
246 let mut resource_cache = repo.diff_resource_cache(gix::diff::blob::pipeline::Mode::ToGit, Default::default())?;
247
248 let (previous_oid, oid) = match change {
249 recorder::Change::Addition { oid, .. } => {
250 // Setting `previous_oid` to `ObjectId::empty_blob` makes `diff` see an addition.
251 (ObjectId::empty_blob(gix::hash::Kind::Sha1), oid)
252 }
253 recorder::Change::Deletion { oid: previous_oid, .. } => {
254 // Setting `oid` to `ObjectId::empty_blob` makes `diff` see a deletion.
255 (previous_oid, ObjectId::empty_blob(gix::hash::Kind::Sha1))
256 }
257 recorder::Change::Modification { previous_oid, oid, .. } => (previous_oid, oid),
258 };
259
260 resource_cache.set_resource(
261 previous_oid,
262 gix::object::tree::EntryKind::Blob,
263 path.as_slice().into(),
264 gix::diff::blob::ResourceKind::OldOrSource,
265 &repo.objects,
266 )?;
267 resource_cache.set_resource(
268 oid,
269 gix::object::tree::EntryKind::Blob,
270 path.as_slice().into(),
271 gix::diff::blob::ResourceKind::NewOrDestination,
272 &repo.objects,
273 )?;
274
275 let outcome = resource_cache.prepare_diff()?;
276
277 let old_data = String::from_utf8_lossy(outcome.old.data.as_slice().unwrap_or_default());
278 let new_data = String::from_utf8_lossy(outcome.new.data.as_slice().unwrap_or_default());
279
280 let input =
281 gix::diff::blob::intern::InternedInput::new(tokens_for_diffing(&old_data), tokens_for_diffing(&new_data));
282
283 let unified_diff_builder = UnifiedDiffBuilder::new(&input);
284
285 use gix::diff::blob::platform::prepare_diff::Operation;
286
287 let algorithm = match outcome.operation {
288 Operation::InternalDiff { algorithm } => algorithm,
289 Operation::ExternalCommand { .. } => {
290 // `unreachable!` is also used in [`Platform::lines()`](gix::object::blob::diff::Platform::lines()).
291 unreachable!("We disabled that")
292 }
293 Operation::SourceOrDestinationIsBinary => {
294 anyhow::bail!("Source or destination is binary and we can't diff that")
295 }
296 };
297
298 let unified_diff = gix::diff::blob::diff(algorithm, &input, unified_diff_builder);
299
300 out.write_all(unified_diff.as_bytes())?;
301
302 Ok(())
303}
304
305pub(crate) fn tokens_for_diffing(data: &str) -> impl TokenSource<Token = &str> {
306 gix::diff::blob::sources::lines(data)
307}
src/plumbing/main.rs+15−0
@@ -277,6 +277,21 @@ pub fn main() -> Result<()> { · 276 unmodified lines
277 core::repository::diff::tree(repository(Mode::Lenient)?, out, old_treeish, new_treeish)277 core::repository::diff::tree(repository(Mode::Lenient)?, out, old_treeish, new_treeish)
278 },278 },
279 ),279 ),
280 crate::plumbing::options::diff::SubCommands::File {
281 old_treeish,
282 new_treeish,
283 path,
284 } => prepare_and_run(
285 "diff-file",
286 trace,
287 verbose,
288 progress,
289 progress_keep_open,
290 None,
291 move |_progress, out, _err| {
292 core::repository::diff::file(repository(Mode::Lenient)?, out, old_treeish, new_treeish, path)
293 },
294 ),
280 },295 },
281 Subcommands::Log(crate::plumbing::options::log::Platform { pathspec }) => prepare_and_run(296 Subcommands::Log(crate::plumbing::options::log::Platform { pathspec }) => prepare_and_run(
282 "log",297 "log",
src/plumbing/options/mod.rs+12−0
@@ -517,6 +517,18 @@ pub mod diff { · 516 unmodified lines
517 #[clap(value_parser = crate::shared::AsBString)]517 #[clap(value_parser = crate::shared::AsBString)]
518 new_treeish: BString,518 new_treeish: BString,
519 },519 },
520 /// Diff two versions of a file.
521 File {
522 /// A rev-spec representing the 'before' or old tree.
523 #[clap(value_parser = crate::shared::AsBString)]
524 old_treeish: BString,
525 /// A rev-spec representing the 'after' or new tree.
526 #[clap(value_parser = crate::shared::AsBString)]
527 new_treeish: BString,
528 /// The path to the file to run diff for.
529 #[clap(value_parser = crate::shared::AsBString)]
530 path: BString,
531 },
520 }532 }
521}533}
522 534

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t