Skip to content

Commit 47d4e6a

Browse files
authored
Expose git_diff_blobs API (#548)
* Generify ForeachCallbacks a bit. In particular, this makes the file callback optional as well. It also renames the struct to be a bit less specific to the foreach method; the next patch will reuse this in a different API. * Expose the git_diff_blobs API. Fixes #547 * Update diff example to allow basic diffing of blobs. This could be improved by adding color support, etc.
1 parent c42e8e9 commit 47d4e6a

File tree

3 files changed

+160
-49
lines changed

3 files changed

+160
-49
lines changed

examples/diff.rs

Lines changed: 66 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414

1515
#![deny(warnings)]
1616

17-
use git2::{Diff, DiffOptions, Error, Object, ObjectType, Repository};
18-
use git2::{DiffFindOptions, DiffFormat};
17+
use git2::{Blob, Diff, DiffOptions, Error, Object, ObjectType, Oid, Repository};
18+
use git2::{DiffDelta, DiffFindOptions, DiffFormat, DiffHunk, DiffLine};
1919
use std::str;
2020
use structopt::StructOpt;
2121

@@ -26,6 +26,9 @@ struct Args {
2626
arg_from_oid: Option<String>,
2727
#[structopt(name = "to_oid")]
2828
arg_to_oid: Option<String>,
29+
#[structopt(name = "blobs", long)]
30+
/// treat from_oid and to_oid as blob ids
31+
flag_blobs: bool,
2932
#[structopt(name = "patch", short, long)]
3033
/// show output in patch format
3134
flag_patch: bool,
@@ -137,6 +140,38 @@ enum Cache {
137140
None,
138141
}
139142

143+
fn line_color(line: &DiffLine) -> Option<&'static str> {
144+
match line.origin() {
145+
'+' => Some(GREEN),
146+
'-' => Some(RED),
147+
'>' => Some(GREEN),
148+
'<' => Some(RED),
149+
'F' => Some(BOLD),
150+
'H' => Some(CYAN),
151+
_ => None,
152+
}
153+
}
154+
155+
fn print_diff_line(
156+
_delta: DiffDelta,
157+
_hunk: Option<DiffHunk>,
158+
line: DiffLine,
159+
args: &Args,
160+
) -> bool {
161+
if args.color() {
162+
print!("{}", RESET);
163+
if let Some(color) = line_color(&line) {
164+
print!("{}", color);
165+
}
166+
}
167+
match line.origin() {
168+
'+' | '-' | ' ' => print!("{}", line.origin()),
169+
_ => {}
170+
}
171+
print!("{}", str::from_utf8(line.content()).unwrap());
172+
true
173+
}
174+
140175
fn run(args: &Args) -> Result<(), Error> {
141176
let path = args.flag_git_dir.as_ref().map(|s| &s[..]).unwrap_or(".");
142177
let repo = Repository::open(path)?;
@@ -171,6 +206,26 @@ fn run(args: &Args) -> Result<(), Error> {
171206
opts.id_abbrev(40);
172207
}
173208

209+
if args.flag_blobs {
210+
let b1 = resolve_blob(&repo, args.arg_from_oid.as_ref())?;
211+
let b2 = resolve_blob(&repo, args.arg_to_oid.as_ref())?;
212+
repo.diff_blobs(
213+
b1.as_ref(),
214+
None,
215+
b2.as_ref(),
216+
None,
217+
Some(&mut opts),
218+
None,
219+
None,
220+
None,
221+
Some(&mut |d, h, l| print_diff_line(d, h, l, args)),
222+
)?;
223+
if args.color() {
224+
print!("{}", RESET);
225+
}
226+
return Ok(());
227+
}
228+
174229
// Prepare the diff to inspect
175230
let t1 = tree_to_treeish(&repo, args.arg_from_oid.as_ref())?;
176231
let t2 = tree_to_treeish(&repo, args.arg_to_oid.as_ref())?;
@@ -220,37 +275,7 @@ fn run(args: &Args) -> Result<(), Error> {
220275
print_stats(&diff, args)?;
221276
}
222277
if args.flag_patch || !stats {
223-
if args.color() {
224-
print!("{}", RESET);
225-
}
226-
let mut last_color = None;
227-
diff.print(args.diff_format(), |_delta, _hunk, line| {
228-
if args.color() {
229-
let next = match line.origin() {
230-
'+' => Some(GREEN),
231-
'-' => Some(RED),
232-
'>' => Some(GREEN),
233-
'<' => Some(RED),
234-
'F' => Some(BOLD),
235-
'H' => Some(CYAN),
236-
_ => None,
237-
};
238-
if args.color() && next != last_color {
239-
if last_color == Some(BOLD) || next == Some(BOLD) {
240-
print!("{}", RESET);
241-
}
242-
print!("{}", next.unwrap_or(RESET));
243-
last_color = next;
244-
}
245-
}
246-
247-
match line.origin() {
248-
'+' | '-' | ' ' => print!("{}", line.origin()),
249-
_ => {}
250-
}
251-
print!("{}", str::from_utf8(line.content()).unwrap());
252-
true
253-
})?;
278+
diff.print(args.diff_format(), |d, h, l| print_diff_line(d, h, l, args))?;
254279
if args.color() {
255280
print!("{}", RESET);
256281
}
@@ -292,6 +317,14 @@ fn tree_to_treeish<'a>(
292317
Ok(Some(tree))
293318
}
294319

320+
fn resolve_blob<'a>(repo: &'a Repository, arg: Option<&String>) -> Result<Option<Blob<'a>>, Error> {
321+
let arg = match arg {
322+
Some(s) => Oid::from_str(s)?,
323+
None => return Ok(None),
324+
};
325+
repo.find_blob(arg).map(|b| Some(b))
326+
}
327+
295328
impl Args {
296329
fn cache(&self) -> Cache {
297330
if self.flag_cached {

src/diff.rs

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,11 @@ pub type BinaryCb<'a> = dyn FnMut(DiffDelta<'_>, DiffBinary<'_>) -> bool + 'a;
109109
pub type HunkCb<'a> = dyn FnMut(DiffDelta<'_>, DiffHunk<'_>) -> bool + 'a;
110110
pub type LineCb<'a> = dyn FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool + 'a;
111111

112-
struct ForeachCallbacks<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h> {
113-
file: &'a mut FileCb<'b>,
114-
binary: Option<&'c mut BinaryCb<'d>>,
115-
hunk: Option<&'e mut HunkCb<'f>>,
116-
line: Option<&'g mut LineCb<'h>>,
112+
pub struct DiffCallbacks<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h> {
113+
pub file: Option<&'a mut FileCb<'b>>,
114+
pub binary: Option<&'c mut BinaryCb<'d>>,
115+
pub hunk: Option<&'e mut HunkCb<'f>>,
116+
pub line: Option<&'g mut LineCb<'h>>,
117117
}
118118

119119
impl<'repo> Diff<'repo> {
@@ -182,8 +182,8 @@ impl<'repo> Diff<'repo> {
182182
hunk_cb: Option<&mut HunkCb<'_>>,
183183
line_cb: Option<&mut LineCb<'_>>,
184184
) -> Result<(), Error> {
185-
let mut cbs = ForeachCallbacks {
186-
file: file_cb,
185+
let mut cbs = DiffCallbacks {
186+
file: Some(file_cb),
187187
binary: binary_cb,
188188
hunk: hunk_cb,
189189
line: line_cb,
@@ -267,7 +267,7 @@ pub extern "C" fn print_cb(
267267
}
268268
}
269269

270-
extern "C" fn file_cb_c(
270+
pub extern "C" fn file_cb_c(
271271
delta: *const raw::git_diff_delta,
272272
progress: f32,
273273
data: *mut c_void,
@@ -276,8 +276,11 @@ extern "C" fn file_cb_c(
276276
let delta = Binding::from_raw(delta as *mut _);
277277

278278
let r = panic::wrap(|| {
279-
let cbs = data as *mut ForeachCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
280-
((*cbs).file)(delta, progress)
279+
let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
280+
match (*cbs).file {
281+
Some(ref mut cb) => cb(delta, progress),
282+
None => false,
283+
}
281284
});
282285
if r == Some(true) {
283286
0
@@ -287,7 +290,7 @@ extern "C" fn file_cb_c(
287290
}
288291
}
289292

290-
extern "C" fn binary_cb_c(
293+
pub extern "C" fn binary_cb_c(
291294
delta: *const raw::git_diff_delta,
292295
binary: *const raw::git_diff_binary,
293296
data: *mut c_void,
@@ -297,7 +300,7 @@ extern "C" fn binary_cb_c(
297300
let binary = Binding::from_raw(binary);
298301

299302
let r = panic::wrap(|| {
300-
let cbs = data as *mut ForeachCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
303+
let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
301304
match (*cbs).binary {
302305
Some(ref mut cb) => cb(delta, binary),
303306
None => false,
@@ -311,7 +314,7 @@ extern "C" fn binary_cb_c(
311314
}
312315
}
313316

314-
extern "C" fn hunk_cb_c(
317+
pub extern "C" fn hunk_cb_c(
315318
delta: *const raw::git_diff_delta,
316319
hunk: *const raw::git_diff_hunk,
317320
data: *mut c_void,
@@ -321,7 +324,7 @@ extern "C" fn hunk_cb_c(
321324
let hunk = Binding::from_raw(hunk);
322325

323326
let r = panic::wrap(|| {
324-
let cbs = data as *mut ForeachCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
327+
let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
325328
match (*cbs).hunk {
326329
Some(ref mut cb) => cb(delta, hunk),
327330
None => false,
@@ -335,7 +338,7 @@ extern "C" fn hunk_cb_c(
335338
}
336339
}
337340

338-
extern "C" fn line_cb_c(
341+
pub extern "C" fn line_cb_c(
339342
delta: *const raw::git_diff_delta,
340343
hunk: *const raw::git_diff_hunk,
341344
line: *const raw::git_diff_line,
@@ -347,7 +350,7 @@ extern "C" fn line_cb_c(
347350
let line = Binding::from_raw(line);
348351

349352
let r = panic::wrap(|| {
350-
let cbs = data as *mut ForeachCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
353+
let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
351354
match (*cbs).line {
352355
Some(ref mut cb) => cb(delta, hunk, line),
353356
None => false,

src/repo.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ use std::ptr;
88
use std::str;
99

1010
use crate::build::{CheckoutBuilder, RepoBuilder};
11+
use crate::diff::{
12+
binary_cb_c, file_cb_c, hunk_cb_c, line_cb_c, BinaryCb, DiffCallbacks, FileCb, HunkCb, LineCb,
13+
};
1114
use crate::oid_array::OidArray;
1215
use crate::stash::{stash_cb, StashApplyOptions, StashCbData};
1316
use crate::string_array::StringArray;
@@ -2249,6 +2252,78 @@ impl Repository {
22492252
}
22502253
}
22512254

2255+
/// Directly run a diff on two blobs.
2256+
///
2257+
/// Compared to a file, a blob lacks some contextual information. As such, the
2258+
/// `DiffFile` given to the callback will have some fake data; i.e. mode will be
2259+
/// 0 and path will be `None`.
2260+
///
2261+
/// `None` is allowed for either `old_blob` or `new_blob` and will be treated
2262+
/// as an empty blob, with the oid set to zero in the `DiffFile`. Passing `None`
2263+
/// for both blobs is a noop; no callbacks will be made at all.
2264+
///
2265+
/// We do run a binary content check on the blob content and if either blob looks
2266+
/// like binary data, the `DiffFile` binary attribute will be set to 1 and no call to
2267+
/// the `hunk_cb` nor `line_cb` will be made (unless you set the `force_text`
2268+
/// option).
2269+
pub fn diff_blobs(
2270+
&self,
2271+
old_blob: Option<&Blob<'_>>,
2272+
old_as_path: Option<&str>,
2273+
new_blob: Option<&Blob<'_>>,
2274+
new_as_path: Option<&str>,
2275+
opts: Option<&mut DiffOptions>,
2276+
file_cb: Option<&mut FileCb<'_>>,
2277+
binary_cb: Option<&mut BinaryCb<'_>>,
2278+
hunk_cb: Option<&mut HunkCb<'_>>,
2279+
line_cb: Option<&mut LineCb<'_>>,
2280+
) -> Result<(), Error> {
2281+
let old_as_path = crate::opt_cstr(old_as_path)?;
2282+
let new_as_path = crate::opt_cstr(new_as_path)?;
2283+
let mut cbs = DiffCallbacks {
2284+
file: file_cb,
2285+
binary: binary_cb,
2286+
hunk: hunk_cb,
2287+
line: line_cb,
2288+
};
2289+
let ptr = &mut cbs as *mut _;
2290+
unsafe {
2291+
let file_cb_c: raw::git_diff_file_cb = if cbs.file.is_some() {
2292+
Some(file_cb_c)
2293+
} else {
2294+
None
2295+
};
2296+
let binary_cb_c: raw::git_diff_binary_cb = if cbs.binary.is_some() {
2297+
Some(binary_cb_c)
2298+
} else {
2299+
None
2300+
};
2301+
let hunk_cb_c: raw::git_diff_hunk_cb = if cbs.hunk.is_some() {
2302+
Some(hunk_cb_c)
2303+
} else {
2304+
None
2305+
};
2306+
let line_cb_c: raw::git_diff_line_cb = if cbs.line.is_some() {
2307+
Some(line_cb_c)
2308+
} else {
2309+
None
2310+
};
2311+
try_call!(raw::git_diff_blobs(
2312+
old_blob.map(|s| s.raw()),
2313+
old_as_path,
2314+
new_blob.map(|s| s.raw()),
2315+
new_as_path,
2316+
opts.map(|s| s.raw()),
2317+
file_cb_c,
2318+
binary_cb_c,
2319+
hunk_cb_c,
2320+
line_cb_c,
2321+
ptr as *mut _
2322+
));
2323+
Ok(())
2324+
}
2325+
}
2326+
22522327
/// Create a diff with the difference between two tree objects.
22532328
///
22542329
/// This is equivalent to `git diff <old-tree> <new-tree>`

0 commit comments

Comments
 (0)