Skip to content

Commit b10cc6f

Browse files
authored
Merge pull request #1880 from cruessler/add-gix-diff-file
Add `gix diff file`
2 parents 77dbd4b + 1039ae9 commit b10cc6f

File tree

3 files changed

+110
-0
lines changed

3 files changed

+110
-0
lines changed

gitoxide-core/src/repository/diff.rs

+87
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
use anyhow::Context;
12
use gix::bstr::{BString, ByteSlice};
3+
use gix::diff::blob::intern::TokenSource;
4+
use gix::diff::blob::unified_diff::{ContextSize, NewlineSeparator};
5+
use gix::diff::blob::UnifiedDiff;
26
use gix::objs::tree::EntryMode;
37
use gix::odb::store::RefreshMode;
48
use gix::prelude::ObjectIdExt;
@@ -111,3 +115,86 @@ fn typed_location(mut location: BString, mode: EntryMode) -> BString {
111115
}
112116
location
113117
}
118+
119+
pub fn file(
120+
mut repo: gix::Repository,
121+
out: &mut dyn std::io::Write,
122+
old_revspec: BString,
123+
new_revspec: 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_resolved_revspec = repo.rev_parse(old_revspec.as_bstr())?;
129+
let new_resolved_revspec = repo.rev_parse(new_revspec.as_bstr())?;
130+
131+
let old_blob_id = old_resolved_revspec
132+
.single()
133+
.context(format!("rev-spec '{old_revspec}' must resolve to a single object"))?;
134+
let new_blob_id = new_resolved_revspec
135+
.single()
136+
.context(format!("rev-spec '{new_revspec}' must resolve to a single object"))?;
137+
138+
let (old_path, _) = old_resolved_revspec
139+
.path_and_mode()
140+
.context(format!("rev-spec '{old_revspec}' must contain a path"))?;
141+
let (new_path, _) = new_resolved_revspec
142+
.path_and_mode()
143+
.context(format!("rev-spec '{new_revspec}' must contain a path"))?;
144+
145+
let mut resource_cache = repo.diff_resource_cache(
146+
gix::diff::blob::pipeline::Mode::ToGitUnlessBinaryToTextIsPresent,
147+
Default::default(),
148+
)?;
149+
150+
resource_cache.set_resource(
151+
old_blob_id.into(),
152+
gix::object::tree::EntryKind::Blob,
153+
old_path,
154+
gix::diff::blob::ResourceKind::OldOrSource,
155+
&repo.objects,
156+
)?;
157+
resource_cache.set_resource(
158+
new_blob_id.into(),
159+
gix::object::tree::EntryKind::Blob,
160+
new_path,
161+
gix::diff::blob::ResourceKind::NewOrDestination,
162+
&repo.objects,
163+
)?;
164+
165+
let outcome = resource_cache.prepare_diff()?;
166+
167+
use gix::diff::blob::platform::prepare_diff::Operation;
168+
169+
let algorithm = match outcome.operation {
170+
Operation::InternalDiff { algorithm } => algorithm,
171+
Operation::ExternalCommand { .. } => {
172+
unreachable!("We disabled that")
173+
}
174+
Operation::SourceOrDestinationIsBinary => {
175+
anyhow::bail!("Source or destination is binary and we can't diff that")
176+
}
177+
};
178+
179+
let interner = gix::diff::blob::intern::InternedInput::new(
180+
tokens_for_diffing(outcome.old.data.as_slice().unwrap_or_default()),
181+
tokens_for_diffing(outcome.new.data.as_slice().unwrap_or_default()),
182+
);
183+
184+
let unified_diff = UnifiedDiff::new(
185+
&interner,
186+
String::new(),
187+
NewlineSeparator::AfterHeaderAndLine("\n"),
188+
ContextSize::symmetrical(3),
189+
);
190+
191+
let unified_diff = gix::diff::blob::diff(algorithm, &interner, unified_diff)?;
192+
193+
out.write_all(unified_diff.as_bytes())?;
194+
195+
Ok(())
196+
}
197+
198+
pub(crate) fn tokens_for_diffing(data: &[u8]) -> impl TokenSource<Token = &[u8]> {
199+
gix::diff::blob::sources::byte_lines(data)
200+
}

src/plumbing/main.rs

+14
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,20 @@ 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_revspec,
282+
new_revspec,
283+
} => prepare_and_run(
284+
"diff-file",
285+
trace,
286+
verbose,
287+
progress,
288+
progress_keep_open,
289+
None,
290+
move |_progress, out, _err| {
291+
core::repository::diff::file(repository(Mode::Lenient)?, out, old_revspec, new_revspec)
292+
},
293+
),
280294
},
281295
Subcommands::Log(crate::plumbing::options::log::Platform { pathspec }) => prepare_and_run(
282296
"log",

src/plumbing/options/mod.rs

+9
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,15 @@ 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 state of the file, like '@~100:file'
523+
#[clap(value_parser = crate::shared::AsBString)]
524+
old_revspec: BString,
525+
/// A rev-spec representing the 'after' or new state of the file, like ':file'
526+
#[clap(value_parser = crate::shared::AsBString)]
527+
new_revspec: BString,
528+
},
520529
}
521530
}
522531

0 commit comments

Comments
 (0)