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,
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,
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,
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     /// Ignore blank lines
ignore_blank_lines(&mut self, ignore: bool) -> &mut DiffOptions818     pub fn ignore_blank_lines(&mut self, ignore: bool) -> &mut DiffOptions {
819         self.flag(raw::GIT_DIFF_IGNORE_BLANK_LINES, ignore)
820     }
821 
822     /// When generating patch text, include the content of untracked files.
823     ///
824     /// This automatically turns on `include_untracked` but it does not turn on
825     /// `recurse_untracked_dirs`. Add that flag if you want the content of every
826     /// single untracked file.
show_untracked_content(&mut self, show: bool) -> &mut DiffOptions827     pub fn show_untracked_content(&mut self, show: bool) -> &mut DiffOptions {
828         self.flag(raw::GIT_DIFF_SHOW_UNTRACKED_CONTENT, show)
829     }
830 
831     /// When generating output, include the names of unmodified files if they
832     /// are included in the `Diff`. Normally these are skipped in the formats
833     /// that list files (e.g. name-only, name-status, raw). Even with this these
834     /// will not be included in the patch format.
show_unmodified(&mut self, show: bool) -> &mut DiffOptions835     pub fn show_unmodified(&mut self, show: bool) -> &mut DiffOptions {
836         self.flag(raw::GIT_DIFF_SHOW_UNMODIFIED, show)
837     }
838 
839     /// Use the "patience diff" algorithm
patience(&mut self, patience: bool) -> &mut DiffOptions840     pub fn patience(&mut self, patience: bool) -> &mut DiffOptions {
841         self.flag(raw::GIT_DIFF_PATIENCE, patience)
842     }
843 
844     /// Take extra time to find the minimal diff
minimal(&mut self, minimal: bool) -> &mut DiffOptions845     pub fn minimal(&mut self, minimal: bool) -> &mut DiffOptions {
846         self.flag(raw::GIT_DIFF_MINIMAL, minimal)
847     }
848 
849     /// Include the necessary deflate/delta information so that `git-apply` can
850     /// apply given diff information to binary files.
show_binary(&mut self, show: bool) -> &mut DiffOptions851     pub fn show_binary(&mut self, show: bool) -> &mut DiffOptions {
852         self.flag(raw::GIT_DIFF_SHOW_BINARY, show)
853     }
854 
855     /// Use a heuristic that takes indentation and whitespace into account
856     /// which generally can produce better diffs when dealing with ambiguous
857     /// diff hunks.
indent_heuristic(&mut self, heuristic: bool) -> &mut DiffOptions858     pub fn indent_heuristic(&mut self, heuristic: bool) -> &mut DiffOptions {
859         self.flag(raw::GIT_DIFF_INDENT_HEURISTIC, heuristic)
860     }
861 
862     /// Set the number of unchanged lines that define the boundary of a hunk
863     /// (and to display before and after).
864     ///
865     /// The default value for this is 3.
context_lines(&mut self, lines: u32) -> &mut DiffOptions866     pub fn context_lines(&mut self, lines: u32) -> &mut DiffOptions {
867         self.raw.context_lines = lines;
868         self
869     }
870 
871     /// Set the maximum number of unchanged lines between hunk boundaries before
872     /// the hunks will be merged into one.
873     ///
874     /// The default value for this is 0.
interhunk_lines(&mut self, lines: u32) -> &mut DiffOptions875     pub fn interhunk_lines(&mut self, lines: u32) -> &mut DiffOptions {
876         self.raw.interhunk_lines = lines;
877         self
878     }
879 
880     /// The default value for this is `core.abbrev` or 7 if unset.
id_abbrev(&mut self, abbrev: u16) -> &mut DiffOptions881     pub fn id_abbrev(&mut self, abbrev: u16) -> &mut DiffOptions {
882         self.raw.id_abbrev = abbrev;
883         self
884     }
885 
886     /// Maximum size (in bytes) above which a blob will be marked as binary
887     /// automatically.
888     ///
889     /// A negative value will disable this entirely.
890     ///
891     /// The default value for this is 512MB.
max_size(&mut self, size: i64) -> &mut DiffOptions892     pub fn max_size(&mut self, size: i64) -> &mut DiffOptions {
893         self.raw.max_size = size as raw::git_off_t;
894         self
895     }
896 
897     /// The virtual "directory" to prefix old file names with in hunk headers.
898     ///
899     /// The default value for this is "a".
old_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions900     pub fn old_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions {
901         self.old_prefix = Some(t.into_c_string().unwrap());
902         self
903     }
904 
905     /// The virtual "directory" to prefix new file names with in hunk headers.
906     ///
907     /// The default value for this is "b".
new_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions908     pub fn new_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions {
909         self.new_prefix = Some(t.into_c_string().unwrap());
910         self
911     }
912 
913     /// Add to the array of paths/fnmatch patterns to constrain the diff.
pathspec<T: IntoCString>(&mut self, pathspec: T) -> &mut DiffOptions914     pub fn pathspec<T: IntoCString>(&mut self, pathspec: T) -> &mut DiffOptions {
915         let s = util::cstring_to_repo_path(pathspec).unwrap();
916         self.pathspec_ptrs.push(s.as_ptr());
917         self.pathspec.push(s);
918         self
919     }
920 
921     /// Acquire a pointer to the underlying raw options.
922     ///
923     /// This function is unsafe as the pointer is only valid so long as this
924     /// structure is not moved, modified, or used elsewhere.
raw(&mut self) -> *const raw::git_diff_options925     pub unsafe fn raw(&mut self) -> *const raw::git_diff_options {
926         self.raw.old_prefix = self
927             .old_prefix
928             .as_ref()
929             .map(|s| s.as_ptr())
930             .unwrap_or(ptr::null());
931         self.raw.new_prefix = self
932             .new_prefix
933             .as_ref()
934             .map(|s| s.as_ptr())
935             .unwrap_or(ptr::null());
936         self.raw.pathspec.count = self.pathspec_ptrs.len() as size_t;
937         self.raw.pathspec.strings = self.pathspec_ptrs.as_ptr() as *mut _;
938         &self.raw as *const _
939     }
940 
941     // TODO: expose ignore_submodules, notify_cb/notify_payload
942 }
943 
944 impl<'diff> Iterator for Deltas<'diff> {
945     type Item = DiffDelta<'diff>;
next(&mut self) -> Option<DiffDelta<'diff>>946     fn next(&mut self) -> Option<DiffDelta<'diff>> {
947         self.range.next().and_then(|i| self.diff.get_delta(i))
948     }
size_hint(&self) -> (usize, Option<usize>)949     fn size_hint(&self) -> (usize, Option<usize>) {
950         self.range.size_hint()
951     }
952 }
953 impl<'diff> DoubleEndedIterator for Deltas<'diff> {
next_back(&mut self) -> Option<DiffDelta<'diff>>954     fn next_back(&mut self) -> Option<DiffDelta<'diff>> {
955         self.range.next_back().and_then(|i| self.diff.get_delta(i))
956     }
957 }
958 impl<'diff> ExactSizeIterator for Deltas<'diff> {}
959 
960 /// Line origin constants.
961 #[derive(Copy, Clone, Debug, PartialEq)]
962 pub enum DiffLineType {
963     /// These values will be sent to `git_diff_line_cb` along with the line
964     Context,
965     ///
966     Addition,
967     ///
968     Deletion,
969     /// Both files have no LF at end
970     ContextEOFNL,
971     /// Old has no LF at end, new does
972     AddEOFNL,
973     /// Old has LF at end, new does not
974     DeleteEOFNL,
975     /// The following values will only be sent to a `git_diff_line_cb` when
976     /// the content of a diff is being formatted through `git_diff_print`.
977     FileHeader,
978     ///
979     HunkHeader,
980     /// For "Binary files x and y differ"
981     Binary,
982 }
983 
984 impl Binding for DiffLineType {
985     type Raw = raw::git_diff_line_t;
from_raw(raw: raw::git_diff_line_t) -> Self986     unsafe fn from_raw(raw: raw::git_diff_line_t) -> Self {
987         match raw {
988             raw::GIT_DIFF_LINE_CONTEXT => DiffLineType::Context,
989             raw::GIT_DIFF_LINE_ADDITION => DiffLineType::Addition,
990             raw::GIT_DIFF_LINE_DELETION => DiffLineType::Deletion,
991             raw::GIT_DIFF_LINE_CONTEXT_EOFNL => DiffLineType::ContextEOFNL,
992             raw::GIT_DIFF_LINE_ADD_EOFNL => DiffLineType::AddEOFNL,
993             raw::GIT_DIFF_LINE_DEL_EOFNL => DiffLineType::DeleteEOFNL,
994             raw::GIT_DIFF_LINE_FILE_HDR => DiffLineType::FileHeader,
995             raw::GIT_DIFF_LINE_HUNK_HDR => DiffLineType::HunkHeader,
996             raw::GIT_DIFF_LINE_BINARY => DiffLineType::Binary,
997             _ => panic!("Unknown git diff line type"),
998         }
999     }
raw(&self) -> raw::git_diff_line_t1000     fn raw(&self) -> raw::git_diff_line_t {
1001         match *self {
1002             DiffLineType::Context => raw::GIT_DIFF_LINE_CONTEXT,
1003             DiffLineType::Addition => raw::GIT_DIFF_LINE_ADDITION,
1004             DiffLineType::Deletion => raw::GIT_DIFF_LINE_DELETION,
1005             DiffLineType::ContextEOFNL => raw::GIT_DIFF_LINE_CONTEXT_EOFNL,
1006             DiffLineType::AddEOFNL => raw::GIT_DIFF_LINE_ADD_EOFNL,
1007             DiffLineType::DeleteEOFNL => raw::GIT_DIFF_LINE_DEL_EOFNL,
1008             DiffLineType::FileHeader => raw::GIT_DIFF_LINE_FILE_HDR,
1009             DiffLineType::HunkHeader => raw::GIT_DIFF_LINE_HUNK_HDR,
1010             DiffLineType::Binary => raw::GIT_DIFF_LINE_BINARY,
1011         }
1012     }
1013 }
1014 
1015 impl<'a> DiffLine<'a> {
1016     /// Line number in old file or `None` for added line
old_lineno(&self) -> Option<u32>1017     pub fn old_lineno(&self) -> Option<u32> {
1018         match unsafe { (*self.raw).old_lineno } {
1019             n if n < 0 => None,
1020             n => Some(n as u32),
1021         }
1022     }
1023 
1024     /// Line number in new file or `None` for deleted line
new_lineno(&self) -> Option<u32>1025     pub fn new_lineno(&self) -> Option<u32> {
1026         match unsafe { (*self.raw).new_lineno } {
1027             n if n < 0 => None,
1028             n => Some(n as u32),
1029         }
1030     }
1031 
1032     /// Number of newline characters in content
num_lines(&self) -> u321033     pub fn num_lines(&self) -> u32 {
1034         unsafe { (*self.raw).num_lines as u32 }
1035     }
1036 
1037     /// Offset in the original file to the content
content_offset(&self) -> i641038     pub fn content_offset(&self) -> i64 {
1039         unsafe { (*self.raw).content_offset as i64 }
1040     }
1041 
1042     /// Content of this line as bytes.
content(&self) -> &'a [u8]1043     pub fn content(&self) -> &'a [u8] {
1044         unsafe {
1045             slice::from_raw_parts(
1046                 (*self.raw).content as *const u8,
1047                 (*self.raw).content_len as usize,
1048             )
1049         }
1050     }
1051 
1052     /// origin of this `DiffLine`.
1053     ///
origin_value(&self) -> DiffLineType1054     pub fn origin_value(&self) -> DiffLineType {
1055         unsafe { Binding::from_raw((*self.raw).origin as raw::git_diff_line_t) }
1056     }
1057 
1058     /// Sigil showing the origin of this `DiffLine`.
1059     ///
1060     ///  * ` ` - Line context
1061     ///  * `+` - Line addition
1062     ///  * `-` - Line deletion
1063     ///  * `=` - Context (End of file)
1064     ///  * `>` - Add (End of file)
1065     ///  * `<` - Remove (End of file)
1066     ///  * `F` - File header
1067     ///  * `H` - Hunk header
1068     ///  * `B` - Line binary
origin(&self) -> char1069     pub fn origin(&self) -> char {
1070         match unsafe { (*self.raw).origin as raw::git_diff_line_t } {
1071             raw::GIT_DIFF_LINE_CONTEXT => ' ',
1072             raw::GIT_DIFF_LINE_ADDITION => '+',
1073             raw::GIT_DIFF_LINE_DELETION => '-',
1074             raw::GIT_DIFF_LINE_CONTEXT_EOFNL => '=',
1075             raw::GIT_DIFF_LINE_ADD_EOFNL => '>',
1076             raw::GIT_DIFF_LINE_DEL_EOFNL => '<',
1077             raw::GIT_DIFF_LINE_FILE_HDR => 'F',
1078             raw::GIT_DIFF_LINE_HUNK_HDR => 'H',
1079             raw::GIT_DIFF_LINE_BINARY => 'B',
1080             _ => ' ',
1081         }
1082     }
1083 }
1084 
1085 impl<'a> Binding for DiffLine<'a> {
1086     type Raw = *const raw::git_diff_line;
from_raw(raw: *const raw::git_diff_line) -> DiffLine<'a>1087     unsafe fn from_raw(raw: *const raw::git_diff_line) -> DiffLine<'a> {
1088         DiffLine {
1089             raw,
1090             _marker: marker::PhantomData,
1091         }
1092     }
raw(&self) -> *const raw::git_diff_line1093     fn raw(&self) -> *const raw::git_diff_line {
1094         self.raw
1095     }
1096 }
1097 
1098 impl<'a> std::fmt::Debug for DiffLine<'a> {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error>1099     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
1100         let mut ds = f.debug_struct("DiffLine");
1101         if let Some(old_lineno) = &self.old_lineno() {
1102             ds.field("old_lineno", old_lineno);
1103         }
1104         if let Some(new_lineno) = &self.new_lineno() {
1105             ds.field("new_lineno", new_lineno);
1106         }
1107         ds.field("num_lines", &self.num_lines())
1108             .field("content_offset", &self.content_offset())
1109             .field("content", &self.content())
1110             .field("origin", &self.origin())
1111             .finish()
1112     }
1113 }
1114 
1115 impl<'a> DiffHunk<'a> {
1116     /// Starting line number in old_file
old_start(&self) -> u321117     pub fn old_start(&self) -> u32 {
1118         unsafe { (*self.raw).old_start as u32 }
1119     }
1120 
1121     /// Number of lines in old_file
old_lines(&self) -> u321122     pub fn old_lines(&self) -> u32 {
1123         unsafe { (*self.raw).old_lines as u32 }
1124     }
1125 
1126     /// Starting line number in new_file
new_start(&self) -> u321127     pub fn new_start(&self) -> u32 {
1128         unsafe { (*self.raw).new_start as u32 }
1129     }
1130 
1131     /// Number of lines in new_file
new_lines(&self) -> u321132     pub fn new_lines(&self) -> u32 {
1133         unsafe { (*self.raw).new_lines as u32 }
1134     }
1135 
1136     /// Header text
header(&self) -> &'a [u8]1137     pub fn header(&self) -> &'a [u8] {
1138         unsafe {
1139             slice::from_raw_parts(
1140                 (*self.raw).header.as_ptr() as *const u8,
1141                 (*self.raw).header_len as usize,
1142             )
1143         }
1144     }
1145 }
1146 
1147 impl<'a> Binding for DiffHunk<'a> {
1148     type Raw = *const raw::git_diff_hunk;
from_raw(raw: *const raw::git_diff_hunk) -> DiffHunk<'a>1149     unsafe fn from_raw(raw: *const raw::git_diff_hunk) -> DiffHunk<'a> {
1150         DiffHunk {
1151             raw,
1152             _marker: marker::PhantomData,
1153         }
1154     }
raw(&self) -> *const raw::git_diff_hunk1155     fn raw(&self) -> *const raw::git_diff_hunk {
1156         self.raw
1157     }
1158 }
1159 
1160 impl<'a> std::fmt::Debug for DiffHunk<'a> {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error>1161     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
1162         f.debug_struct("DiffHunk")
1163             .field("old_start", &self.old_start())
1164             .field("old_lines", &self.old_lines())
1165             .field("new_start", &self.new_start())
1166             .field("new_lines", &self.new_lines())
1167             .field("header", &self.header())
1168             .finish()
1169     }
1170 }
1171 
1172 impl DiffStats {
1173     /// Get the total number of files changed in a diff.
files_changed(&self) -> usize1174     pub fn files_changed(&self) -> usize {
1175         unsafe { raw::git_diff_stats_files_changed(&*self.raw) as usize }
1176     }
1177 
1178     /// Get the total number of insertions in a diff
insertions(&self) -> usize1179     pub fn insertions(&self) -> usize {
1180         unsafe { raw::git_diff_stats_insertions(&*self.raw) as usize }
1181     }
1182 
1183     /// Get the total number of deletions in a diff
deletions(&self) -> usize1184     pub fn deletions(&self) -> usize {
1185         unsafe { raw::git_diff_stats_deletions(&*self.raw) as usize }
1186     }
1187 
1188     /// Print diff statistics to a Buf
to_buf(&self, format: DiffStatsFormat, width: usize) -> Result<Buf, Error>1189     pub fn to_buf(&self, format: DiffStatsFormat, width: usize) -> Result<Buf, Error> {
1190         let buf = Buf::new();
1191         unsafe {
1192             try_call!(raw::git_diff_stats_to_buf(
1193                 buf.raw(),
1194                 self.raw,
1195                 format.bits(),
1196                 width as size_t
1197             ));
1198         }
1199         Ok(buf)
1200     }
1201 }
1202 
1203 impl Binding for DiffStats {
1204     type Raw = *mut raw::git_diff_stats;
1205 
from_raw(raw: *mut raw::git_diff_stats) -> DiffStats1206     unsafe fn from_raw(raw: *mut raw::git_diff_stats) -> DiffStats {
1207         DiffStats { raw }
1208     }
raw(&self) -> *mut raw::git_diff_stats1209     fn raw(&self) -> *mut raw::git_diff_stats {
1210         self.raw
1211     }
1212 }
1213 
1214 impl Drop for DiffStats {
drop(&mut self)1215     fn drop(&mut self) {
1216         unsafe { raw::git_diff_stats_free(self.raw) }
1217     }
1218 }
1219 
1220 impl std::fmt::Debug for DiffStats {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error>1221     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
1222         f.debug_struct("DiffStats")
1223             .field("files_changed", &self.files_changed())
1224             .field("insertions", &self.insertions())
1225             .field("deletions", &self.deletions())
1226             .finish()
1227     }
1228 }
1229 
1230 impl<'a> DiffBinary<'a> {
1231     /// Returns whether there is data in this binary structure or not.
1232     ///
1233     /// If this is `true`, then this was produced and included binary content.
1234     /// If this is `false` then this was generated knowing only that a binary
1235     /// file changed but without providing the data, probably from a patch that
1236     /// said `Binary files a/file.txt and b/file.txt differ`.
contains_data(&self) -> bool1237     pub fn contains_data(&self) -> bool {
1238         unsafe { (*self.raw).contains_data == 1 }
1239     }
1240 
1241     /// The contents of the old file.
old_file(&self) -> DiffBinaryFile<'a>1242     pub fn old_file(&self) -> DiffBinaryFile<'a> {
1243         unsafe { Binding::from_raw(&(*self.raw).old_file as *const _) }
1244     }
1245 
1246     /// The contents of the new file.
new_file(&self) -> DiffBinaryFile<'a>1247     pub fn new_file(&self) -> DiffBinaryFile<'a> {
1248         unsafe { Binding::from_raw(&(*self.raw).new_file as *const _) }
1249     }
1250 }
1251 
1252 impl<'a> Binding for DiffBinary<'a> {
1253     type Raw = *const raw::git_diff_binary;
from_raw(raw: *const raw::git_diff_binary) -> DiffBinary<'a>1254     unsafe fn from_raw(raw: *const raw::git_diff_binary) -> DiffBinary<'a> {
1255         DiffBinary {
1256             raw,
1257             _marker: marker::PhantomData,
1258         }
1259     }
raw(&self) -> *const raw::git_diff_binary1260     fn raw(&self) -> *const raw::git_diff_binary {
1261         self.raw
1262     }
1263 }
1264 
1265 impl<'a> DiffBinaryFile<'a> {
1266     /// The type of binary data for this file
kind(&self) -> DiffBinaryKind1267     pub fn kind(&self) -> DiffBinaryKind {
1268         unsafe { Binding::from_raw((*self.raw).kind) }
1269     }
1270 
1271     /// The binary data, deflated
data(&self) -> &[u8]1272     pub fn data(&self) -> &[u8] {
1273         unsafe {
1274             slice::from_raw_parts((*self.raw).data as *const u8, (*self.raw).datalen as usize)
1275         }
1276     }
1277 
1278     /// The length of the binary data after inflation
inflated_len(&self) -> usize1279     pub fn inflated_len(&self) -> usize {
1280         unsafe { (*self.raw).inflatedlen as usize }
1281     }
1282 }
1283 
1284 impl<'a> Binding for DiffBinaryFile<'a> {
1285     type Raw = *const raw::git_diff_binary_file;
from_raw(raw: *const raw::git_diff_binary_file) -> DiffBinaryFile<'a>1286     unsafe fn from_raw(raw: *const raw::git_diff_binary_file) -> DiffBinaryFile<'a> {
1287         DiffBinaryFile {
1288             raw,
1289             _marker: marker::PhantomData,
1290         }
1291     }
raw(&self) -> *const raw::git_diff_binary_file1292     fn raw(&self) -> *const raw::git_diff_binary_file {
1293         self.raw
1294     }
1295 }
1296 
1297 impl Binding for DiffBinaryKind {
1298     type Raw = raw::git_diff_binary_t;
from_raw(raw: raw::git_diff_binary_t) -> DiffBinaryKind1299     unsafe fn from_raw(raw: raw::git_diff_binary_t) -> DiffBinaryKind {
1300         match raw {
1301             raw::GIT_DIFF_BINARY_NONE => DiffBinaryKind::None,
1302             raw::GIT_DIFF_BINARY_LITERAL => DiffBinaryKind::Literal,
1303             raw::GIT_DIFF_BINARY_DELTA => DiffBinaryKind::Delta,
1304             _ => panic!("Unknown git diff binary kind"),
1305         }
1306     }
raw(&self) -> raw::git_diff_binary_t1307     fn raw(&self) -> raw::git_diff_binary_t {
1308         match *self {
1309             DiffBinaryKind::None => raw::GIT_DIFF_BINARY_NONE,
1310             DiffBinaryKind::Literal => raw::GIT_DIFF_BINARY_LITERAL,
1311             DiffBinaryKind::Delta => raw::GIT_DIFF_BINARY_DELTA,
1312         }
1313     }
1314 }
1315 
1316 impl Default for DiffFindOptions {
default() -> Self1317     fn default() -> Self {
1318         Self::new()
1319     }
1320 }
1321 
1322 impl DiffFindOptions {
1323     /// Creates a new set of empty diff find options.
1324     ///
1325     /// All flags and other options are defaulted to false or their otherwise
1326     /// zero equivalents.
new() -> DiffFindOptions1327     pub fn new() -> DiffFindOptions {
1328         let mut opts = DiffFindOptions {
1329             raw: unsafe { mem::zeroed() },
1330         };
1331         assert_eq!(
1332             unsafe { raw::git_diff_find_init_options(&mut opts.raw, 1) },
1333             0
1334         );
1335         opts
1336     }
1337 
flag(&mut self, opt: u32, val: bool) -> &mut DiffFindOptions1338     fn flag(&mut self, opt: u32, val: bool) -> &mut DiffFindOptions {
1339         if val {
1340             self.raw.flags |= opt;
1341         } else {
1342             self.raw.flags &= !opt;
1343         }
1344         self
1345     }
1346 
1347     /// Reset all flags back to their unset state, indicating that
1348     /// `diff.renames` should be used instead. This is overridden once any flag
1349     /// is set.
by_config(&mut self) -> &mut DiffFindOptions1350     pub fn by_config(&mut self) -> &mut DiffFindOptions {
1351         self.flag(0xffffffff, false)
1352     }
1353 
1354     /// Look for renames?
renames(&mut self, find: bool) -> &mut DiffFindOptions1355     pub fn renames(&mut self, find: bool) -> &mut DiffFindOptions {
1356         self.flag(raw::GIT_DIFF_FIND_RENAMES, find)
1357     }
1358 
1359     /// Consider old side of modified for renames?
renames_from_rewrites(&mut self, find: bool) -> &mut DiffFindOptions1360     pub fn renames_from_rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
1361         self.flag(raw::GIT_DIFF_FIND_RENAMES_FROM_REWRITES, find)
1362     }
1363 
1364     /// Look for copies?
copies(&mut self, find: bool) -> &mut DiffFindOptions1365     pub fn copies(&mut self, find: bool) -> &mut DiffFindOptions {
1366         self.flag(raw::GIT_DIFF_FIND_COPIES, find)
1367     }
1368 
1369     /// Consider unmodified as copy sources?
1370     ///
1371     /// For this to work correctly, use `include_unmodified` when the initial
1372     /// diff is being generated.
copies_from_unmodified(&mut self, find: bool) -> &mut DiffFindOptions1373     pub fn copies_from_unmodified(&mut self, find: bool) -> &mut DiffFindOptions {
1374         self.flag(raw::GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED, find)
1375     }
1376 
1377     /// Mark significant rewrites for split.
rewrites(&mut self, find: bool) -> &mut DiffFindOptions1378     pub fn rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
1379         self.flag(raw::GIT_DIFF_FIND_REWRITES, find)
1380     }
1381 
1382     /// Actually split large rewrites into delete/add pairs
break_rewrites(&mut self, find: bool) -> &mut DiffFindOptions1383     pub fn break_rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
1384         self.flag(raw::GIT_DIFF_BREAK_REWRITES, find)
1385     }
1386 
1387     #[doc(hidden)]
break_rewries(&mut self, find: bool) -> &mut DiffFindOptions1388     pub fn break_rewries(&mut self, find: bool) -> &mut DiffFindOptions {
1389         self.break_rewrites(find)
1390     }
1391 
1392     /// Find renames/copies for untracked items in working directory.
1393     ///
1394     /// For this to work correctly use the `include_untracked` option when the
1395     /// initial diff is being generated.
for_untracked(&mut self, find: bool) -> &mut DiffFindOptions1396     pub fn for_untracked(&mut self, find: bool) -> &mut DiffFindOptions {
1397         self.flag(raw::GIT_DIFF_FIND_FOR_UNTRACKED, find)
1398     }
1399 
1400     /// Turn on all finding features.
all(&mut self, find: bool) -> &mut DiffFindOptions1401     pub fn all(&mut self, find: bool) -> &mut DiffFindOptions {
1402         self.flag(raw::GIT_DIFF_FIND_ALL, find)
1403     }
1404 
1405     /// Measure similarity ignoring leading whitespace (default)
ignore_leading_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions1406     pub fn ignore_leading_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions {
1407         self.flag(raw::GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE, ignore)
1408     }
1409 
1410     /// Measure similarity ignoring all whitespace
ignore_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions1411     pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions {
1412         self.flag(raw::GIT_DIFF_FIND_IGNORE_WHITESPACE, ignore)
1413     }
1414 
1415     /// Measure similarity including all data
dont_ignore_whitespace(&mut self, dont: bool) -> &mut DiffFindOptions1416     pub fn dont_ignore_whitespace(&mut self, dont: bool) -> &mut DiffFindOptions {
1417         self.flag(raw::GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE, dont)
1418     }
1419 
1420     /// Measure similarity only by comparing SHAs (fast and cheap)
exact_match_only(&mut self, exact: bool) -> &mut DiffFindOptions1421     pub fn exact_match_only(&mut self, exact: bool) -> &mut DiffFindOptions {
1422         self.flag(raw::GIT_DIFF_FIND_EXACT_MATCH_ONLY, exact)
1423     }
1424 
1425     /// Do not break rewrites unless they contribute to a rename.
1426     ///
1427     /// Normally, `break_rewrites` and `rewrites` will measure the
1428     /// self-similarity of modified files and split the ones that have changed a
1429     /// lot into a delete/add pair.  Then the sides of that pair will be
1430     /// considered candidates for rename and copy detection
1431     ///
1432     /// If you add this flag in and the split pair is not used for an actual
1433     /// rename or copy, then the modified record will be restored to a regular
1434     /// modified record instead of being split.
break_rewrites_for_renames_only(&mut self, b: bool) -> &mut DiffFindOptions1435     pub fn break_rewrites_for_renames_only(&mut self, b: bool) -> &mut DiffFindOptions {
1436         self.flag(raw::GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY, b)
1437     }
1438 
1439     /// Remove any unmodified deltas after find_similar is done.
1440     ///
1441     /// Using `copies_from_unmodified` to emulate the `--find-copies-harder`
1442     /// behavior requires building a diff with the `include_unmodified` flag. If
1443     /// you do not want unmodified records in the final result, pas this flag to
1444     /// have them removed.
remove_unmodified(&mut self, remove: bool) -> &mut DiffFindOptions1445     pub fn remove_unmodified(&mut self, remove: bool) -> &mut DiffFindOptions {
1446         self.flag(raw::GIT_DIFF_FIND_REMOVE_UNMODIFIED, remove)
1447     }
1448 
1449     /// Similarity to consider a file renamed (default 50)
rename_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions1450     pub fn rename_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1451         self.raw.rename_threshold = thresh;
1452         self
1453     }
1454 
1455     /// Similarity of modified to be glegible rename source (default 50)
rename_from_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions1456     pub fn rename_from_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1457         self.raw.rename_from_rewrite_threshold = thresh;
1458         self
1459     }
1460 
1461     /// Similarity to consider a file copy (default 50)
copy_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions1462     pub fn copy_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1463         self.raw.copy_threshold = thresh;
1464         self
1465     }
1466 
1467     /// Similarity to split modify into delete/add pair (default 60)
break_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions1468     pub fn break_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1469         self.raw.break_rewrite_threshold = thresh;
1470         self
1471     }
1472 
1473     /// Maximum similarity sources to examine for a file (somewhat like
1474     /// git-diff's `-l` option or `diff.renameLimit` config)
1475     ///
1476     /// Defaults to 200
rename_limit(&mut self, limit: usize) -> &mut DiffFindOptions1477     pub fn rename_limit(&mut self, limit: usize) -> &mut DiffFindOptions {
1478         self.raw.rename_limit = limit as size_t;
1479         self
1480     }
1481 
1482     // TODO: expose git_diff_similarity_metric
1483 }
1484 
1485 impl Default for DiffFormatEmailOptions {
default() -> Self1486     fn default() -> Self {
1487         Self::new()
1488     }
1489 }
1490 
1491 impl DiffFormatEmailOptions {
1492     /// Creates a new set of email options,
1493     /// initialized to the default values
new() -> Self1494     pub fn new() -> Self {
1495         let mut opts = DiffFormatEmailOptions {
1496             raw: unsafe { mem::zeroed() },
1497         };
1498         assert_eq!(
1499             unsafe { raw::git_diff_format_email_options_init(&mut opts.raw, 1) },
1500             0
1501         );
1502         opts
1503     }
1504 
flag(&mut self, opt: u32, val: bool) -> &mut Self1505     fn flag(&mut self, opt: u32, val: bool) -> &mut Self {
1506         if val {
1507             self.raw.flags |= opt;
1508         } else {
1509             self.raw.flags &= !opt;
1510         }
1511         self
1512     }
1513 
1514     /// Exclude `[PATCH]` from the subject header
exclude_subject_patch_header(&mut self, should_exclude: bool) -> &mut Self1515     pub fn exclude_subject_patch_header(&mut self, should_exclude: bool) -> &mut Self {
1516         self.flag(
1517             raw::GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER,
1518             should_exclude,
1519         )
1520     }
1521 }
1522 
1523 impl DiffPatchidOptions {
1524     /// Creates a new set of patchid options,
1525     /// initialized to the default values
new() -> Self1526     pub fn new() -> Self {
1527         let mut opts = DiffPatchidOptions {
1528             raw: unsafe { mem::zeroed() },
1529         };
1530         assert_eq!(
1531             unsafe {
1532                 raw::git_diff_patchid_options_init(
1533                     &mut opts.raw,
1534                     raw::GIT_DIFF_PATCHID_OPTIONS_VERSION,
1535                 )
1536             },
1537             0
1538         );
1539         opts
1540     }
1541 }
1542 
1543 #[cfg(test)]
1544 mod tests {
1545     use crate::{DiffLineType, DiffOptions, Oid, Signature, Time};
1546     use std::borrow::Borrow;
1547     use std::fs::File;
1548     use std::io::Write;
1549     use std::path::Path;
1550 
1551     #[test]
smoke()1552     fn smoke() {
1553         let (_td, repo) = crate::test::repo_init();
1554         let diff = repo.diff_tree_to_workdir(None, None).unwrap();
1555         assert_eq!(diff.deltas().len(), 0);
1556         let stats = diff.stats().unwrap();
1557         assert_eq!(stats.insertions(), 0);
1558         assert_eq!(stats.deletions(), 0);
1559         assert_eq!(stats.files_changed(), 0);
1560         let patchid = diff.patchid(None).unwrap();
1561         assert_ne!(patchid, Oid::zero());
1562     }
1563 
1564     #[test]
foreach_smoke()1565     fn foreach_smoke() {
1566         let (_td, repo) = crate::test::repo_init();
1567         let diff = t!(repo.diff_tree_to_workdir(None, None));
1568         let mut count = 0;
1569         t!(diff.foreach(
1570             &mut |_file, _progress| {
1571                 count = count + 1;
1572                 true
1573             },
1574             None,
1575             None,
1576             None
1577         ));
1578         assert_eq!(count, 0);
1579     }
1580 
1581     #[test]
foreach_file_only()1582     fn foreach_file_only() {
1583         let path = Path::new("foo");
1584         let (td, repo) = crate::test::repo_init();
1585         t!(t!(File::create(&td.path().join(path))).write_all(b"bar"));
1586         let mut opts = DiffOptions::new();
1587         opts.include_untracked(true);
1588         let diff = t!(repo.diff_tree_to_workdir(None, Some(&mut opts)));
1589         let mut count = 0;
1590         let mut result = None;
1591         t!(diff.foreach(
1592             &mut |file, _progress| {
1593                 count = count + 1;
1594                 result = file.new_file().path().map(ToOwned::to_owned);
1595                 true
1596             },
1597             None,
1598             None,
1599             None
1600         ));
1601         assert_eq!(result.as_ref().map(Borrow::borrow), Some(path));
1602         assert_eq!(count, 1);
1603     }
1604 
1605     #[test]
foreach_file_and_hunk()1606     fn foreach_file_and_hunk() {
1607         let path = Path::new("foo");
1608         let (td, repo) = crate::test::repo_init();
1609         t!(t!(File::create(&td.path().join(path))).write_all(b"bar"));
1610         let mut index = t!(repo.index());
1611         t!(index.add_path(path));
1612         let mut opts = DiffOptions::new();
1613         opts.include_untracked(true);
1614         let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
1615         let mut new_lines = 0;
1616         t!(diff.foreach(
1617             &mut |_file, _progress| { true },
1618             None,
1619             Some(&mut |_file, hunk| {
1620                 new_lines = hunk.new_lines();
1621                 true
1622             }),
1623             None
1624         ));
1625         assert_eq!(new_lines, 1);
1626     }
1627 
1628     #[test]
foreach_all_callbacks()1629     fn foreach_all_callbacks() {
1630         let fib = vec![0, 1, 1, 2, 3, 5, 8];
1631         // Verified with a node implementation of deflate, might be worth
1632         // adding a deflate lib to do this inline here.
1633         let deflated_fib = vec![120, 156, 99, 96, 100, 100, 98, 102, 229, 0, 0, 0, 53, 0, 21];
1634         let foo_path = Path::new("foo");
1635         let bin_path = Path::new("bin");
1636         let (td, repo) = crate::test::repo_init();
1637         t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n"));
1638         t!(t!(File::create(&td.path().join(bin_path))).write_all(&fib));
1639         let mut index = t!(repo.index());
1640         t!(index.add_path(foo_path));
1641         t!(index.add_path(bin_path));
1642         let mut opts = DiffOptions::new();
1643         opts.include_untracked(true).show_binary(true);
1644         let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
1645         let mut bin_content = None;
1646         let mut new_lines = 0;
1647         let mut line_content = None;
1648         t!(diff.foreach(
1649             &mut |_file, _progress| { true },
1650             Some(&mut |_file, binary| {
1651                 bin_content = Some(binary.new_file().data().to_owned());
1652                 true
1653             }),
1654             Some(&mut |_file, hunk| {
1655                 new_lines = hunk.new_lines();
1656                 true
1657             }),
1658             Some(&mut |_file, _hunk, line| {
1659                 line_content = String::from_utf8(line.content().into()).ok();
1660                 true
1661             })
1662         ));
1663         assert_eq!(bin_content, Some(deflated_fib));
1664         assert_eq!(new_lines, 1);
1665         assert_eq!(line_content, Some("bar\n".to_string()));
1666     }
1667 
1668     #[test]
format_email_simple()1669     fn format_email_simple() {
1670         let (_td, repo) = crate::test::repo_init();
1671         const COMMIT_MESSAGE: &str = "Modify some content";
1672         const EXPECTED_EMAIL_START: &str = concat!(
1673             "From f1234fb0588b6ed670779a34ba5c51ef962f285f Mon Sep 17 00:00:00 2001\n",
1674             "From: Techcable <dummy@dummy.org>\n",
1675             "Date: Tue, 11 Jan 1972 17:46:40 +0000\n",
1676             "Subject: [PATCH] Modify some content\n",
1677             "\n",
1678             "---\n",
1679             " file1.txt | 8 +++++---\n",
1680             " 1 file changed, 5 insertions(+), 3 deletions(-)\n",
1681             "\n",
1682             "diff --git a/file1.txt b/file1.txt\n",
1683             "index 94aaae8..af8f41d 100644\n",
1684             "--- a/file1.txt\n",
1685             "+++ b/file1.txt\n",
1686             "@@ -1,15 +1,17 @@\n",
1687             " file1.txt\n",
1688             " file1.txt\n",
1689             "+_file1.txt_\n",
1690             " file1.txt\n",
1691             " file1.txt\n",
1692             " file1.txt\n",
1693             " file1.txt\n",
1694             "+\n",
1695             "+\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             "-file1.txt\n",
1703             "-file1.txt\n",
1704             "+_file1.txt_\n",
1705             "+_file1.txt_\n",
1706             " file1.txt\n",
1707             "--\n"
1708         );
1709         const ORIGINAL_FILE: &str = concat!(
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             "file1.txt\n",
1721             "file1.txt\n",
1722             "file1.txt\n",
1723             "file1.txt\n",
1724             "file1.txt\n"
1725         );
1726         const UPDATED_FILE: &str = concat!(
1727             "file1.txt\n",
1728             "file1.txt\n",
1729             "_file1.txt_\n",
1730             "file1.txt\n",
1731             "file1.txt\n",
1732             "file1.txt\n",
1733             "file1.txt\n",
1734             "\n",
1735             "\n",
1736             "file1.txt\n",
1737             "file1.txt\n",
1738             "file1.txt\n",
1739             "file1.txt\n",
1740             "file1.txt\n",
1741             "_file1.txt_\n",
1742             "_file1.txt_\n",
1743             "file1.txt\n"
1744         );
1745         const FILE_MODE: i32 = 0o100644;
1746         let original_file = repo.blob(ORIGINAL_FILE.as_bytes()).unwrap();
1747         let updated_file = repo.blob(UPDATED_FILE.as_bytes()).unwrap();
1748         let mut original_tree = repo.treebuilder(None).unwrap();
1749         original_tree
1750             .insert("file1.txt", original_file, FILE_MODE)
1751             .unwrap();
1752         let original_tree = original_tree.write().unwrap();
1753         let mut updated_tree = repo.treebuilder(None).unwrap();
1754         updated_tree
1755             .insert("file1.txt", updated_file, FILE_MODE)
1756             .unwrap();
1757         let updated_tree = updated_tree.write().unwrap();
1758         let time = Time::new(64_000_000, 0);
1759         let author = Signature::new("Techcable", "dummy@dummy.org", &time).unwrap();
1760         let updated_commit = repo
1761             .commit(
1762                 None,
1763                 &author,
1764                 &author,
1765                 COMMIT_MESSAGE,
1766                 &repo.find_tree(updated_tree).unwrap(),
1767                 &[], // NOTE: Have no parents to ensure stable hash
1768             )
1769             .unwrap();
1770         let updated_commit = repo.find_commit(updated_commit).unwrap();
1771         let mut diff = repo
1772             .diff_tree_to_tree(
1773                 Some(&repo.find_tree(original_tree).unwrap()),
1774                 Some(&repo.find_tree(updated_tree).unwrap()),
1775                 None,
1776             )
1777             .unwrap();
1778         let actual_email = diff.format_email(1, 1, &updated_commit, None).unwrap();
1779         let actual_email = actual_email.as_str().unwrap();
1780         assert!(
1781             actual_email.starts_with(EXPECTED_EMAIL_START),
1782             "Unexpected email:\n{}",
1783             actual_email
1784         );
1785         let mut remaining_lines = actual_email[EXPECTED_EMAIL_START.len()..].lines();
1786         let version_line = remaining_lines.next();
1787         assert!(
1788             version_line.unwrap().starts_with("libgit2"),
1789             "Invalid version line: {:?}",
1790             version_line
1791         );
1792         while let Some(line) = remaining_lines.next() {
1793             assert_eq!(line.trim(), "")
1794         }
1795     }
1796 
1797     #[test]
foreach_diff_line_origin_value()1798     fn foreach_diff_line_origin_value() {
1799         let foo_path = Path::new("foo");
1800         let (td, repo) = crate::test::repo_init();
1801         t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n"));
1802         let mut index = t!(repo.index());
1803         t!(index.add_path(foo_path));
1804         let mut opts = DiffOptions::new();
1805         opts.include_untracked(true);
1806         let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
1807         let mut origin_values: Vec<DiffLineType> = Vec::new();
1808         t!(diff.foreach(
1809             &mut |_file, _progress| { true },
1810             None,
1811             None,
1812             Some(&mut |_file, _hunk, line| {
1813                 origin_values.push(line.origin_value());
1814                 true
1815             })
1816         ));
1817         assert_eq!(origin_values.len(), 1);
1818         assert_eq!(origin_values[0], DiffLineType::Addition);
1819     }
1820 
1821     #[test]
foreach_exits_with_euser()1822     fn foreach_exits_with_euser() {
1823         let foo_path = Path::new("foo");
1824         let bar_path = Path::new("foo");
1825 
1826         let (td, repo) = crate::test::repo_init();
1827         t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n"));
1828 
1829         let mut index = t!(repo.index());
1830         t!(index.add_path(foo_path));
1831         t!(index.add_path(bar_path));
1832 
1833         let mut opts = DiffOptions::new();
1834         opts.include_untracked(true);
1835         let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
1836 
1837         let mut calls = 0;
1838         let result = diff.foreach(
1839             &mut |_file, _progress| {
1840                 calls += 1;
1841                 false
1842             },
1843             None,
1844             None,
1845             None,
1846         );
1847 
1848         assert_eq!(result.unwrap_err().code(), crate::ErrorCode::User);
1849     }
1850 }
1851