Skip to content

Commit dbb1532

Browse files
cruesslerByron
authored andcommitted
feat: add first 'debug' version of gix diff file
1 parent 77dbd4b commit dbb1532

File tree

3 files changed

+222
-1
lines changed

3 files changed

+222
-1
lines changed

Diff for: gitoxide-core/src/repository/diff.rs

+195-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
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;
7+
use gix::ObjectId;
58

69
pub fn tree(
710
mut repo: gix::Repository,
@@ -111,3 +114,194 @@ fn typed_location(mut location: BString, mode: EntryMode) -> BString {
111114
}
112115
location
113116
}
117+
118+
pub 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+
305+
pub(crate) fn tokens_for_diffing(data: &str) -> impl TokenSource<Token = &str> {
306+
gix::diff::blob::sources::lines(data)
307+
}

Diff for: src/plumbing/main.rs

+15
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",

Diff for: src/plumbing/options/mod.rs

+12
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,18 @@ pub mod diff {
517517
#[clap(value_parser = crate::shared::AsBString)]
518518
new_treeish: BString,
519519
},
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+
},
520532
}
521533
}
522534

0 commit comments

Comments
 (0)