1use libc::{c_char, c_int, c_void, size_t};
2use std::ffi::CString;
3use std::iter::FusedIterator;
4use std::marker;
5use std::mem;
6use std::ops::Range;
7use std::path::Path;
8use std::ptr;
9use std::slice;
10
11use crate::util::{self, Binding};
12use crate::{panic, raw, Buf, Delta, DiffFormat, Error, FileMode, Oid, Repository};
13use crate::{DiffFlags, DiffStatsFormat, IntoCString};
14
15pub struct Diff<'repo> {
21    raw: *mut raw::git_diff,
22    _marker: marker::PhantomData<&'repo Repository>,
23}
24
25unsafe impl<'repo> Send for Diff<'repo> {}
26
27pub struct DiffDelta<'a> {
29    raw: *mut raw::git_diff_delta,
30    _marker: marker::PhantomData<&'a raw::git_diff_delta>,
31}
32
33pub struct DiffFile<'a> {
39    raw: *const raw::git_diff_file,
40    _marker: marker::PhantomData<&'a raw::git_diff_file>,
41}
42
43pub struct DiffOptions {
45    pathspec: Vec<CString>,
46    pathspec_ptrs: Vec<*const c_char>,
47    old_prefix: Option<CString>,
48    new_prefix: Option<CString>,
49    raw: raw::git_diff_options,
50}
51
52pub struct DiffFindOptions {
54    raw: raw::git_diff_find_options,
55}
56
57pub struct DiffFormatEmailOptions {
59    raw: raw::git_diff_format_email_options,
60}
61
62pub struct DiffPatchidOptions {
64    raw: raw::git_diff_patchid_options,
65}
66
67pub struct Deltas<'diff> {
69    range: Range<usize>,
70    diff: &'diff Diff<'diff>,
71}
72
73pub struct DiffLine<'a> {
75    raw: *const raw::git_diff_line,
76    _marker: marker::PhantomData<&'a raw::git_diff_line>,
77}
78
79pub struct DiffHunk<'a> {
81    raw: *const raw::git_diff_hunk,
82    _marker: marker::PhantomData<&'a raw::git_diff_hunk>,
83}
84
85pub struct DiffStats {
87    raw: *mut raw::git_diff_stats,
88}
89
90pub struct DiffBinary<'a> {
92    raw: *const raw::git_diff_binary,
93    _marker: marker::PhantomData<&'a raw::git_diff_binary>,
94}
95
96pub struct DiffBinaryFile<'a> {
98    raw: *const raw::git_diff_binary_file,
99    _marker: marker::PhantomData<&'a raw::git_diff_binary_file>,
100}
101
102#[derive(Copy, Clone, Debug)]
107pub enum DiffBinaryKind {
108    None,
110    Literal,
112    Delta,
114}
115
116type PrintCb<'a> = dyn FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool + 'a;
117
118pub type FileCb<'a> = dyn FnMut(DiffDelta<'_>, f32) -> bool + 'a;
119pub type BinaryCb<'a> = dyn FnMut(DiffDelta<'_>, DiffBinary<'_>) -> bool + 'a;
120pub type HunkCb<'a> = dyn FnMut(DiffDelta<'_>, DiffHunk<'_>) -> bool + 'a;
121pub type LineCb<'a> = dyn FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool + 'a;
122
123pub struct DiffCallbacks<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h> {
124    pub file: Option<&'a mut FileCb<'b>>,
125    pub binary: Option<&'c mut BinaryCb<'d>>,
126    pub hunk: Option<&'e mut HunkCb<'f>>,
127    pub line: Option<&'g mut LineCb<'h>>,
128}
129
130impl<'repo> Diff<'repo> {
131    pub fn merge(&mut self, from: &Diff<'repo>) -> Result<(), Error> {
140        unsafe {
141            try_call!(raw::git_diff_merge(self.raw, &*from.raw));
142        }
143        Ok(())
144    }
145
146    pub fn deltas(&self) -> Deltas<'_> {
148        let num_deltas = unsafe { raw::git_diff_num_deltas(&*self.raw) };
149        Deltas {
150            range: 0..(num_deltas as usize),
151            diff: self,
152        }
153    }
154
155    pub fn get_delta(&self, i: usize) -> Option<DiffDelta<'_>> {
157        unsafe {
158            let ptr = raw::git_diff_get_delta(&*self.raw, i as size_t);
159            Binding::from_raw_opt(ptr as *mut _)
160        }
161    }
162
163    pub fn is_sorted_icase(&self) -> bool {
165        unsafe { raw::git_diff_is_sorted_icase(&*self.raw) == 1 }
166    }
167
168    pub fn print<F>(&self, format: DiffFormat, mut cb: F) -> Result<(), Error>
173    where
174        F: FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool,
175    {
176        let mut cb: &mut PrintCb<'_> = &mut cb;
177        let ptr = &mut cb as *mut _;
178        let print: raw::git_diff_line_cb = Some(print_cb);
179        unsafe {
180            try_call!(raw::git_diff_print(self.raw, format, print, ptr as *mut _));
181            Ok(())
182        }
183    }
184
185    pub fn foreach(
190        &self,
191        file_cb: &mut FileCb<'_>,
192        binary_cb: Option<&mut BinaryCb<'_>>,
193        hunk_cb: Option<&mut HunkCb<'_>>,
194        line_cb: Option<&mut LineCb<'_>>,
195    ) -> Result<(), Error> {
196        let mut cbs = DiffCallbacks {
197            file: Some(file_cb),
198            binary: binary_cb,
199            hunk: hunk_cb,
200            line: line_cb,
201        };
202        let ptr = &mut cbs as *mut _;
203        unsafe {
204            let binary_cb_c: raw::git_diff_binary_cb = if cbs.binary.is_some() {
205                Some(binary_cb_c)
206            } else {
207                None
208            };
209            let hunk_cb_c: raw::git_diff_hunk_cb = if cbs.hunk.is_some() {
210                Some(hunk_cb_c)
211            } else {
212                None
213            };
214            let line_cb_c: raw::git_diff_line_cb = if cbs.line.is_some() {
215                Some(line_cb_c)
216            } else {
217                None
218            };
219            let file_cb: raw::git_diff_file_cb = Some(file_cb_c);
220            try_call!(raw::git_diff_foreach(
221                self.raw,
222                file_cb,
223                binary_cb_c,
224                hunk_cb_c,
225                line_cb_c,
226                ptr as *mut _
227            ));
228            Ok(())
229        }
230    }
231
232    pub fn stats(&self) -> Result<DiffStats, Error> {
234        let mut ret = ptr::null_mut();
235        unsafe {
236            try_call!(raw::git_diff_get_stats(&mut ret, self.raw));
237            Ok(Binding::from_raw(ret))
238        }
239    }
240
241    pub fn find_similar(&mut self, opts: Option<&mut DiffFindOptions>) -> Result<(), Error> {
248        let opts = opts.map(|opts| &opts.raw);
249        unsafe {
250            try_call!(raw::git_diff_find_similar(self.raw, opts));
251        }
252        Ok(())
253    }
254
255    #[doc(hidden)]
259    #[deprecated(note = "refactored to `Email::from_diff` to match upstream")]
260    pub fn format_email(
261        &mut self,
262        patch_no: usize,
263        total_patches: usize,
264        commit: &crate::Commit<'repo>,
265        opts: Option<&mut DiffFormatEmailOptions>,
266    ) -> Result<Buf, Error> {
267        assert!(patch_no > 0);
268        assert!(patch_no <= total_patches);
269        let mut default = DiffFormatEmailOptions::default();
270        let raw_opts = opts.map_or(&mut default.raw, |opts| &mut opts.raw);
271        let summary = commit.summary_bytes().unwrap();
272        let mut message = commit.message_bytes();
273        assert!(message.starts_with(summary));
274        message = &message[summary.len()..];
275        raw_opts.patch_no = patch_no;
276        raw_opts.total_patches = total_patches;
277        let id = commit.id();
278        raw_opts.id = id.raw();
279        raw_opts.summary = summary.as_ptr() as *const _;
280        raw_opts.body = message.as_ptr() as *const _;
281        raw_opts.author = commit.author().raw();
282        let buf = Buf::new();
283        #[allow(deprecated)]
284        unsafe {
285            try_call!(raw::git_diff_format_email(buf.raw(), self.raw, &*raw_opts));
286        }
287        Ok(buf)
288    }
289
290    pub fn patchid(&self, opts: Option<&mut DiffPatchidOptions>) -> Result<Oid, Error> {
292        let mut raw = raw::git_oid {
293            id: [0; raw::GIT_OID_RAWSZ],
294        };
295        unsafe {
296            try_call!(raw::git_diff_patchid(
297                &mut raw,
298                self.raw,
299                opts.map(|o| &mut o.raw)
300            ));
301            Ok(Binding::from_raw(&raw as *const _))
302        }
303    }
304
305    }
307impl Diff<'static> {
308    pub fn from_buffer(buffer: &[u8]) -> Result<Diff<'static>, Error> {
316        crate::init();
317        let mut diff: *mut raw::git_diff = std::ptr::null_mut();
318        unsafe {
319            try_call!(raw::git_diff_from_buffer(
321                &mut diff,
322                buffer.as_ptr() as *const c_char,
323                buffer.len()
324            ));
325            Ok(Diff::from_raw(diff))
326        }
327    }
328}
329
330pub extern "C" fn print_cb(
331    delta: *const raw::git_diff_delta,
332    hunk: *const raw::git_diff_hunk,
333    line: *const raw::git_diff_line,
334    data: *mut c_void,
335) -> c_int {
336    unsafe {
337        let delta = Binding::from_raw(delta as *mut _);
338        let hunk = Binding::from_raw_opt(hunk);
339        let line = Binding::from_raw(line);
340
341        let r = panic::wrap(|| {
342            let data = data as *mut &mut PrintCb<'_>;
343            (*data)(delta, hunk, line)
344        });
345        if r == Some(true) {
346            raw::GIT_OK
347        } else {
348            raw::GIT_EUSER
349        }
350    }
351}
352
353pub extern "C" fn file_cb_c(
354    delta: *const raw::git_diff_delta,
355    progress: f32,
356    data: *mut c_void,
357) -> c_int {
358    unsafe {
359        let delta = Binding::from_raw(delta as *mut _);
360
361        let r = panic::wrap(|| {
362            let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
363            match (*cbs).file {
364                Some(ref mut cb) => cb(delta, progress),
365                None => false,
366            }
367        });
368        if r == Some(true) {
369            raw::GIT_OK
370        } else {
371            raw::GIT_EUSER
372        }
373    }
374}
375
376pub extern "C" fn binary_cb_c(
377    delta: *const raw::git_diff_delta,
378    binary: *const raw::git_diff_binary,
379    data: *mut c_void,
380) -> c_int {
381    unsafe {
382        let delta = Binding::from_raw(delta as *mut _);
383        let binary = Binding::from_raw(binary);
384
385        let r = panic::wrap(|| {
386            let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
387            match (*cbs).binary {
388                Some(ref mut cb) => cb(delta, binary),
389                None => false,
390            }
391        });
392        if r == Some(true) {
393            raw::GIT_OK
394        } else {
395            raw::GIT_EUSER
396        }
397    }
398}
399
400pub extern "C" fn hunk_cb_c(
401    delta: *const raw::git_diff_delta,
402    hunk: *const raw::git_diff_hunk,
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(hunk);
408
409        let r = panic::wrap(|| {
410            let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
411            match (*cbs).hunk {
412                Some(ref mut cb) => cb(delta, hunk),
413                None => false,
414            }
415        });
416        if r == Some(true) {
417            raw::GIT_OK
418        } else {
419            raw::GIT_EUSER
420        }
421    }
422}
423
424pub extern "C" fn line_cb_c(
425    delta: *const raw::git_diff_delta,
426    hunk: *const raw::git_diff_hunk,
427    line: *const raw::git_diff_line,
428    data: *mut c_void,
429) -> c_int {
430    unsafe {
431        let delta = Binding::from_raw(delta as *mut _);
432        let hunk = Binding::from_raw_opt(hunk);
433        let line = Binding::from_raw(line);
434
435        let r = panic::wrap(|| {
436            let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
437            match (*cbs).line {
438                Some(ref mut cb) => cb(delta, hunk, line),
439                None => false,
440            }
441        });
442        if r == Some(true) {
443            raw::GIT_OK
444        } else {
445            raw::GIT_EUSER
446        }
447    }
448}
449
450impl<'repo> Binding for Diff<'repo> {
451    type Raw = *mut raw::git_diff;
452    unsafe fn from_raw(raw: *mut raw::git_diff) -> Diff<'repo> {
453        Diff {
454            raw,
455            _marker: marker::PhantomData,
456        }
457    }
458    fn raw(&self) -> *mut raw::git_diff {
459        self.raw
460    }
461}
462
463impl<'repo> Drop for Diff<'repo> {
464    fn drop(&mut self) {
465        unsafe { raw::git_diff_free(self.raw) }
466    }
467}
468
469impl<'a> DiffDelta<'a> {
470    pub fn flags(&self) -> DiffFlags {
474        let flags = unsafe { (*self.raw).flags };
475        let mut result = DiffFlags::empty();
476
477        #[cfg(target_env = "msvc")]
478        fn as_u32(flag: i32) -> u32 {
479            flag as u32
480        }
481        #[cfg(not(target_env = "msvc"))]
482        fn as_u32(flag: u32) -> u32 {
483            flag
484        }
485
486        if (flags & as_u32(raw::GIT_DIFF_FLAG_BINARY)) != 0 {
487            result |= DiffFlags::BINARY;
488        }
489        if (flags & as_u32(raw::GIT_DIFF_FLAG_NOT_BINARY)) != 0 {
490            result |= DiffFlags::NOT_BINARY;
491        }
492        if (flags & as_u32(raw::GIT_DIFF_FLAG_VALID_ID)) != 0 {
493            result |= DiffFlags::VALID_ID;
494        }
495        if (flags & as_u32(raw::GIT_DIFF_FLAG_EXISTS)) != 0 {
496            result |= DiffFlags::EXISTS;
497        }
498        result
499    }
500
501    pub fn nfiles(&self) -> u16 {
508        unsafe { (*self.raw).nfiles }
509    }
510
511    pub fn status(&self) -> Delta {
515        match unsafe { (*self.raw).status } {
516            raw::GIT_DELTA_UNMODIFIED => Delta::Unmodified,
517            raw::GIT_DELTA_ADDED => Delta::Added,
518            raw::GIT_DELTA_DELETED => Delta::Deleted,
519            raw::GIT_DELTA_MODIFIED => Delta::Modified,
520            raw::GIT_DELTA_RENAMED => Delta::Renamed,
521            raw::GIT_DELTA_COPIED => Delta::Copied,
522            raw::GIT_DELTA_IGNORED => Delta::Ignored,
523            raw::GIT_DELTA_UNTRACKED => Delta::Untracked,
524            raw::GIT_DELTA_TYPECHANGE => Delta::Typechange,
525            raw::GIT_DELTA_UNREADABLE => Delta::Unreadable,
526            raw::GIT_DELTA_CONFLICTED => Delta::Conflicted,
527            n => panic!("unknown diff status: {}", n),
528        }
529    }
530
531    pub fn old_file(&self) -> DiffFile<'a> {
536        unsafe { Binding::from_raw(&(*self.raw).old_file as *const _) }
537    }
538
539    pub fn new_file(&self) -> DiffFile<'a> {
544        unsafe { Binding::from_raw(&(*self.raw).new_file as *const _) }
545    }
546}
547
548impl<'a> Binding for DiffDelta<'a> {
549    type Raw = *mut raw::git_diff_delta;
550    unsafe fn from_raw(raw: *mut raw::git_diff_delta) -> DiffDelta<'a> {
551        DiffDelta {
552            raw,
553            _marker: marker::PhantomData,
554        }
555    }
556    fn raw(&self) -> *mut raw::git_diff_delta {
557        self.raw
558    }
559}
560
561impl<'a> std::fmt::Debug for DiffDelta<'a> {
562    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
563        f.debug_struct("DiffDelta")
564            .field("nfiles", &self.nfiles())
565            .field("status", &self.status())
566            .field("old_file", &self.old_file())
567            .field("new_file", &self.new_file())
568            .finish()
569    }
570}
571
572impl<'a> DiffFile<'a> {
573    pub fn id(&self) -> Oid {
578        unsafe { Binding::from_raw(&(*self.raw).id as *const _) }
579    }
580
581    pub fn path_bytes(&self) -> Option<&'a [u8]> {
584        static FOO: () = ();
585        unsafe { crate::opt_bytes(&FOO, (*self.raw).path) }
586    }
587
588    pub fn path(&self) -> Option<&'a Path> {
591        self.path_bytes().map(util::bytes2path)
592    }
593
594    pub fn size(&self) -> u64 {
596        unsafe { (*self.raw).size as u64 }
597    }
598
599    pub fn is_binary(&self) -> bool {
601        unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_BINARY as u32 != 0 }
602    }
603
604    pub fn is_not_binary(&self) -> bool {
606        unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_NOT_BINARY as u32 != 0 }
607    }
608
609    pub fn is_valid_id(&self) -> bool {
611        unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_VALID_ID as u32 != 0 }
612    }
613
614    pub fn exists(&self) -> bool {
616        unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_EXISTS as u32 != 0 }
617    }
618
619    pub fn mode(&self) -> FileMode {
621        match unsafe { (*self.raw).mode.into() } {
622            raw::GIT_FILEMODE_UNREADABLE => FileMode::Unreadable,
623            raw::GIT_FILEMODE_TREE => FileMode::Tree,
624            raw::GIT_FILEMODE_BLOB => FileMode::Blob,
625            raw::GIT_FILEMODE_BLOB_GROUP_WRITABLE => FileMode::BlobGroupWritable,
626            raw::GIT_FILEMODE_BLOB_EXECUTABLE => FileMode::BlobExecutable,
627            raw::GIT_FILEMODE_LINK => FileMode::Link,
628            raw::GIT_FILEMODE_COMMIT => FileMode::Commit,
629            mode => panic!("unknown mode: {}", mode),
630        }
631    }
632}
633
634impl<'a> Binding for DiffFile<'a> {
635    type Raw = *const raw::git_diff_file;
636    unsafe fn from_raw(raw: *const raw::git_diff_file) -> DiffFile<'a> {
637        DiffFile {
638            raw,
639            _marker: marker::PhantomData,
640        }
641    }
642    fn raw(&self) -> *const raw::git_diff_file {
643        self.raw
644    }
645}
646
647impl<'a> std::fmt::Debug for DiffFile<'a> {
648    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
649        let mut ds = f.debug_struct("DiffFile");
650        ds.field("id", &self.id());
651        if let Some(path_bytes) = &self.path_bytes() {
652            ds.field("path_bytes", path_bytes);
653        }
654        if let Some(path) = &self.path() {
655            ds.field("path", path);
656        }
657        ds.field("size", &self.size()).finish()
658    }
659}
660
661impl Default for DiffOptions {
662    fn default() -> Self {
663        Self::new()
664    }
665}
666
667impl DiffOptions {
668    pub fn new() -> DiffOptions {
673        let mut opts = DiffOptions {
674            pathspec: Vec::new(),
675            pathspec_ptrs: Vec::new(),
676            raw: unsafe { mem::zeroed() },
677            old_prefix: None,
678            new_prefix: None,
679        };
680        assert_eq!(unsafe { raw::git_diff_init_options(&mut opts.raw, 1) }, 0);
681        opts
682    }
683
684    fn flag(&mut self, opt: raw::git_diff_option_t, val: bool) -> &mut DiffOptions {
685        let opt = opt as u32;
686        if val {
687            self.raw.flags |= opt;
688        } else {
689            self.raw.flags &= !opt;
690        }
691        self
692    }
693
694    pub fn reverse(&mut self, reverse: bool) -> &mut DiffOptions {
696        self.flag(raw::GIT_DIFF_REVERSE, reverse)
697    }
698
699    pub fn include_ignored(&mut self, include: bool) -> &mut DiffOptions {
701        self.flag(raw::GIT_DIFF_INCLUDE_IGNORED, include)
702    }
703
704    pub fn recurse_ignored_dirs(&mut self, recurse: bool) -> &mut DiffOptions {
706        self.flag(raw::GIT_DIFF_RECURSE_IGNORED_DIRS, recurse)
707    }
708
709    pub fn include_untracked(&mut self, include: bool) -> &mut DiffOptions {
711        self.flag(raw::GIT_DIFF_INCLUDE_UNTRACKED, include)
712    }
713
714    pub fn recurse_untracked_dirs(&mut self, recurse: bool) -> &mut DiffOptions {
717        self.flag(raw::GIT_DIFF_RECURSE_UNTRACKED_DIRS, recurse)
718    }
719
720    pub fn include_unmodified(&mut self, include: bool) -> &mut DiffOptions {
722        self.flag(raw::GIT_DIFF_INCLUDE_UNMODIFIED, include)
723    }
724
725    pub fn include_typechange(&mut self, include: bool) -> &mut DiffOptions {
727        self.flag(raw::GIT_DIFF_INCLUDE_TYPECHANGE, include)
728    }
729
730    pub fn include_typechange_trees(&mut self, include: bool) -> &mut DiffOptions {
736        self.flag(raw::GIT_DIFF_INCLUDE_TYPECHANGE_TREES, include)
737    }
738
739    pub fn ignore_filemode(&mut self, ignore: bool) -> &mut DiffOptions {
741        self.flag(raw::GIT_DIFF_IGNORE_FILEMODE, ignore)
742    }
743
744    pub fn ignore_submodules(&mut self, ignore: bool) -> &mut DiffOptions {
746        self.flag(raw::GIT_DIFF_IGNORE_SUBMODULES, ignore)
747    }
748
749    pub fn ignore_case(&mut self, ignore: bool) -> &mut DiffOptions {
751        self.flag(raw::GIT_DIFF_IGNORE_CASE, ignore)
752    }
753
754    pub fn disable_pathspec_match(&mut self, disable: bool) -> &mut DiffOptions {
757        self.flag(raw::GIT_DIFF_DISABLE_PATHSPEC_MATCH, disable)
758    }
759
760    pub fn skip_binary_check(&mut self, skip: bool) -> &mut DiffOptions {
764        self.flag(raw::GIT_DIFF_SKIP_BINARY_CHECK, skip)
765    }
766
767    pub fn enable_fast_untracked_dirs(&mut self, enable: bool) -> &mut DiffOptions {
776        self.flag(raw::GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS, enable)
777    }
778
779    pub fn update_index(&mut self, update: bool) -> &mut DiffOptions {
784        self.flag(raw::GIT_DIFF_UPDATE_INDEX, update)
785    }
786
787    pub fn include_unreadable(&mut self, include: bool) -> &mut DiffOptions {
789        self.flag(raw::GIT_DIFF_INCLUDE_UNREADABLE, include)
790    }
791
792    pub fn include_unreadable_as_untracked(&mut self, include: bool) -> &mut DiffOptions {
794        self.flag(raw::GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED, include)
795    }
796
797    pub fn force_text(&mut self, force: bool) -> &mut DiffOptions {
799        self.flag(raw::GIT_DIFF_FORCE_TEXT, force)
800    }
801
802    pub fn force_binary(&mut self, force: bool) -> &mut DiffOptions {
804        self.flag(raw::GIT_DIFF_FORCE_BINARY, force)
805    }
806
807    pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut DiffOptions {
809        self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE, ignore)
810    }
811
812    pub fn ignore_whitespace_change(&mut self, ignore: bool) -> &mut DiffOptions {
814        self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE_CHANGE, ignore)
815    }
816
817    pub fn ignore_whitespace_eol(&mut self, ignore: bool) -> &mut DiffOptions {
819        self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE_EOL, ignore)
820    }
821
822    pub fn ignore_blank_lines(&mut self, ignore: bool) -> &mut DiffOptions {
824        self.flag(raw::GIT_DIFF_IGNORE_BLANK_LINES, ignore)
825    }
826
827    pub fn show_untracked_content(&mut self, show: bool) -> &mut DiffOptions {
833        self.flag(raw::GIT_DIFF_SHOW_UNTRACKED_CONTENT, show)
834    }
835
836    pub fn show_unmodified(&mut self, show: bool) -> &mut DiffOptions {
841        self.flag(raw::GIT_DIFF_SHOW_UNMODIFIED, show)
842    }
843
844    pub fn patience(&mut self, patience: bool) -> &mut DiffOptions {
846        self.flag(raw::GIT_DIFF_PATIENCE, patience)
847    }
848
849    pub fn minimal(&mut self, minimal: bool) -> &mut DiffOptions {
851        self.flag(raw::GIT_DIFF_MINIMAL, minimal)
852    }
853
854    pub fn show_binary(&mut self, show: bool) -> &mut DiffOptions {
857        self.flag(raw::GIT_DIFF_SHOW_BINARY, show)
858    }
859
860    pub fn indent_heuristic(&mut self, heuristic: bool) -> &mut DiffOptions {
864        self.flag(raw::GIT_DIFF_INDENT_HEURISTIC, heuristic)
865    }
866
867    pub fn context_lines(&mut self, lines: u32) -> &mut DiffOptions {
872        self.raw.context_lines = lines;
873        self
874    }
875
876    pub fn interhunk_lines(&mut self, lines: u32) -> &mut DiffOptions {
881        self.raw.interhunk_lines = lines;
882        self
883    }
884
885    pub fn id_abbrev(&mut self, abbrev: u16) -> &mut DiffOptions {
887        self.raw.id_abbrev = abbrev;
888        self
889    }
890
891    pub fn max_size(&mut self, size: i64) -> &mut DiffOptions {
898        self.raw.max_size = size as raw::git_off_t;
899        self
900    }
901
902    pub fn old_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions {
906        self.old_prefix = Some(t.into_c_string().unwrap());
907        self
908    }
909
910    pub fn new_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions {
914        self.new_prefix = Some(t.into_c_string().unwrap());
915        self
916    }
917
918    pub fn pathspec<T: IntoCString>(&mut self, pathspec: T) -> &mut DiffOptions {
920        let s = util::cstring_to_repo_path(pathspec).unwrap();
921        self.pathspec_ptrs.push(s.as_ptr());
922        self.pathspec.push(s);
923        self
924    }
925
926    pub unsafe fn raw(&mut self) -> *const raw::git_diff_options {
931        self.raw.old_prefix = self
932            .old_prefix
933            .as_ref()
934            .map(|s| s.as_ptr())
935            .unwrap_or(ptr::null());
936        self.raw.new_prefix = self
937            .new_prefix
938            .as_ref()
939            .map(|s| s.as_ptr())
940            .unwrap_or(ptr::null());
941        self.raw.pathspec.count = self.pathspec_ptrs.len() as size_t;
942        self.raw.pathspec.strings = self.pathspec_ptrs.as_ptr() as *mut _;
943        &self.raw as *const _
944    }
945
946    }
948
949impl<'diff> Iterator for Deltas<'diff> {
950    type Item = DiffDelta<'diff>;
951    fn next(&mut self) -> Option<DiffDelta<'diff>> {
952        self.range.next().and_then(|i| self.diff.get_delta(i))
953    }
954    fn size_hint(&self) -> (usize, Option<usize>) {
955        self.range.size_hint()
956    }
957}
958impl<'diff> DoubleEndedIterator for Deltas<'diff> {
959    fn next_back(&mut self) -> Option<DiffDelta<'diff>> {
960        self.range.next_back().and_then(|i| self.diff.get_delta(i))
961    }
962}
963impl<'diff> FusedIterator for Deltas<'diff> {}
964
965impl<'diff> ExactSizeIterator for Deltas<'diff> {}
966
967#[derive(Copy, Clone, Debug, PartialEq)]
969pub enum DiffLineType {
970    Context,
972    Addition,
974    Deletion,
976    ContextEOFNL,
978    AddEOFNL,
980    DeleteEOFNL,
982    FileHeader,
985    HunkHeader,
987    Binary,
989}
990
991impl Binding for DiffLineType {
992    type Raw = raw::git_diff_line_t;
993    unsafe fn from_raw(raw: raw::git_diff_line_t) -> Self {
994        match raw {
995            raw::GIT_DIFF_LINE_CONTEXT => DiffLineType::Context,
996            raw::GIT_DIFF_LINE_ADDITION => DiffLineType::Addition,
997            raw::GIT_DIFF_LINE_DELETION => DiffLineType::Deletion,
998            raw::GIT_DIFF_LINE_CONTEXT_EOFNL => DiffLineType::ContextEOFNL,
999            raw::GIT_DIFF_LINE_ADD_EOFNL => DiffLineType::AddEOFNL,
1000            raw::GIT_DIFF_LINE_DEL_EOFNL => DiffLineType::DeleteEOFNL,
1001            raw::GIT_DIFF_LINE_FILE_HDR => DiffLineType::FileHeader,
1002            raw::GIT_DIFF_LINE_HUNK_HDR => DiffLineType::HunkHeader,
1003            raw::GIT_DIFF_LINE_BINARY => DiffLineType::Binary,
1004            _ => panic!("Unknown git diff line type"),
1005        }
1006    }
1007    fn raw(&self) -> raw::git_diff_line_t {
1008        match *self {
1009            DiffLineType::Context => raw::GIT_DIFF_LINE_CONTEXT,
1010            DiffLineType::Addition => raw::GIT_DIFF_LINE_ADDITION,
1011            DiffLineType::Deletion => raw::GIT_DIFF_LINE_DELETION,
1012            DiffLineType::ContextEOFNL => raw::GIT_DIFF_LINE_CONTEXT_EOFNL,
1013            DiffLineType::AddEOFNL => raw::GIT_DIFF_LINE_ADD_EOFNL,
1014            DiffLineType::DeleteEOFNL => raw::GIT_DIFF_LINE_DEL_EOFNL,
1015            DiffLineType::FileHeader => raw::GIT_DIFF_LINE_FILE_HDR,
1016            DiffLineType::HunkHeader => raw::GIT_DIFF_LINE_HUNK_HDR,
1017            DiffLineType::Binary => raw::GIT_DIFF_LINE_BINARY,
1018        }
1019    }
1020}
1021
1022impl<'a> DiffLine<'a> {
1023    pub fn old_lineno(&self) -> Option<u32> {
1025        match unsafe { (*self.raw).old_lineno } {
1026            n if n < 0 => None,
1027            n => Some(n as u32),
1028        }
1029    }
1030
1031    pub fn new_lineno(&self) -> Option<u32> {
1033        match unsafe { (*self.raw).new_lineno } {
1034            n if n < 0 => None,
1035            n => Some(n as u32),
1036        }
1037    }
1038
1039    pub fn num_lines(&self) -> u32 {
1041        unsafe { (*self.raw).num_lines as u32 }
1042    }
1043
1044    pub fn content_offset(&self) -> i64 {
1046        unsafe { (*self.raw).content_offset as i64 }
1047    }
1048
1049    pub fn content(&self) -> &'a [u8] {
1051        unsafe {
1052            slice::from_raw_parts(
1053                (*self.raw).content as *const u8,
1054                (*self.raw).content_len as usize,
1055            )
1056        }
1057    }
1058
1059    pub fn origin_value(&self) -> DiffLineType {
1062        unsafe { Binding::from_raw((*self.raw).origin as raw::git_diff_line_t) }
1063    }
1064
1065    pub fn origin(&self) -> char {
1077        match unsafe { (*self.raw).origin as raw::git_diff_line_t } {
1078            raw::GIT_DIFF_LINE_CONTEXT => ' ',
1079            raw::GIT_DIFF_LINE_ADDITION => '+',
1080            raw::GIT_DIFF_LINE_DELETION => '-',
1081            raw::GIT_DIFF_LINE_CONTEXT_EOFNL => '=',
1082            raw::GIT_DIFF_LINE_ADD_EOFNL => '>',
1083            raw::GIT_DIFF_LINE_DEL_EOFNL => '<',
1084            raw::GIT_DIFF_LINE_FILE_HDR => 'F',
1085            raw::GIT_DIFF_LINE_HUNK_HDR => 'H',
1086            raw::GIT_DIFF_LINE_BINARY => 'B',
1087            _ => ' ',
1088        }
1089    }
1090}
1091
1092impl<'a> Binding for DiffLine<'a> {
1093    type Raw = *const raw::git_diff_line;
1094    unsafe fn from_raw(raw: *const raw::git_diff_line) -> DiffLine<'a> {
1095        DiffLine {
1096            raw,
1097            _marker: marker::PhantomData,
1098        }
1099    }
1100    fn raw(&self) -> *const raw::git_diff_line {
1101        self.raw
1102    }
1103}
1104
1105impl<'a> std::fmt::Debug for DiffLine<'a> {
1106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
1107        let mut ds = f.debug_struct("DiffLine");
1108        if let Some(old_lineno) = &self.old_lineno() {
1109            ds.field("old_lineno", old_lineno);
1110        }
1111        if let Some(new_lineno) = &self.new_lineno() {
1112            ds.field("new_lineno", new_lineno);
1113        }
1114        ds.field("num_lines", &self.num_lines())
1115            .field("content_offset", &self.content_offset())
1116            .field("content", &self.content())
1117            .field("origin", &self.origin())
1118            .finish()
1119    }
1120}
1121
1122impl<'a> DiffHunk<'a> {
1123    pub fn old_start(&self) -> u32 {
1125        unsafe { (*self.raw).old_start as u32 }
1126    }
1127
1128    pub fn old_lines(&self) -> u32 {
1130        unsafe { (*self.raw).old_lines as u32 }
1131    }
1132
1133    pub fn new_start(&self) -> u32 {
1135        unsafe { (*self.raw).new_start as u32 }
1136    }
1137
1138    pub fn new_lines(&self) -> u32 {
1140        unsafe { (*self.raw).new_lines as u32 }
1141    }
1142
1143    pub fn header(&self) -> &'a [u8] {
1145        unsafe {
1146            slice::from_raw_parts(
1147                (*self.raw).header.as_ptr() as *const u8,
1148                (*self.raw).header_len as usize,
1149            )
1150        }
1151    }
1152}
1153
1154impl<'a> Binding for DiffHunk<'a> {
1155    type Raw = *const raw::git_diff_hunk;
1156    unsafe fn from_raw(raw: *const raw::git_diff_hunk) -> DiffHunk<'a> {
1157        DiffHunk {
1158            raw,
1159            _marker: marker::PhantomData,
1160        }
1161    }
1162    fn raw(&self) -> *const raw::git_diff_hunk {
1163        self.raw
1164    }
1165}
1166
1167impl<'a> std::fmt::Debug for DiffHunk<'a> {
1168    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
1169        f.debug_struct("DiffHunk")
1170            .field("old_start", &self.old_start())
1171            .field("old_lines", &self.old_lines())
1172            .field("new_start", &self.new_start())
1173            .field("new_lines", &self.new_lines())
1174            .field("header", &self.header())
1175            .finish()
1176    }
1177}
1178
1179impl DiffStats {
1180    pub fn files_changed(&self) -> usize {
1182        unsafe { raw::git_diff_stats_files_changed(&*self.raw) as usize }
1183    }
1184
1185    pub fn insertions(&self) -> usize {
1187        unsafe { raw::git_diff_stats_insertions(&*self.raw) as usize }
1188    }
1189
1190    pub fn deletions(&self) -> usize {
1192        unsafe { raw::git_diff_stats_deletions(&*self.raw) as usize }
1193    }
1194
1195    pub fn to_buf(&self, format: DiffStatsFormat, width: usize) -> Result<Buf, Error> {
1197        let buf = Buf::new();
1198        unsafe {
1199            try_call!(raw::git_diff_stats_to_buf(
1200                buf.raw(),
1201                self.raw,
1202                format.bits(),
1203                width as size_t
1204            ));
1205        }
1206        Ok(buf)
1207    }
1208}
1209
1210impl Binding for DiffStats {
1211    type Raw = *mut raw::git_diff_stats;
1212
1213    unsafe fn from_raw(raw: *mut raw::git_diff_stats) -> DiffStats {
1214        DiffStats { raw }
1215    }
1216    fn raw(&self) -> *mut raw::git_diff_stats {
1217        self.raw
1218    }
1219}
1220
1221impl Drop for DiffStats {
1222    fn drop(&mut self) {
1223        unsafe { raw::git_diff_stats_free(self.raw) }
1224    }
1225}
1226
1227impl std::fmt::Debug for DiffStats {
1228    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
1229        f.debug_struct("DiffStats")
1230            .field("files_changed", &self.files_changed())
1231            .field("insertions", &self.insertions())
1232            .field("deletions", &self.deletions())
1233            .finish()
1234    }
1235}
1236
1237impl<'a> DiffBinary<'a> {
1238    pub fn contains_data(&self) -> bool {
1245        unsafe { (*self.raw).contains_data == 1 }
1246    }
1247
1248    pub fn old_file(&self) -> DiffBinaryFile<'a> {
1250        unsafe { Binding::from_raw(&(*self.raw).old_file as *const _) }
1251    }
1252
1253    pub fn new_file(&self) -> DiffBinaryFile<'a> {
1255        unsafe { Binding::from_raw(&(*self.raw).new_file as *const _) }
1256    }
1257}
1258
1259impl<'a> Binding for DiffBinary<'a> {
1260    type Raw = *const raw::git_diff_binary;
1261    unsafe fn from_raw(raw: *const raw::git_diff_binary) -> DiffBinary<'a> {
1262        DiffBinary {
1263            raw,
1264            _marker: marker::PhantomData,
1265        }
1266    }
1267    fn raw(&self) -> *const raw::git_diff_binary {
1268        self.raw
1269    }
1270}
1271
1272impl<'a> DiffBinaryFile<'a> {
1273    pub fn kind(&self) -> DiffBinaryKind {
1275        unsafe { Binding::from_raw((*self.raw).kind) }
1276    }
1277
1278    pub fn data(&self) -> &[u8] {
1280        unsafe {
1281            slice::from_raw_parts((*self.raw).data as *const u8, (*self.raw).datalen as usize)
1282        }
1283    }
1284
1285    pub fn inflated_len(&self) -> usize {
1287        unsafe { (*self.raw).inflatedlen as usize }
1288    }
1289}
1290
1291impl<'a> Binding for DiffBinaryFile<'a> {
1292    type Raw = *const raw::git_diff_binary_file;
1293    unsafe fn from_raw(raw: *const raw::git_diff_binary_file) -> DiffBinaryFile<'a> {
1294        DiffBinaryFile {
1295            raw,
1296            _marker: marker::PhantomData,
1297        }
1298    }
1299    fn raw(&self) -> *const raw::git_diff_binary_file {
1300        self.raw
1301    }
1302}
1303
1304impl Binding for DiffBinaryKind {
1305    type Raw = raw::git_diff_binary_t;
1306    unsafe fn from_raw(raw: raw::git_diff_binary_t) -> DiffBinaryKind {
1307        match raw {
1308            raw::GIT_DIFF_BINARY_NONE => DiffBinaryKind::None,
1309            raw::GIT_DIFF_BINARY_LITERAL => DiffBinaryKind::Literal,
1310            raw::GIT_DIFF_BINARY_DELTA => DiffBinaryKind::Delta,
1311            _ => panic!("Unknown git diff binary kind"),
1312        }
1313    }
1314    fn raw(&self) -> raw::git_diff_binary_t {
1315        match *self {
1316            DiffBinaryKind::None => raw::GIT_DIFF_BINARY_NONE,
1317            DiffBinaryKind::Literal => raw::GIT_DIFF_BINARY_LITERAL,
1318            DiffBinaryKind::Delta => raw::GIT_DIFF_BINARY_DELTA,
1319        }
1320    }
1321}
1322
1323impl Default for DiffFindOptions {
1324    fn default() -> Self {
1325        Self::new()
1326    }
1327}
1328
1329impl DiffFindOptions {
1330    pub fn new() -> DiffFindOptions {
1335        let mut opts = DiffFindOptions {
1336            raw: unsafe { mem::zeroed() },
1337        };
1338        assert_eq!(
1339            unsafe { raw::git_diff_find_init_options(&mut opts.raw, 1) },
1340            0
1341        );
1342        opts
1343    }
1344
1345    fn flag(&mut self, opt: u32, val: bool) -> &mut DiffFindOptions {
1346        if val {
1347            self.raw.flags |= opt;
1348        } else {
1349            self.raw.flags &= !opt;
1350        }
1351        self
1352    }
1353
1354    pub fn by_config(&mut self) -> &mut DiffFindOptions {
1358        self.flag(0xffffffff, false)
1359    }
1360
1361    pub fn renames(&mut self, find: bool) -> &mut DiffFindOptions {
1363        self.flag(raw::GIT_DIFF_FIND_RENAMES, find)
1364    }
1365
1366    pub fn renames_from_rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
1368        self.flag(raw::GIT_DIFF_FIND_RENAMES_FROM_REWRITES, find)
1369    }
1370
1371    pub fn copies(&mut self, find: bool) -> &mut DiffFindOptions {
1373        self.flag(raw::GIT_DIFF_FIND_COPIES, find)
1374    }
1375
1376    pub fn copies_from_unmodified(&mut self, find: bool) -> &mut DiffFindOptions {
1381        self.flag(raw::GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED, find)
1382    }
1383
1384    pub fn rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
1386        self.flag(raw::GIT_DIFF_FIND_REWRITES, find)
1387    }
1388
1389    pub fn break_rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
1391        self.flag(raw::GIT_DIFF_BREAK_REWRITES, find)
1392    }
1393
1394    #[doc(hidden)]
1395    pub fn break_rewries(&mut self, find: bool) -> &mut DiffFindOptions {
1396        self.break_rewrites(find)
1397    }
1398
1399    pub fn for_untracked(&mut self, find: bool) -> &mut DiffFindOptions {
1404        self.flag(raw::GIT_DIFF_FIND_FOR_UNTRACKED, find)
1405    }
1406
1407    pub fn all(&mut self, find: bool) -> &mut DiffFindOptions {
1409        self.flag(raw::GIT_DIFF_FIND_ALL, find)
1410    }
1411
1412    pub fn ignore_leading_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions {
1414        self.flag(raw::GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE, ignore)
1415    }
1416
1417    pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions {
1419        self.flag(raw::GIT_DIFF_FIND_IGNORE_WHITESPACE, ignore)
1420    }
1421
1422    pub fn dont_ignore_whitespace(&mut self, dont: bool) -> &mut DiffFindOptions {
1424        self.flag(raw::GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE, dont)
1425    }
1426
1427    pub fn exact_match_only(&mut self, exact: bool) -> &mut DiffFindOptions {
1429        self.flag(raw::GIT_DIFF_FIND_EXACT_MATCH_ONLY, exact)
1430    }
1431
1432    pub fn break_rewrites_for_renames_only(&mut self, b: bool) -> &mut DiffFindOptions {
1443        self.flag(raw::GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY, b)
1444    }
1445
1446    pub fn remove_unmodified(&mut self, remove: bool) -> &mut DiffFindOptions {
1453        self.flag(raw::GIT_DIFF_FIND_REMOVE_UNMODIFIED, remove)
1454    }
1455
1456    pub fn rename_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1458        self.raw.rename_threshold = thresh;
1459        self
1460    }
1461
1462    pub fn rename_from_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1464        self.raw.rename_from_rewrite_threshold = thresh;
1465        self
1466    }
1467
1468    pub fn copy_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1470        self.raw.copy_threshold = thresh;
1471        self
1472    }
1473
1474    pub fn break_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1476        self.raw.break_rewrite_threshold = thresh;
1477        self
1478    }
1479
1480    pub fn rename_limit(&mut self, limit: usize) -> &mut DiffFindOptions {
1485        self.raw.rename_limit = limit as size_t;
1486        self
1487    }
1488
1489    pub unsafe fn raw(&mut self) -> *const raw::git_diff_find_options {
1493        &self.raw
1494    }
1495}
1496
1497impl Default for DiffFormatEmailOptions {
1498    fn default() -> Self {
1499        Self::new()
1500    }
1501}
1502
1503impl DiffFormatEmailOptions {
1504    pub fn new() -> Self {
1507        let mut opts = DiffFormatEmailOptions {
1508            raw: unsafe { mem::zeroed() },
1509        };
1510        assert_eq!(
1511            unsafe { raw::git_diff_format_email_options_init(&mut opts.raw, 1) },
1512            0
1513        );
1514        opts
1515    }
1516
1517    fn flag(&mut self, opt: u32, val: bool) -> &mut Self {
1518        if val {
1519            self.raw.flags |= opt;
1520        } else {
1521            self.raw.flags &= !opt;
1522        }
1523        self
1524    }
1525
1526    pub fn exclude_subject_patch_header(&mut self, should_exclude: bool) -> &mut Self {
1528        self.flag(
1529            raw::GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER,
1530            should_exclude,
1531        )
1532    }
1533}
1534
1535impl DiffPatchidOptions {
1536    pub fn new() -> Self {
1539        let mut opts = DiffPatchidOptions {
1540            raw: unsafe { mem::zeroed() },
1541        };
1542        assert_eq!(
1543            unsafe {
1544                raw::git_diff_patchid_options_init(
1545                    &mut opts.raw,
1546                    raw::GIT_DIFF_PATCHID_OPTIONS_VERSION,
1547                )
1548            },
1549            0
1550        );
1551        opts
1552    }
1553}
1554
1555#[cfg(test)]
1556mod tests {
1557    use crate::{DiffLineType, DiffOptions, Oid, Signature, Time};
1558    use std::borrow::Borrow;
1559    use std::fs::File;
1560    use std::io::Write;
1561    use std::path::Path;
1562
1563    #[test]
1564    fn smoke() {
1565        let (_td, repo) = crate::test::repo_init();
1566        let diff = repo.diff_tree_to_workdir(None, None).unwrap();
1567        assert_eq!(diff.deltas().len(), 0);
1568        let stats = diff.stats().unwrap();
1569        assert_eq!(stats.insertions(), 0);
1570        assert_eq!(stats.deletions(), 0);
1571        assert_eq!(stats.files_changed(), 0);
1572        let patchid = diff.patchid(None).unwrap();
1573        assert_ne!(patchid, Oid::zero());
1574    }
1575
1576    #[test]
1577    fn foreach_smoke() {
1578        let (_td, repo) = crate::test::repo_init();
1579        let diff = t!(repo.diff_tree_to_workdir(None, None));
1580        let mut count = 0;
1581        t!(diff.foreach(
1582            &mut |_file, _progress| {
1583                count = count + 1;
1584                true
1585            },
1586            None,
1587            None,
1588            None
1589        ));
1590        assert_eq!(count, 0);
1591    }
1592
1593    #[test]
1594    fn foreach_file_only() {
1595        let path = Path::new("foo");
1596        let (td, repo) = crate::test::repo_init();
1597        t!(t!(File::create(&td.path().join(path))).write_all(b"bar"));
1598        let mut opts = DiffOptions::new();
1599        opts.include_untracked(true);
1600        let diff = t!(repo.diff_tree_to_workdir(None, Some(&mut opts)));
1601        let mut count = 0;
1602        let mut result = None;
1603        t!(diff.foreach(
1604            &mut |file, _progress| {
1605                count = count + 1;
1606                result = file.new_file().path().map(ToOwned::to_owned);
1607                true
1608            },
1609            None,
1610            None,
1611            None
1612        ));
1613        assert_eq!(result.as_ref().map(Borrow::borrow), Some(path));
1614        assert_eq!(count, 1);
1615    }
1616
1617    #[test]
1618    fn foreach_file_and_hunk() {
1619        let path = Path::new("foo");
1620        let (td, repo) = crate::test::repo_init();
1621        t!(t!(File::create(&td.path().join(path))).write_all(b"bar"));
1622        let mut index = t!(repo.index());
1623        t!(index.add_path(path));
1624        let mut opts = DiffOptions::new();
1625        opts.include_untracked(true);
1626        let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
1627        let mut new_lines = 0;
1628        t!(diff.foreach(
1629            &mut |_file, _progress| { true },
1630            None,
1631            Some(&mut |_file, hunk| {
1632                new_lines = hunk.new_lines();
1633                true
1634            }),
1635            None
1636        ));
1637        assert_eq!(new_lines, 1);
1638    }
1639
1640    #[test]
1641    fn foreach_all_callbacks() {
1642        let fib = vec![0, 1, 1, 2, 3, 5, 8];
1643        let deflated_fib = vec![120, 156, 99, 96, 100, 100, 98, 102, 229, 0, 0, 0, 53, 0, 21];
1646        let foo_path = Path::new("foo");
1647        let bin_path = Path::new("bin");
1648        let (td, repo) = crate::test::repo_init();
1649        t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n"));
1650        t!(t!(File::create(&td.path().join(bin_path))).write_all(&fib));
1651        let mut index = t!(repo.index());
1652        t!(index.add_path(foo_path));
1653        t!(index.add_path(bin_path));
1654        let mut opts = DiffOptions::new();
1655        opts.include_untracked(true).show_binary(true);
1656        let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
1657        let mut bin_content = None;
1658        let mut new_lines = 0;
1659        let mut line_content = None;
1660        t!(diff.foreach(
1661            &mut |_file, _progress| { true },
1662            Some(&mut |_file, binary| {
1663                bin_content = Some(binary.new_file().data().to_owned());
1664                true
1665            }),
1666            Some(&mut |_file, hunk| {
1667                new_lines = hunk.new_lines();
1668                true
1669            }),
1670            Some(&mut |_file, _hunk, line| {
1671                line_content = String::from_utf8(line.content().into()).ok();
1672                true
1673            })
1674        ));
1675        assert_eq!(bin_content, Some(deflated_fib));
1676        assert_eq!(new_lines, 1);
1677        assert_eq!(line_content, Some("bar\n".to_string()));
1678    }
1679
1680    #[test]
1681    fn format_email_simple() {
1682        let (_td, repo) = crate::test::repo_init();
1683        const COMMIT_MESSAGE: &str = "Modify some content";
1684        const EXPECTED_EMAIL_START: &str = concat!(
1685            "From f1234fb0588b6ed670779a34ba5c51ef962f285f Mon Sep 17 00:00:00 2001\n",
1686            "From: Techcable <dummy@dummy.org>\n",
1687            "Date: Tue, 11 Jan 1972 17:46:40 +0000\n",
1688            "Subject: [PATCH] Modify some content\n",
1689            "\n",
1690            "---\n",
1691            " file1.txt | 8 +++++---\n",
1692            " 1 file changed, 5 insertions(+), 3 deletions(-)\n",
1693            "\n",
1694            "diff --git a/file1.txt b/file1.txt\n",
1695            "index 94aaae8..af8f41d 100644\n",
1696            "--- a/file1.txt\n",
1697            "+++ b/file1.txt\n",
1698            "@@ -1,15 +1,17 @@\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            "+\n",
1707            "+\n",
1708            " file1.txt\n",
1709            " file1.txt\n",
1710            " file1.txt\n",
1711            " file1.txt\n",
1712            " file1.txt\n",
1713            "-file1.txt\n",
1714            "-file1.txt\n",
1715            "-file1.txt\n",
1716            "+_file1.txt_\n",
1717            "+_file1.txt_\n",
1718            " file1.txt\n",
1719            "--\n"
1720        );
1721        const ORIGINAL_FILE: &str = concat!(
1722            "file1.txt\n",
1723            "file1.txt\n",
1724            "file1.txt\n",
1725            "file1.txt\n",
1726            "file1.txt\n",
1727            "file1.txt\n",
1728            "file1.txt\n",
1729            "file1.txt\n",
1730            "file1.txt\n",
1731            "file1.txt\n",
1732            "file1.txt\n",
1733            "file1.txt\n",
1734            "file1.txt\n",
1735            "file1.txt\n",
1736            "file1.txt\n"
1737        );
1738        const UPDATED_FILE: &str = concat!(
1739            "file1.txt\n",
1740            "file1.txt\n",
1741            "_file1.txt_\n",
1742            "file1.txt\n",
1743            "file1.txt\n",
1744            "file1.txt\n",
1745            "file1.txt\n",
1746            "\n",
1747            "\n",
1748            "file1.txt\n",
1749            "file1.txt\n",
1750            "file1.txt\n",
1751            "file1.txt\n",
1752            "file1.txt\n",
1753            "_file1.txt_\n",
1754            "_file1.txt_\n",
1755            "file1.txt\n"
1756        );
1757        const FILE_MODE: i32 = 0o100644;
1758        let original_file = repo.blob(ORIGINAL_FILE.as_bytes()).unwrap();
1759        let updated_file = repo.blob(UPDATED_FILE.as_bytes()).unwrap();
1760        let mut original_tree = repo.treebuilder(None).unwrap();
1761        original_tree
1762            .insert("file1.txt", original_file, FILE_MODE)
1763            .unwrap();
1764        let original_tree = original_tree.write().unwrap();
1765        let mut updated_tree = repo.treebuilder(None).unwrap();
1766        updated_tree
1767            .insert("file1.txt", updated_file, FILE_MODE)
1768            .unwrap();
1769        let updated_tree = updated_tree.write().unwrap();
1770        let time = Time::new(64_000_000, 0);
1771        let author = Signature::new("Techcable", "dummy@dummy.org", &time).unwrap();
1772        let updated_commit = repo
1773            .commit(
1774                None,
1775                &author,
1776                &author,
1777                COMMIT_MESSAGE,
1778                &repo.find_tree(updated_tree).unwrap(),
1779                &[], )
1781            .unwrap();
1782        let updated_commit = repo.find_commit(updated_commit).unwrap();
1783        let mut diff = repo
1784            .diff_tree_to_tree(
1785                Some(&repo.find_tree(original_tree).unwrap()),
1786                Some(&repo.find_tree(updated_tree).unwrap()),
1787                None,
1788            )
1789            .unwrap();
1790        #[allow(deprecated)]
1791        let actual_email = diff.format_email(1, 1, &updated_commit, None).unwrap();
1792        let actual_email = actual_email.as_str().unwrap();
1793        assert!(
1794            actual_email.starts_with(EXPECTED_EMAIL_START),
1795            "Unexpected email:\n{}",
1796            actual_email
1797        );
1798        let mut remaining_lines = actual_email[EXPECTED_EMAIL_START.len()..].lines();
1799        let version_line = remaining_lines.next();
1800        assert!(
1801            version_line.unwrap().starts_with("libgit2"),
1802            "Invalid version line: {:?}",
1803            version_line
1804        );
1805        while let Some(line) = remaining_lines.next() {
1806            assert_eq!(line.trim(), "")
1807        }
1808    }
1809
1810    #[test]
1811    fn foreach_diff_line_origin_value() {
1812        let foo_path = Path::new("foo");
1813        let (td, repo) = crate::test::repo_init();
1814        t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n"));
1815        let mut index = t!(repo.index());
1816        t!(index.add_path(foo_path));
1817        let mut opts = DiffOptions::new();
1818        opts.include_untracked(true);
1819        let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
1820        let mut origin_values: Vec<DiffLineType> = Vec::new();
1821        t!(diff.foreach(
1822            &mut |_file, _progress| { true },
1823            None,
1824            None,
1825            Some(&mut |_file, _hunk, line| {
1826                origin_values.push(line.origin_value());
1827                true
1828            })
1829        ));
1830        assert_eq!(origin_values.len(), 1);
1831        assert_eq!(origin_values[0], DiffLineType::Addition);
1832    }
1833
1834    #[test]
1835    fn foreach_exits_with_euser() {
1836        let foo_path = Path::new("foo");
1837        let bar_path = Path::new("foo");
1838
1839        let (td, repo) = crate::test::repo_init();
1840        t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n"));
1841
1842        let mut index = t!(repo.index());
1843        t!(index.add_path(foo_path));
1844        t!(index.add_path(bar_path));
1845
1846        let mut opts = DiffOptions::new();
1847        opts.include_untracked(true);
1848        let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
1849
1850        let mut calls = 0;
1851        let result = diff.foreach(
1852            &mut |_file, _progress| {
1853                calls += 1;
1854                false
1855            },
1856            None,
1857            None,
1858            None,
1859        );
1860
1861        assert_eq!(result.unwrap_err().code(), crate::ErrorCode::User);
1862    }
1863}