Skip to content

Expose git_diff_blobs API #548

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 66 additions & 33 deletions examples/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@

#![deny(warnings)]

use git2::{Diff, DiffOptions, Error, Object, ObjectType, Repository};
use git2::{DiffFindOptions, DiffFormat};
use git2::{Blob, Diff, DiffOptions, Error, Object, ObjectType, Oid, Repository};
use git2::{DiffDelta, DiffFindOptions, DiffFormat, DiffHunk, DiffLine};
use std::str;
use structopt::StructOpt;

Expand All @@ -26,6 +26,9 @@ struct Args {
arg_from_oid: Option<String>,
#[structopt(name = "to_oid")]
arg_to_oid: Option<String>,
#[structopt(name = "blobs", long)]
/// treat from_oid and to_oid as blob ids
flag_blobs: bool,
#[structopt(name = "patch", short, long)]
/// show output in patch format
flag_patch: bool,
Expand Down Expand Up @@ -137,6 +140,38 @@ enum Cache {
None,
}

fn line_color(line: &DiffLine) -> Option<&'static str> {
match line.origin() {
'+' => Some(GREEN),
'-' => Some(RED),
'>' => Some(GREEN),
'<' => Some(RED),
'F' => Some(BOLD),
'H' => Some(CYAN),
_ => None,
}
}

fn print_diff_line(
_delta: DiffDelta,
_hunk: Option<DiffHunk>,
line: DiffLine,
args: &Args,
) -> bool {
if args.color() {
print!("{}", RESET);
if let Some(color) = line_color(&line) {
print!("{}", color);
}
}
match line.origin() {
'+' | '-' | ' ' => print!("{}", line.origin()),
_ => {}
}
print!("{}", str::from_utf8(line.content()).unwrap());
true
}

fn run(args: &Args) -> Result<(), Error> {
let path = args.flag_git_dir.as_ref().map(|s| &s[..]).unwrap_or(".");
let repo = Repository::open(path)?;
Expand Down Expand Up @@ -171,6 +206,26 @@ fn run(args: &Args) -> Result<(), Error> {
opts.id_abbrev(40);
}

if args.flag_blobs {
let b1 = resolve_blob(&repo, args.arg_from_oid.as_ref())?;
let b2 = resolve_blob(&repo, args.arg_to_oid.as_ref())?;
repo.diff_blobs(
b1.as_ref(),
None,
b2.as_ref(),
None,
Some(&mut opts),
None,
None,
None,
Some(&mut |d, h, l| print_diff_line(d, h, l, args)),
)?;
if args.color() {
print!("{}", RESET);
}
return Ok(());
}

// Prepare the diff to inspect
let t1 = tree_to_treeish(&repo, args.arg_from_oid.as_ref())?;
let t2 = tree_to_treeish(&repo, args.arg_to_oid.as_ref())?;
Expand Down Expand Up @@ -220,37 +275,7 @@ fn run(args: &Args) -> Result<(), Error> {
print_stats(&diff, args)?;
}
if args.flag_patch || !stats {
if args.color() {
print!("{}", RESET);
}
let mut last_color = None;
diff.print(args.diff_format(), |_delta, _hunk, line| {
if args.color() {
let next = match line.origin() {
'+' => Some(GREEN),
'-' => Some(RED),
'>' => Some(GREEN),
'<' => Some(RED),
'F' => Some(BOLD),
'H' => Some(CYAN),
_ => None,
};
if args.color() && next != last_color {
if last_color == Some(BOLD) || next == Some(BOLD) {
print!("{}", RESET);
}
print!("{}", next.unwrap_or(RESET));
last_color = next;
}
}

match line.origin() {
'+' | '-' | ' ' => print!("{}", line.origin()),
_ => {}
}
print!("{}", str::from_utf8(line.content()).unwrap());
true
})?;
diff.print(args.diff_format(), |d, h, l| print_diff_line(d, h, l, args))?;
if args.color() {
print!("{}", RESET);
}
Expand Down Expand Up @@ -292,6 +317,14 @@ fn tree_to_treeish<'a>(
Ok(Some(tree))
}

fn resolve_blob<'a>(repo: &'a Repository, arg: Option<&String>) -> Result<Option<Blob<'a>>, Error> {
let arg = match arg {
Some(s) => Oid::from_str(s)?,
None => return Ok(None),
};
repo.find_blob(arg).map(|b| Some(b))
}

impl Args {
fn cache(&self) -> Cache {
if self.flag_cached {
Expand Down
35 changes: 19 additions & 16 deletions src/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,11 @@ pub type BinaryCb<'a> = dyn FnMut(DiffDelta<'_>, DiffBinary<'_>) -> bool + 'a;
pub type HunkCb<'a> = dyn FnMut(DiffDelta<'_>, DiffHunk<'_>) -> bool + 'a;
pub type LineCb<'a> = dyn FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool + 'a;

struct ForeachCallbacks<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h> {
file: &'a mut FileCb<'b>,
binary: Option<&'c mut BinaryCb<'d>>,
hunk: Option<&'e mut HunkCb<'f>>,
line: Option<&'g mut LineCb<'h>>,
pub struct DiffCallbacks<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h> {
pub file: Option<&'a mut FileCb<'b>>,
pub binary: Option<&'c mut BinaryCb<'d>>,
pub hunk: Option<&'e mut HunkCb<'f>>,
pub line: Option<&'g mut LineCb<'h>>,
}

impl<'repo> Diff<'repo> {
Expand Down Expand Up @@ -182,8 +182,8 @@ impl<'repo> Diff<'repo> {
hunk_cb: Option<&mut HunkCb<'_>>,
line_cb: Option<&mut LineCb<'_>>,
) -> Result<(), Error> {
let mut cbs = ForeachCallbacks {
file: file_cb,
let mut cbs = DiffCallbacks {
file: Some(file_cb),
binary: binary_cb,
hunk: hunk_cb,
line: line_cb,
Expand Down Expand Up @@ -267,7 +267,7 @@ pub extern "C" fn print_cb(
}
}

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

let r = panic::wrap(|| {
let cbs = data as *mut ForeachCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
((*cbs).file)(delta, progress)
let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
match (*cbs).file {
Some(ref mut cb) => cb(delta, progress),
None => false,
}
});
if r == Some(true) {
0
Expand All @@ -287,7 +290,7 @@ extern "C" fn file_cb_c(
}
}

extern "C" fn binary_cb_c(
pub extern "C" fn binary_cb_c(
delta: *const raw::git_diff_delta,
binary: *const raw::git_diff_binary,
data: *mut c_void,
Expand All @@ -297,7 +300,7 @@ extern "C" fn binary_cb_c(
let binary = Binding::from_raw(binary);

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

extern "C" fn hunk_cb_c(
pub extern "C" fn hunk_cb_c(
delta: *const raw::git_diff_delta,
hunk: *const raw::git_diff_hunk,
data: *mut c_void,
Expand All @@ -321,7 +324,7 @@ extern "C" fn hunk_cb_c(
let hunk = Binding::from_raw(hunk);

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

extern "C" fn line_cb_c(
pub extern "C" fn line_cb_c(
delta: *const raw::git_diff_delta,
hunk: *const raw::git_diff_hunk,
line: *const raw::git_diff_line,
Expand All @@ -347,7 +350,7 @@ extern "C" fn line_cb_c(
let line = Binding::from_raw(line);

let r = panic::wrap(|| {
let cbs = data as *mut ForeachCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
match (*cbs).line {
Some(ref mut cb) => cb(delta, hunk, line),
None => false,
Expand Down
75 changes: 75 additions & 0 deletions src/repo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ use std::ptr;
use std::str;

use crate::build::{CheckoutBuilder, RepoBuilder};
use crate::diff::{
binary_cb_c, file_cb_c, hunk_cb_c, line_cb_c, BinaryCb, DiffCallbacks, FileCb, HunkCb, LineCb,
};
use crate::oid_array::OidArray;
use crate::stash::{stash_cb, StashApplyOptions, StashCbData};
use crate::string_array::StringArray;
Expand Down Expand Up @@ -2249,6 +2252,78 @@ impl Repository {
}
}

/// Directly run a diff on two blobs.
///
/// Compared to a file, a blob lacks some contextual information. As such, the
/// `DiffFile` given to the callback will have some fake data; i.e. mode will be
/// 0 and path will be `None`.
///
/// `None` is allowed for either `old_blob` or `new_blob` and will be treated
/// as an empty blob, with the oid set to zero in the `DiffFile`. Passing `None`
/// for both blobs is a noop; no callbacks will be made at all.
///
/// We do run a binary content check on the blob content and if either blob looks
/// like binary data, the `DiffFile` binary attribute will be set to 1 and no call to
/// the `hunk_cb` nor `line_cb` will be made (unless you set the `force_text`
/// option).
pub fn diff_blobs(
&self,
old_blob: Option<&Blob<'_>>,
old_as_path: Option<&str>,
new_blob: Option<&Blob<'_>>,
new_as_path: Option<&str>,
opts: Option<&mut DiffOptions>,
file_cb: Option<&mut FileCb<'_>>,
binary_cb: Option<&mut BinaryCb<'_>>,
hunk_cb: Option<&mut HunkCb<'_>>,
line_cb: Option<&mut LineCb<'_>>,
) -> Result<(), Error> {
let old_as_path = crate::opt_cstr(old_as_path)?;
let new_as_path = crate::opt_cstr(new_as_path)?;
let mut cbs = DiffCallbacks {
file: file_cb,
binary: binary_cb,
hunk: hunk_cb,
line: line_cb,
};
let ptr = &mut cbs as *mut _;
unsafe {
let file_cb_c: raw::git_diff_file_cb = if cbs.file.is_some() {
Some(file_cb_c)
} else {
None
};
let binary_cb_c: raw::git_diff_binary_cb = if cbs.binary.is_some() {
Some(binary_cb_c)
} else {
None
};
let hunk_cb_c: raw::git_diff_hunk_cb = if cbs.hunk.is_some() {
Some(hunk_cb_c)
} else {
None
};
let line_cb_c: raw::git_diff_line_cb = if cbs.line.is_some() {
Some(line_cb_c)
} else {
None
};
try_call!(raw::git_diff_blobs(
old_blob.map(|s| s.raw()),
old_as_path,
new_blob.map(|s| s.raw()),
new_as_path,
opts.map(|s| s.raw()),
file_cb_c,
binary_cb_c,
hunk_cb_c,
line_cb_c,
ptr as *mut _
));
Ok(())
}
}

/// Create a diff with the difference between two tree objects.
///
/// This is equivalent to `git diff <old-tree> <new-tree>`
Expand Down