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