use libc::{c_char, c_int, c_void, size_t};
use std::ffi::CString;
use std::iter::FusedIterator;
use std::marker;
use std::mem;
use std::ops::Range;
use std::path::Path;
use std::ptr;
use std::slice;
use crate::util::{self, Binding};
use crate::{panic, raw, Buf, Delta, DiffFormat, Error, FileMode, Oid, Repository};
use crate::{DiffFlags, DiffStatsFormat, IntoCString};
pub struct Diff<'repo> {
raw: *mut raw::git_diff,
_marker: marker::PhantomData<&'repo Repository>,
}
unsafe impl<'repo> Send for Diff<'repo> {}
pub struct DiffDelta<'a> {
raw: *mut raw::git_diff_delta,
_marker: marker::PhantomData<&'a raw::git_diff_delta>,
}
pub struct DiffFile<'a> {
raw: *const raw::git_diff_file,
_marker: marker::PhantomData<&'a raw::git_diff_file>,
}
pub struct DiffOptions {
pathspec: Vec<CString>,
pathspec_ptrs: Vec<*const c_char>,
old_prefix: Option<CString>,
new_prefix: Option<CString>,
raw: raw::git_diff_options,
}
pub struct DiffFindOptions {
raw: raw::git_diff_find_options,
}
pub struct DiffFormatEmailOptions {
raw: raw::git_diff_format_email_options,
}
pub struct DiffPatchidOptions {
raw: raw::git_diff_patchid_options,
}
pub struct Deltas<'diff> {
range: Range<usize>,
diff: &'diff Diff<'diff>,
}
pub struct DiffLine<'a> {
raw: *const raw::git_diff_line,
_marker: marker::PhantomData<&'a raw::git_diff_line>,
}
pub struct DiffHunk<'a> {
raw: *const raw::git_diff_hunk,
_marker: marker::PhantomData<&'a raw::git_diff_hunk>,
}
pub struct DiffStats {
raw: *mut raw::git_diff_stats,
}
pub struct DiffBinary<'a> {
raw: *const raw::git_diff_binary,
_marker: marker::PhantomData<&'a raw::git_diff_binary>,
}
pub struct DiffBinaryFile<'a> {
raw: *const raw::git_diff_binary_file,
_marker: marker::PhantomData<&'a raw::git_diff_binary_file>,
}
#[derive(Copy, Clone, Debug)]
pub enum DiffBinaryKind {
None,
Literal,
Delta,
}
type PrintCb<'a> = dyn FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool + 'a;
pub type FileCb<'a> = dyn FnMut(DiffDelta<'_>, f32) -> bool + 'a;
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;
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> {
pub fn merge(&mut self, from: &Diff<'repo>) -> Result<(), Error> {
unsafe {
try_call!(raw::git_diff_merge(self.raw, &*from.raw));
}
Ok(())
}
pub fn deltas(&self) -> Deltas<'_> {
let num_deltas = unsafe { raw::git_diff_num_deltas(&*self.raw) };
Deltas {
range: 0..(num_deltas as usize),
diff: self,
}
}
pub fn get_delta(&self, i: usize) -> Option<DiffDelta<'_>> {
unsafe {
let ptr = raw::git_diff_get_delta(&*self.raw, i as size_t);
Binding::from_raw_opt(ptr as *mut _)
}
}
pub fn is_sorted_icase(&self) -> bool {
unsafe { raw::git_diff_is_sorted_icase(&*self.raw) == 1 }
}
pub fn print<F>(&self, format: DiffFormat, mut cb: F) -> Result<(), Error>
where
F: FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool,
{
let mut cb: &mut PrintCb<'_> = &mut cb;
let ptr = &mut cb as *mut _;
let print: raw::git_diff_line_cb = Some(print_cb);
unsafe {
try_call!(raw::git_diff_print(self.raw, format, print, ptr as *mut _));
Ok(())
}
}
pub fn foreach(
&self,
file_cb: &mut FileCb<'_>,
binary_cb: Option<&mut BinaryCb<'_>>,
hunk_cb: Option<&mut HunkCb<'_>>,
line_cb: Option<&mut LineCb<'_>>,
) -> Result<(), Error> {
let mut cbs = DiffCallbacks {
file: Some(file_cb),
binary: binary_cb,
hunk: hunk_cb,
line: line_cb,
};
let ptr = &mut cbs as *mut _;
unsafe {
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
};
let file_cb: raw::git_diff_file_cb = Some(file_cb_c);
try_call!(raw::git_diff_foreach(
self.raw,
file_cb,
binary_cb_c,
hunk_cb_c,
line_cb_c,
ptr as *mut _
));
Ok(())
}
}
pub fn stats(&self) -> Result<DiffStats, Error> {
let mut ret = ptr::null_mut();
unsafe {
try_call!(raw::git_diff_get_stats(&mut ret, self.raw));
Ok(Binding::from_raw(ret))
}
}
pub fn find_similar(&mut self, opts: Option<&mut DiffFindOptions>) -> Result<(), Error> {
let opts = opts.map(|opts| &opts.raw);
unsafe {
try_call!(raw::git_diff_find_similar(self.raw, opts));
}
Ok(())
}
#[doc(hidden)]
#[deprecated(note = "refactored to `Email::from_diff` to match upstream")]
pub fn format_email(
&mut self,
patch_no: usize,
total_patches: usize,
commit: &crate::Commit<'repo>,
opts: Option<&mut DiffFormatEmailOptions>,
) -> Result<Buf, Error> {
assert!(patch_no > 0);
assert!(patch_no <= total_patches);
let mut default = DiffFormatEmailOptions::default();
let raw_opts = opts.map_or(&mut default.raw, |opts| &mut opts.raw);
let summary = commit.summary_bytes().unwrap();
let mut message = commit.message_bytes();
assert!(message.starts_with(summary));
message = &message[summary.len()..];
raw_opts.patch_no = patch_no;
raw_opts.total_patches = total_patches;
let id = commit.id();
raw_opts.id = id.raw();
raw_opts.summary = summary.as_ptr() as *const _;
raw_opts.body = message.as_ptr() as *const _;
raw_opts.author = commit.author().raw();
let buf = Buf::new();
#[allow(deprecated)]
unsafe {
try_call!(raw::git_diff_format_email(buf.raw(), self.raw, &*raw_opts));
}
Ok(buf)
}
pub fn patchid(&self, opts: Option<&mut DiffPatchidOptions>) -> Result<Oid, Error> {
let mut raw = raw::git_oid {
id: [0; raw::GIT_OID_RAWSZ],
};
unsafe {
try_call!(raw::git_diff_patchid(
&mut raw,
self.raw,
opts.map(|o| &mut o.raw)
));
Ok(Binding::from_raw(&raw as *const _))
}
}
}
impl Diff<'static> {
pub fn from_buffer(buffer: &[u8]) -> Result<Diff<'static>, Error> {
crate::init();
let mut diff: *mut raw::git_diff = std::ptr::null_mut();
unsafe {
try_call!(raw::git_diff_from_buffer(
&mut diff,
buffer.as_ptr() as *const c_char,
buffer.len()
));
Ok(Diff::from_raw(diff))
}
}
}
pub extern "C" fn print_cb(
delta: *const raw::git_diff_delta,
hunk: *const raw::git_diff_hunk,
line: *const raw::git_diff_line,
data: *mut c_void,
) -> c_int {
unsafe {
let delta = Binding::from_raw(delta as *mut _);
let hunk = Binding::from_raw_opt(hunk);
let line = Binding::from_raw(line);
let r = panic::wrap(|| {
let data = data as *mut &mut PrintCb<'_>;
(*data)(delta, hunk, line)
});
if r == Some(true) {
raw::GIT_OK
} else {
raw::GIT_EUSER
}
}
}
pub extern "C" fn file_cb_c(
delta: *const raw::git_diff_delta,
progress: f32,
data: *mut c_void,
) -> c_int {
unsafe {
let delta = Binding::from_raw(delta as *mut _);
let r = panic::wrap(|| {
let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
match (*cbs).file {
Some(ref mut cb) => cb(delta, progress),
None => false,
}
});
if r == Some(true) {
raw::GIT_OK
} else {
raw::GIT_EUSER
}
}
}
pub extern "C" fn binary_cb_c(
delta: *const raw::git_diff_delta,
binary: *const raw::git_diff_binary,
data: *mut c_void,
) -> c_int {
unsafe {
let delta = Binding::from_raw(delta as *mut _);
let binary = Binding::from_raw(binary);
let r = panic::wrap(|| {
let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
match (*cbs).binary {
Some(ref mut cb) => cb(delta, binary),
None => false,
}
});
if r == Some(true) {
raw::GIT_OK
} else {
raw::GIT_EUSER
}
}
}
pub extern "C" fn hunk_cb_c(
delta: *const raw::git_diff_delta,
hunk: *const raw::git_diff_hunk,
data: *mut c_void,
) -> c_int {
unsafe {
let delta = Binding::from_raw(delta as *mut _);
let hunk = Binding::from_raw(hunk);
let r = panic::wrap(|| {
let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
match (*cbs).hunk {
Some(ref mut cb) => cb(delta, hunk),
None => false,
}
});
if r == Some(true) {
raw::GIT_OK
} else {
raw::GIT_EUSER
}
}
}
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,
data: *mut c_void,
) -> c_int {
unsafe {
let delta = Binding::from_raw(delta as *mut _);
let hunk = Binding::from_raw_opt(hunk);
let line = Binding::from_raw(line);
let r = panic::wrap(|| {
let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
match (*cbs).line {
Some(ref mut cb) => cb(delta, hunk, line),
None => false,
}
});
if r == Some(true) {
raw::GIT_OK
} else {
raw::GIT_EUSER
}
}
}
impl<'repo> Binding for Diff<'repo> {
type Raw = *mut raw::git_diff;
unsafe fn from_raw(raw: *mut raw::git_diff) -> Diff<'repo> {
Diff {
raw,
_marker: marker::PhantomData,
}
}
fn raw(&self) -> *mut raw::git_diff {
self.raw
}
}
impl<'repo> Drop for Diff<'repo> {
fn drop(&mut self) {
unsafe { raw::git_diff_free(self.raw) }
}
}
impl<'a> DiffDelta<'a> {
pub fn flags(&self) -> DiffFlags {
let flags = unsafe { (*self.raw).flags };
let mut result = DiffFlags::empty();
#[cfg(target_env = "msvc")]
fn as_u32(flag: i32) -> u32 {
flag as u32
}
#[cfg(not(target_env = "msvc"))]
fn as_u32(flag: u32) -> u32 {
flag
}
if (flags & as_u32(raw::GIT_DIFF_FLAG_BINARY)) != 0 {
result |= DiffFlags::BINARY;
}
if (flags & as_u32(raw::GIT_DIFF_FLAG_NOT_BINARY)) != 0 {
result |= DiffFlags::NOT_BINARY;
}
if (flags & as_u32(raw::GIT_DIFF_FLAG_VALID_ID)) != 0 {
result |= DiffFlags::VALID_ID;
}
if (flags & as_u32(raw::GIT_DIFF_FLAG_EXISTS)) != 0 {
result |= DiffFlags::EXISTS;
}
result
}
pub fn nfiles(&self) -> u16 {
unsafe { (*self.raw).nfiles }
}
pub fn status(&self) -> Delta {
match unsafe { (*self.raw).status } {
raw::GIT_DELTA_UNMODIFIED => Delta::Unmodified,
raw::GIT_DELTA_ADDED => Delta::Added,
raw::GIT_DELTA_DELETED => Delta::Deleted,
raw::GIT_DELTA_MODIFIED => Delta::Modified,
raw::GIT_DELTA_RENAMED => Delta::Renamed,
raw::GIT_DELTA_COPIED => Delta::Copied,
raw::GIT_DELTA_IGNORED => Delta::Ignored,
raw::GIT_DELTA_UNTRACKED => Delta::Untracked,
raw::GIT_DELTA_TYPECHANGE => Delta::Typechange,
raw::GIT_DELTA_UNREADABLE => Delta::Unreadable,
raw::GIT_DELTA_CONFLICTED => Delta::Conflicted,
n => panic!("unknown diff status: {}", n),
}
}
pub fn old_file(&self) -> DiffFile<'a> {
unsafe { Binding::from_raw(&(*self.raw).old_file as *const _) }
}
pub fn new_file(&self) -> DiffFile<'a> {
unsafe { Binding::from_raw(&(*self.raw).new_file as *const _) }
}
}
impl<'a> Binding for DiffDelta<'a> {
type Raw = *mut raw::git_diff_delta;
unsafe fn from_raw(raw: *mut raw::git_diff_delta) -> DiffDelta<'a> {
DiffDelta {
raw,
_marker: marker::PhantomData,
}
}
fn raw(&self) -> *mut raw::git_diff_delta {
self.raw
}
}
impl<'a> std::fmt::Debug for DiffDelta<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.debug_struct("DiffDelta")
.field("nfiles", &self.nfiles())
.field("status", &self.status())
.field("old_file", &self.old_file())
.field("new_file", &self.new_file())
.finish()
}
}
impl<'a> DiffFile<'a> {
pub fn id(&self) -> Oid {
unsafe { Binding::from_raw(&(*self.raw).id as *const _) }
}
pub fn path_bytes(&self) -> Option<&'a [u8]> {
static FOO: () = ();
unsafe { crate::opt_bytes(&FOO, (*self.raw).path) }
}
pub fn path(&self) -> Option<&'a Path> {
self.path_bytes().map(util::bytes2path)
}
pub fn size(&self) -> u64 {
unsafe { (*self.raw).size as u64 }
}
pub fn is_binary(&self) -> bool {
unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_BINARY as u32 != 0 }
}
pub fn is_not_binary(&self) -> bool {
unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_NOT_BINARY as u32 != 0 }
}
pub fn is_valid_id(&self) -> bool {
unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_VALID_ID as u32 != 0 }
}
pub fn exists(&self) -> bool {
unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_EXISTS as u32 != 0 }
}
pub fn mode(&self) -> FileMode {
match unsafe { (*self.raw).mode.into() } {
raw::GIT_FILEMODE_UNREADABLE => FileMode::Unreadable,
raw::GIT_FILEMODE_TREE => FileMode::Tree,
raw::GIT_FILEMODE_BLOB => FileMode::Blob,
raw::GIT_FILEMODE_BLOB_GROUP_WRITABLE => FileMode::BlobGroupWritable,
raw::GIT_FILEMODE_BLOB_EXECUTABLE => FileMode::BlobExecutable,
raw::GIT_FILEMODE_LINK => FileMode::Link,
raw::GIT_FILEMODE_COMMIT => FileMode::Commit,
mode => panic!("unknown mode: {}", mode),
}
}
}
impl<'a> Binding for DiffFile<'a> {
type Raw = *const raw::git_diff_file;
unsafe fn from_raw(raw: *const raw::git_diff_file) -> DiffFile<'a> {
DiffFile {
raw,
_marker: marker::PhantomData,
}
}
fn raw(&self) -> *const raw::git_diff_file {
self.raw
}
}
impl<'a> std::fmt::Debug for DiffFile<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
let mut ds = f.debug_struct("DiffFile");
ds.field("id", &self.id());
if let Some(path_bytes) = &self.path_bytes() {
ds.field("path_bytes", path_bytes);
}
if let Some(path) = &self.path() {
ds.field("path", path);
}
ds.field("size", &self.size()).finish()
}
}
impl Default for DiffOptions {
fn default() -> Self {
Self::new()
}
}
impl DiffOptions {
pub fn new() -> DiffOptions {
let mut opts = DiffOptions {
pathspec: Vec::new(),
pathspec_ptrs: Vec::new(),
raw: unsafe { mem::zeroed() },
old_prefix: None,
new_prefix: None,
};
assert_eq!(unsafe { raw::git_diff_init_options(&mut opts.raw, 1) }, 0);
opts
}
fn flag(&mut self, opt: raw::git_diff_option_t, val: bool) -> &mut DiffOptions {
let opt = opt as u32;
if val {
self.raw.flags |= opt;
} else {
self.raw.flags &= !opt;
}
self
}
pub fn reverse(&mut self, reverse: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_REVERSE, reverse)
}
pub fn include_ignored(&mut self, include: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_INCLUDE_IGNORED, include)
}
pub fn recurse_ignored_dirs(&mut self, recurse: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_RECURSE_IGNORED_DIRS, recurse)
}
pub fn include_untracked(&mut self, include: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_INCLUDE_UNTRACKED, include)
}
pub fn recurse_untracked_dirs(&mut self, recurse: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_RECURSE_UNTRACKED_DIRS, recurse)
}
pub fn include_unmodified(&mut self, include: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_INCLUDE_UNMODIFIED, include)
}
pub fn include_typechange(&mut self, include: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_INCLUDE_TYPECHANGE, include)
}
pub fn include_typechange_trees(&mut self, include: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_INCLUDE_TYPECHANGE_TREES, include)
}
pub fn ignore_filemode(&mut self, ignore: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_IGNORE_FILEMODE, ignore)
}
pub fn ignore_submodules(&mut self, ignore: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_IGNORE_SUBMODULES, ignore)
}
pub fn ignore_case(&mut self, ignore: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_IGNORE_CASE, ignore)
}
pub fn disable_pathspec_match(&mut self, disable: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_DISABLE_PATHSPEC_MATCH, disable)
}
pub fn skip_binary_check(&mut self, skip: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_SKIP_BINARY_CHECK, skip)
}
pub fn enable_fast_untracked_dirs(&mut self, enable: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS, enable)
}
pub fn update_index(&mut self, update: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_UPDATE_INDEX, update)
}
pub fn include_unreadable(&mut self, include: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_INCLUDE_UNREADABLE, include)
}
pub fn include_unreadable_as_untracked(&mut self, include: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED, include)
}
pub fn force_text(&mut self, force: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_FORCE_TEXT, force)
}
pub fn force_binary(&mut self, force: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_FORCE_BINARY, force)
}
pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE, ignore)
}
pub fn ignore_whitespace_change(&mut self, ignore: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE_CHANGE, ignore)
}
pub fn ignore_whitespace_eol(&mut self, ignore: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE_EOL, ignore)
}
pub fn ignore_blank_lines(&mut self, ignore: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_IGNORE_BLANK_LINES, ignore)
}
pub fn show_untracked_content(&mut self, show: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_SHOW_UNTRACKED_CONTENT, show)
}
pub fn show_unmodified(&mut self, show: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_SHOW_UNMODIFIED, show)
}
pub fn patience(&mut self, patience: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_PATIENCE, patience)
}
pub fn minimal(&mut self, minimal: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_MINIMAL, minimal)
}
pub fn show_binary(&mut self, show: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_SHOW_BINARY, show)
}
pub fn indent_heuristic(&mut self, heuristic: bool) -> &mut DiffOptions {
self.flag(raw::GIT_DIFF_INDENT_HEURISTIC, heuristic)
}
pub fn context_lines(&mut self, lines: u32) -> &mut DiffOptions {
self.raw.context_lines = lines;
self
}
pub fn interhunk_lines(&mut self, lines: u32) -> &mut DiffOptions {
self.raw.interhunk_lines = lines;
self
}
pub fn id_abbrev(&mut self, abbrev: u16) -> &mut DiffOptions {
self.raw.id_abbrev = abbrev;
self
}
pub fn max_size(&mut self, size: i64) -> &mut DiffOptions {
self.raw.max_size = size as raw::git_off_t;
self
}
pub fn old_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions {
self.old_prefix = Some(t.into_c_string().unwrap());
self
}
pub fn new_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions {
self.new_prefix = Some(t.into_c_string().unwrap());
self
}
pub fn pathspec<T: IntoCString>(&mut self, pathspec: T) -> &mut DiffOptions {
let s = util::cstring_to_repo_path(pathspec).unwrap();
self.pathspec_ptrs.push(s.as_ptr());
self.pathspec.push(s);
self
}
pub unsafe fn raw(&mut self) -> *const raw::git_diff_options {
self.raw.old_prefix = self
.old_prefix
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(ptr::null());
self.raw.new_prefix = self
.new_prefix
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(ptr::null());
self.raw.pathspec.count = self.pathspec_ptrs.len() as size_t;
self.raw.pathspec.strings = self.pathspec_ptrs.as_ptr() as *mut _;
&self.raw as *const _
}
}
impl<'diff> Iterator for Deltas<'diff> {
type Item = DiffDelta<'diff>;
fn next(&mut self) -> Option<DiffDelta<'diff>> {
self.range.next().and_then(|i| self.diff.get_delta(i))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.range.size_hint()
}
}
impl<'diff> DoubleEndedIterator for Deltas<'diff> {
fn next_back(&mut self) -> Option<DiffDelta<'diff>> {
self.range.next_back().and_then(|i| self.diff.get_delta(i))
}
}
impl<'diff> FusedIterator for Deltas<'diff> {}
impl<'diff> ExactSizeIterator for Deltas<'diff> {}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum DiffLineType {
Context,
Addition,
Deletion,
ContextEOFNL,
AddEOFNL,
DeleteEOFNL,
FileHeader,
HunkHeader,
Binary,
}
impl Binding for DiffLineType {
type Raw = raw::git_diff_line_t;
unsafe fn from_raw(raw: raw::git_diff_line_t) -> Self {
match raw {
raw::GIT_DIFF_LINE_CONTEXT => DiffLineType::Context,
raw::GIT_DIFF_LINE_ADDITION => DiffLineType::Addition,
raw::GIT_DIFF_LINE_DELETION => DiffLineType::Deletion,
raw::GIT_DIFF_LINE_CONTEXT_EOFNL => DiffLineType::ContextEOFNL,
raw::GIT_DIFF_LINE_ADD_EOFNL => DiffLineType::AddEOFNL,
raw::GIT_DIFF_LINE_DEL_EOFNL => DiffLineType::DeleteEOFNL,
raw::GIT_DIFF_LINE_FILE_HDR => DiffLineType::FileHeader,
raw::GIT_DIFF_LINE_HUNK_HDR => DiffLineType::HunkHeader,
raw::GIT_DIFF_LINE_BINARY => DiffLineType::Binary,
_ => panic!("Unknown git diff line type"),
}
}
fn raw(&self) -> raw::git_diff_line_t {
match *self {
DiffLineType::Context => raw::GIT_DIFF_LINE_CONTEXT,
DiffLineType::Addition => raw::GIT_DIFF_LINE_ADDITION,
DiffLineType::Deletion => raw::GIT_DIFF_LINE_DELETION,
DiffLineType::ContextEOFNL => raw::GIT_DIFF_LINE_CONTEXT_EOFNL,
DiffLineType::AddEOFNL => raw::GIT_DIFF_LINE_ADD_EOFNL,
DiffLineType::DeleteEOFNL => raw::GIT_DIFF_LINE_DEL_EOFNL,
DiffLineType::FileHeader => raw::GIT_DIFF_LINE_FILE_HDR,
DiffLineType::HunkHeader => raw::GIT_DIFF_LINE_HUNK_HDR,
DiffLineType::Binary => raw::GIT_DIFF_LINE_BINARY,
}
}
}
impl<'a> DiffLine<'a> {
pub fn old_lineno(&self) -> Option<u32> {
match unsafe { (*self.raw).old_lineno } {
n if n < 0 => None,
n => Some(n as u32),
}
}
pub fn new_lineno(&self) -> Option<u32> {
match unsafe { (*self.raw).new_lineno } {
n if n < 0 => None,
n => Some(n as u32),
}
}
pub fn num_lines(&self) -> u32 {
unsafe { (*self.raw).num_lines as u32 }
}
pub fn content_offset(&self) -> i64 {
unsafe { (*self.raw).content_offset as i64 }
}
pub fn content(&self) -> &'a [u8] {
unsafe {
slice::from_raw_parts(
(*self.raw).content as *const u8,
(*self.raw).content_len as usize,
)
}
}
pub fn origin_value(&self) -> DiffLineType {
unsafe { Binding::from_raw((*self.raw).origin as raw::git_diff_line_t) }
}
pub fn origin(&self) -> char {
match unsafe { (*self.raw).origin as raw::git_diff_line_t } {
raw::GIT_DIFF_LINE_CONTEXT => ' ',
raw::GIT_DIFF_LINE_ADDITION => '+',
raw::GIT_DIFF_LINE_DELETION => '-',
raw::GIT_DIFF_LINE_CONTEXT_EOFNL => '=',
raw::GIT_DIFF_LINE_ADD_EOFNL => '>',
raw::GIT_DIFF_LINE_DEL_EOFNL => '<',
raw::GIT_DIFF_LINE_FILE_HDR => 'F',
raw::GIT_DIFF_LINE_HUNK_HDR => 'H',
raw::GIT_DIFF_LINE_BINARY => 'B',
_ => ' ',
}
}
}
impl<'a> Binding for DiffLine<'a> {
type Raw = *const raw::git_diff_line;
unsafe fn from_raw(raw: *const raw::git_diff_line) -> DiffLine<'a> {
DiffLine {
raw,
_marker: marker::PhantomData,
}
}
fn raw(&self) -> *const raw::git_diff_line {
self.raw
}
}
impl<'a> std::fmt::Debug for DiffLine<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
let mut ds = f.debug_struct("DiffLine");
if let Some(old_lineno) = &self.old_lineno() {
ds.field("old_lineno", old_lineno);
}
if let Some(new_lineno) = &self.new_lineno() {
ds.field("new_lineno", new_lineno);
}
ds.field("num_lines", &self.num_lines())
.field("content_offset", &self.content_offset())
.field("content", &self.content())
.field("origin", &self.origin())
.finish()
}
}
impl<'a> DiffHunk<'a> {
pub fn old_start(&self) -> u32 {
unsafe { (*self.raw).old_start as u32 }
}
pub fn old_lines(&self) -> u32 {
unsafe { (*self.raw).old_lines as u32 }
}
pub fn new_start(&self) -> u32 {
unsafe { (*self.raw).new_start as u32 }
}
pub fn new_lines(&self) -> u32 {
unsafe { (*self.raw).new_lines as u32 }
}
pub fn header(&self) -> &'a [u8] {
unsafe {
slice::from_raw_parts(
(*self.raw).header.as_ptr() as *const u8,
(*self.raw).header_len as usize,
)
}
}
}
impl<'a> Binding for DiffHunk<'a> {
type Raw = *const raw::git_diff_hunk;
unsafe fn from_raw(raw: *const raw::git_diff_hunk) -> DiffHunk<'a> {
DiffHunk {
raw,
_marker: marker::PhantomData,
}
}
fn raw(&self) -> *const raw::git_diff_hunk {
self.raw
}
}
impl<'a> std::fmt::Debug for DiffHunk<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.debug_struct("DiffHunk")
.field("old_start", &self.old_start())
.field("old_lines", &self.old_lines())
.field("new_start", &self.new_start())
.field("new_lines", &self.new_lines())
.field("header", &self.header())
.finish()
}
}
impl DiffStats {
pub fn files_changed(&self) -> usize {
unsafe { raw::git_diff_stats_files_changed(&*self.raw) as usize }
}
pub fn insertions(&self) -> usize {
unsafe { raw::git_diff_stats_insertions(&*self.raw) as usize }
}
pub fn deletions(&self) -> usize {
unsafe { raw::git_diff_stats_deletions(&*self.raw) as usize }
}
pub fn to_buf(&self, format: DiffStatsFormat, width: usize) -> Result<Buf, Error> {
let buf = Buf::new();
unsafe {
try_call!(raw::git_diff_stats_to_buf(
buf.raw(),
self.raw,
format.bits(),
width as size_t
));
}
Ok(buf)
}
}
impl Binding for DiffStats {
type Raw = *mut raw::git_diff_stats;
unsafe fn from_raw(raw: *mut raw::git_diff_stats) -> DiffStats {
DiffStats { raw }
}
fn raw(&self) -> *mut raw::git_diff_stats {
self.raw
}
}
impl Drop for DiffStats {
fn drop(&mut self) {
unsafe { raw::git_diff_stats_free(self.raw) }
}
}
impl std::fmt::Debug for DiffStats {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.debug_struct("DiffStats")
.field("files_changed", &self.files_changed())
.field("insertions", &self.insertions())
.field("deletions", &self.deletions())
.finish()
}
}
impl<'a> DiffBinary<'a> {
pub fn contains_data(&self) -> bool {
unsafe { (*self.raw).contains_data == 1 }
}
pub fn old_file(&self) -> DiffBinaryFile<'a> {
unsafe { Binding::from_raw(&(*self.raw).old_file as *const _) }
}
pub fn new_file(&self) -> DiffBinaryFile<'a> {
unsafe { Binding::from_raw(&(*self.raw).new_file as *const _) }
}
}
impl<'a> Binding for DiffBinary<'a> {
type Raw = *const raw::git_diff_binary;
unsafe fn from_raw(raw: *const raw::git_diff_binary) -> DiffBinary<'a> {
DiffBinary {
raw,
_marker: marker::PhantomData,
}
}
fn raw(&self) -> *const raw::git_diff_binary {
self.raw
}
}
impl<'a> DiffBinaryFile<'a> {
pub fn kind(&self) -> DiffBinaryKind {
unsafe { Binding::from_raw((*self.raw).kind) }
}
pub fn data(&self) -> &[u8] {
unsafe {
slice::from_raw_parts((*self.raw).data as *const u8, (*self.raw).datalen as usize)
}
}
pub fn inflated_len(&self) -> usize {
unsafe { (*self.raw).inflatedlen as usize }
}
}
impl<'a> Binding for DiffBinaryFile<'a> {
type Raw = *const raw::git_diff_binary_file;
unsafe fn from_raw(raw: *const raw::git_diff_binary_file) -> DiffBinaryFile<'a> {
DiffBinaryFile {
raw,
_marker: marker::PhantomData,
}
}
fn raw(&self) -> *const raw::git_diff_binary_file {
self.raw
}
}
impl Binding for DiffBinaryKind {
type Raw = raw::git_diff_binary_t;
unsafe fn from_raw(raw: raw::git_diff_binary_t) -> DiffBinaryKind {
match raw {
raw::GIT_DIFF_BINARY_NONE => DiffBinaryKind::None,
raw::GIT_DIFF_BINARY_LITERAL => DiffBinaryKind::Literal,
raw::GIT_DIFF_BINARY_DELTA => DiffBinaryKind::Delta,
_ => panic!("Unknown git diff binary kind"),
}
}
fn raw(&self) -> raw::git_diff_binary_t {
match *self {
DiffBinaryKind::None => raw::GIT_DIFF_BINARY_NONE,
DiffBinaryKind::Literal => raw::GIT_DIFF_BINARY_LITERAL,
DiffBinaryKind::Delta => raw::GIT_DIFF_BINARY_DELTA,
}
}
}
impl Default for DiffFindOptions {
fn default() -> Self {
Self::new()
}
}
impl DiffFindOptions {
pub fn new() -> DiffFindOptions {
let mut opts = DiffFindOptions {
raw: unsafe { mem::zeroed() },
};
assert_eq!(
unsafe { raw::git_diff_find_init_options(&mut opts.raw, 1) },
0
);
opts
}
fn flag(&mut self, opt: u32, val: bool) -> &mut DiffFindOptions {
if val {
self.raw.flags |= opt;
} else {
self.raw.flags &= !opt;
}
self
}
pub fn by_config(&mut self) -> &mut DiffFindOptions {
self.flag(0xffffffff, false)
}
pub fn renames(&mut self, find: bool) -> &mut DiffFindOptions {
self.flag(raw::GIT_DIFF_FIND_RENAMES, find)
}
pub fn renames_from_rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
self.flag(raw::GIT_DIFF_FIND_RENAMES_FROM_REWRITES, find)
}
pub fn copies(&mut self, find: bool) -> &mut DiffFindOptions {
self.flag(raw::GIT_DIFF_FIND_COPIES, find)
}
pub fn copies_from_unmodified(&mut self, find: bool) -> &mut DiffFindOptions {
self.flag(raw::GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED, find)
}
pub fn rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
self.flag(raw::GIT_DIFF_FIND_REWRITES, find)
}
pub fn break_rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
self.flag(raw::GIT_DIFF_BREAK_REWRITES, find)
}
#[doc(hidden)]
pub fn break_rewries(&mut self, find: bool) -> &mut DiffFindOptions {
self.break_rewrites(find)
}
pub fn for_untracked(&mut self, find: bool) -> &mut DiffFindOptions {
self.flag(raw::GIT_DIFF_FIND_FOR_UNTRACKED, find)
}
pub fn all(&mut self, find: bool) -> &mut DiffFindOptions {
self.flag(raw::GIT_DIFF_FIND_ALL, find)
}
pub fn ignore_leading_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions {
self.flag(raw::GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE, ignore)
}
pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions {
self.flag(raw::GIT_DIFF_FIND_IGNORE_WHITESPACE, ignore)
}
pub fn dont_ignore_whitespace(&mut self, dont: bool) -> &mut DiffFindOptions {
self.flag(raw::GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE, dont)
}
pub fn exact_match_only(&mut self, exact: bool) -> &mut DiffFindOptions {
self.flag(raw::GIT_DIFF_FIND_EXACT_MATCH_ONLY, exact)
}
pub fn break_rewrites_for_renames_only(&mut self, b: bool) -> &mut DiffFindOptions {
self.flag(raw::GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY, b)
}
pub fn remove_unmodified(&mut self, remove: bool) -> &mut DiffFindOptions {
self.flag(raw::GIT_DIFF_FIND_REMOVE_UNMODIFIED, remove)
}
pub fn rename_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
self.raw.rename_threshold = thresh;
self
}
pub fn rename_from_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
self.raw.rename_from_rewrite_threshold = thresh;
self
}
pub fn copy_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
self.raw.copy_threshold = thresh;
self
}
pub fn break_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
self.raw.break_rewrite_threshold = thresh;
self
}
pub fn rename_limit(&mut self, limit: usize) -> &mut DiffFindOptions {
self.raw.rename_limit = limit as size_t;
self
}
pub unsafe fn raw(&mut self) -> *const raw::git_diff_find_options {
&self.raw
}
}
impl Default for DiffFormatEmailOptions {
fn default() -> Self {
Self::new()
}
}
impl DiffFormatEmailOptions {
pub fn new() -> Self {
let mut opts = DiffFormatEmailOptions {
raw: unsafe { mem::zeroed() },
};
assert_eq!(
unsafe { raw::git_diff_format_email_options_init(&mut opts.raw, 1) },
0
);
opts
}
fn flag(&mut self, opt: u32, val: bool) -> &mut Self {
if val {
self.raw.flags |= opt;
} else {
self.raw.flags &= !opt;
}
self
}
pub fn exclude_subject_patch_header(&mut self, should_exclude: bool) -> &mut Self {
self.flag(
raw::GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER,
should_exclude,
)
}
}
impl DiffPatchidOptions {
pub fn new() -> Self {
let mut opts = DiffPatchidOptions {
raw: unsafe { mem::zeroed() },
};
assert_eq!(
unsafe {
raw::git_diff_patchid_options_init(
&mut opts.raw,
raw::GIT_DIFF_PATCHID_OPTIONS_VERSION,
)
},
0
);
opts
}
}
#[cfg(test)]
mod tests {
use crate::{DiffLineType, DiffOptions, Oid, Signature, Time};
use std::borrow::Borrow;
use std::fs::File;
use std::io::Write;
use std::path::Path;
#[test]
fn smoke() {
let (_td, repo) = crate::test::repo_init();
let diff = repo.diff_tree_to_workdir(None, None).unwrap();
assert_eq!(diff.deltas().len(), 0);
let stats = diff.stats().unwrap();
assert_eq!(stats.insertions(), 0);
assert_eq!(stats.deletions(), 0);
assert_eq!(stats.files_changed(), 0);
let patchid = diff.patchid(None).unwrap();
assert_ne!(patchid, Oid::zero());
}
#[test]
fn foreach_smoke() {
let (_td, repo) = crate::test::repo_init();
let diff = t!(repo.diff_tree_to_workdir(None, None));
let mut count = 0;
t!(diff.foreach(
&mut |_file, _progress| {
count = count + 1;
true
},
None,
None,
None
));
assert_eq!(count, 0);
}
#[test]
fn foreach_file_only() {
let path = Path::new("foo");
let (td, repo) = crate::test::repo_init();
t!(t!(File::create(&td.path().join(path))).write_all(b"bar"));
let mut opts = DiffOptions::new();
opts.include_untracked(true);
let diff = t!(repo.diff_tree_to_workdir(None, Some(&mut opts)));
let mut count = 0;
let mut result = None;
t!(diff.foreach(
&mut |file, _progress| {
count = count + 1;
result = file.new_file().path().map(ToOwned::to_owned);
true
},
None,
None,
None
));
assert_eq!(result.as_ref().map(Borrow::borrow), Some(path));
assert_eq!(count, 1);
}
#[test]
fn foreach_file_and_hunk() {
let path = Path::new("foo");
let (td, repo) = crate::test::repo_init();
t!(t!(File::create(&td.path().join(path))).write_all(b"bar"));
let mut index = t!(repo.index());
t!(index.add_path(path));
let mut opts = DiffOptions::new();
opts.include_untracked(true);
let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
let mut new_lines = 0;
t!(diff.foreach(
&mut |_file, _progress| { true },
None,
Some(&mut |_file, hunk| {
new_lines = hunk.new_lines();
true
}),
None
));
assert_eq!(new_lines, 1);
}
#[test]
fn foreach_all_callbacks() {
let fib = vec![0, 1, 1, 2, 3, 5, 8];
let deflated_fib = vec![120, 156, 99, 96, 100, 100, 98, 102, 229, 0, 0, 0, 53, 0, 21];
let foo_path = Path::new("foo");
let bin_path = Path::new("bin");
let (td, repo) = crate::test::repo_init();
t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n"));
t!(t!(File::create(&td.path().join(bin_path))).write_all(&fib));
let mut index = t!(repo.index());
t!(index.add_path(foo_path));
t!(index.add_path(bin_path));
let mut opts = DiffOptions::new();
opts.include_untracked(true).show_binary(true);
let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
let mut bin_content = None;
let mut new_lines = 0;
let mut line_content = None;
t!(diff.foreach(
&mut |_file, _progress| { true },
Some(&mut |_file, binary| {
bin_content = Some(binary.new_file().data().to_owned());
true
}),
Some(&mut |_file, hunk| {
new_lines = hunk.new_lines();
true
}),
Some(&mut |_file, _hunk, line| {
line_content = String::from_utf8(line.content().into()).ok();
true
})
));
assert_eq!(bin_content, Some(deflated_fib));
assert_eq!(new_lines, 1);
assert_eq!(line_content, Some("bar\n".to_string()));
}
#[test]
fn format_email_simple() {
let (_td, repo) = crate::test::repo_init();
const COMMIT_MESSAGE: &str = "Modify some content";
const EXPECTED_EMAIL_START: &str = concat!(
"From f1234fb0588b6ed670779a34ba5c51ef962f285f Mon Sep 17 00:00:00 2001\n",
"From: Techcable <dummy@dummy.org>\n",
"Date: Tue, 11 Jan 1972 17:46:40 +0000\n",
"Subject: [PATCH] Modify some content\n",
"\n",
"---\n",
" file1.txt | 8 +++++---\n",
" 1 file changed, 5 insertions(+), 3 deletions(-)\n",
"\n",
"diff --git a/file1.txt b/file1.txt\n",
"index 94aaae8..af8f41d 100644\n",
"--- a/file1.txt\n",
"+++ b/file1.txt\n",
"@@ -1,15 +1,17 @@\n",
" file1.txt\n",
" file1.txt\n",
"+_file1.txt_\n",
" file1.txt\n",
" file1.txt\n",
" file1.txt\n",
" file1.txt\n",
"+\n",
"+\n",
" file1.txt\n",
" file1.txt\n",
" file1.txt\n",
" file1.txt\n",
" file1.txt\n",
"-file1.txt\n",
"-file1.txt\n",
"-file1.txt\n",
"+_file1.txt_\n",
"+_file1.txt_\n",
" file1.txt\n",
"--\n"
);
const ORIGINAL_FILE: &str = concat!(
"file1.txt\n",
"file1.txt\n",
"file1.txt\n",
"file1.txt\n",
"file1.txt\n",
"file1.txt\n",
"file1.txt\n",
"file1.txt\n",
"file1.txt\n",
"file1.txt\n",
"file1.txt\n",
"file1.txt\n",
"file1.txt\n",
"file1.txt\n",
"file1.txt\n"
);
const UPDATED_FILE: &str = concat!(
"file1.txt\n",
"file1.txt\n",
"_file1.txt_\n",
"file1.txt\n",
"file1.txt\n",
"file1.txt\n",
"file1.txt\n",
"\n",
"\n",
"file1.txt\n",
"file1.txt\n",
"file1.txt\n",
"file1.txt\n",
"file1.txt\n",
"_file1.txt_\n",
"_file1.txt_\n",
"file1.txt\n"
);
const FILE_MODE: i32 = 0o100644;
let original_file = repo.blob(ORIGINAL_FILE.as_bytes()).unwrap();
let updated_file = repo.blob(UPDATED_FILE.as_bytes()).unwrap();
let mut original_tree = repo.treebuilder(None).unwrap();
original_tree
.insert("file1.txt", original_file, FILE_MODE)
.unwrap();
let original_tree = original_tree.write().unwrap();
let mut updated_tree = repo.treebuilder(None).unwrap();
updated_tree
.insert("file1.txt", updated_file, FILE_MODE)
.unwrap();
let updated_tree = updated_tree.write().unwrap();
let time = Time::new(64_000_000, 0);
let author = Signature::new("Techcable", "dummy@dummy.org", &time).unwrap();
let updated_commit = repo
.commit(
None,
&author,
&author,
COMMIT_MESSAGE,
&repo.find_tree(updated_tree).unwrap(),
&[], )
.unwrap();
let updated_commit = repo.find_commit(updated_commit).unwrap();
let mut diff = repo
.diff_tree_to_tree(
Some(&repo.find_tree(original_tree).unwrap()),
Some(&repo.find_tree(updated_tree).unwrap()),
None,
)
.unwrap();
#[allow(deprecated)]
let actual_email = diff.format_email(1, 1, &updated_commit, None).unwrap();
let actual_email = actual_email.as_str().unwrap();
assert!(
actual_email.starts_with(EXPECTED_EMAIL_START),
"Unexpected email:\n{}",
actual_email
);
let mut remaining_lines = actual_email[EXPECTED_EMAIL_START.len()..].lines();
let version_line = remaining_lines.next();
assert!(
version_line.unwrap().starts_with("libgit2"),
"Invalid version line: {:?}",
version_line
);
while let Some(line) = remaining_lines.next() {
assert_eq!(line.trim(), "")
}
}
#[test]
fn foreach_diff_line_origin_value() {
let foo_path = Path::new("foo");
let (td, repo) = crate::test::repo_init();
t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n"));
let mut index = t!(repo.index());
t!(index.add_path(foo_path));
let mut opts = DiffOptions::new();
opts.include_untracked(true);
let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
let mut origin_values: Vec<DiffLineType> = Vec::new();
t!(diff.foreach(
&mut |_file, _progress| { true },
None,
None,
Some(&mut |_file, _hunk, line| {
origin_values.push(line.origin_value());
true
})
));
assert_eq!(origin_values.len(), 1);
assert_eq!(origin_values[0], DiffLineType::Addition);
}
#[test]
fn foreach_exits_with_euser() {
let foo_path = Path::new("foo");
let bar_path = Path::new("foo");
let (td, repo) = crate::test::repo_init();
t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n"));
let mut index = t!(repo.index());
t!(index.add_path(foo_path));
t!(index.add_path(bar_path));
let mut opts = DiffOptions::new();
opts.include_untracked(true);
let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
let mut calls = 0;
let result = diff.foreach(
&mut |_file, _progress| {
calls += 1;
false
},
None,
None,
None,
);
assert_eq!(result.unwrap_err().code(), crate::ErrorCode::User);
}
}