Skip to content

Commit c4cc5d4

Browse files
committed
feat: add first 'debug' version of gix diff file
1 parent d0ef276 commit c4cc5d4

File tree

3 files changed

+208
-1
lines changed

3 files changed

+208
-1
lines changed

gitoxide-core/src/repository/diff.rs

Lines changed: 181 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use gix::bstr::{BString, ByteSlice};
1+
use gix::bstr::{BStr, BString, ByteSlice};
2+
use gix::diff::blob::intern::TokenSource;
3+
use gix::diff::blob::UnifiedDiffBuilder;
24
use gix::objs::tree::EntryMode;
35
use gix::odb::store::RefreshMode;
46
use gix::prelude::ObjectIdExt;
@@ -111,3 +113,181 @@ fn typed_location(mut location: BString, mode: EntryMode) -> BString {
111113
}
112114
location
113115
}
116+
117+
pub fn file(
118+
mut repo: gix::Repository,
119+
out: &mut dyn std::io::Write,
120+
old_treeish: BString,
121+
new_treeish: BString,
122+
path: BString,
123+
) -> Result<(), anyhow::Error> {
124+
repo.object_cache_size_if_unset(repo.compute_object_cache_size_for_tree_diffs(&**repo.index_or_empty()?));
125+
repo.objects.refresh = RefreshMode::Never;
126+
127+
let old_tree_id = repo.rev_parse_single(old_treeish.as_bstr())?;
128+
let new_tree_id = repo.rev_parse_single(new_treeish.as_bstr())?;
129+
130+
let old_tree = old_tree_id.object()?.peel_to_tree()?;
131+
let new_tree = new_tree_id.object()?.peel_to_tree()?;
132+
133+
let mut old_tree_buf = Vec::new();
134+
let mut new_tree_buf = Vec::new();
135+
136+
use gix::diff::object::FindExt;
137+
138+
let old_tree_iter = repo.objects.find_tree_iter(&old_tree.id(), &mut old_tree_buf)?;
139+
let new_tree_iter = repo.objects.find_tree_iter(&new_tree.id(), &mut new_tree_buf)?;
140+
141+
use gix::diff::tree::{
142+
recorder::{self, Location},
143+
Recorder,
144+
};
145+
146+
struct FindChangeToPath {
147+
inner: Recorder,
148+
interesting_path: BString,
149+
change: Option<recorder::Change>,
150+
}
151+
152+
impl FindChangeToPath {
153+
fn new(interesting_path: BString) -> Self {
154+
let inner = Recorder::default().track_location(Some(Location::Path));
155+
156+
FindChangeToPath {
157+
inner,
158+
interesting_path,
159+
change: None,
160+
}
161+
}
162+
}
163+
164+
use gix::diff::tree::{visit, Visit};
165+
166+
impl Visit for FindChangeToPath {
167+
fn pop_front_tracked_path_and_set_current(&mut self) {
168+
self.inner.pop_front_tracked_path_and_set_current();
169+
}
170+
171+
fn push_back_tracked_path_component(&mut self, component: &BStr) {
172+
self.inner.push_back_tracked_path_component(component);
173+
}
174+
175+
fn push_path_component(&mut self, component: &BStr) {
176+
self.inner.push_path_component(component);
177+
}
178+
179+
fn pop_path_component(&mut self) {
180+
self.inner.pop_path_component();
181+
}
182+
183+
fn visit(&mut self, change: visit::Change) -> visit::Action {
184+
if self.inner.path() == self.interesting_path {
185+
self.change = Some(match change {
186+
visit::Change::Deletion {
187+
entry_mode,
188+
oid,
189+
relation,
190+
} => recorder::Change::Deletion {
191+
entry_mode,
192+
oid,
193+
path: self.inner.path_clone(),
194+
relation,
195+
},
196+
visit::Change::Addition {
197+
entry_mode,
198+
oid,
199+
relation,
200+
} => recorder::Change::Addition {
201+
entry_mode,
202+
oid,
203+
path: self.inner.path_clone(),
204+
relation,
205+
},
206+
visit::Change::Modification {
207+
previous_entry_mode,
208+
previous_oid,
209+
entry_mode,
210+
oid,
211+
} => recorder::Change::Modification {
212+
previous_entry_mode,
213+
previous_oid,
214+
entry_mode,
215+
oid,
216+
path: self.inner.path_clone(),
217+
},
218+
});
219+
220+
visit::Action::Cancel
221+
} else {
222+
visit::Action::Continue
223+
}
224+
}
225+
}
226+
227+
let mut recorder = FindChangeToPath::new(path);
228+
let state = gix::diff::tree::State::default();
229+
let result = gix::diff::tree(old_tree_iter, new_tree_iter, state, &repo.objects, &mut recorder);
230+
231+
let Some(change) = (match result {
232+
Ok(_) | Err(gix::diff::tree::Error::Cancelled) => recorder.change,
233+
Err(_) => todo!(),
234+
}) else {
235+
todo!()
236+
};
237+
238+
match change {
239+
recorder::Change::Addition { .. } => todo!(),
240+
recorder::Change::Deletion { .. } => todo!(),
241+
recorder::Change::Modification {
242+
previous_oid,
243+
oid,
244+
path,
245+
..
246+
} => {
247+
let mut resource_cache =
248+
repo.diff_resource_cache(gix::diff::blob::pipeline::Mode::ToGit, Default::default())?;
249+
250+
resource_cache.set_resource(
251+
previous_oid,
252+
gix::object::tree::EntryKind::Blob,
253+
path.as_slice().into(),
254+
gix::diff::blob::ResourceKind::OldOrSource,
255+
&repo.objects,
256+
)?;
257+
resource_cache.set_resource(
258+
oid,
259+
gix::object::tree::EntryKind::Blob,
260+
path.as_slice().into(),
261+
gix::diff::blob::ResourceKind::NewOrDestination,
262+
&repo.objects,
263+
)?;
264+
265+
let outcome = resource_cache.prepare_diff()?;
266+
267+
let old_data = String::from_utf8_lossy(outcome.old.data.as_slice().unwrap_or_default());
268+
let new_data = String::from_utf8_lossy(outcome.new.data.as_slice().unwrap_or_default());
269+
270+
let input = gix::diff::blob::intern::InternedInput::new(
271+
tokens_for_diffing(&old_data),
272+
tokens_for_diffing(&new_data),
273+
);
274+
275+
let unified_diff_builder = UnifiedDiffBuilder::new(&input);
276+
277+
// TODO
278+
// The algorithm should come from `diff.algorithm`.
279+
//
280+
// See https://docs.rs/gix/latest/gix/object/blob/diff/struct.Platform.html#method.lines
281+
let unified_diff =
282+
gix::diff::blob::diff(gix::diff::blob::Algorithm::Histogram, &input, unified_diff_builder);
283+
284+
out.write_all(unified_diff.as_bytes())?;
285+
}
286+
}
287+
288+
Ok(())
289+
}
290+
291+
pub(crate) fn tokens_for_diffing(data: &str) -> impl TokenSource<Token = &str> {
292+
gix::diff::blob::sources::lines(data)
293+
}

src/plumbing/main.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,21 @@ pub fn main() -> Result<()> {
277277
core::repository::diff::tree(repository(Mode::Lenient)?, out, old_treeish, new_treeish)
278278
},
279279
),
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+
),
280295
},
281296
Subcommands::Log(crate::plumbing::options::log::Platform { pathspec }) => prepare_and_run(
282297
"log",

src/plumbing/options/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,18 @@ pub mod diff {
514514
#[clap(value_parser = crate::shared::AsBString)]
515515
new_treeish: BString,
516516
},
517+
/// Diff two versions of a file.
518+
File {
519+
/// A rev-spec representing the 'before' or old tree.
520+
#[clap(value_parser = crate::shared::AsBString)]
521+
old_treeish: BString,
522+
/// A rev-spec representing the 'after' or new tree.
523+
#[clap(value_parser = crate::shared::AsBString)]
524+
new_treeish: BString,
525+
/// The path to the file to run diff for.
526+
#[clap(value_parser = crate::shared::AsBString)]
527+
path: BString,
528+
},
517529
}
518530
}
519531

0 commit comments

Comments
 (0)