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, FileMode, Oid, Repository};
12 use crate::{DiffFlags, 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 /// Control behavior of formatting emails
57 pub struct DiffFormatEmailOptions {
58     raw: raw::git_diff_format_email_options,
59 }
60 
61 /// Control behavior of formatting emails
62 pub struct DiffPatchidOptions {
63     raw: raw::git_diff_patchid_options,
64 }
65 
66 /// An iterator over the diffs in a delta
67 pub struct Deltas<'diff> {
68     range: Range<usize>,
69     diff: &'diff Diff<'diff>,
70 }
71 
72 /// Structure describing a line (or data span) of a diff.
73 pub struct DiffLine<'a> {
74     raw: *const raw::git_diff_line,
75     _marker: marker::PhantomData<&'a raw::git_diff_line>,
76 }
77 
78 /// Structure describing a hunk of a diff.
79 pub struct DiffHunk<'a> {
80     raw: *const raw::git_diff_hunk,
81     _marker: marker::PhantomData<&'a raw::git_diff_hunk>,
82 }
83 
84 /// Structure describing a hunk of a diff.
85 pub struct DiffStats {
86     raw: *mut raw::git_diff_stats,
87 }
88 
89 /// Structure describing the binary contents of a diff.
90 pub struct DiffBinary<'a> {
91     raw: *const raw::git_diff_binary,
92     _marker: marker::PhantomData<&'a raw::git_diff_binary>,
93 }
94 
95 /// The contents of one of the files in a binary diff.
96 pub struct DiffBinaryFile<'a> {
97     raw: *const raw::git_diff_binary_file,
98     _marker: marker::PhantomData<&'a raw::git_diff_binary_file>,
99 }
100 
101 /// When producing a binary diff, the binary data returned will be
102 /// either the deflated full ("literal") contents of the file, or
103 /// the deflated binary delta between the two sides (whichever is
104 /// smaller).
105 #[derive(Copy, Clone, Debug)]
106 pub enum DiffBinaryKind {
107     /// There is no binary delta
108     None,
109     /// The binary data is the literal contents of the file
110     Literal,
111     /// The binary data is the delta from one side to the other
112     Delta,
113 }
114 
115 type PrintCb<'a> = dyn FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool + 'a;
116 
117 pub type FileCb<'a> = dyn FnMut(DiffDelta<'_>, f32) -> bool + 'a;
118 pub type BinaryCb<'a> = dyn FnMut(DiffDelta<'_>, DiffBinary<'_>) -> bool + 'a;
119 pub type HunkCb<'a> = dyn FnMut(DiffDelta<'_>, DiffHunk<'_>) -> bool + 'a;
120 pub type LineCb<'a> = dyn FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool + 'a;
121 
122 pub struct DiffCallbacks<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h> {
123     pub file: Option<&'a mut FileCb<'b>>,
124     pub binary: Option<&'c mut BinaryCb<'d>>,
125     pub hunk: Option<&'e mut HunkCb<'f>>,
126     pub line: Option<&'g mut LineCb<'h>>,
127 }
128 
129 impl<'repo> Diff<'repo> {
130     /// Merge one diff into another.
131     ///
132     /// This merges items from the "from" list into the "self" list.  The
133     /// resulting diff will have all items that appear in either list.
134     /// If an item appears in both lists, then it will be "merged" to appear
135     /// as if the old version was from the "onto" list and the new version
136     /// is from the "from" list (with the exception that if the item has a
137     /// pending DELETE in the middle, then it will show as deleted).
merge(&mut self, from: &Diff<'repo>) -> Result<(), Error>138     pub fn merge(&mut self, from: &Diff<'repo>) -> Result<(), Error> {
139         unsafe {
140             try_call!(raw::git_diff_merge(self.raw, &*from.raw));
141         }
142         Ok(())
143     }
144 
145     /// Returns an iterator over the deltas in this diff.
deltas(&self) -> Deltas<'_>146     pub fn deltas(&self) -> Deltas<'_> {
147         let num_deltas = unsafe { raw::git_diff_num_deltas(&*self.raw) };
148         Deltas {
149             range: 0..(num_deltas as usize),
150             diff: self,
151         }
152     }
153 
154     /// Return the diff delta for an entry in the diff list.
get_delta(&self, i: usize) -> Option<DiffDelta<'_>>155     pub fn get_delta(&self, i: usize) -> Option<DiffDelta<'_>> {
156         unsafe {
157             let ptr = raw::git_diff_get_delta(&*self.raw, i as size_t);
158             Binding::from_raw_opt(ptr as *mut _)
159         }
160     }
161 
162     /// Check if deltas are sorted case sensitively or insensitively.
is_sorted_icase(&self) -> bool163     pub fn is_sorted_icase(&self) -> bool {
164         unsafe { raw::git_diff_is_sorted_icase(&*self.raw) == 1 }
165     }
166 
167     /// Iterate over a diff generating formatted text output.
168     ///
169     /// Returning `false` from the callback will terminate the iteration and
170     /// return an error from this function.
print<F>(&self, format: DiffFormat, mut cb: F) -> Result<(), Error> where F: FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool,171     pub fn print<F>(&self, format: DiffFormat, mut cb: F) -> Result<(), Error>
172     where
173         F: FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool,
174     {
175         let mut cb: &mut PrintCb<'_> = &mut cb;
176         let ptr = &mut cb as *mut _;
177         let print: raw::git_diff_line_cb = Some(print_cb);
178         unsafe {
179             try_call!(raw::git_diff_print(self.raw, format, print, ptr as *mut _));
180             Ok(())
181         }
182     }
183 
184     /// Loop over all deltas in a diff issuing callbacks.
185     ///
186     /// Returning `false` from any callback will terminate the iteration and
187     /// 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>188     pub fn foreach(
189         &self,
190         file_cb: &mut FileCb<'_>,
191         binary_cb: Option<&mut BinaryCb<'_>>,
192         hunk_cb: Option<&mut HunkCb<'_>>,
193         line_cb: Option<&mut LineCb<'_>>,
194     ) -> Result<(), Error> {
195         let mut cbs = DiffCallbacks {
196             file: Some(file_cb),
197             binary: binary_cb,
198             hunk: hunk_cb,
199             line: line_cb,
200         };
201         let ptr = &mut cbs as *mut _;
202         unsafe {
203             let binary_cb_c: raw::git_diff_binary_cb = if cbs.binary.is_some() {
204                 Some(binary_cb_c)
205             } else {
206                 None
207             };
208             let hunk_cb_c: raw::git_diff_hunk_cb = if cbs.hunk.is_some() {
209                 Some(hunk_cb_c)
210             } else {
211                 None
212             };
213             let line_cb_c: raw::git_diff_line_cb = if cbs.line.is_some() {
214                 Some(line_cb_c)
215             } else {
216                 None
217             };
218             let file_cb: raw::git_diff_file_cb = Some(file_cb_c);
219             try_call!(raw::git_diff_foreach(
220                 self.raw,
221                 file_cb,
222                 binary_cb_c,
223                 hunk_cb_c,
224                 line_cb_c,
225                 ptr as *mut _
226             ));
227             Ok(())
228         }
229     }
230 
231     /// Accumulate diff statistics for all patches.
stats(&self) -> Result<DiffStats, Error>232     pub fn stats(&self) -> Result<DiffStats, Error> {
233         let mut ret = ptr::null_mut();
234         unsafe {
235             try_call!(raw::git_diff_get_stats(&mut ret, self.raw));
236             Ok(Binding::from_raw(ret))
237         }
238     }
239 
240     /// Transform a diff marking file renames, copies, etc.
241     ///
242     /// This modifies a diff in place, replacing old entries that look like
243     /// renames or copies with new entries reflecting those changes. This also
244     /// will, if requested, break modified files into add/remove pairs if the
245     /// amount of change is above a threshold.
find_similar(&mut self, opts: Option<&mut DiffFindOptions>) -> Result<(), Error>246     pub fn find_similar(&mut self, opts: Option<&mut DiffFindOptions>) -> Result<(), Error> {
247         let opts = opts.map(|opts| &opts.raw);
248         unsafe {
249             try_call!(raw::git_diff_find_similar(self.raw, opts));
250         }
251         Ok(())
252     }
253 
254     /// Create an e-mail ready patch from a diff.
255     ///
256     /// Matches the format created by `git format-patch`
format_email( &mut self, patch_no: usize, total_patches: usize, commit: &crate::Commit<'repo>, opts: Option<&mut DiffFormatEmailOptions>, ) -> Result<Buf, Error>257     pub fn format_email(
258         &mut self,
259         patch_no: usize,
260         total_patches: usize,
261         commit: &crate::Commit<'repo>,
262         opts: Option<&mut DiffFormatEmailOptions>,
263     ) -> Result<Buf, Error> {
264         assert!(patch_no > 0);
265         assert!(patch_no <= total_patches);
266         let mut default = DiffFormatEmailOptions::default();
267         let mut raw_opts = opts.map_or(&mut default.raw, |opts| &mut opts.raw);
268         let summary = commit.summary_bytes().unwrap();
269         let mut message = commit.message_bytes();
270         assert!(message.starts_with(summary));
271         message = &message[summary.len()..];
272         raw_opts.patch_no = patch_no;
273         raw_opts.total_patches = total_patches;
274         let id = commit.id();
275         raw_opts.id = id.raw();
276         raw_opts.summary = summary.as_ptr() as *const _;
277         raw_opts.body = message.as_ptr() as *const _;
278         raw_opts.author = commit.author().raw();
279         let buf = Buf::new();
280         unsafe {
281             try_call!(raw::git_diff_format_email(buf.raw(), self.raw, &*raw_opts));
282         }
283         Ok(buf)
284     }
285 
286     /// Create an patchid from a diff.
patchid(&self, opts: Option<&mut DiffPatchidOptions>) -> Result<Oid, Error>287     pub fn patchid(&self, opts: Option<&mut DiffPatchidOptions>) -> Result<Oid, Error> {
288         let mut raw = raw::git_oid {
289             id: [0; raw::GIT_OID_RAWSZ],
290         };
291         unsafe {
292             try_call!(raw::git_diff_patchid(
293                 &mut raw,
294                 self.raw,
295                 opts.map(|o| &mut o.raw)
296             ));
297             Ok(Binding::from_raw(&raw as *const _))
298         }
299     }
300 
301     // TODO: num_deltas_of_type, find_similar
302 }
303 impl Diff<'static> {
304     /// Read the contents of a git patch file into a `git_diff` object.
305     ///
306     /// The diff object produced is similar to the one that would be
307     /// produced if you actually produced it computationally by comparing
308     /// two trees, however there may be subtle differences. For example,
309     /// a patch file likely contains abbreviated object IDs, so the
310     /// object IDs parsed by this function will also be abreviated.
from_buffer(buffer: &[u8]) -> Result<Diff<'static>, Error>311     pub fn from_buffer(buffer: &[u8]) -> Result<Diff<'static>, Error> {
312         crate::init();
313         let mut diff: *mut raw::git_diff = std::ptr::null_mut();
314         unsafe {
315             // NOTE: Doesn't depend on repo, so lifetime can be 'static
316             try_call!(raw::git_diff_from_buffer(
317                 &mut diff,
318                 buffer.as_ptr() as *const c_char,
319                 buffer.len()
320             ));
321             Ok(Diff::from_raw(diff))
322         }
323     }
324 }
325 
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_int326 pub extern "C" fn print_cb(
327     delta: *const raw::git_diff_delta,
328     hunk: *const raw::git_diff_hunk,
329     line: *const raw::git_diff_line,
330     data: *mut c_void,
331 ) -> c_int {
332     unsafe {
333         let delta = Binding::from_raw(delta as *mut _);
334         let hunk = Binding::from_raw_opt(hunk);
335         let line = Binding::from_raw(line);
336 
337         let r = panic::wrap(|| {
338             let data = data as *mut &mut PrintCb<'_>;
339             (*data)(delta, hunk, line)
340         });
341         if r == Some(true) {
342             raw::GIT_OK
343         } else {
344             raw::GIT_EUSER
345         }
346     }
347 }
348 
file_cb_c( delta: *const raw::git_diff_delta, progress: f32, data: *mut c_void, ) -> c_int349 pub extern "C" fn file_cb_c(
350     delta: *const raw::git_diff_delta,
351     progress: f32,
352     data: *mut c_void,
353 ) -> c_int {
354     unsafe {
355         let delta = Binding::from_raw(delta as *mut _);
356 
357         let r = panic::wrap(|| {
358             let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
359             match (*cbs).file {
360                 Some(ref mut cb) => cb(delta, progress),
361                 None => false,
362             }
363         });
364         if r == Some(true) {
365             raw::GIT_OK
366         } else {
367             raw::GIT_EUSER
368         }
369     }
370 }
371 
binary_cb_c( delta: *const raw::git_diff_delta, binary: *const raw::git_diff_binary, data: *mut c_void, ) -> c_int372 pub extern "C" fn binary_cb_c(
373     delta: *const raw::git_diff_delta,
374     binary: *const raw::git_diff_binary,
375     data: *mut c_void,
376 ) -> c_int {
377     unsafe {
378         let delta = Binding::from_raw(delta as *mut _);
379         let binary = Binding::from_raw(binary);
380 
381         let r = panic::wrap(|| {
382             let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
383             match (*cbs).binary {
384                 Some(ref mut cb) => cb(delta, binary),
385                 None => false,
386             }
387         });
388         if r == Some(true) {
389             raw::GIT_OK
390         } else {
391             raw::GIT_EUSER
392         }
393     }
394 }
395 
hunk_cb_c( delta: *const raw::git_diff_delta, hunk: *const raw::git_diff_hunk, data: *mut c_void, ) -> c_int396 pub extern "C" fn hunk_cb_c(
397     delta: *const raw::git_diff_delta,
398     hunk: *const raw::git_diff_hunk,
399     data: *mut c_void,
400 ) -> c_int {
401     unsafe {
402         let delta = Binding::from_raw(delta as *mut _);
403         let hunk = Binding::from_raw(hunk);
404 
405         let r = panic::wrap(|| {
406             let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
407             match (*cbs).hunk {
408                 Some(ref mut cb) => cb(delta, hunk),
409                 None => false,
410             }
411         });
412         if r == Some(true) {
413             raw::GIT_OK
414         } else {
415             raw::GIT_EUSER
416         }
417     }
418 }
419 
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_int420 pub extern "C" fn line_cb_c(
421     delta: *const raw::git_diff_delta,
422     hunk: *const raw::git_diff_hunk,
423     line: *const raw::git_diff_line,
424     data: *mut c_void,
425 ) -> c_int {
426     unsafe {
427         let delta = Binding::from_raw(delta as *mut _);
428         let hunk = Binding::from_raw_opt(hunk);
429         let line = Binding::from_raw(line);
430 
431         let r = panic::wrap(|| {
432             let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
433             match (*cbs).line {
434                 Some(ref mut cb) => cb(delta, hunk, line),
435                 None => false,
436             }
437         });
438         if r == Some(true) {
439             raw::GIT_OK
440         } else {
441             raw::GIT_EUSER
442         }
443     }
444 }
445 
446 impl<'repo> Binding for Diff<'repo> {
447     type Raw = *mut raw::git_diff;
from_raw(raw: *mut raw::git_diff) -> Diff<'repo>448     unsafe fn from_raw(raw: *mut raw::git_diff) -> Diff<'repo> {
449         Diff {
450             raw: raw,
451             _marker: marker::PhantomData,
452         }
453     }
raw(&self) -> *mut raw::git_diff454     fn raw(&self) -> *mut raw::git_diff {
455         self.raw
456     }
457 }
458 
459 impl<'repo> Drop for Diff<'repo> {
drop(&mut self)460     fn drop(&mut self) {
461         unsafe { raw::git_diff_free(self.raw) }
462     }
463 }
464 
465 impl<'a> DiffDelta<'a> {
466     /// Returns the flags on the delta.
467     ///
468     /// For more information, see `DiffFlags`'s documentation.
flags(&self) -> DiffFlags469     pub fn flags(&self) -> DiffFlags {
470         let flags = unsafe { (*self.raw).flags };
471         let mut result = DiffFlags::empty();
472 
473         #[cfg(target_env = "msvc")]
474         fn as_u32(flag: i32) -> u32 {
475             flag as u32
476         }
477         #[cfg(not(target_env = "msvc"))]
478         fn as_u32(flag: u32) -> u32 {
479             flag
480         }
481 
482         if (flags & as_u32(raw::GIT_DIFF_FLAG_BINARY)) != 0 {
483             result |= DiffFlags::BINARY;
484         }
485         if (flags & as_u32(raw::GIT_DIFF_FLAG_NOT_BINARY)) != 0 {
486             result |= DiffFlags::NOT_BINARY;
487         }
488         if (flags & as_u32(raw::GIT_DIFF_FLAG_VALID_ID)) != 0 {
489             result |= DiffFlags::VALID_ID;
490         }
491         if (flags & as_u32(raw::GIT_DIFF_FLAG_EXISTS)) != 0 {
492             result |= DiffFlags::EXISTS;
493         }
494         result
495     }
496 
497     // TODO: expose when diffs are more exposed
498     // pub fn similarity(&self) -> u16 {
499     //     unsafe { (*self.raw).similarity }
500     // }
501 
502     /// Returns the number of files in this delta.
nfiles(&self) -> u16503     pub fn nfiles(&self) -> u16 {
504         unsafe { (*self.raw).nfiles }
505     }
506 
507     /// Returns the status of this entry
508     ///
509     /// For more information, see `Delta`'s documentation
status(&self) -> Delta510     pub fn status(&self) -> Delta {
511         match unsafe { (*self.raw).status } {
512             raw::GIT_DELTA_UNMODIFIED => Delta::Unmodified,
513             raw::GIT_DELTA_ADDED => Delta::Added,
514             raw::GIT_DELTA_DELETED => Delta::Deleted,
515             raw::GIT_DELTA_MODIFIED => Delta::Modified,
516             raw::GIT_DELTA_RENAMED => Delta::Renamed,
517             raw::GIT_DELTA_COPIED => Delta::Copied,
518             raw::GIT_DELTA_IGNORED => Delta::Ignored,
519             raw::GIT_DELTA_UNTRACKED => Delta::Untracked,
520             raw::GIT_DELTA_TYPECHANGE => Delta::Typechange,
521             raw::GIT_DELTA_UNREADABLE => Delta::Unreadable,
522             raw::GIT_DELTA_CONFLICTED => Delta::Conflicted,
523             n => panic!("unknown diff status: {}", n),
524         }
525     }
526 
527     /// Return the file which represents the "from" side of the diff.
528     ///
529     /// What side this means depends on the function that was used to generate
530     /// the diff and will be documented on the function itself.
old_file(&self) -> DiffFile<'a>531     pub fn old_file(&self) -> DiffFile<'a> {
532         unsafe { Binding::from_raw(&(*self.raw).old_file as *const _) }
533     }
534 
535     /// Return the file which represents the "to" side of the diff.
536     ///
537     /// What side this means depends on the function that was used to generate
538     /// the diff and will be documented on the function itself.
new_file(&self) -> DiffFile<'a>539     pub fn new_file(&self) -> DiffFile<'a> {
540         unsafe { Binding::from_raw(&(*self.raw).new_file as *const _) }
541     }
542 }
543 
544 impl<'a> Binding for DiffDelta<'a> {
545     type Raw = *mut raw::git_diff_delta;
from_raw(raw: *mut raw::git_diff_delta) -> DiffDelta<'a>546     unsafe fn from_raw(raw: *mut raw::git_diff_delta) -> DiffDelta<'a> {
547         DiffDelta {
548             raw: raw,
549             _marker: marker::PhantomData,
550         }
551     }
raw(&self) -> *mut raw::git_diff_delta552     fn raw(&self) -> *mut raw::git_diff_delta {
553         self.raw
554     }
555 }
556 
557 impl<'a> std::fmt::Debug for DiffDelta<'a> {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error>558     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
559         f.debug_struct("DiffDelta")
560             .field("nfiles", &self.nfiles())
561             .field("status", &self.status())
562             .field("old_file", &self.old_file())
563             .field("new_file", &self.new_file())
564             .finish()
565     }
566 }
567 
568 impl<'a> DiffFile<'a> {
569     /// Returns the Oid of this item.
570     ///
571     /// If this entry represents an absent side of a diff (e.g. the `old_file`
572     /// of a `Added` delta), then the oid returned will be zeroes.
id(&self) -> Oid573     pub fn id(&self) -> Oid {
574         unsafe { Binding::from_raw(&(*self.raw).id as *const _) }
575     }
576 
577     /// Returns the path, in bytes, of the entry relative to the working
578     /// directory of the repository.
path_bytes(&self) -> Option<&'a [u8]>579     pub fn path_bytes(&self) -> Option<&'a [u8]> {
580         static FOO: () = ();
581         unsafe { crate::opt_bytes(&FOO, (*self.raw).path) }
582     }
583 
584     /// Returns the path of the entry relative to the working directory of the
585     /// repository.
path(&self) -> Option<&'a Path>586     pub fn path(&self) -> Option<&'a Path> {
587         self.path_bytes().map(util::bytes2path)
588     }
589 
590     /// Returns the size of this entry, in bytes
size(&self) -> u64591     pub fn size(&self) -> u64 {
592         unsafe { (*self.raw).size as u64 }
593     }
594 
595     /// Returns `true` if file(s) are treated as binary data.
is_binary(&self) -> bool596     pub fn is_binary(&self) -> bool {
597         unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_BINARY as u32 != 0 }
598     }
599 
600     /// Returns `true` if file(s) are treated as text data.
is_not_binary(&self) -> bool601     pub fn is_not_binary(&self) -> bool {
602         unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_NOT_BINARY as u32 != 0 }
603     }
604 
605     /// Returns `true` if `id` value is known correct.
is_valid_id(&self) -> bool606     pub fn is_valid_id(&self) -> bool {
607         unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_VALID_ID as u32 != 0 }
608     }
609 
610     /// Returns `true` if file exists at this side of the delta.
exists(&self) -> bool611     pub fn exists(&self) -> bool {
612         unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_EXISTS as u32 != 0 }
613     }
614 
615     /// Returns file mode.
mode(&self) -> FileMode616     pub fn mode(&self) -> FileMode {
617         match unsafe { (*self.raw).mode.into() } {
618             raw::GIT_FILEMODE_UNREADABLE => FileMode::Unreadable,
619             raw::GIT_FILEMODE_TREE => FileMode::Tree,
620             raw::GIT_FILEMODE_BLOB => FileMode::Blob,
621             raw::GIT_FILEMODE_BLOB_EXECUTABLE => FileMode::BlobExecutable,
622             raw::GIT_FILEMODE_LINK => FileMode::Link,
623             raw::GIT_FILEMODE_COMMIT => FileMode::Commit,
624             mode => panic!("unknown mode: {}", mode),
625         }
626     }
627 }
628 
629 impl<'a> Binding for DiffFile<'a> {
630     type Raw = *const raw::git_diff_file;
from_raw(raw: *const raw::git_diff_file) -> DiffFile<'a>631     unsafe fn from_raw(raw: *const raw::git_diff_file) -> DiffFile<'a> {
632         DiffFile {
633             raw: raw,
634             _marker: marker::PhantomData,
635         }
636     }
raw(&self) -> *const raw::git_diff_file637     fn raw(&self) -> *const raw::git_diff_file {
638         self.raw
639     }
640 }
641 
642 impl<'a> std::fmt::Debug for DiffFile<'a> {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error>643     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
644         let mut ds = f.debug_struct("DiffFile");
645         ds.field("id", &self.id());
646         if let Some(path_bytes) = &self.path_bytes() {
647             ds.field("path_bytes", path_bytes);
648         }
649         if let Some(path) = &self.path() {
650             ds.field("path", path);
651         }
652         ds.field("size", &self.size()).finish()
653     }
654 }
655 
656 impl Default for DiffOptions {
default() -> Self657     fn default() -> Self {
658         Self::new()
659     }
660 }
661 
662 impl DiffOptions {
663     /// Creates a new set of empty diff options.
664     ///
665     /// All flags and other options are defaulted to false or their otherwise
666     /// zero equivalents.
new() -> DiffOptions667     pub fn new() -> DiffOptions {
668         let mut opts = DiffOptions {
669             pathspec: Vec::new(),
670             pathspec_ptrs: Vec::new(),
671             raw: unsafe { mem::zeroed() },
672             old_prefix: None,
673             new_prefix: None,
674         };
675         assert_eq!(unsafe { raw::git_diff_init_options(&mut opts.raw, 1) }, 0);
676         opts
677     }
678 
flag(&mut self, opt: i32, val: bool) -> &mut DiffOptions679     fn flag(&mut self, opt: i32, val: bool) -> &mut DiffOptions {
680         let opt = opt as u32;
681         if val {
682             self.raw.flags |= opt;
683         } else {
684             self.raw.flags &= !opt;
685         }
686         self
687     }
688 
689     /// Flag indicating whether the sides of the diff will be reversed.
reverse(&mut self, reverse: bool) -> &mut DiffOptions690     pub fn reverse(&mut self, reverse: bool) -> &mut DiffOptions {
691         self.flag(raw::GIT_DIFF_REVERSE, reverse)
692     }
693 
694     /// Flag indicating whether ignored files are included.
include_ignored(&mut self, include: bool) -> &mut DiffOptions695     pub fn include_ignored(&mut self, include: bool) -> &mut DiffOptions {
696         self.flag(raw::GIT_DIFF_INCLUDE_IGNORED, include)
697     }
698 
699     /// Flag indicating whether ignored directories are traversed deeply or not.
recurse_ignored_dirs(&mut self, recurse: bool) -> &mut DiffOptions700     pub fn recurse_ignored_dirs(&mut self, recurse: bool) -> &mut DiffOptions {
701         self.flag(raw::GIT_DIFF_RECURSE_IGNORED_DIRS, recurse)
702     }
703 
704     /// Flag indicating whether untracked files are in the diff
include_untracked(&mut self, include: bool) -> &mut DiffOptions705     pub fn include_untracked(&mut self, include: bool) -> &mut DiffOptions {
706         self.flag(raw::GIT_DIFF_INCLUDE_UNTRACKED, include)
707     }
708 
709     /// Flag indicating whether untracked directories are traversed deeply or
710     /// not.
recurse_untracked_dirs(&mut self, recurse: bool) -> &mut DiffOptions711     pub fn recurse_untracked_dirs(&mut self, recurse: bool) -> &mut DiffOptions {
712         self.flag(raw::GIT_DIFF_RECURSE_UNTRACKED_DIRS, recurse)
713     }
714 
715     /// Flag indicating whether unmodified files are in the diff.
include_unmodified(&mut self, include: bool) -> &mut DiffOptions716     pub fn include_unmodified(&mut self, include: bool) -> &mut DiffOptions {
717         self.flag(raw::GIT_DIFF_INCLUDE_UNMODIFIED, include)
718     }
719 
720     /// If enabled, then Typechange delta records are generated.
include_typechange(&mut self, include: bool) -> &mut DiffOptions721     pub fn include_typechange(&mut self, include: bool) -> &mut DiffOptions {
722         self.flag(raw::GIT_DIFF_INCLUDE_TYPECHANGE, include)
723     }
724 
725     /// Event with `include_typechange`, the tree returned generally shows a
726     /// deleted blob. This flag correctly labels the tree transitions as a
727     /// typechange record with the `new_file`'s mode set to tree.
728     ///
729     /// Note that the tree SHA will not be available.
include_typechange_trees(&mut self, include: bool) -> &mut DiffOptions730     pub fn include_typechange_trees(&mut self, include: bool) -> &mut DiffOptions {
731         self.flag(raw::GIT_DIFF_INCLUDE_TYPECHANGE_TREES, include)
732     }
733 
734     /// Flag indicating whether file mode changes are ignored.
ignore_filemode(&mut self, ignore: bool) -> &mut DiffOptions735     pub fn ignore_filemode(&mut self, ignore: bool) -> &mut DiffOptions {
736         self.flag(raw::GIT_DIFF_IGNORE_FILEMODE, ignore)
737     }
738 
739     /// Flag indicating whether all submodules should be treated as unmodified.
ignore_submodules(&mut self, ignore: bool) -> &mut DiffOptions740     pub fn ignore_submodules(&mut self, ignore: bool) -> &mut DiffOptions {
741         self.flag(raw::GIT_DIFF_IGNORE_SUBMODULES, ignore)
742     }
743 
744     /// Flag indicating whether case insensitive filenames should be used.
ignore_case(&mut self, ignore: bool) -> &mut DiffOptions745     pub fn ignore_case(&mut self, ignore: bool) -> &mut DiffOptions {
746         self.flag(raw::GIT_DIFF_IGNORE_CASE, ignore)
747     }
748 
749     /// If pathspecs are specified, this flag means that they should be applied
750     /// as an exact match instead of a fnmatch pattern.
disable_pathspec_match(&mut self, disable: bool) -> &mut DiffOptions751     pub fn disable_pathspec_match(&mut self, disable: bool) -> &mut DiffOptions {
752         self.flag(raw::GIT_DIFF_DISABLE_PATHSPEC_MATCH, disable)
753     }
754 
755     /// Disable updating the `binary` flag in delta records. This is useful when
756     /// iterating over a diff if you don't need hunk and data callbacks and want
757     /// to avoid having to load a file completely.
skip_binary_check(&mut self, skip: bool) -> &mut DiffOptions758     pub fn skip_binary_check(&mut self, skip: bool) -> &mut DiffOptions {
759         self.flag(raw::GIT_DIFF_SKIP_BINARY_CHECK, skip)
760     }
761 
762     /// When diff finds an untracked directory, to match the behavior of core
763     /// Git, it scans the contents for ignored and untracked files. If all
764     /// contents are ignored, then the directory is ignored; if any contents are
765     /// not ignored, then the directory is untracked. This is extra work that
766     /// may not matter in many cases.
767     ///
768     /// This flag turns off that scan and immediately labels an untracked
769     /// directory as untracked (changing the behavior to not match core git).
enable_fast_untracked_dirs(&mut self, enable: bool) -> &mut DiffOptions770     pub fn enable_fast_untracked_dirs(&mut self, enable: bool) -> &mut DiffOptions {
771         self.flag(raw::GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS, enable)
772     }
773 
774     /// When diff finds a file in the working directory with stat information
775     /// different from the index, but the OID ends up being the same, write the
776     /// correct stat information into the index. Note: without this flag, diff
777     /// will always leave the index untouched.
update_index(&mut self, update: bool) -> &mut DiffOptions778     pub fn update_index(&mut self, update: bool) -> &mut DiffOptions {
779         self.flag(raw::GIT_DIFF_UPDATE_INDEX, update)
780     }
781 
782     /// Include unreadable files in the diff
include_unreadable(&mut self, include: bool) -> &mut DiffOptions783     pub fn include_unreadable(&mut self, include: bool) -> &mut DiffOptions {
784         self.flag(raw::GIT_DIFF_INCLUDE_UNREADABLE, include)
785     }
786 
787     /// Include unreadable files in the diff as untracked files
include_unreadable_as_untracked(&mut self, include: bool) -> &mut DiffOptions788     pub fn include_unreadable_as_untracked(&mut self, include: bool) -> &mut DiffOptions {
789         self.flag(raw::GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED, include)
790     }
791 
792     /// Treat all files as text, disabling binary attributes and detection.
force_text(&mut self, force: bool) -> &mut DiffOptions793     pub fn force_text(&mut self, force: bool) -> &mut DiffOptions {
794         self.flag(raw::GIT_DIFF_FORCE_TEXT, force)
795     }
796 
797     /// Treat all files as binary, disabling text diffs
force_binary(&mut self, force: bool) -> &mut DiffOptions798     pub fn force_binary(&mut self, force: bool) -> &mut DiffOptions {
799         self.flag(raw::GIT_DIFF_FORCE_BINARY, force)
800     }
801 
802     /// Ignore all whitespace
ignore_whitespace(&mut self, ignore: bool) -> &mut DiffOptions803     pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut DiffOptions {
804         self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE, ignore)
805     }
806 
807     /// Ignore changes in the amount of whitespace
ignore_whitespace_change(&mut self, ignore: bool) -> &mut DiffOptions808     pub fn ignore_whitespace_change(&mut self, ignore: bool) -> &mut DiffOptions {
809         self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE_CHANGE, ignore)
810     }
811 
812     /// Ignore whitespace at the end of line
ignore_whitespace_eol(&mut self, ignore: bool) -> &mut DiffOptions813     pub fn ignore_whitespace_eol(&mut self, ignore: bool) -> &mut DiffOptions {
814         self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE_EOL, ignore)
815     }
816 
817     /// When generating patch text, include the content of untracked files.
818     ///
819     /// This automatically turns on `include_untracked` but it does not turn on
820     /// `recurse_untracked_dirs`. Add that flag if you want the content of every
821     /// single untracked file.
show_untracked_content(&mut self, show: bool) -> &mut DiffOptions822     pub fn show_untracked_content(&mut self, show: bool) -> &mut DiffOptions {
823         self.flag(raw::GIT_DIFF_SHOW_UNTRACKED_CONTENT, show)
824     }
825 
826     /// When generating output, include the names of unmodified files if they
827     /// are included in the `Diff`. Normally these are skipped in the formats
828     /// that list files (e.g. name-only, name-status, raw). Even with this these
829     /// will not be included in the patch format.
show_unmodified(&mut self, show: bool) -> &mut DiffOptions830     pub fn show_unmodified(&mut self, show: bool) -> &mut DiffOptions {
831         self.flag(raw::GIT_DIFF_SHOW_UNMODIFIED, show)
832     }
833 
834     /// Use the "patience diff" algorithm
patience(&mut self, patience: bool) -> &mut DiffOptions835     pub fn patience(&mut self, patience: bool) -> &mut DiffOptions {
836         self.flag(raw::GIT_DIFF_PATIENCE, patience)
837     }
838 
839     /// Take extra time to find the minimal diff
minimal(&mut self, minimal: bool) -> &mut DiffOptions840     pub fn minimal(&mut self, minimal: bool) -> &mut DiffOptions {
841         self.flag(raw::GIT_DIFF_MINIMAL, minimal)
842     }
843 
844     /// Include the necessary deflate/delta information so that `git-apply` can
845     /// apply given diff information to binary files.
show_binary(&mut self, show: bool) -> &mut DiffOptions846     pub fn show_binary(&mut self, show: bool) -> &mut DiffOptions {
847         self.flag(raw::GIT_DIFF_SHOW_BINARY, show)
848     }
849 
850     /// Use a heuristic that takes indentation and whitespace into account
851     /// which generally can produce better diffs when dealing with ambiguous
852     /// diff hunks.
indent_heuristic(&mut self, heuristic: bool) -> &mut DiffOptions853     pub fn indent_heuristic(&mut self, heuristic: bool) -> &mut DiffOptions {
854         self.flag(raw::GIT_DIFF_INDENT_HEURISTIC, heuristic)
855     }
856 
857     /// Set the number of unchanged lines that define the boundary of a hunk
858     /// (and to display before and after).
859     ///
860     /// The default value for this is 3.
context_lines(&mut self, lines: u32) -> &mut DiffOptions861     pub fn context_lines(&mut self, lines: u32) -> &mut DiffOptions {
862         self.raw.context_lines = lines;
863         self
864     }
865 
866     /// Set the maximum number of unchanged lines between hunk boundaries before
867     /// the hunks will be merged into one.
868     ///
869     /// The default value for this is 0.
interhunk_lines(&mut self, lines: u32) -> &mut DiffOptions870     pub fn interhunk_lines(&mut self, lines: u32) -> &mut DiffOptions {
871         self.raw.interhunk_lines = lines;
872         self
873     }
874 
875     /// The default value for this is `core.abbrev` or 7 if unset.
id_abbrev(&mut self, abbrev: u16) -> &mut DiffOptions876     pub fn id_abbrev(&mut self, abbrev: u16) -> &mut DiffOptions {
877         self.raw.id_abbrev = abbrev;
878         self
879     }
880 
881     /// Maximum size (in bytes) above which a blob will be marked as binary
882     /// automatically.
883     ///
884     /// A negative value will disable this entirely.
885     ///
886     /// The default value for this is 512MB.
max_size(&mut self, size: i64) -> &mut DiffOptions887     pub fn max_size(&mut self, size: i64) -> &mut DiffOptions {
888         self.raw.max_size = size as raw::git_off_t;
889         self
890     }
891 
892     /// The virtual "directory" to prefix old file names with in hunk headers.
893     ///
894     /// The default value for this is "a".
old_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions895     pub fn old_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions {
896         self.old_prefix = Some(t.into_c_string().unwrap());
897         self
898     }
899 
900     /// The virtual "directory" to prefix new file names with in hunk headers.
901     ///
902     /// The default value for this is "b".
new_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions903     pub fn new_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions {
904         self.new_prefix = Some(t.into_c_string().unwrap());
905         self
906     }
907 
908     /// Add to the array of paths/fnmatch patterns to constrain the diff.
pathspec<T: IntoCString>(&mut self, pathspec: T) -> &mut DiffOptions909     pub fn pathspec<T: IntoCString>(&mut self, pathspec: T) -> &mut DiffOptions {
910         let s = util::cstring_to_repo_path(pathspec).unwrap();
911         self.pathspec_ptrs.push(s.as_ptr());
912         self.pathspec.push(s);
913         self
914     }
915 
916     /// Acquire a pointer to the underlying raw options.
917     ///
918     /// This function is unsafe as the pointer is only valid so long as this
919     /// structure is not moved, modified, or used elsewhere.
raw(&mut self) -> *const raw::git_diff_options920     pub unsafe fn raw(&mut self) -> *const raw::git_diff_options {
921         self.raw.old_prefix = self
922             .old_prefix
923             .as_ref()
924             .map(|s| s.as_ptr())
925             .unwrap_or(ptr::null());
926         self.raw.new_prefix = self
927             .new_prefix
928             .as_ref()
929             .map(|s| s.as_ptr())
930             .unwrap_or(ptr::null());
931         self.raw.pathspec.count = self.pathspec_ptrs.len() as size_t;
932         self.raw.pathspec.strings = self.pathspec_ptrs.as_ptr() as *mut _;
933         &self.raw as *const _
934     }
935 
936     // TODO: expose ignore_submodules, notify_cb/notify_payload
937 }
938 
939 impl<'diff> Iterator for Deltas<'diff> {
940     type Item = DiffDelta<'diff>;
next(&mut self) -> Option<DiffDelta<'diff>>941     fn next(&mut self) -> Option<DiffDelta<'diff>> {
942         self.range.next().and_then(|i| self.diff.get_delta(i))
943     }
size_hint(&self) -> (usize, Option<usize>)944     fn size_hint(&self) -> (usize, Option<usize>) {
945         self.range.size_hint()
946     }
947 }
948 impl<'diff> DoubleEndedIterator for Deltas<'diff> {
next_back(&mut self) -> Option<DiffDelta<'diff>>949     fn next_back(&mut self) -> Option<DiffDelta<'diff>> {
950         self.range.next_back().and_then(|i| self.diff.get_delta(i))
951     }
952 }
953 impl<'diff> ExactSizeIterator for Deltas<'diff> {}
954 
955 /// Line origin constants.
956 #[derive(Copy, Clone, Debug, PartialEq)]
957 pub enum DiffLineType {
958     /// These values will be sent to `git_diff_line_cb` along with the line
959     Context,
960     ///
961     Addition,
962     ///
963     Deletion,
964     /// Both files have no LF at end
965     ContextEOFNL,
966     /// Old has no LF at end, new does
967     AddEOFNL,
968     /// Old has LF at end, new does not
969     DeleteEOFNL,
970     /// The following values will only be sent to a `git_diff_line_cb` when
971     /// the content of a diff is being formatted through `git_diff_print`.
972     FileHeader,
973     ///
974     HunkHeader,
975     /// For "Binary files x and y differ"
976     Binary,
977 }
978 
979 impl Binding for DiffLineType {
980     type Raw = raw::git_diff_line_t;
from_raw(raw: raw::git_diff_line_t) -> Self981     unsafe fn from_raw(raw: raw::git_diff_line_t) -> Self {
982         match raw {
983             raw::GIT_DIFF_LINE_CONTEXT => DiffLineType::Context,
984             raw::GIT_DIFF_LINE_ADDITION => DiffLineType::Addition,
985             raw::GIT_DIFF_LINE_DELETION => DiffLineType::Deletion,
986             raw::GIT_DIFF_LINE_CONTEXT_EOFNL => DiffLineType::ContextEOFNL,
987             raw::GIT_DIFF_LINE_ADD_EOFNL => DiffLineType::AddEOFNL,
988             raw::GIT_DIFF_LINE_DEL_EOFNL => DiffLineType::DeleteEOFNL,
989             raw::GIT_DIFF_LINE_FILE_HDR => DiffLineType::FileHeader,
990             raw::GIT_DIFF_LINE_HUNK_HDR => DiffLineType::HunkHeader,
991             raw::GIT_DIFF_LINE_BINARY => DiffLineType::Binary,
992             _ => panic!("Unknown git diff line type"),
993         }
994     }
raw(&self) -> raw::git_diff_line_t995     fn raw(&self) -> raw::git_diff_line_t {
996         match *self {
997             DiffLineType::Context => raw::GIT_DIFF_LINE_CONTEXT,
998             DiffLineType::Addition => raw::GIT_DIFF_LINE_ADDITION,
999             DiffLineType::Deletion => raw::GIT_DIFF_LINE_DELETION,
1000             DiffLineType::ContextEOFNL => raw::GIT_DIFF_LINE_CONTEXT_EOFNL,
1001             DiffLineType::AddEOFNL => raw::GIT_DIFF_LINE_ADD_EOFNL,
1002             DiffLineType::DeleteEOFNL => raw::GIT_DIFF_LINE_DEL_EOFNL,
1003             DiffLineType::FileHeader => raw::GIT_DIFF_LINE_FILE_HDR,
1004             DiffLineType::HunkHeader => raw::GIT_DIFF_LINE_HUNK_HDR,
1005             DiffLineType::Binary => raw::GIT_DIFF_LINE_BINARY,
1006         }
1007     }
1008 }
1009 
1010 impl<'a> DiffLine<'a> {
1011     /// Line number in old file or `None` for added line
old_lineno(&self) -> Option<u32>1012     pub fn old_lineno(&self) -> Option<u32> {
1013         match unsafe { (*self.raw).old_lineno } {
1014             n if n < 0 => None,
1015             n => Some(n as u32),
1016         }
1017     }
1018 
1019     /// Line number in new file or `None` for deleted line
new_lineno(&self) -> Option<u32>1020     pub fn new_lineno(&self) -> Option<u32> {
1021         match unsafe { (*self.raw).new_lineno } {
1022             n if n < 0 => None,
1023             n => Some(n as u32),
1024         }
1025     }
1026 
1027     /// Number of newline characters in content
num_lines(&self) -> u321028     pub fn num_lines(&self) -> u32 {
1029         unsafe { (*self.raw).num_lines as u32 }
1030     }
1031 
1032     /// Offset in the original file to the content
content_offset(&self) -> i641033     pub fn content_offset(&self) -> i64 {
1034         unsafe { (*self.raw).content_offset as i64 }
1035     }
1036 
1037     /// Content of this line as bytes.
content(&self) -> &'a [u8]1038     pub fn content(&self) -> &'a [u8] {
1039         unsafe {
1040             slice::from_raw_parts(
1041                 (*self.raw).content as *const u8,
1042                 (*self.raw).content_len as usize,
1043             )
1044         }
1045     }
1046 
1047     /// origin of this `DiffLine`.
1048     ///
origin_value(&self) -> DiffLineType1049     pub fn origin_value(&self) -> DiffLineType {
1050         unsafe { Binding::from_raw((*self.raw).origin as raw::git_diff_line_t) }
1051     }
1052 
1053     /// Sigil showing the origin of this `DiffLine`.
1054     ///
1055     ///  * ` ` - Line context
1056     ///  * `+` - Line addition
1057     ///  * `-` - Line deletion
1058     ///  * `=` - Context (End of file)
1059     ///  * `>` - Add (End of file)
1060     ///  * `<` - Remove (End of file)
1061     ///  * `F` - File header
1062     ///  * `H` - Hunk header
1063     ///  * `B` - Line binary
origin(&self) -> char1064     pub fn origin(&self) -> char {
1065         match unsafe { (*self.raw).origin as raw::git_diff_line_t } {
1066             raw::GIT_DIFF_LINE_CONTEXT => ' ',
1067             raw::GIT_DIFF_LINE_ADDITION => '+',
1068             raw::GIT_DIFF_LINE_DELETION => '-',
1069             raw::GIT_DIFF_LINE_CONTEXT_EOFNL => '=',
1070             raw::GIT_DIFF_LINE_ADD_EOFNL => '>',
1071             raw::GIT_DIFF_LINE_DEL_EOFNL => '<',
1072             raw::GIT_DIFF_LINE_FILE_HDR => 'F',
1073             raw::GIT_DIFF_LINE_HUNK_HDR => 'H',
1074             raw::GIT_DIFF_LINE_BINARY => 'B',
1075             _ => ' ',
1076         }
1077     }
1078 }
1079 
1080 impl<'a> Binding for DiffLine<'a> {
1081     type Raw = *const raw::git_diff_line;
from_raw(raw: *const raw::git_diff_line) -> DiffLine<'a>1082     unsafe fn from_raw(raw: *const raw::git_diff_line) -> DiffLine<'a> {
1083         DiffLine {
1084             raw: raw,
1085             _marker: marker::PhantomData,
1086         }
1087     }
raw(&self) -> *const raw::git_diff_line1088     fn raw(&self) -> *const raw::git_diff_line {
1089         self.raw
1090     }
1091 }
1092 
1093 impl<'a> std::fmt::Debug for DiffLine<'a> {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error>1094     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
1095         let mut ds = f.debug_struct("DiffLine");
1096         if let Some(old_lineno) = &self.old_lineno() {
1097             ds.field("old_lineno", old_lineno);
1098         }
1099         if let Some(new_lineno) = &self.new_lineno() {
1100             ds.field("new_lineno", new_lineno);
1101         }
1102         ds.field("num_lines", &self.num_lines())
1103             .field("content_offset", &self.content_offset())
1104             .field("content", &self.content())
1105             .field("origin", &self.origin())
1106             .finish()
1107     }
1108 }
1109 
1110 impl<'a> DiffHunk<'a> {
1111     /// Starting line number in old_file
old_start(&self) -> u321112     pub fn old_start(&self) -> u32 {
1113         unsafe { (*self.raw).old_start as u32 }
1114     }
1115 
1116     /// Number of lines in old_file
old_lines(&self) -> u321117     pub fn old_lines(&self) -> u32 {
1118         unsafe { (*self.raw).old_lines as u32 }
1119     }
1120 
1121     /// Starting line number in new_file
new_start(&self) -> u321122     pub fn new_start(&self) -> u32 {
1123         unsafe { (*self.raw).new_start as u32 }
1124     }
1125 
1126     /// Number of lines in new_file
new_lines(&self) -> u321127     pub fn new_lines(&self) -> u32 {
1128         unsafe { (*self.raw).new_lines as u32 }
1129     }
1130 
1131     /// Header text
header(&self) -> &'a [u8]1132     pub fn header(&self) -> &'a [u8] {
1133         unsafe {
1134             slice::from_raw_parts(
1135                 (*self.raw).header.as_ptr() as *const u8,
1136                 (*self.raw).header_len as usize,
1137             )
1138         }
1139     }
1140 }
1141 
1142 impl<'a> Binding for DiffHunk<'a> {
1143     type Raw = *const raw::git_diff_hunk;
from_raw(raw: *const raw::git_diff_hunk) -> DiffHunk<'a>1144     unsafe fn from_raw(raw: *const raw::git_diff_hunk) -> DiffHunk<'a> {
1145         DiffHunk {
1146             raw: raw,
1147             _marker: marker::PhantomData,
1148         }
1149     }
raw(&self) -> *const raw::git_diff_hunk1150     fn raw(&self) -> *const raw::git_diff_hunk {
1151         self.raw
1152     }
1153 }
1154 
1155 impl<'a> std::fmt::Debug for DiffHunk<'a> {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error>1156     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
1157         f.debug_struct("DiffHunk")
1158             .field("old_start", &self.old_start())
1159             .field("old_lines", &self.old_lines())
1160             .field("new_start", &self.new_start())
1161             .field("new_lines", &self.new_lines())
1162             .field("header", &self.header())
1163             .finish()
1164     }
1165 }
1166 
1167 impl DiffStats {
1168     /// Get the total number of files chaned in a diff.
files_changed(&self) -> usize1169     pub fn files_changed(&self) -> usize {
1170         unsafe { raw::git_diff_stats_files_changed(&*self.raw) as usize }
1171     }
1172 
1173     /// Get the total number of insertions in a diff
insertions(&self) -> usize1174     pub fn insertions(&self) -> usize {
1175         unsafe { raw::git_diff_stats_insertions(&*self.raw) as usize }
1176     }
1177 
1178     /// Get the total number of deletions in a diff
deletions(&self) -> usize1179     pub fn deletions(&self) -> usize {
1180         unsafe { raw::git_diff_stats_deletions(&*self.raw) as usize }
1181     }
1182 
1183     /// Print diff statistics to a Buf
to_buf(&self, format: DiffStatsFormat, width: usize) -> Result<Buf, Error>1184     pub fn to_buf(&self, format: DiffStatsFormat, width: usize) -> Result<Buf, Error> {
1185         let buf = Buf::new();
1186         unsafe {
1187             try_call!(raw::git_diff_stats_to_buf(
1188                 buf.raw(),
1189                 self.raw,
1190                 format.bits(),
1191                 width as size_t
1192             ));
1193         }
1194         Ok(buf)
1195     }
1196 }
1197 
1198 impl Binding for DiffStats {
1199     type Raw = *mut raw::git_diff_stats;
1200 
from_raw(raw: *mut raw::git_diff_stats) -> DiffStats1201     unsafe fn from_raw(raw: *mut raw::git_diff_stats) -> DiffStats {
1202         DiffStats { raw: raw }
1203     }
raw(&self) -> *mut raw::git_diff_stats1204     fn raw(&self) -> *mut raw::git_diff_stats {
1205         self.raw
1206     }
1207 }
1208 
1209 impl Drop for DiffStats {
drop(&mut self)1210     fn drop(&mut self) {
1211         unsafe { raw::git_diff_stats_free(self.raw) }
1212     }
1213 }
1214 
1215 impl std::fmt::Debug for DiffStats {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error>1216     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
1217         f.debug_struct("DiffStats")
1218             .field("files_changed", &self.files_changed())
1219             .field("insertions", &self.insertions())
1220             .field("deletions", &self.deletions())
1221             .finish()
1222     }
1223 }
1224 
1225 impl<'a> DiffBinary<'a> {
1226     /// Returns whether there is data in this binary structure or not.
1227     ///
1228     /// If this is `true`, then this was produced and included binary content.
1229     /// If this is `false` then this was generated knowing only that a binary
1230     /// file changed but without providing the data, probably from a patch that
1231     /// said `Binary files a/file.txt and b/file.txt differ`.
contains_data(&self) -> bool1232     pub fn contains_data(&self) -> bool {
1233         unsafe { (*self.raw).contains_data == 1 }
1234     }
1235 
1236     /// The contents of the old file.
old_file(&self) -> DiffBinaryFile<'a>1237     pub fn old_file(&self) -> DiffBinaryFile<'a> {
1238         unsafe { Binding::from_raw(&(*self.raw).old_file as *const _) }
1239     }
1240 
1241     /// The contents of the new file.
new_file(&self) -> DiffBinaryFile<'a>1242     pub fn new_file(&self) -> DiffBinaryFile<'a> {
1243         unsafe { Binding::from_raw(&(*self.raw).new_file as *const _) }
1244     }
1245 }
1246 
1247 impl<'a> Binding for DiffBinary<'a> {
1248     type Raw = *const raw::git_diff_binary;
from_raw(raw: *const raw::git_diff_binary) -> DiffBinary<'a>1249     unsafe fn from_raw(raw: *const raw::git_diff_binary) -> DiffBinary<'a> {
1250         DiffBinary {
1251             raw: raw,
1252             _marker: marker::PhantomData,
1253         }
1254     }
raw(&self) -> *const raw::git_diff_binary1255     fn raw(&self) -> *const raw::git_diff_binary {
1256         self.raw
1257     }
1258 }
1259 
1260 impl<'a> DiffBinaryFile<'a> {
1261     /// The type of binary data for this file
kind(&self) -> DiffBinaryKind1262     pub fn kind(&self) -> DiffBinaryKind {
1263         unsafe { Binding::from_raw((*self.raw).kind) }
1264     }
1265 
1266     /// The binary data, deflated
data(&self) -> &[u8]1267     pub fn data(&self) -> &[u8] {
1268         unsafe {
1269             slice::from_raw_parts((*self.raw).data as *const u8, (*self.raw).datalen as usize)
1270         }
1271     }
1272 
1273     /// The length of the binary data after inflation
inflated_len(&self) -> usize1274     pub fn inflated_len(&self) -> usize {
1275         unsafe { (*self.raw).inflatedlen as usize }
1276     }
1277 }
1278 
1279 impl<'a> Binding for DiffBinaryFile<'a> {
1280     type Raw = *const raw::git_diff_binary_file;
from_raw(raw: *const raw::git_diff_binary_file) -> DiffBinaryFile<'a>1281     unsafe fn from_raw(raw: *const raw::git_diff_binary_file) -> DiffBinaryFile<'a> {
1282         DiffBinaryFile {
1283             raw: raw,
1284             _marker: marker::PhantomData,
1285         }
1286     }
raw(&self) -> *const raw::git_diff_binary_file1287     fn raw(&self) -> *const raw::git_diff_binary_file {
1288         self.raw
1289     }
1290 }
1291 
1292 impl Binding for DiffBinaryKind {
1293     type Raw = raw::git_diff_binary_t;
from_raw(raw: raw::git_diff_binary_t) -> DiffBinaryKind1294     unsafe fn from_raw(raw: raw::git_diff_binary_t) -> DiffBinaryKind {
1295         match raw {
1296             raw::GIT_DIFF_BINARY_NONE => DiffBinaryKind::None,
1297             raw::GIT_DIFF_BINARY_LITERAL => DiffBinaryKind::Literal,
1298             raw::GIT_DIFF_BINARY_DELTA => DiffBinaryKind::Delta,
1299             _ => panic!("Unknown git diff binary kind"),
1300         }
1301     }
raw(&self) -> raw::git_diff_binary_t1302     fn raw(&self) -> raw::git_diff_binary_t {
1303         match *self {
1304             DiffBinaryKind::None => raw::GIT_DIFF_BINARY_NONE,
1305             DiffBinaryKind::Literal => raw::GIT_DIFF_BINARY_LITERAL,
1306             DiffBinaryKind::Delta => raw::GIT_DIFF_BINARY_DELTA,
1307         }
1308     }
1309 }
1310 
1311 impl Default for DiffFindOptions {
default() -> Self1312     fn default() -> Self {
1313         Self::new()
1314     }
1315 }
1316 
1317 impl DiffFindOptions {
1318     /// Creates a new set of empty diff find options.
1319     ///
1320     /// All flags and other options are defaulted to false or their otherwise
1321     /// zero equivalents.
new() -> DiffFindOptions1322     pub fn new() -> DiffFindOptions {
1323         let mut opts = DiffFindOptions {
1324             raw: unsafe { mem::zeroed() },
1325         };
1326         assert_eq!(
1327             unsafe { raw::git_diff_find_init_options(&mut opts.raw, 1) },
1328             0
1329         );
1330         opts
1331     }
1332 
flag(&mut self, opt: u32, val: bool) -> &mut DiffFindOptions1333     fn flag(&mut self, opt: u32, val: bool) -> &mut DiffFindOptions {
1334         if val {
1335             self.raw.flags |= opt;
1336         } else {
1337             self.raw.flags &= !opt;
1338         }
1339         self
1340     }
1341 
1342     /// Reset all flags back to their unset state, indicating that
1343     /// `diff.renames` should be used instead. This is overridden once any flag
1344     /// is set.
by_config(&mut self) -> &mut DiffFindOptions1345     pub fn by_config(&mut self) -> &mut DiffFindOptions {
1346         self.flag(0xffffffff, false)
1347     }
1348 
1349     /// Look for renames?
renames(&mut self, find: bool) -> &mut DiffFindOptions1350     pub fn renames(&mut self, find: bool) -> &mut DiffFindOptions {
1351         self.flag(raw::GIT_DIFF_FIND_RENAMES, find)
1352     }
1353 
1354     /// Consider old side of modified for renames?
renames_from_rewrites(&mut self, find: bool) -> &mut DiffFindOptions1355     pub fn renames_from_rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
1356         self.flag(raw::GIT_DIFF_FIND_RENAMES_FROM_REWRITES, find)
1357     }
1358 
1359     /// Look for copies?
copies(&mut self, find: bool) -> &mut DiffFindOptions1360     pub fn copies(&mut self, find: bool) -> &mut DiffFindOptions {
1361         self.flag(raw::GIT_DIFF_FIND_COPIES, find)
1362     }
1363 
1364     /// Consider unmodified as copy sources?
1365     ///
1366     /// For this to work correctly, use `include_unmodified` when the initial
1367     /// diff is being generated.
copies_from_unmodified(&mut self, find: bool) -> &mut DiffFindOptions1368     pub fn copies_from_unmodified(&mut self, find: bool) -> &mut DiffFindOptions {
1369         self.flag(raw::GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED, find)
1370     }
1371 
1372     /// Mark significant rewrites for split.
rewrites(&mut self, find: bool) -> &mut DiffFindOptions1373     pub fn rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
1374         self.flag(raw::GIT_DIFF_FIND_REWRITES, find)
1375     }
1376 
1377     /// Actually split large rewrites into delete/add pairs
break_rewrites(&mut self, find: bool) -> &mut DiffFindOptions1378     pub fn break_rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
1379         self.flag(raw::GIT_DIFF_BREAK_REWRITES, find)
1380     }
1381 
1382     #[doc(hidden)]
break_rewries(&mut self, find: bool) -> &mut DiffFindOptions1383     pub fn break_rewries(&mut self, find: bool) -> &mut DiffFindOptions {
1384         self.break_rewrites(find)
1385     }
1386 
1387     /// Find renames/copies for untracked items in working directory.
1388     ///
1389     /// For this to work correctly use the `include_untracked` option when the
1390     /// initial diff is being generated.
for_untracked(&mut self, find: bool) -> &mut DiffFindOptions1391     pub fn for_untracked(&mut self, find: bool) -> &mut DiffFindOptions {
1392         self.flag(raw::GIT_DIFF_FIND_FOR_UNTRACKED, find)
1393     }
1394 
1395     /// Turn on all finding features.
all(&mut self, find: bool) -> &mut DiffFindOptions1396     pub fn all(&mut self, find: bool) -> &mut DiffFindOptions {
1397         self.flag(raw::GIT_DIFF_FIND_ALL, find)
1398     }
1399 
1400     /// Measure similarity ignoring leading whitespace (default)
ignore_leading_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions1401     pub fn ignore_leading_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions {
1402         self.flag(raw::GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE, ignore)
1403     }
1404 
1405     /// Measure similarity ignoring all whitespace
ignore_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions1406     pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions {
1407         self.flag(raw::GIT_DIFF_FIND_IGNORE_WHITESPACE, ignore)
1408     }
1409 
1410     /// Measure similarity including all data
dont_ignore_whitespace(&mut self, dont: bool) -> &mut DiffFindOptions1411     pub fn dont_ignore_whitespace(&mut self, dont: bool) -> &mut DiffFindOptions {
1412         self.flag(raw::GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE, dont)
1413     }
1414 
1415     /// Measure similarity only by comparing SHAs (fast and cheap)
exact_match_only(&mut self, exact: bool) -> &mut DiffFindOptions1416     pub fn exact_match_only(&mut self, exact: bool) -> &mut DiffFindOptions {
1417         self.flag(raw::GIT_DIFF_FIND_EXACT_MATCH_ONLY, exact)
1418     }
1419 
1420     /// Do not break rewrites unless they contribute to a rename.
1421     ///
1422     /// Normally, `break_rewrites` and `rewrites` will measure the
1423     /// self-similarity of modified files and split the ones that have changed a
1424     /// lot into a delete/add pair.  Then the sides of that pair will be
1425     /// considered candidates for rename and copy detection
1426     ///
1427     /// If you add this flag in and the split pair is not used for an actual
1428     /// rename or copy, then the modified record will be restored to a regular
1429     /// modified record instead of being split.
break_rewrites_for_renames_only(&mut self, b: bool) -> &mut DiffFindOptions1430     pub fn break_rewrites_for_renames_only(&mut self, b: bool) -> &mut DiffFindOptions {
1431         self.flag(raw::GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY, b)
1432     }
1433 
1434     /// Remove any unmodified deltas after find_similar is done.
1435     ///
1436     /// Using `copies_from_unmodified` to emulate the `--find-copies-harder`
1437     /// behavior requires building a diff with the `include_unmodified` flag. If
1438     /// you do not want unmodified records in the final result, pas this flag to
1439     /// have them removed.
remove_unmodified(&mut self, remove: bool) -> &mut DiffFindOptions1440     pub fn remove_unmodified(&mut self, remove: bool) -> &mut DiffFindOptions {
1441         self.flag(raw::GIT_DIFF_FIND_REMOVE_UNMODIFIED, remove)
1442     }
1443 
1444     /// Similarity to consider a file renamed (default 50)
rename_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions1445     pub fn rename_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1446         self.raw.rename_threshold = thresh;
1447         self
1448     }
1449 
1450     /// Similarity of modified to be glegible rename source (default 50)
rename_from_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions1451     pub fn rename_from_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1452         self.raw.rename_from_rewrite_threshold = thresh;
1453         self
1454     }
1455 
1456     /// Similarity to consider a file copy (default 50)
copy_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions1457     pub fn copy_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1458         self.raw.copy_threshold = thresh;
1459         self
1460     }
1461 
1462     /// Similarity to split modify into delete/add pair (default 60)
break_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions1463     pub fn break_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1464         self.raw.break_rewrite_threshold = thresh;
1465         self
1466     }
1467 
1468     /// Maximum similarity sources to examine for a file (somewhat like
1469     /// git-diff's `-l` option or `diff.renameLimit` config)
1470     ///
1471     /// Defaults to 200
rename_limit(&mut self, limit: usize) -> &mut DiffFindOptions1472     pub fn rename_limit(&mut self, limit: usize) -> &mut DiffFindOptions {
1473         self.raw.rename_limit = limit as size_t;
1474         self
1475     }
1476 
1477     // TODO: expose git_diff_similarity_metric
1478 }
1479 
1480 impl Default for DiffFormatEmailOptions {
default() -> Self1481     fn default() -> Self {
1482         Self::new()
1483     }
1484 }
1485 
1486 impl DiffFormatEmailOptions {
1487     /// Creates a new set of email options,
1488     /// initialized to the default values
new() -> Self1489     pub fn new() -> Self {
1490         let mut opts = DiffFormatEmailOptions {
1491             raw: unsafe { mem::zeroed() },
1492         };
1493         assert_eq!(
1494             unsafe { raw::git_diff_format_email_options_init(&mut opts.raw, 1) },
1495             0
1496         );
1497         opts
1498     }
1499 
flag(&mut self, opt: u32, val: bool) -> &mut Self1500     fn flag(&mut self, opt: u32, val: bool) -> &mut Self {
1501         if val {
1502             self.raw.flags |= opt;
1503         } else {
1504             self.raw.flags &= !opt;
1505         }
1506         self
1507     }
1508 
1509     /// Exclude `[PATCH]` from the subject header
exclude_subject_patch_header(&mut self, should_exclude: bool) -> &mut Self1510     pub fn exclude_subject_patch_header(&mut self, should_exclude: bool) -> &mut Self {
1511         self.flag(
1512             raw::GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER,
1513             should_exclude,
1514         )
1515     }
1516 }
1517 
1518 impl DiffPatchidOptions {
1519     /// Creates a new set of patchid options,
1520     /// initialized to the default values
new() -> Self1521     pub fn new() -> Self {
1522         let mut opts = DiffPatchidOptions {
1523             raw: unsafe { mem::zeroed() },
1524         };
1525         assert_eq!(
1526             unsafe {
1527                 raw::git_diff_patchid_options_init(
1528                     &mut opts.raw,
1529                     raw::GIT_DIFF_PATCHID_OPTIONS_VERSION,
1530                 )
1531             },
1532             0
1533         );
1534         opts
1535     }
1536 }
1537 
1538 #[cfg(test)]
1539 mod tests {
1540     use crate::{DiffLineType, DiffOptions, Oid, Signature, Time};
1541     use std::borrow::Borrow;
1542     use std::fs::File;
1543     use std::io::Write;
1544     use std::path::Path;
1545 
1546     #[test]
smoke()1547     fn smoke() {
1548         let (_td, repo) = crate::test::repo_init();
1549         let diff = repo.diff_tree_to_workdir(None, None).unwrap();
1550         assert_eq!(diff.deltas().len(), 0);
1551         let stats = diff.stats().unwrap();
1552         assert_eq!(stats.insertions(), 0);
1553         assert_eq!(stats.deletions(), 0);
1554         assert_eq!(stats.files_changed(), 0);
1555         let patchid = diff.patchid(None).unwrap();
1556         assert_ne!(patchid, Oid::zero());
1557     }
1558 
1559     #[test]
foreach_smoke()1560     fn foreach_smoke() {
1561         let (_td, repo) = crate::test::repo_init();
1562         let diff = t!(repo.diff_tree_to_workdir(None, None));
1563         let mut count = 0;
1564         t!(diff.foreach(
1565             &mut |_file, _progress| {
1566                 count = count + 1;
1567                 true
1568             },
1569             None,
1570             None,
1571             None
1572         ));
1573         assert_eq!(count, 0);
1574     }
1575 
1576     #[test]
foreach_file_only()1577     fn foreach_file_only() {
1578         let path = Path::new("foo");
1579         let (td, repo) = crate::test::repo_init();
1580         t!(t!(File::create(&td.path().join(path))).write_all(b"bar"));
1581         let mut opts = DiffOptions::new();
1582         opts.include_untracked(true);
1583         let diff = t!(repo.diff_tree_to_workdir(None, Some(&mut opts)));
1584         let mut count = 0;
1585         let mut result = None;
1586         t!(diff.foreach(
1587             &mut |file, _progress| {
1588                 count = count + 1;
1589                 result = file.new_file().path().map(ToOwned::to_owned);
1590                 true
1591             },
1592             None,
1593             None,
1594             None
1595         ));
1596         assert_eq!(result.as_ref().map(Borrow::borrow), Some(path));
1597         assert_eq!(count, 1);
1598     }
1599 
1600     #[test]
foreach_file_and_hunk()1601     fn foreach_file_and_hunk() {
1602         let path = Path::new("foo");
1603         let (td, repo) = crate::test::repo_init();
1604         t!(t!(File::create(&td.path().join(path))).write_all(b"bar"));
1605         let mut index = t!(repo.index());
1606         t!(index.add_path(path));
1607         let mut opts = DiffOptions::new();
1608         opts.include_untracked(true);
1609         let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
1610         let mut new_lines = 0;
1611         t!(diff.foreach(
1612             &mut |_file, _progress| { true },
1613             None,
1614             Some(&mut |_file, hunk| {
1615                 new_lines = hunk.new_lines();
1616                 true
1617             }),
1618             None
1619         ));
1620         assert_eq!(new_lines, 1);
1621     }
1622 
1623     #[test]
foreach_all_callbacks()1624     fn foreach_all_callbacks() {
1625         let fib = vec![0, 1, 1, 2, 3, 5, 8];
1626         // Verified with a node implementation of deflate, might be worth
1627         // adding a deflate lib to do this inline here.
1628         let deflated_fib = vec![120, 156, 99, 96, 100, 100, 98, 102, 229, 0, 0, 0, 53, 0, 21];
1629         let foo_path = Path::new("foo");
1630         let bin_path = Path::new("bin");
1631         let (td, repo) = crate::test::repo_init();
1632         t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n"));
1633         t!(t!(File::create(&td.path().join(bin_path))).write_all(&fib));
1634         let mut index = t!(repo.index());
1635         t!(index.add_path(foo_path));
1636         t!(index.add_path(bin_path));
1637         let mut opts = DiffOptions::new();
1638         opts.include_untracked(true).show_binary(true);
1639         let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
1640         let mut bin_content = None;
1641         let mut new_lines = 0;
1642         let mut line_content = None;
1643         t!(diff.foreach(
1644             &mut |_file, _progress| { true },
1645             Some(&mut |_file, binary| {
1646                 bin_content = Some(binary.new_file().data().to_owned());
1647                 true
1648             }),
1649             Some(&mut |_file, hunk| {
1650                 new_lines = hunk.new_lines();
1651                 true
1652             }),
1653             Some(&mut |_file, _hunk, line| {
1654                 line_content = String::from_utf8(line.content().into()).ok();
1655                 true
1656             })
1657         ));
1658         assert_eq!(bin_content, Some(deflated_fib));
1659         assert_eq!(new_lines, 1);
1660         assert_eq!(line_content, Some("bar\n".to_string()));
1661     }
1662 
1663     #[test]
format_email_simple()1664     fn format_email_simple() {
1665         let (_td, repo) = crate::test::repo_init();
1666         const COMMIT_MESSAGE: &str = "Modify some content";
1667         const EXPECTED_EMAIL_START: &str = concat!(
1668             "From f1234fb0588b6ed670779a34ba5c51ef962f285f Mon Sep 17 00:00:00 2001\n",
1669             "From: Techcable <dummy@dummy.org>\n",
1670             "Date: Tue, 11 Jan 1972 17:46:40 +0000\n",
1671             "Subject: [PATCH] Modify some content\n",
1672             "\n",
1673             "---\n",
1674             " file1.txt | 8 +++++---\n",
1675             " 1 file changed, 5 insertions(+), 3 deletions(-)\n",
1676             "\n",
1677             "diff --git a/file1.txt b/file1.txt\n",
1678             "index 94aaae8..af8f41d 100644\n",
1679             "--- a/file1.txt\n",
1680             "+++ b/file1.txt\n",
1681             "@@ -1,15 +1,17 @@\n",
1682             " file1.txt\n",
1683             " file1.txt\n",
1684             "+_file1.txt_\n",
1685             " file1.txt\n",
1686             " file1.txt\n",
1687             " file1.txt\n",
1688             " file1.txt\n",
1689             "+\n",
1690             "+\n",
1691             " file1.txt\n",
1692             " file1.txt\n",
1693             " file1.txt\n",
1694             " file1.txt\n",
1695             " file1.txt\n",
1696             "-file1.txt\n",
1697             "-file1.txt\n",
1698             "-file1.txt\n",
1699             "+_file1.txt_\n",
1700             "+_file1.txt_\n",
1701             " file1.txt\n",
1702             "--\n"
1703         );
1704         const ORIGINAL_FILE: &str = concat!(
1705             "file1.txt\n",
1706             "file1.txt\n",
1707             "file1.txt\n",
1708             "file1.txt\n",
1709             "file1.txt\n",
1710             "file1.txt\n",
1711             "file1.txt\n",
1712             "file1.txt\n",
1713             "file1.txt\n",
1714             "file1.txt\n",
1715             "file1.txt\n",
1716             "file1.txt\n",
1717             "file1.txt\n",
1718             "file1.txt\n",
1719             "file1.txt\n"
1720         );
1721         const UPDATED_FILE: &str = concat!(
1722             "file1.txt\n",
1723             "file1.txt\n",
1724             "_file1.txt_\n",
1725             "file1.txt\n",
1726             "file1.txt\n",
1727             "file1.txt\n",
1728             "file1.txt\n",
1729             "\n",
1730             "\n",
1731             "file1.txt\n",
1732             "file1.txt\n",
1733             "file1.txt\n",
1734             "file1.txt\n",
1735             "file1.txt\n",
1736             "_file1.txt_\n",
1737             "_file1.txt_\n",
1738             "file1.txt\n"
1739         );
1740         const FILE_MODE: i32 = 0o100644;
1741         let original_file = repo.blob(ORIGINAL_FILE.as_bytes()).unwrap();
1742         let updated_file = repo.blob(UPDATED_FILE.as_bytes()).unwrap();
1743         let mut original_tree = repo.treebuilder(None).unwrap();
1744         original_tree
1745             .insert("file1.txt", original_file, FILE_MODE)
1746             .unwrap();
1747         let original_tree = original_tree.write().unwrap();
1748         let mut updated_tree = repo.treebuilder(None).unwrap();
1749         updated_tree
1750             .insert("file1.txt", updated_file, FILE_MODE)
1751             .unwrap();
1752         let updated_tree = updated_tree.write().unwrap();
1753         let time = Time::new(64_000_000, 0);
1754         let author = Signature::new("Techcable", "dummy@dummy.org", &time).unwrap();
1755         let updated_commit = repo
1756             .commit(
1757                 None,
1758                 &author,
1759                 &author,
1760                 COMMIT_MESSAGE,
1761                 &repo.find_tree(updated_tree).unwrap(),
1762                 &[], // NOTE: Have no parents to ensure stable hash
1763             )
1764             .unwrap();
1765         let updated_commit = repo.find_commit(updated_commit).unwrap();
1766         let mut diff = repo
1767             .diff_tree_to_tree(
1768                 Some(&repo.find_tree(original_tree).unwrap()),
1769                 Some(&repo.find_tree(updated_tree).unwrap()),
1770                 None,
1771             )
1772             .unwrap();
1773         let actual_email = diff.format_email(1, 1, &updated_commit, None).unwrap();
1774         let actual_email = actual_email.as_str().unwrap();
1775         assert!(
1776             actual_email.starts_with(EXPECTED_EMAIL_START),
1777             "Unexpected email:\n{}",
1778             actual_email
1779         );
1780         let mut remaining_lines = actual_email[EXPECTED_EMAIL_START.len()..].lines();
1781         let version_line = remaining_lines.next();
1782         assert!(
1783             version_line.unwrap().starts_with("libgit2"),
1784             "Invalid version line: {:?}",
1785             version_line
1786         );
1787         while let Some(line) = remaining_lines.next() {
1788             assert_eq!(line.trim(), "")
1789         }
1790     }
1791 
1792     #[test]
foreach_diff_line_origin_value()1793     fn foreach_diff_line_origin_value() {
1794         let foo_path = Path::new("foo");
1795         let (td, repo) = crate::test::repo_init();
1796         t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n"));
1797         let mut index = t!(repo.index());
1798         t!(index.add_path(foo_path));
1799         let mut opts = DiffOptions::new();
1800         opts.include_untracked(true);
1801         let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
1802         let mut origin_values: Vec<DiffLineType> = Vec::new();
1803         t!(diff.foreach(
1804             &mut |_file, _progress| { true },
1805             None,
1806             None,
1807             Some(&mut |_file, _hunk, line| {
1808                 origin_values.push(line.origin_value());
1809                 true
1810             })
1811         ));
1812         assert_eq!(origin_values.len(), 1);
1813         assert_eq!(origin_values[0], DiffLineType::Addition);
1814     }
1815 
1816     #[test]
foreach_exits_with_euser()1817     fn foreach_exits_with_euser() {
1818         let foo_path = Path::new("foo");
1819         let bar_path = Path::new("foo");
1820 
1821         let (td, repo) = crate::test::repo_init();
1822         t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n"));
1823 
1824         let mut index = t!(repo.index());
1825         t!(index.add_path(foo_path));
1826         t!(index.add_path(bar_path));
1827 
1828         let mut opts = DiffOptions::new();
1829         opts.include_untracked(true);
1830         let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
1831 
1832         let mut calls = 0;
1833         let result = diff.foreach(
1834             &mut |_file, _progress| {
1835                 calls += 1;
1836                 false
1837             },
1838             None,
1839             None,
1840             None,
1841         );
1842 
1843         assert_eq!(result.unwrap_err().code(), crate::ErrorCode::User);
1844     }
1845 }
1846