1 use libc::{c_char, c_int, c_void, size_t};
2 use std::ffi::CString;
3 use std::marker;
4 use std::mem;
5 use std::ops::Range;
6 use std::path::Path;
7 use std::ptr;
8 use std::slice;
9 
10 use crate::util::{self, Binding};
11 use crate::{panic, raw, Buf, Delta, DiffFormat, Error, Oid, Repository};
12 use crate::{DiffStatsFormat, IntoCString};
13 
14 /// The diff object that contains all individual file deltas.
15 ///
16 /// This is an opaque structure which will be allocated by one of the diff
17 /// generator functions on the `Repository` structure (e.g. `diff_tree_to_tree`
18 /// or other `diff_*` functions).
19 pub struct Diff<'repo> {
20     raw: *mut raw::git_diff,
21     _marker: marker::PhantomData<&'repo Repository>,
22 }
23 
24 unsafe impl<'repo> Send for Diff<'repo> {}
25 
26 /// Description of changes to one entry.
27 pub struct DiffDelta<'a> {
28     raw: *mut raw::git_diff_delta,
29     _marker: marker::PhantomData<&'a raw::git_diff_delta>,
30 }
31 
32 /// Description of one side of a delta.
33 ///
34 /// Although this is called a "file" it could represent a file, a symbolic
35 /// link, a submodule commit id, or even a tree (although that only happens if
36 /// you are tracking type changes or ignored/untracked directories).
37 pub struct DiffFile<'a> {
38     raw: *const raw::git_diff_file,
39     _marker: marker::PhantomData<&'a raw::git_diff_file>,
40 }
41 
42 /// Structure describing options about how the diff should be executed.
43 pub struct DiffOptions {
44     pathspec: Vec<CString>,
45     pathspec_ptrs: Vec<*const c_char>,
46     old_prefix: Option<CString>,
47     new_prefix: Option<CString>,
48     raw: raw::git_diff_options,
49 }
50 
51 /// Control behavior of rename and copy detection
52 pub struct DiffFindOptions {
53     raw: raw::git_diff_find_options,
54 }
55 
56 /// An iterator over the diffs in a delta
57 pub struct Deltas<'diff> {
58     range: Range<usize>,
59     diff: &'diff Diff<'diff>,
60 }
61 
62 /// Structure describing a line (or data span) of a diff.
63 pub struct DiffLine<'a> {
64     raw: *const raw::git_diff_line,
65     _marker: marker::PhantomData<&'a raw::git_diff_line>,
66 }
67 
68 /// Structure describing a hunk of a diff.
69 pub struct DiffHunk<'a> {
70     raw: *const raw::git_diff_hunk,
71     _marker: marker::PhantomData<&'a raw::git_diff_hunk>,
72 }
73 
74 /// Structure describing a hunk of a diff.
75 pub struct DiffStats {
76     raw: *mut raw::git_diff_stats,
77 }
78 
79 /// Structure describing the binary contents of a diff.
80 pub struct DiffBinary<'a> {
81     raw: *const raw::git_diff_binary,
82     _marker: marker::PhantomData<&'a raw::git_diff_binary>,
83 }
84 
85 /// The contents of one of the files in a binary diff.
86 pub struct DiffBinaryFile<'a> {
87     raw: *const raw::git_diff_binary_file,
88     _marker: marker::PhantomData<&'a raw::git_diff_binary_file>,
89 }
90 
91 /// When producing a binary diff, the binary data returned will be
92 /// either the deflated full ("literal") contents of the file, or
93 /// the deflated binary delta between the two sides (whichever is
94 /// smaller).
95 #[derive(Copy, Clone, Debug)]
96 pub enum DiffBinaryKind {
97     /// There is no binary delta
98     None,
99     /// The binary data is the literal contents of the file
100     Literal,
101     /// The binary data is the delta from one side to the other
102     Delta,
103 }
104 
105 type PrintCb<'a> = dyn FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool + 'a;
106 
107 pub type FileCb<'a> = dyn FnMut(DiffDelta<'_>, f32) -> bool + 'a;
108 pub type BinaryCb<'a> = dyn FnMut(DiffDelta<'_>, DiffBinary<'_>) -> bool + 'a;
109 pub type HunkCb<'a> = dyn FnMut(DiffDelta<'_>, DiffHunk<'_>) -> bool + 'a;
110 pub type LineCb<'a> = dyn FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool + 'a;
111 
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>>,
117 }
118 
119 impl<'repo> Diff<'repo> {
120     /// Merge one diff into another.
121     ///
122     /// This merges items from the "from" list into the "self" list.  The
123     /// resulting diff will have all items that appear in either list.
124     /// If an item appears in both lists, then it will be "merged" to appear
125     /// as if the old version was from the "onto" list and the new version
126     /// is from the "from" list (with the exception that if the item has a
127     /// pending DELETE in the middle, then it will show as deleted).
merge(&mut self, from: &Diff<'repo>) -> Result<(), Error>128     pub fn merge(&mut self, from: &Diff<'repo>) -> Result<(), Error> {
129         unsafe {
130             try_call!(raw::git_diff_merge(self.raw, &*from.raw));
131         }
132         Ok(())
133     }
134 
135     /// Returns an iterator over the deltas in this diff.
deltas(&self) -> Deltas<'_>136     pub fn deltas(&self) -> Deltas<'_> {
137         let num_deltas = unsafe { raw::git_diff_num_deltas(&*self.raw) };
138         Deltas {
139             range: 0..(num_deltas as usize),
140             diff: self,
141         }
142     }
143 
144     /// Return the diff delta for an entry in the diff list.
get_delta(&self, i: usize) -> Option<DiffDelta<'_>>145     pub fn get_delta(&self, i: usize) -> Option<DiffDelta<'_>> {
146         unsafe {
147             let ptr = raw::git_diff_get_delta(&*self.raw, i as size_t);
148             Binding::from_raw_opt(ptr as *mut _)
149         }
150     }
151 
152     /// Check if deltas are sorted case sensitively or insensitively.
is_sorted_icase(&self) -> bool153     pub fn is_sorted_icase(&self) -> bool {
154         unsafe { raw::git_diff_is_sorted_icase(&*self.raw) == 1 }
155     }
156 
157     /// Iterate over a diff generating formatted text output.
158     ///
159     /// Returning `false` from the callback will terminate the iteration and
160     /// return an error from this function.
print<F>(&self, format: DiffFormat, mut cb: F) -> Result<(), Error> where F: FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool,161     pub fn print<F>(&self, format: DiffFormat, mut cb: F) -> Result<(), Error>
162     where
163         F: FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool,
164     {
165         let mut cb: &mut PrintCb<'_> = &mut cb;
166         let ptr = &mut cb as *mut _;
167         unsafe {
168             try_call!(raw::git_diff_print(
169                 self.raw,
170                 format,
171                 print_cb,
172                 ptr as *mut _
173             ));
174             Ok(())
175         }
176     }
177 
178     /// Loop over all deltas in a diff issuing callbacks.
179     ///
180     /// Returning `false` from any callback will terminate the iteration and
181     /// return an error from this function.
foreach( &self, file_cb: &mut FileCb<'_>, binary_cb: Option<&mut BinaryCb<'_>>, hunk_cb: Option<&mut HunkCb<'_>>, line_cb: Option<&mut LineCb<'_>>, ) -> Result<(), Error>182     pub fn foreach(
183         &self,
184         file_cb: &mut FileCb<'_>,
185         binary_cb: Option<&mut BinaryCb<'_>>,
186         hunk_cb: Option<&mut HunkCb<'_>>,
187         line_cb: Option<&mut LineCb<'_>>,
188     ) -> Result<(), Error> {
189         let mut cbs = ForeachCallbacks {
190             file: file_cb,
191             binary: binary_cb,
192             hunk: hunk_cb,
193             line: line_cb,
194         };
195         let ptr = &mut cbs as *mut _;
196         unsafe {
197             let binary_cb_c = if cbs.binary.is_some() {
198                 Some(binary_cb_c as raw::git_diff_binary_cb)
199             } else {
200                 None
201             };
202             let hunk_cb_c = if cbs.hunk.is_some() {
203                 Some(hunk_cb_c as raw::git_diff_hunk_cb)
204             } else {
205                 None
206             };
207             let line_cb_c = if cbs.line.is_some() {
208                 Some(line_cb_c as raw::git_diff_line_cb)
209             } else {
210                 None
211             };
212             try_call!(raw::git_diff_foreach(
213                 self.raw,
214                 file_cb_c,
215                 binary_cb_c,
216                 hunk_cb_c,
217                 line_cb_c,
218                 ptr as *mut _
219             ));
220             Ok(())
221         }
222     }
223 
224     /// Accumulate diff statistics for all patches.
stats(&self) -> Result<DiffStats, Error>225     pub fn stats(&self) -> Result<DiffStats, Error> {
226         let mut ret = ptr::null_mut();
227         unsafe {
228             try_call!(raw::git_diff_get_stats(&mut ret, self.raw));
229             Ok(Binding::from_raw(ret))
230         }
231     }
232 
233     /// Transform a diff marking file renames, copies, etc.
234     ///
235     /// This modifies a diff in place, replacing old entries that look like
236     /// renames or copies with new entries reflecting those changes. This also
237     /// will, if requested, break modified files into add/remove pairs if the
238     /// amount of change is above a threshold.
find_similar(&mut self, opts: Option<&mut DiffFindOptions>) -> Result<(), Error>239     pub fn find_similar(&mut self, opts: Option<&mut DiffFindOptions>) -> Result<(), Error> {
240         let opts = opts.map(|opts| &opts.raw);
241         unsafe {
242             try_call!(raw::git_diff_find_similar(self.raw, opts));
243         }
244         Ok(())
245     }
246 
247     // TODO: num_deltas_of_type, format_email, find_similar
248 }
249 
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_int250 pub extern "C" fn print_cb(
251     delta: *const raw::git_diff_delta,
252     hunk: *const raw::git_diff_hunk,
253     line: *const raw::git_diff_line,
254     data: *mut c_void,
255 ) -> c_int {
256     unsafe {
257         let delta = Binding::from_raw(delta as *mut _);
258         let hunk = Binding::from_raw_opt(hunk);
259         let line = Binding::from_raw(line);
260 
261         let r = panic::wrap(|| {
262             let data = data as *mut &mut PrintCb<'_>;
263             (*data)(delta, hunk, line)
264         });
265         if r == Some(true) {
266             0
267         } else {
268             -1
269         }
270     }
271 }
272 
file_cb_c( delta: *const raw::git_diff_delta, progress: f32, data: *mut c_void, ) -> c_int273 extern "C" fn file_cb_c(
274     delta: *const raw::git_diff_delta,
275     progress: f32,
276     data: *mut c_void,
277 ) -> c_int {
278     unsafe {
279         let delta = Binding::from_raw(delta as *mut _);
280 
281         let r = panic::wrap(|| {
282             let cbs = data as *mut ForeachCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
283             ((*cbs).file)(delta, progress)
284         });
285         if r == Some(true) {
286             0
287         } else {
288             -1
289         }
290     }
291 }
292 
binary_cb_c( delta: *const raw::git_diff_delta, binary: *const raw::git_diff_binary, data: *mut c_void, ) -> c_int293 extern "C" fn binary_cb_c(
294     delta: *const raw::git_diff_delta,
295     binary: *const raw::git_diff_binary,
296     data: *mut c_void,
297 ) -> c_int {
298     unsafe {
299         let delta = Binding::from_raw(delta as *mut _);
300         let binary = Binding::from_raw(binary);
301 
302         let r = panic::wrap(|| {
303             let cbs = data as *mut ForeachCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
304             match (*cbs).binary {
305                 Some(ref mut cb) => cb(delta, binary),
306                 None => false,
307             }
308         });
309         if r == Some(true) {
310             0
311         } else {
312             -1
313         }
314     }
315 }
316 
hunk_cb_c( delta: *const raw::git_diff_delta, hunk: *const raw::git_diff_hunk, data: *mut c_void, ) -> c_int317 extern "C" fn hunk_cb_c(
318     delta: *const raw::git_diff_delta,
319     hunk: *const raw::git_diff_hunk,
320     data: *mut c_void,
321 ) -> c_int {
322     unsafe {
323         let delta = Binding::from_raw(delta as *mut _);
324         let hunk = Binding::from_raw(hunk);
325 
326         let r = panic::wrap(|| {
327             let cbs = data as *mut ForeachCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
328             match (*cbs).hunk {
329                 Some(ref mut cb) => cb(delta, hunk),
330                 None => false,
331             }
332         });
333         if r == Some(true) {
334             0
335         } else {
336             -1
337         }
338     }
339 }
340 
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_int341 extern "C" fn line_cb_c(
342     delta: *const raw::git_diff_delta,
343     hunk: *const raw::git_diff_hunk,
344     line: *const raw::git_diff_line,
345     data: *mut c_void,
346 ) -> c_int {
347     unsafe {
348         let delta = Binding::from_raw(delta as *mut _);
349         let hunk = Binding::from_raw_opt(hunk);
350         let line = Binding::from_raw(line);
351 
352         let r = panic::wrap(|| {
353             let cbs = data as *mut ForeachCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
354             match (*cbs).line {
355                 Some(ref mut cb) => cb(delta, hunk, line),
356                 None => false,
357             }
358         });
359         if r == Some(true) {
360             0
361         } else {
362             -1
363         }
364     }
365 }
366 
367 impl<'repo> Binding for Diff<'repo> {
368     type Raw = *mut raw::git_diff;
from_raw(raw: *mut raw::git_diff) -> Diff<'repo>369     unsafe fn from_raw(raw: *mut raw::git_diff) -> Diff<'repo> {
370         Diff {
371             raw: raw,
372             _marker: marker::PhantomData,
373         }
374     }
raw(&self) -> *mut raw::git_diff375     fn raw(&self) -> *mut raw::git_diff {
376         self.raw
377     }
378 }
379 
380 impl<'repo> Drop for Diff<'repo> {
drop(&mut self)381     fn drop(&mut self) {
382         unsafe { raw::git_diff_free(self.raw) }
383     }
384 }
385 
386 impl<'a> DiffDelta<'a> {
387     // TODO: expose when diffs are more exposed
388     // pub fn similarity(&self) -> u16 {
389     //     unsafe { (*self.raw).similarity }
390     // }
391 
392     /// Returns the number of files in this delta.
nfiles(&self) -> u16393     pub fn nfiles(&self) -> u16 {
394         unsafe { (*self.raw).nfiles }
395     }
396 
397     /// Returns the status of this entry
398     ///
399     /// For more information, see `Delta`'s documentation
status(&self) -> Delta400     pub fn status(&self) -> Delta {
401         match unsafe { (*self.raw).status } {
402             raw::GIT_DELTA_UNMODIFIED => Delta::Unmodified,
403             raw::GIT_DELTA_ADDED => Delta::Added,
404             raw::GIT_DELTA_DELETED => Delta::Deleted,
405             raw::GIT_DELTA_MODIFIED => Delta::Modified,
406             raw::GIT_DELTA_RENAMED => Delta::Renamed,
407             raw::GIT_DELTA_COPIED => Delta::Copied,
408             raw::GIT_DELTA_IGNORED => Delta::Ignored,
409             raw::GIT_DELTA_UNTRACKED => Delta::Untracked,
410             raw::GIT_DELTA_TYPECHANGE => Delta::Typechange,
411             raw::GIT_DELTA_UNREADABLE => Delta::Unreadable,
412             raw::GIT_DELTA_CONFLICTED => Delta::Conflicted,
413             n => panic!("unknown diff status: {}", n),
414         }
415     }
416 
417     /// Return the file which represents the "from" side of the diff.
418     ///
419     /// What side this means depends on the function that was used to generate
420     /// the diff and will be documented on the function itself.
old_file(&self) -> DiffFile<'a>421     pub fn old_file(&self) -> DiffFile<'a> {
422         unsafe { Binding::from_raw(&(*self.raw).old_file as *const _) }
423     }
424 
425     /// Return the file which represents the "to" side of the diff.
426     ///
427     /// What side this means depends on the function that was used to generate
428     /// the diff and will be documented on the function itself.
new_file(&self) -> DiffFile<'a>429     pub fn new_file(&self) -> DiffFile<'a> {
430         unsafe { Binding::from_raw(&(*self.raw).new_file as *const _) }
431     }
432 }
433 
434 impl<'a> Binding for DiffDelta<'a> {
435     type Raw = *mut raw::git_diff_delta;
from_raw(raw: *mut raw::git_diff_delta) -> DiffDelta<'a>436     unsafe fn from_raw(raw: *mut raw::git_diff_delta) -> DiffDelta<'a> {
437         DiffDelta {
438             raw: raw,
439             _marker: marker::PhantomData,
440         }
441     }
raw(&self) -> *mut raw::git_diff_delta442     fn raw(&self) -> *mut raw::git_diff_delta {
443         self.raw
444     }
445 }
446 
447 impl<'a> DiffFile<'a> {
448     /// Returns the Oid of this item.
449     ///
450     /// If this entry represents an absent side of a diff (e.g. the `old_file`
451     /// of a `Added` delta), then the oid returned will be zeroes.
id(&self) -> Oid452     pub fn id(&self) -> Oid {
453         unsafe { Binding::from_raw(&(*self.raw).id as *const _) }
454     }
455 
456     /// Returns the path, in bytes, of the entry relative to the working
457     /// directory of the repository.
path_bytes(&self) -> Option<&'a [u8]>458     pub fn path_bytes(&self) -> Option<&'a [u8]> {
459         static FOO: () = ();
460         unsafe { crate::opt_bytes(&FOO, (*self.raw).path) }
461     }
462 
463     /// Returns the path of the entry relative to the working directory of the
464     /// repository.
path(&self) -> Option<&'a Path>465     pub fn path(&self) -> Option<&'a Path> {
466         self.path_bytes().map(util::bytes2path)
467     }
468 
469     /// Returns the size of this entry, in bytes
size(&self) -> u64470     pub fn size(&self) -> u64 {
471         unsafe { (*self.raw).size as u64 }
472     }
473 
474     // TODO: expose flags/mode
475 }
476 
477 impl<'a> Binding for DiffFile<'a> {
478     type Raw = *const raw::git_diff_file;
from_raw(raw: *const raw::git_diff_file) -> DiffFile<'a>479     unsafe fn from_raw(raw: *const raw::git_diff_file) -> DiffFile<'a> {
480         DiffFile {
481             raw: raw,
482             _marker: marker::PhantomData,
483         }
484     }
raw(&self) -> *const raw::git_diff_file485     fn raw(&self) -> *const raw::git_diff_file {
486         self.raw
487     }
488 }
489 
490 impl Default for DiffOptions {
default() -> Self491     fn default() -> Self {
492         Self::new()
493     }
494 }
495 
496 impl DiffOptions {
497     /// Creates a new set of empty diff options.
498     ///
499     /// All flags and other options are defaulted to false or their otherwise
500     /// zero equivalents.
new() -> DiffOptions501     pub fn new() -> DiffOptions {
502         let mut opts = DiffOptions {
503             pathspec: Vec::new(),
504             pathspec_ptrs: Vec::new(),
505             raw: unsafe { mem::zeroed() },
506             old_prefix: None,
507             new_prefix: None,
508         };
509         assert_eq!(unsafe { raw::git_diff_init_options(&mut opts.raw, 1) }, 0);
510         opts
511     }
512 
flag(&mut self, opt: i32, val: bool) -> &mut DiffOptions513     fn flag(&mut self, opt: i32, val: bool) -> &mut DiffOptions {
514         let opt = opt as u32;
515         if val {
516             self.raw.flags |= opt;
517         } else {
518             self.raw.flags &= !opt;
519         }
520         self
521     }
522 
523     /// Flag indicating whether the sides of the diff will be reversed.
reverse(&mut self, reverse: bool) -> &mut DiffOptions524     pub fn reverse(&mut self, reverse: bool) -> &mut DiffOptions {
525         self.flag(raw::GIT_DIFF_REVERSE, reverse)
526     }
527 
528     /// Flag indicating whether ignored files are included.
include_ignored(&mut self, include: bool) -> &mut DiffOptions529     pub fn include_ignored(&mut self, include: bool) -> &mut DiffOptions {
530         self.flag(raw::GIT_DIFF_INCLUDE_IGNORED, include)
531     }
532 
533     /// Flag indicating whether ignored directories are traversed deeply or not.
recurse_ignored_dirs(&mut self, recurse: bool) -> &mut DiffOptions534     pub fn recurse_ignored_dirs(&mut self, recurse: bool) -> &mut DiffOptions {
535         self.flag(raw::GIT_DIFF_RECURSE_IGNORED_DIRS, recurse)
536     }
537 
538     /// Flag indicating whether untracked files are in the diff
include_untracked(&mut self, include: bool) -> &mut DiffOptions539     pub fn include_untracked(&mut self, include: bool) -> &mut DiffOptions {
540         self.flag(raw::GIT_DIFF_INCLUDE_UNTRACKED, include)
541     }
542 
543     /// Flag indicating whether untracked directories are deeply traversed or
544     /// not.
recurse_untracked_dirs(&mut self, recurse: bool) -> &mut DiffOptions545     pub fn recurse_untracked_dirs(&mut self, recurse: bool) -> &mut DiffOptions {
546         self.flag(raw::GIT_DIFF_RECURSE_UNTRACKED_DIRS, recurse)
547     }
548 
549     /// Flag indicating whether unmodified files are in the diff.
include_unmodified(&mut self, include: bool) -> &mut DiffOptions550     pub fn include_unmodified(&mut self, include: bool) -> &mut DiffOptions {
551         self.flag(raw::GIT_DIFF_INCLUDE_UNMODIFIED, include)
552     }
553 
554     /// If entrabled, then Typechange delta records are generated.
include_typechange(&mut self, include: bool) -> &mut DiffOptions555     pub fn include_typechange(&mut self, include: bool) -> &mut DiffOptions {
556         self.flag(raw::GIT_DIFF_INCLUDE_TYPECHANGE, include)
557     }
558 
559     /// Event with `include_typechange`, the tree treturned generally shows a
560     /// deleted blow. This flag correctly labels the tree transitions as a
561     /// typechange record with the `new_file`'s mode set to tree.
562     ///
563     /// Note that the tree SHA will not be available.
include_typechange_trees(&mut self, include: bool) -> &mut DiffOptions564     pub fn include_typechange_trees(&mut self, include: bool) -> &mut DiffOptions {
565         self.flag(raw::GIT_DIFF_INCLUDE_TYPECHANGE_TREES, include)
566     }
567 
568     /// Flag indicating whether file mode changes are ignored.
ignore_filemode(&mut self, ignore: bool) -> &mut DiffOptions569     pub fn ignore_filemode(&mut self, ignore: bool) -> &mut DiffOptions {
570         self.flag(raw::GIT_DIFF_IGNORE_FILEMODE, ignore)
571     }
572 
573     /// Flag indicating whether all submodules should be treated as unmodified.
ignore_submodules(&mut self, ignore: bool) -> &mut DiffOptions574     pub fn ignore_submodules(&mut self, ignore: bool) -> &mut DiffOptions {
575         self.flag(raw::GIT_DIFF_IGNORE_SUBMODULES, ignore)
576     }
577 
578     /// Flag indicating whether case insensitive filenames should be used.
ignore_case(&mut self, ignore: bool) -> &mut DiffOptions579     pub fn ignore_case(&mut self, ignore: bool) -> &mut DiffOptions {
580         self.flag(raw::GIT_DIFF_IGNORE_CASE, ignore)
581     }
582 
583     /// If pathspecs are specified, this flag means that they should be applied
584     /// as an exact match instead of a fnmatch pattern.
disable_pathspec_match(&mut self, disable: bool) -> &mut DiffOptions585     pub fn disable_pathspec_match(&mut self, disable: bool) -> &mut DiffOptions {
586         self.flag(raw::GIT_DIFF_DISABLE_PATHSPEC_MATCH, disable)
587     }
588 
589     /// Disable updating the `binary` flag in delta records. This is useful when
590     /// iterating over a diff if you don't need hunk and data callbacks and want
591     /// to avoid having to load a file completely.
skip_binary_check(&mut self, skip: bool) -> &mut DiffOptions592     pub fn skip_binary_check(&mut self, skip: bool) -> &mut DiffOptions {
593         self.flag(raw::GIT_DIFF_SKIP_BINARY_CHECK, skip)
594     }
595 
596     /// When diff finds an untracked directory, to match the behavior of core
597     /// Git, it scans the contents for ignored and untracked files. If all
598     /// contents are ignored, then the directory is ignored; if any contents are
599     /// not ignored, then the directory is untracked. This is extra work that
600     /// may not matter in many cases.
601     ///
602     /// This flag turns off that scan and immediately labels an untracked
603     /// directory as untracked (changing the behavior to not match core git).
enable_fast_untracked_dirs(&mut self, enable: bool) -> &mut DiffOptions604     pub fn enable_fast_untracked_dirs(&mut self, enable: bool) -> &mut DiffOptions {
605         self.flag(raw::GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS, enable)
606     }
607 
608     /// When diff finds a file in the working directory with stat information
609     /// different from the index, but the OID ends up being the same, write the
610     /// correct stat information into the index. Note: without this flag, diff
611     /// will always leave the index untouched.
update_index(&mut self, update: bool) -> &mut DiffOptions612     pub fn update_index(&mut self, update: bool) -> &mut DiffOptions {
613         self.flag(raw::GIT_DIFF_UPDATE_INDEX, update)
614     }
615 
616     /// Include unreadable files in the diff
include_unreadable(&mut self, include: bool) -> &mut DiffOptions617     pub fn include_unreadable(&mut self, include: bool) -> &mut DiffOptions {
618         self.flag(raw::GIT_DIFF_INCLUDE_UNREADABLE, include)
619     }
620 
621     /// Include unreadable files in the diff
include_unreadable_as_untracked(&mut self, include: bool) -> &mut DiffOptions622     pub fn include_unreadable_as_untracked(&mut self, include: bool) -> &mut DiffOptions {
623         self.flag(raw::GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED, include)
624     }
625 
626     /// Treat all files as text, disabling binary attributes and detection.
force_text(&mut self, force: bool) -> &mut DiffOptions627     pub fn force_text(&mut self, force: bool) -> &mut DiffOptions {
628         self.flag(raw::GIT_DIFF_FORCE_TEXT, force)
629     }
630 
631     /// Treat all files as binary, disabling text diffs
force_binary(&mut self, force: bool) -> &mut DiffOptions632     pub fn force_binary(&mut self, force: bool) -> &mut DiffOptions {
633         self.flag(raw::GIT_DIFF_FORCE_TEXT, force)
634     }
635 
636     /// Ignore all whitespace
ignore_whitespace(&mut self, ignore: bool) -> &mut DiffOptions637     pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut DiffOptions {
638         self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE, ignore)
639     }
640 
641     /// Ignore changes in the amount of whitespace
ignore_whitespace_change(&mut self, ignore: bool) -> &mut DiffOptions642     pub fn ignore_whitespace_change(&mut self, ignore: bool) -> &mut DiffOptions {
643         self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE_CHANGE, ignore)
644     }
645 
646     /// Ignore whitespace at tend of line
ignore_whitespace_eol(&mut self, ignore: bool) -> &mut DiffOptions647     pub fn ignore_whitespace_eol(&mut self, ignore: bool) -> &mut DiffOptions {
648         self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE_EOL, ignore)
649     }
650 
651     /// When generating patch text, include the content of untracked files.
652     ///
653     /// This automatically turns on `include_untracked` but it does not turn on
654     /// `recurse_untracked_dirs`. Add that flag if you want the content of every
655     /// single untracked file.
show_untracked_content(&mut self, show: bool) -> &mut DiffOptions656     pub fn show_untracked_content(&mut self, show: bool) -> &mut DiffOptions {
657         self.flag(raw::GIT_DIFF_SHOW_UNTRACKED_CONTENT, show)
658     }
659 
660     /// When generating output, include the names of unmodified files if they
661     /// are included in the `Diff`. Normally these are skipped in the formats
662     /// that list files (e.g. name-only, name-status, raw). Even with this these
663     /// will not be included in the patch format.
show_unmodified(&mut self, show: bool) -> &mut DiffOptions664     pub fn show_unmodified(&mut self, show: bool) -> &mut DiffOptions {
665         self.flag(raw::GIT_DIFF_SHOW_UNMODIFIED, show)
666     }
667 
668     /// Use the "patience diff" algorithm
patience(&mut self, patience: bool) -> &mut DiffOptions669     pub fn patience(&mut self, patience: bool) -> &mut DiffOptions {
670         self.flag(raw::GIT_DIFF_PATIENCE, patience)
671     }
672 
673     /// Take extra time to find the minimal diff
minimal(&mut self, minimal: bool) -> &mut DiffOptions674     pub fn minimal(&mut self, minimal: bool) -> &mut DiffOptions {
675         self.flag(raw::GIT_DIFF_MINIMAL, minimal)
676     }
677 
678     /// Include the necessary deflate/delta information so that `git-apply` can
679     /// apply given diff information to binary files.
show_binary(&mut self, show: bool) -> &mut DiffOptions680     pub fn show_binary(&mut self, show: bool) -> &mut DiffOptions {
681         self.flag(raw::GIT_DIFF_SHOW_BINARY, show)
682     }
683 
684     /// Use a heuristic that takes indentation and whitespace into account
685     /// which generally can produce better diffs when dealing with ambiguous
686     /// diff hunks.
indent_heuristic(&mut self, heuristic: bool) -> &mut DiffOptions687     pub fn indent_heuristic(&mut self, heuristic: bool) -> &mut DiffOptions {
688         self.flag(raw::GIT_DIFF_INDENT_HEURISTIC, heuristic)
689     }
690 
691     /// Set the number of unchanged lines that define the boundary of a hunk
692     /// (and to display before and after).
693     ///
694     /// The default value for this is 3.
context_lines(&mut self, lines: u32) -> &mut DiffOptions695     pub fn context_lines(&mut self, lines: u32) -> &mut DiffOptions {
696         self.raw.context_lines = lines;
697         self
698     }
699 
700     /// Set the maximum number of unchanged lines between hunk boundaries before
701     /// the hunks will be merged into one.
702     ///
703     /// The default value for this is 0.
interhunk_lines(&mut self, lines: u32) -> &mut DiffOptions704     pub fn interhunk_lines(&mut self, lines: u32) -> &mut DiffOptions {
705         self.raw.interhunk_lines = lines;
706         self
707     }
708 
709     /// The default value for this is `core.abbrev` or 7 if unset.
id_abbrev(&mut self, abbrev: u16) -> &mut DiffOptions710     pub fn id_abbrev(&mut self, abbrev: u16) -> &mut DiffOptions {
711         self.raw.id_abbrev = abbrev;
712         self
713     }
714 
715     /// Maximum size (in bytes) above which a blob will be marked as binary
716     /// automatically.
717     ///
718     /// A negative value will disable this entirely.
719     ///
720     /// The default value for this is 512MB.
max_size(&mut self, size: i64) -> &mut DiffOptions721     pub fn max_size(&mut self, size: i64) -> &mut DiffOptions {
722         self.raw.max_size = size as raw::git_off_t;
723         self
724     }
725 
726     /// The virtual "directory" to prefix old file names with in hunk headers.
727     ///
728     /// The default value for this is "a".
old_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions729     pub fn old_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions {
730         self.old_prefix = Some(t.into_c_string().unwrap());
731         self
732     }
733 
734     /// The virtual "directory" to prefix new file names with in hunk headers.
735     ///
736     /// The default value for this is "b".
new_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions737     pub fn new_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions {
738         self.new_prefix = Some(t.into_c_string().unwrap());
739         self
740     }
741 
742     /// Add to the array of paths/fnmatch patterns to constrain the diff.
pathspec<T: IntoCString>(&mut self, pathspec: T) -> &mut DiffOptions743     pub fn pathspec<T: IntoCString>(&mut self, pathspec: T) -> &mut DiffOptions {
744         let s = pathspec.into_c_string().unwrap();
745         self.pathspec_ptrs.push(s.as_ptr());
746         self.pathspec.push(s);
747         self
748     }
749 
750     /// Acquire a pointer to the underlying raw options.
751     ///
752     /// This function is unsafe as the pointer is only valid so long as this
753     /// structure is not moved, modified, or used elsewhere.
raw(&mut self) -> *const raw::git_diff_options754     pub unsafe fn raw(&mut self) -> *const raw::git_diff_options {
755         self.raw.old_prefix = self
756             .old_prefix
757             .as_ref()
758             .map(|s| s.as_ptr())
759             .unwrap_or(ptr::null());
760         self.raw.new_prefix = self
761             .new_prefix
762             .as_ref()
763             .map(|s| s.as_ptr())
764             .unwrap_or(ptr::null());
765         self.raw.pathspec.count = self.pathspec_ptrs.len() as size_t;
766         self.raw.pathspec.strings = self.pathspec_ptrs.as_ptr() as *mut _;
767         &self.raw as *const _
768     }
769 
770     // TODO: expose ignore_submodules, notify_cb/notify_payload
771 }
772 
773 impl<'diff> Iterator for Deltas<'diff> {
774     type Item = DiffDelta<'diff>;
next(&mut self) -> Option<DiffDelta<'diff>>775     fn next(&mut self) -> Option<DiffDelta<'diff>> {
776         self.range.next().and_then(|i| self.diff.get_delta(i))
777     }
size_hint(&self) -> (usize, Option<usize>)778     fn size_hint(&self) -> (usize, Option<usize>) {
779         self.range.size_hint()
780     }
781 }
782 impl<'diff> DoubleEndedIterator for Deltas<'diff> {
next_back(&mut self) -> Option<DiffDelta<'diff>>783     fn next_back(&mut self) -> Option<DiffDelta<'diff>> {
784         self.range.next_back().and_then(|i| self.diff.get_delta(i))
785     }
786 }
787 impl<'diff> ExactSizeIterator for Deltas<'diff> {}
788 
789 impl<'a> DiffLine<'a> {
790     /// Line number in old file or `None` for added line
old_lineno(&self) -> Option<u32>791     pub fn old_lineno(&self) -> Option<u32> {
792         match unsafe { (*self.raw).old_lineno } {
793             n if n < 0 => None,
794             n => Some(n as u32),
795         }
796     }
797 
798     /// Line number in new file or `None` for deleted line
new_lineno(&self) -> Option<u32>799     pub fn new_lineno(&self) -> Option<u32> {
800         match unsafe { (*self.raw).new_lineno } {
801             n if n < 0 => None,
802             n => Some(n as u32),
803         }
804     }
805 
806     /// Number of newline characters in content
num_lines(&self) -> u32807     pub fn num_lines(&self) -> u32 {
808         unsafe { (*self.raw).num_lines as u32 }
809     }
810 
811     /// Offset in the original file to the content
content_offset(&self) -> i64812     pub fn content_offset(&self) -> i64 {
813         unsafe { (*self.raw).content_offset as i64 }
814     }
815 
816     /// Content of this line as bytes.
content(&self) -> &[u8]817     pub fn content(&self) -> &[u8] {
818         unsafe {
819             slice::from_raw_parts(
820                 (*self.raw).content as *const u8,
821                 (*self.raw).content_len as usize,
822             )
823         }
824     }
825 
826     /// Sigil showing the origin of this `DiffLine`.
827     ///
828     ///  * ` ` - Line context
829     ///  * `+` - Line addition
830     ///  * `-` - Line deletion
831     ///  * `=` - Context (End of file)
832     ///  * `>` - Add (End of file)
833     ///  * `<` - Remove (End of file)
834     ///  * `F` - File header
835     ///  * `H` - Hunk header
836     ///  * `B` - Line binary
origin(&self) -> char837     pub fn origin(&self) -> char {
838         match unsafe { (*self.raw).origin as raw::git_diff_line_t } {
839             raw::GIT_DIFF_LINE_CONTEXT => ' ',
840             raw::GIT_DIFF_LINE_ADDITION => '+',
841             raw::GIT_DIFF_LINE_DELETION => '-',
842             raw::GIT_DIFF_LINE_CONTEXT_EOFNL => '=',
843             raw::GIT_DIFF_LINE_ADD_EOFNL => '>',
844             raw::GIT_DIFF_LINE_DEL_EOFNL => '<',
845             raw::GIT_DIFF_LINE_FILE_HDR => 'F',
846             raw::GIT_DIFF_LINE_HUNK_HDR => 'H',
847             raw::GIT_DIFF_LINE_BINARY => 'B',
848             _ => ' ',
849         }
850     }
851 }
852 
853 impl<'a> Binding for DiffLine<'a> {
854     type Raw = *const raw::git_diff_line;
from_raw(raw: *const raw::git_diff_line) -> DiffLine<'a>855     unsafe fn from_raw(raw: *const raw::git_diff_line) -> DiffLine<'a> {
856         DiffLine {
857             raw: raw,
858             _marker: marker::PhantomData,
859         }
860     }
raw(&self) -> *const raw::git_diff_line861     fn raw(&self) -> *const raw::git_diff_line {
862         self.raw
863     }
864 }
865 
866 impl<'a> DiffHunk<'a> {
867     /// Starting line number in old_file
old_start(&self) -> u32868     pub fn old_start(&self) -> u32 {
869         unsafe { (*self.raw).old_start as u32 }
870     }
871 
872     /// Number of lines in old_file
old_lines(&self) -> u32873     pub fn old_lines(&self) -> u32 {
874         unsafe { (*self.raw).old_lines as u32 }
875     }
876 
877     /// Starting line number in new_file
new_start(&self) -> u32878     pub fn new_start(&self) -> u32 {
879         unsafe { (*self.raw).new_start as u32 }
880     }
881 
882     /// Number of lines in new_file
new_lines(&self) -> u32883     pub fn new_lines(&self) -> u32 {
884         unsafe { (*self.raw).new_lines as u32 }
885     }
886 
887     /// Header text
header(&self) -> &[u8]888     pub fn header(&self) -> &[u8] {
889         unsafe {
890             slice::from_raw_parts(
891                 (*self.raw).header.as_ptr() as *const u8,
892                 (*self.raw).header_len as usize,
893             )
894         }
895     }
896 }
897 
898 impl<'a> Binding for DiffHunk<'a> {
899     type Raw = *const raw::git_diff_hunk;
from_raw(raw: *const raw::git_diff_hunk) -> DiffHunk<'a>900     unsafe fn from_raw(raw: *const raw::git_diff_hunk) -> DiffHunk<'a> {
901         DiffHunk {
902             raw: raw,
903             _marker: marker::PhantomData,
904         }
905     }
raw(&self) -> *const raw::git_diff_hunk906     fn raw(&self) -> *const raw::git_diff_hunk {
907         self.raw
908     }
909 }
910 
911 impl DiffStats {
912     /// Get the total number of files chaned in a diff.
files_changed(&self) -> usize913     pub fn files_changed(&self) -> usize {
914         unsafe { raw::git_diff_stats_files_changed(&*self.raw) as usize }
915     }
916 
917     /// Get the total number of insertions in a diff
insertions(&self) -> usize918     pub fn insertions(&self) -> usize {
919         unsafe { raw::git_diff_stats_insertions(&*self.raw) as usize }
920     }
921 
922     /// Get the total number of deletions in a diff
deletions(&self) -> usize923     pub fn deletions(&self) -> usize {
924         unsafe { raw::git_diff_stats_deletions(&*self.raw) as usize }
925     }
926 
927     /// Print diff statistics to a Buf
to_buf(&self, format: DiffStatsFormat, width: usize) -> Result<Buf, Error>928     pub fn to_buf(&self, format: DiffStatsFormat, width: usize) -> Result<Buf, Error> {
929         let buf = Buf::new();
930         unsafe {
931             try_call!(raw::git_diff_stats_to_buf(
932                 buf.raw(),
933                 self.raw,
934                 format.bits(),
935                 width as size_t
936             ));
937         }
938         Ok(buf)
939     }
940 }
941 
942 impl Binding for DiffStats {
943     type Raw = *mut raw::git_diff_stats;
944 
from_raw(raw: *mut raw::git_diff_stats) -> DiffStats945     unsafe fn from_raw(raw: *mut raw::git_diff_stats) -> DiffStats {
946         DiffStats { raw: raw }
947     }
raw(&self) -> *mut raw::git_diff_stats948     fn raw(&self) -> *mut raw::git_diff_stats {
949         self.raw
950     }
951 }
952 
953 impl Drop for DiffStats {
drop(&mut self)954     fn drop(&mut self) {
955         unsafe { raw::git_diff_stats_free(self.raw) }
956     }
957 }
958 
959 impl<'a> DiffBinary<'a> {
960     /// Returns whether there is data in this binary structure or not.
961     ///
962     /// If this is `true`, then this was produced and included binary content.
963     /// If this is `false` then this was generated knowing only that a binary
964     /// file changed but without providing the data, probably from a patch that
965     /// said `Binary files a/file.txt and b/file.txt differ`.
contains_data(&self) -> bool966     pub fn contains_data(&self) -> bool {
967         unsafe { (*self.raw).contains_data == 1 }
968     }
969 
970     /// The contents of the old file.
old_file(&self) -> DiffBinaryFile<'a>971     pub fn old_file(&self) -> DiffBinaryFile<'a> {
972         unsafe { Binding::from_raw(&(*self.raw).old_file as *const _) }
973     }
974 
975     /// The contents of the new file.
new_file(&self) -> DiffBinaryFile<'a>976     pub fn new_file(&self) -> DiffBinaryFile<'a> {
977         unsafe { Binding::from_raw(&(*self.raw).new_file as *const _) }
978     }
979 }
980 
981 impl<'a> Binding for DiffBinary<'a> {
982     type Raw = *const raw::git_diff_binary;
from_raw(raw: *const raw::git_diff_binary) -> DiffBinary<'a>983     unsafe fn from_raw(raw: *const raw::git_diff_binary) -> DiffBinary<'a> {
984         DiffBinary {
985             raw: raw,
986             _marker: marker::PhantomData,
987         }
988     }
raw(&self) -> *const raw::git_diff_binary989     fn raw(&self) -> *const raw::git_diff_binary {
990         self.raw
991     }
992 }
993 
994 impl<'a> DiffBinaryFile<'a> {
995     /// The type of binary data for this file
kind(&self) -> DiffBinaryKind996     pub fn kind(&self) -> DiffBinaryKind {
997         unsafe { Binding::from_raw((*self.raw).kind) }
998     }
999 
1000     /// The binary data, deflated
data(&self) -> &[u8]1001     pub fn data(&self) -> &[u8] {
1002         unsafe {
1003             slice::from_raw_parts((*self.raw).data as *const u8, (*self.raw).datalen as usize)
1004         }
1005     }
1006 
1007     /// The length of the binary data after inflation
inflated_len(&self) -> usize1008     pub fn inflated_len(&self) -> usize {
1009         unsafe { (*self.raw).inflatedlen as usize }
1010     }
1011 }
1012 
1013 impl<'a> Binding for DiffBinaryFile<'a> {
1014     type Raw = *const raw::git_diff_binary_file;
from_raw(raw: *const raw::git_diff_binary_file) -> DiffBinaryFile<'a>1015     unsafe fn from_raw(raw: *const raw::git_diff_binary_file) -> DiffBinaryFile<'a> {
1016         DiffBinaryFile {
1017             raw: raw,
1018             _marker: marker::PhantomData,
1019         }
1020     }
raw(&self) -> *const raw::git_diff_binary_file1021     fn raw(&self) -> *const raw::git_diff_binary_file {
1022         self.raw
1023     }
1024 }
1025 
1026 impl Binding for DiffBinaryKind {
1027     type Raw = raw::git_diff_binary_t;
from_raw(raw: raw::git_diff_binary_t) -> DiffBinaryKind1028     unsafe fn from_raw(raw: raw::git_diff_binary_t) -> DiffBinaryKind {
1029         match raw {
1030             raw::GIT_DIFF_BINARY_NONE => DiffBinaryKind::None,
1031             raw::GIT_DIFF_BINARY_LITERAL => DiffBinaryKind::Literal,
1032             raw::GIT_DIFF_BINARY_DELTA => DiffBinaryKind::Delta,
1033             _ => panic!("Unknown git diff binary kind"),
1034         }
1035     }
raw(&self) -> raw::git_diff_binary_t1036     fn raw(&self) -> raw::git_diff_binary_t {
1037         match *self {
1038             DiffBinaryKind::None => raw::GIT_DIFF_BINARY_NONE,
1039             DiffBinaryKind::Literal => raw::GIT_DIFF_BINARY_LITERAL,
1040             DiffBinaryKind::Delta => raw::GIT_DIFF_BINARY_DELTA,
1041         }
1042     }
1043 }
1044 
1045 impl Default for DiffFindOptions {
default() -> Self1046     fn default() -> Self {
1047         Self::new()
1048     }
1049 }
1050 
1051 impl DiffFindOptions {
1052     /// Creates a new set of empty diff find options.
1053     ///
1054     /// All flags and other options are defaulted to false or their otherwise
1055     /// zero equivalents.
new() -> DiffFindOptions1056     pub fn new() -> DiffFindOptions {
1057         let mut opts = DiffFindOptions {
1058             raw: unsafe { mem::zeroed() },
1059         };
1060         assert_eq!(
1061             unsafe { raw::git_diff_find_init_options(&mut opts.raw, 1) },
1062             0
1063         );
1064         opts
1065     }
1066 
flag(&mut self, opt: u32, val: bool) -> &mut DiffFindOptions1067     fn flag(&mut self, opt: u32, val: bool) -> &mut DiffFindOptions {
1068         if val {
1069             self.raw.flags |= opt;
1070         } else {
1071             self.raw.flags &= !opt;
1072         }
1073         self
1074     }
1075 
1076     /// Reset all flags back to their unset state, indicating that
1077     /// `diff.renames` should be used instead. This is overridden once any flag
1078     /// is set.
by_config(&mut self) -> &mut DiffFindOptions1079     pub fn by_config(&mut self) -> &mut DiffFindOptions {
1080         self.flag(0xffffffff, false)
1081     }
1082 
1083     /// Look for renames?
renames(&mut self, find: bool) -> &mut DiffFindOptions1084     pub fn renames(&mut self, find: bool) -> &mut DiffFindOptions {
1085         self.flag(raw::GIT_DIFF_FIND_RENAMES, find)
1086     }
1087 
1088     /// Consider old side of modified for renames?
renames_from_rewrites(&mut self, find: bool) -> &mut DiffFindOptions1089     pub fn renames_from_rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
1090         self.flag(raw::GIT_DIFF_FIND_RENAMES_FROM_REWRITES, find)
1091     }
1092 
1093     /// Look for copies?
copies(&mut self, find: bool) -> &mut DiffFindOptions1094     pub fn copies(&mut self, find: bool) -> &mut DiffFindOptions {
1095         self.flag(raw::GIT_DIFF_FIND_COPIES, find)
1096     }
1097 
1098     /// Consider unmodified as copy sources?
1099     ///
1100     /// For this to work correctly, use `include_unmodified` when the initial
1101     /// diff is being generated.
copies_from_unmodified(&mut self, find: bool) -> &mut DiffFindOptions1102     pub fn copies_from_unmodified(&mut self, find: bool) -> &mut DiffFindOptions {
1103         self.flag(raw::GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED, find)
1104     }
1105 
1106     /// Mark significant rewrites for split.
rewrites(&mut self, find: bool) -> &mut DiffFindOptions1107     pub fn rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
1108         self.flag(raw::GIT_DIFF_FIND_REWRITES, find)
1109     }
1110 
1111     /// Actually split large rewrites into delete/add pairs
break_rewrites(&mut self, find: bool) -> &mut DiffFindOptions1112     pub fn break_rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
1113         self.flag(raw::GIT_DIFF_BREAK_REWRITES, find)
1114     }
1115 
1116     #[doc(hidden)]
break_rewries(&mut self, find: bool) -> &mut DiffFindOptions1117     pub fn break_rewries(&mut self, find: bool) -> &mut DiffFindOptions {
1118         self.break_rewrites(find)
1119     }
1120 
1121     /// Find renames/copies for untracked items in working directory.
1122     ///
1123     /// For this to work correctly use the `include_untracked` option when the
1124     /// initial diff is being generated.
for_untracked(&mut self, find: bool) -> &mut DiffFindOptions1125     pub fn for_untracked(&mut self, find: bool) -> &mut DiffFindOptions {
1126         self.flag(raw::GIT_DIFF_FIND_FOR_UNTRACKED, find)
1127     }
1128 
1129     /// Turn on all finding features.
all(&mut self, find: bool) -> &mut DiffFindOptions1130     pub fn all(&mut self, find: bool) -> &mut DiffFindOptions {
1131         self.flag(raw::GIT_DIFF_FIND_ALL, find)
1132     }
1133 
1134     /// Measure similarity ignoring leading whitespace (default)
ignore_leading_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions1135     pub fn ignore_leading_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions {
1136         self.flag(raw::GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE, ignore)
1137     }
1138 
1139     /// Measure similarity ignoring all whitespace
ignore_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions1140     pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions {
1141         self.flag(raw::GIT_DIFF_FIND_IGNORE_WHITESPACE, ignore)
1142     }
1143 
1144     /// Measure similarity including all data
dont_ignore_whitespace(&mut self, dont: bool) -> &mut DiffFindOptions1145     pub fn dont_ignore_whitespace(&mut self, dont: bool) -> &mut DiffFindOptions {
1146         self.flag(raw::GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE, dont)
1147     }
1148 
1149     /// Measure similarity only by comparing SHAs (fast and cheap)
exact_match_only(&mut self, exact: bool) -> &mut DiffFindOptions1150     pub fn exact_match_only(&mut self, exact: bool) -> &mut DiffFindOptions {
1151         self.flag(raw::GIT_DIFF_FIND_EXACT_MATCH_ONLY, exact)
1152     }
1153 
1154     /// Do not break rewrites unless they contribute to a rename.
1155     ///
1156     /// Normally, `break_rewrites` and `rewrites` will measure the
1157     /// self-similarity of modified files and split the ones that have changed a
1158     /// lot into a delete/add pair.  Then the sides of that pair will be
1159     /// considered candidates for rename and copy detection
1160     ///
1161     /// If you add this flag in and the split pair is not used for an actual
1162     /// rename or copy, then the modified record will be restored to a regular
1163     /// modified record instead of being split.
break_rewrites_for_renames_only(&mut self, b: bool) -> &mut DiffFindOptions1164     pub fn break_rewrites_for_renames_only(&mut self, b: bool) -> &mut DiffFindOptions {
1165         self.flag(raw::GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY, b)
1166     }
1167 
1168     /// Remove any unmodified deltas after find_similar is done.
1169     ///
1170     /// Using `copies_from_unmodified` to emulate the `--find-copies-harder`
1171     /// behavior requires building a diff with the `include_unmodified` flag. If
1172     /// you do not want unmodified records in the final result, pas this flag to
1173     /// have them removed.
remove_unmodified(&mut self, remove: bool) -> &mut DiffFindOptions1174     pub fn remove_unmodified(&mut self, remove: bool) -> &mut DiffFindOptions {
1175         self.flag(raw::GIT_DIFF_FIND_REMOVE_UNMODIFIED, remove)
1176     }
1177 
1178     /// Similarity to consider a file renamed (default 50)
rename_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions1179     pub fn rename_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1180         self.raw.rename_threshold = thresh;
1181         self
1182     }
1183 
1184     /// Similarity of modified to be glegible rename source (default 50)
rename_from_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions1185     pub fn rename_from_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1186         self.raw.rename_from_rewrite_threshold = thresh;
1187         self
1188     }
1189 
1190     /// Similarity to consider a file copy (default 50)
copy_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions1191     pub fn copy_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1192         self.raw.copy_threshold = thresh;
1193         self
1194     }
1195 
1196     /// Similarity to split modify into delete/add pair (default 60)
break_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions1197     pub fn break_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1198         self.raw.break_rewrite_threshold = thresh;
1199         self
1200     }
1201 
1202     /// Maximum similarity sources to examine for a file (somewhat like
1203     /// git-diff's `-l` option or `diff.renameLimit` config)
1204     ///
1205     /// Defaults to 200
rename_limit(&mut self, limit: usize) -> &mut DiffFindOptions1206     pub fn rename_limit(&mut self, limit: usize) -> &mut DiffFindOptions {
1207         self.raw.rename_limit = limit as size_t;
1208         self
1209     }
1210 
1211     // TODO: expose git_diff_similarity_metric
1212 }
1213 
1214 #[cfg(test)]
1215 mod tests {
1216     use crate::DiffOptions;
1217     use std::borrow::Borrow;
1218     use std::fs::File;
1219     use std::io::Write;
1220     use std::path::Path;
1221 
1222     #[test]
smoke()1223     fn smoke() {
1224         let (_td, repo) = crate::test::repo_init();
1225         let diff = repo.diff_tree_to_workdir(None, None).unwrap();
1226         assert_eq!(diff.deltas().len(), 0);
1227         let stats = diff.stats().unwrap();
1228         assert_eq!(stats.insertions(), 0);
1229         assert_eq!(stats.deletions(), 0);
1230         assert_eq!(stats.files_changed(), 0);
1231     }
1232 
1233     #[test]
foreach_smoke()1234     fn foreach_smoke() {
1235         let (_td, repo) = crate::test::repo_init();
1236         let diff = t!(repo.diff_tree_to_workdir(None, None));
1237         let mut count = 0;
1238         t!(diff.foreach(
1239             &mut |_file, _progress| {
1240                 count = count + 1;
1241                 true
1242             },
1243             None,
1244             None,
1245             None
1246         ));
1247         assert_eq!(count, 0);
1248     }
1249 
1250     #[test]
foreach_file_only()1251     fn foreach_file_only() {
1252         let path = Path::new("foo");
1253         let (td, repo) = crate::test::repo_init();
1254         t!(t!(File::create(&td.path().join(path))).write_all(b"bar"));
1255         let mut opts = DiffOptions::new();
1256         opts.include_untracked(true);
1257         let diff = t!(repo.diff_tree_to_workdir(None, Some(&mut opts)));
1258         let mut count = 0;
1259         let mut result = None;
1260         t!(diff.foreach(
1261             &mut |file, _progress| {
1262                 count = count + 1;
1263                 result = file.new_file().path().map(ToOwned::to_owned);
1264                 true
1265             },
1266             None,
1267             None,
1268             None
1269         ));
1270         assert_eq!(result.as_ref().map(Borrow::borrow), Some(path));
1271         assert_eq!(count, 1);
1272     }
1273 
1274     #[test]
foreach_file_and_hunk()1275     fn foreach_file_and_hunk() {
1276         let path = Path::new("foo");
1277         let (td, repo) = crate::test::repo_init();
1278         t!(t!(File::create(&td.path().join(path))).write_all(b"bar"));
1279         let mut index = t!(repo.index());
1280         t!(index.add_path(path));
1281         let mut opts = DiffOptions::new();
1282         opts.include_untracked(true);
1283         let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
1284         let mut new_lines = 0;
1285         t!(diff.foreach(
1286             &mut |_file, _progress| { true },
1287             None,
1288             Some(&mut |_file, hunk| {
1289                 new_lines = hunk.new_lines();
1290                 true
1291             }),
1292             None
1293         ));
1294         assert_eq!(new_lines, 1);
1295     }
1296 
1297     #[test]
foreach_all_callbacks()1298     fn foreach_all_callbacks() {
1299         let fib = vec![0, 1, 1, 2, 3, 5, 8];
1300         // Verified with a node implementation of deflate, might be worth
1301         // adding a deflate lib to do this inline here.
1302         let deflated_fib = vec![120, 156, 99, 96, 100, 100, 98, 102, 229, 0, 0, 0, 53, 0, 21];
1303         let foo_path = Path::new("foo");
1304         let bin_path = Path::new("bin");
1305         let (td, repo) = crate::test::repo_init();
1306         t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n"));
1307         t!(t!(File::create(&td.path().join(bin_path))).write_all(&fib));
1308         let mut index = t!(repo.index());
1309         t!(index.add_path(foo_path));
1310         t!(index.add_path(bin_path));
1311         let mut opts = DiffOptions::new();
1312         opts.include_untracked(true).show_binary(true);
1313         let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
1314         let mut bin_content = None;
1315         let mut new_lines = 0;
1316         let mut line_content = None;
1317         t!(diff.foreach(
1318             &mut |_file, _progress| { true },
1319             Some(&mut |_file, binary| {
1320                 bin_content = Some(binary.new_file().data().to_owned());
1321                 true
1322             }),
1323             Some(&mut |_file, hunk| {
1324                 new_lines = hunk.new_lines();
1325                 true
1326             }),
1327             Some(&mut |_file, _hunk, line| {
1328                 line_content = String::from_utf8(line.content().into()).ok();
1329                 true
1330             })
1331         ));
1332         assert_eq!(bin_content, Some(deflated_fib));
1333         assert_eq!(new_lines, 1);
1334         assert_eq!(line_content, Some("bar\n".to_string()));
1335     }
1336 }
1337