git2/
diff.rs

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
15/// The diff object that contains all individual file deltas.
16///
17/// This is an opaque structure which will be allocated by one of the diff
18/// generator functions on the `Repository` structure (e.g. `diff_tree_to_tree`
19/// or other `diff_*` functions).
20pub 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
27/// Description of changes to one entry.
28pub struct DiffDelta<'a> {
29    raw: *mut raw::git_diff_delta,
30    _marker: marker::PhantomData<&'a raw::git_diff_delta>,
31}
32
33/// Description of one side of a delta.
34///
35/// Although this is called a "file" it could represent a file, a symbolic
36/// link, a submodule commit id, or even a tree (although that only happens if
37/// you are tracking type changes or ignored/untracked directories).
38pub struct DiffFile<'a> {
39    raw: *const raw::git_diff_file,
40    _marker: marker::PhantomData<&'a raw::git_diff_file>,
41}
42
43/// Structure describing options about how the diff should be executed.
44pub 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
52/// Control behavior of rename and copy detection
53pub struct DiffFindOptions {
54    raw: raw::git_diff_find_options,
55}
56
57/// Control behavior of formatting emails
58pub struct DiffFormatEmailOptions {
59    raw: raw::git_diff_format_email_options,
60}
61
62/// Control behavior of formatting emails
63pub struct DiffPatchidOptions {
64    raw: raw::git_diff_patchid_options,
65}
66
67/// An iterator over the diffs in a delta
68pub struct Deltas<'diff> {
69    range: Range<usize>,
70    diff: &'diff Diff<'diff>,
71}
72
73/// Structure describing a line (or data span) of a diff.
74pub struct DiffLine<'a> {
75    raw: *const raw::git_diff_line,
76    _marker: marker::PhantomData<&'a raw::git_diff_line>,
77}
78
79/// Structure describing a hunk of a diff.
80pub struct DiffHunk<'a> {
81    raw: *const raw::git_diff_hunk,
82    _marker: marker::PhantomData<&'a raw::git_diff_hunk>,
83}
84
85/// Structure describing a hunk of a diff.
86pub struct DiffStats {
87    raw: *mut raw::git_diff_stats,
88}
89
90/// Structure describing the binary contents of a diff.
91pub struct DiffBinary<'a> {
92    raw: *const raw::git_diff_binary,
93    _marker: marker::PhantomData<&'a raw::git_diff_binary>,
94}
95
96/// The contents of one of the files in a binary diff.
97pub struct DiffBinaryFile<'a> {
98    raw: *const raw::git_diff_binary_file,
99    _marker: marker::PhantomData<&'a raw::git_diff_binary_file>,
100}
101
102/// When producing a binary diff, the binary data returned will be
103/// either the deflated full ("literal") contents of the file, or
104/// the deflated binary delta between the two sides (whichever is
105/// smaller).
106#[derive(Copy, Clone, Debug)]
107pub enum DiffBinaryKind {
108    /// There is no binary delta
109    None,
110    /// The binary data is the literal contents of the file
111    Literal,
112    /// The binary data is the delta from one side to the other
113    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    /// Merge one diff into another.
132    ///
133    /// This merges items from the "from" list into the "self" list.  The
134    /// resulting diff will have all items that appear in either list.
135    /// If an item appears in both lists, then it will be "merged" to appear
136    /// as if the old version was from the "onto" list and the new version
137    /// is from the "from" list (with the exception that if the item has a
138    /// pending DELETE in the middle, then it will show as deleted).
139    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    /// Returns an iterator over the deltas in this diff.
147    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    /// Return the diff delta for an entry in the diff list.
156    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    /// Check if deltas are sorted case sensitively or insensitively.
164    pub fn is_sorted_icase(&self) -> bool {
165        unsafe { raw::git_diff_is_sorted_icase(&*self.raw) == 1 }
166    }
167
168    /// Iterate over a diff generating formatted text output.
169    ///
170    /// Returning `false` from the callback will terminate the iteration and
171    /// return an error from this function.
172    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    /// Loop over all deltas in a diff issuing callbacks.
186    ///
187    /// Returning `false` from any callback will terminate the iteration and
188    /// return an error from this function.
189    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    /// Accumulate diff statistics for all patches.
233    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    /// Transform a diff marking file renames, copies, etc.
242    ///
243    /// This modifies a diff in place, replacing old entries that look like
244    /// renames or copies with new entries reflecting those changes. This also
245    /// will, if requested, break modified files into add/remove pairs if the
246    /// amount of change is above a threshold.
247    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    /// Create an e-mail ready patch from a diff.
256    ///
257    /// Matches the format created by `git format-patch`
258    #[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    /// Create a patch ID from a diff.
291    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    // TODO: num_deltas_of_type, find_similar
306}
307impl Diff<'static> {
308    /// Read the contents of a git patch file into a `git_diff` object.
309    ///
310    /// The diff object produced is similar to the one that would be
311    /// produced if you actually produced it computationally by comparing
312    /// two trees, however there may be subtle differences. For example,
313    /// a patch file likely contains abbreviated object IDs, so the
314    /// object IDs parsed by this function will also be abbreviated.
315    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            // NOTE: Doesn't depend on repo, so lifetime can be 'static
320            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    /// Returns the flags on the delta.
471    ///
472    /// For more information, see `DiffFlags`'s documentation.
473    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    // TODO: expose when diffs are more exposed
502    // pub fn similarity(&self) -> u16 {
503    //     unsafe { (*self.raw).similarity }
504    // }
505
506    /// Returns the number of files in this delta.
507    pub fn nfiles(&self) -> u16 {
508        unsafe { (*self.raw).nfiles }
509    }
510
511    /// Returns the status of this entry
512    ///
513    /// For more information, see `Delta`'s documentation
514    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    /// Return the file which represents the "from" side of the diff.
532    ///
533    /// What side this means depends on the function that was used to generate
534    /// the diff and will be documented on the function itself.
535    pub fn old_file(&self) -> DiffFile<'a> {
536        unsafe { Binding::from_raw(&(*self.raw).old_file as *const _) }
537    }
538
539    /// Return the file which represents the "to" side of the diff.
540    ///
541    /// What side this means depends on the function that was used to generate
542    /// the diff and will be documented on the function itself.
543    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    /// Returns the Oid of this item.
574    ///
575    /// If this entry represents an absent side of a diff (e.g. the `old_file`
576    /// of a `Added` delta), then the oid returned will be zeroes.
577    pub fn id(&self) -> Oid {
578        unsafe { Binding::from_raw(&(*self.raw).id as *const _) }
579    }
580
581    /// Returns the path, in bytes, of the entry relative to the working
582    /// directory of the repository.
583    pub fn path_bytes(&self) -> Option<&'a [u8]> {
584        static FOO: () = ();
585        unsafe { crate::opt_bytes(&FOO, (*self.raw).path) }
586    }
587
588    /// Returns the path of the entry relative to the working directory of the
589    /// repository.
590    pub fn path(&self) -> Option<&'a Path> {
591        self.path_bytes().map(util::bytes2path)
592    }
593
594    /// Returns the size of this entry, in bytes
595    pub fn size(&self) -> u64 {
596        unsafe { (*self.raw).size as u64 }
597    }
598
599    /// Returns `true` if file(s) are treated as binary data.
600    pub fn is_binary(&self) -> bool {
601        unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_BINARY as u32 != 0 }
602    }
603
604    /// Returns `true` if file(s) are treated as text data.
605    pub fn is_not_binary(&self) -> bool {
606        unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_NOT_BINARY as u32 != 0 }
607    }
608
609    /// Returns `true` if `id` value is known correct.
610    pub fn is_valid_id(&self) -> bool {
611        unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_VALID_ID as u32 != 0 }
612    }
613
614    /// Returns `true` if file exists at this side of the delta.
615    pub fn exists(&self) -> bool {
616        unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_EXISTS as u32 != 0 }
617    }
618
619    /// Returns file mode.
620    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    /// Creates a new set of empty diff options.
669    ///
670    /// All flags and other options are defaulted to false or their otherwise
671    /// zero equivalents.
672    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    /// Flag indicating whether the sides of the diff will be reversed.
695    pub fn reverse(&mut self, reverse: bool) -> &mut DiffOptions {
696        self.flag(raw::GIT_DIFF_REVERSE, reverse)
697    }
698
699    /// Flag indicating whether ignored files are included.
700    pub fn include_ignored(&mut self, include: bool) -> &mut DiffOptions {
701        self.flag(raw::GIT_DIFF_INCLUDE_IGNORED, include)
702    }
703
704    /// Flag indicating whether ignored directories are traversed deeply or not.
705    pub fn recurse_ignored_dirs(&mut self, recurse: bool) -> &mut DiffOptions {
706        self.flag(raw::GIT_DIFF_RECURSE_IGNORED_DIRS, recurse)
707    }
708
709    /// Flag indicating whether untracked files are in the diff
710    pub fn include_untracked(&mut self, include: bool) -> &mut DiffOptions {
711        self.flag(raw::GIT_DIFF_INCLUDE_UNTRACKED, include)
712    }
713
714    /// Flag indicating whether untracked directories are traversed deeply or
715    /// not.
716    pub fn recurse_untracked_dirs(&mut self, recurse: bool) -> &mut DiffOptions {
717        self.flag(raw::GIT_DIFF_RECURSE_UNTRACKED_DIRS, recurse)
718    }
719
720    /// Flag indicating whether unmodified files are in the diff.
721    pub fn include_unmodified(&mut self, include: bool) -> &mut DiffOptions {
722        self.flag(raw::GIT_DIFF_INCLUDE_UNMODIFIED, include)
723    }
724
725    /// If enabled, then Typechange delta records are generated.
726    pub fn include_typechange(&mut self, include: bool) -> &mut DiffOptions {
727        self.flag(raw::GIT_DIFF_INCLUDE_TYPECHANGE, include)
728    }
729
730    /// Event with `include_typechange`, the tree returned generally shows a
731    /// deleted blob. This flag correctly labels the tree transitions as a
732    /// typechange record with the `new_file`'s mode set to tree.
733    ///
734    /// Note that the tree SHA will not be available.
735    pub fn include_typechange_trees(&mut self, include: bool) -> &mut DiffOptions {
736        self.flag(raw::GIT_DIFF_INCLUDE_TYPECHANGE_TREES, include)
737    }
738
739    /// Flag indicating whether file mode changes are ignored.
740    pub fn ignore_filemode(&mut self, ignore: bool) -> &mut DiffOptions {
741        self.flag(raw::GIT_DIFF_IGNORE_FILEMODE, ignore)
742    }
743
744    /// Flag indicating whether all submodules should be treated as unmodified.
745    pub fn ignore_submodules(&mut self, ignore: bool) -> &mut DiffOptions {
746        self.flag(raw::GIT_DIFF_IGNORE_SUBMODULES, ignore)
747    }
748
749    /// Flag indicating whether case insensitive filenames should be used.
750    pub fn ignore_case(&mut self, ignore: bool) -> &mut DiffOptions {
751        self.flag(raw::GIT_DIFF_IGNORE_CASE, ignore)
752    }
753
754    /// If pathspecs are specified, this flag means that they should be applied
755    /// as an exact match instead of a fnmatch pattern.
756    pub fn disable_pathspec_match(&mut self, disable: bool) -> &mut DiffOptions {
757        self.flag(raw::GIT_DIFF_DISABLE_PATHSPEC_MATCH, disable)
758    }
759
760    /// Disable updating the `binary` flag in delta records. This is useful when
761    /// iterating over a diff if you don't need hunk and data callbacks and want
762    /// to avoid having to load a file completely.
763    pub fn skip_binary_check(&mut self, skip: bool) -> &mut DiffOptions {
764        self.flag(raw::GIT_DIFF_SKIP_BINARY_CHECK, skip)
765    }
766
767    /// When diff finds an untracked directory, to match the behavior of core
768    /// Git, it scans the contents for ignored and untracked files. If all
769    /// contents are ignored, then the directory is ignored; if any contents are
770    /// not ignored, then the directory is untracked. This is extra work that
771    /// may not matter in many cases.
772    ///
773    /// This flag turns off that scan and immediately labels an untracked
774    /// directory as untracked (changing the behavior to not match core git).
775    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    /// When diff finds a file in the working directory with stat information
780    /// different from the index, but the OID ends up being the same, write the
781    /// correct stat information into the index. Note: without this flag, diff
782    /// will always leave the index untouched.
783    pub fn update_index(&mut self, update: bool) -> &mut DiffOptions {
784        self.flag(raw::GIT_DIFF_UPDATE_INDEX, update)
785    }
786
787    /// Include unreadable files in the diff
788    pub fn include_unreadable(&mut self, include: bool) -> &mut DiffOptions {
789        self.flag(raw::GIT_DIFF_INCLUDE_UNREADABLE, include)
790    }
791
792    /// Include unreadable files in the diff as untracked files
793    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    /// Treat all files as text, disabling binary attributes and detection.
798    pub fn force_text(&mut self, force: bool) -> &mut DiffOptions {
799        self.flag(raw::GIT_DIFF_FORCE_TEXT, force)
800    }
801
802    /// Treat all files as binary, disabling text diffs
803    pub fn force_binary(&mut self, force: bool) -> &mut DiffOptions {
804        self.flag(raw::GIT_DIFF_FORCE_BINARY, force)
805    }
806
807    /// Ignore all whitespace
808    pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut DiffOptions {
809        self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE, ignore)
810    }
811
812    /// Ignore changes in the amount of whitespace
813    pub fn ignore_whitespace_change(&mut self, ignore: bool) -> &mut DiffOptions {
814        self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE_CHANGE, ignore)
815    }
816
817    /// Ignore whitespace at the end of line
818    pub fn ignore_whitespace_eol(&mut self, ignore: bool) -> &mut DiffOptions {
819        self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE_EOL, ignore)
820    }
821
822    /// Ignore blank lines
823    pub fn ignore_blank_lines(&mut self, ignore: bool) -> &mut DiffOptions {
824        self.flag(raw::GIT_DIFF_IGNORE_BLANK_LINES, ignore)
825    }
826
827    /// When generating patch text, include the content of untracked files.
828    ///
829    /// This automatically turns on `include_untracked` but it does not turn on
830    /// `recurse_untracked_dirs`. Add that flag if you want the content of every
831    /// single untracked file.
832    pub fn show_untracked_content(&mut self, show: bool) -> &mut DiffOptions {
833        self.flag(raw::GIT_DIFF_SHOW_UNTRACKED_CONTENT, show)
834    }
835
836    /// When generating output, include the names of unmodified files if they
837    /// are included in the `Diff`. Normally these are skipped in the formats
838    /// that list files (e.g. name-only, name-status, raw). Even with this these
839    /// will not be included in the patch format.
840    pub fn show_unmodified(&mut self, show: bool) -> &mut DiffOptions {
841        self.flag(raw::GIT_DIFF_SHOW_UNMODIFIED, show)
842    }
843
844    /// Use the "patience diff" algorithm
845    pub fn patience(&mut self, patience: bool) -> &mut DiffOptions {
846        self.flag(raw::GIT_DIFF_PATIENCE, patience)
847    }
848
849    /// Take extra time to find the minimal diff
850    pub fn minimal(&mut self, minimal: bool) -> &mut DiffOptions {
851        self.flag(raw::GIT_DIFF_MINIMAL, minimal)
852    }
853
854    /// Include the necessary deflate/delta information so that `git-apply` can
855    /// apply given diff information to binary files.
856    pub fn show_binary(&mut self, show: bool) -> &mut DiffOptions {
857        self.flag(raw::GIT_DIFF_SHOW_BINARY, show)
858    }
859
860    /// Use a heuristic that takes indentation and whitespace into account
861    /// which generally can produce better diffs when dealing with ambiguous
862    /// diff hunks.
863    pub fn indent_heuristic(&mut self, heuristic: bool) -> &mut DiffOptions {
864        self.flag(raw::GIT_DIFF_INDENT_HEURISTIC, heuristic)
865    }
866
867    /// Set the number of unchanged lines that define the boundary of a hunk
868    /// (and to display before and after).
869    ///
870    /// The default value for this is 3.
871    pub fn context_lines(&mut self, lines: u32) -> &mut DiffOptions {
872        self.raw.context_lines = lines;
873        self
874    }
875
876    /// Set the maximum number of unchanged lines between hunk boundaries before
877    /// the hunks will be merged into one.
878    ///
879    /// The default value for this is 0.
880    pub fn interhunk_lines(&mut self, lines: u32) -> &mut DiffOptions {
881        self.raw.interhunk_lines = lines;
882        self
883    }
884
885    /// The default value for this is `core.abbrev` or 7 if unset.
886    pub fn id_abbrev(&mut self, abbrev: u16) -> &mut DiffOptions {
887        self.raw.id_abbrev = abbrev;
888        self
889    }
890
891    /// Maximum size (in bytes) above which a blob will be marked as binary
892    /// automatically.
893    ///
894    /// A negative value will disable this entirely.
895    ///
896    /// The default value for this is 512MB.
897    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    /// The virtual "directory" to prefix old file names with in hunk headers.
903    ///
904    /// The default value for this is "a".
905    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    /// The virtual "directory" to prefix new file names with in hunk headers.
911    ///
912    /// The default value for this is "b".
913    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    /// Add to the array of paths/fnmatch patterns to constrain the diff.
919    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    /// Acquire a pointer to the underlying raw options.
927    ///
928    /// This function is unsafe as the pointer is only valid so long as this
929    /// structure is not moved, modified, or used elsewhere.
930    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    // TODO: expose ignore_submodules, notify_cb/notify_payload
947}
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/// Line origin constants.
968#[derive(Copy, Clone, Debug, PartialEq)]
969pub enum DiffLineType {
970    /// These values will be sent to `git_diff_line_cb` along with the line
971    Context,
972    ///
973    Addition,
974    ///
975    Deletion,
976    /// Both files have no LF at end
977    ContextEOFNL,
978    /// Old has no LF at end, new does
979    AddEOFNL,
980    /// Old has LF at end, new does not
981    DeleteEOFNL,
982    /// The following values will only be sent to a `git_diff_line_cb` when
983    /// the content of a diff is being formatted through `git_diff_print`.
984    FileHeader,
985    ///
986    HunkHeader,
987    /// For "Binary files x and y differ"
988    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    /// Line number in old file or `None` for added line
1024    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    /// Line number in new file or `None` for deleted line
1032    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    /// Number of newline characters in content
1040    pub fn num_lines(&self) -> u32 {
1041        unsafe { (*self.raw).num_lines as u32 }
1042    }
1043
1044    /// Offset in the original file to the content
1045    pub fn content_offset(&self) -> i64 {
1046        unsafe { (*self.raw).content_offset as i64 }
1047    }
1048
1049    /// Content of this line as bytes.
1050    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    /// origin of this `DiffLine`.
1060    ///
1061    pub fn origin_value(&self) -> DiffLineType {
1062        unsafe { Binding::from_raw((*self.raw).origin as raw::git_diff_line_t) }
1063    }
1064
1065    /// Sigil showing the origin of this `DiffLine`.
1066    ///
1067    ///  * ` ` - Line context
1068    ///  * `+` - Line addition
1069    ///  * `-` - Line deletion
1070    ///  * `=` - Context (End of file)
1071    ///  * `>` - Add (End of file)
1072    ///  * `<` - Remove (End of file)
1073    ///  * `F` - File header
1074    ///  * `H` - Hunk header
1075    ///  * `B` - Line binary
1076    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    /// Starting line number in old_file
1124    pub fn old_start(&self) -> u32 {
1125        unsafe { (*self.raw).old_start as u32 }
1126    }
1127
1128    /// Number of lines in old_file
1129    pub fn old_lines(&self) -> u32 {
1130        unsafe { (*self.raw).old_lines as u32 }
1131    }
1132
1133    /// Starting line number in new_file
1134    pub fn new_start(&self) -> u32 {
1135        unsafe { (*self.raw).new_start as u32 }
1136    }
1137
1138    /// Number of lines in new_file
1139    pub fn new_lines(&self) -> u32 {
1140        unsafe { (*self.raw).new_lines as u32 }
1141    }
1142
1143    /// Header text
1144    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    /// Get the total number of files changed in a diff.
1181    pub fn files_changed(&self) -> usize {
1182        unsafe { raw::git_diff_stats_files_changed(&*self.raw) as usize }
1183    }
1184
1185    /// Get the total number of insertions in a diff
1186    pub fn insertions(&self) -> usize {
1187        unsafe { raw::git_diff_stats_insertions(&*self.raw) as usize }
1188    }
1189
1190    /// Get the total number of deletions in a diff
1191    pub fn deletions(&self) -> usize {
1192        unsafe { raw::git_diff_stats_deletions(&*self.raw) as usize }
1193    }
1194
1195    /// Print diff statistics to a Buf
1196    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    /// Returns whether there is data in this binary structure or not.
1239    ///
1240    /// If this is `true`, then this was produced and included binary content.
1241    /// If this is `false` then this was generated knowing only that a binary
1242    /// file changed but without providing the data, probably from a patch that
1243    /// said `Binary files a/file.txt and b/file.txt differ`.
1244    pub fn contains_data(&self) -> bool {
1245        unsafe { (*self.raw).contains_data == 1 }
1246    }
1247
1248    /// The contents of the old file.
1249    pub fn old_file(&self) -> DiffBinaryFile<'a> {
1250        unsafe { Binding::from_raw(&(*self.raw).old_file as *const _) }
1251    }
1252
1253    /// The contents of the new file.
1254    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    /// The type of binary data for this file
1274    pub fn kind(&self) -> DiffBinaryKind {
1275        unsafe { Binding::from_raw((*self.raw).kind) }
1276    }
1277
1278    /// The binary data, deflated
1279    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    /// The length of the binary data after inflation
1286    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    /// Creates a new set of empty diff find options.
1331    ///
1332    /// All flags and other options are defaulted to false or their otherwise
1333    /// zero equivalents.
1334    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    /// Reset all flags back to their unset state, indicating that
1355    /// `diff.renames` should be used instead. This is overridden once any flag
1356    /// is set.
1357    pub fn by_config(&mut self) -> &mut DiffFindOptions {
1358        self.flag(0xffffffff, false)
1359    }
1360
1361    /// Look for renames?
1362    pub fn renames(&mut self, find: bool) -> &mut DiffFindOptions {
1363        self.flag(raw::GIT_DIFF_FIND_RENAMES, find)
1364    }
1365
1366    /// Consider old side of modified for renames?
1367    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    /// Look for copies?
1372    pub fn copies(&mut self, find: bool) -> &mut DiffFindOptions {
1373        self.flag(raw::GIT_DIFF_FIND_COPIES, find)
1374    }
1375
1376    /// Consider unmodified as copy sources?
1377    ///
1378    /// For this to work correctly, use `include_unmodified` when the initial
1379    /// diff is being generated.
1380    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    /// Mark significant rewrites for split.
1385    pub fn rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
1386        self.flag(raw::GIT_DIFF_FIND_REWRITES, find)
1387    }
1388
1389    /// Actually split large rewrites into delete/add pairs
1390    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    /// Find renames/copies for untracked items in working directory.
1400    ///
1401    /// For this to work correctly use the `include_untracked` option when the
1402    /// initial diff is being generated.
1403    pub fn for_untracked(&mut self, find: bool) -> &mut DiffFindOptions {
1404        self.flag(raw::GIT_DIFF_FIND_FOR_UNTRACKED, find)
1405    }
1406
1407    /// Turn on all finding features.
1408    pub fn all(&mut self, find: bool) -> &mut DiffFindOptions {
1409        self.flag(raw::GIT_DIFF_FIND_ALL, find)
1410    }
1411
1412    /// Measure similarity ignoring leading whitespace (default)
1413    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    /// Measure similarity ignoring all whitespace
1418    pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions {
1419        self.flag(raw::GIT_DIFF_FIND_IGNORE_WHITESPACE, ignore)
1420    }
1421
1422    /// Measure similarity including all data
1423    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    /// Measure similarity only by comparing SHAs (fast and cheap)
1428    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    /// Do not break rewrites unless they contribute to a rename.
1433    ///
1434    /// Normally, `break_rewrites` and `rewrites` will measure the
1435    /// self-similarity of modified files and split the ones that have changed a
1436    /// lot into a delete/add pair.  Then the sides of that pair will be
1437    /// considered candidates for rename and copy detection
1438    ///
1439    /// If you add this flag in and the split pair is not used for an actual
1440    /// rename or copy, then the modified record will be restored to a regular
1441    /// modified record instead of being split.
1442    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    /// Remove any unmodified deltas after find_similar is done.
1447    ///
1448    /// Using `copies_from_unmodified` to emulate the `--find-copies-harder`
1449    /// behavior requires building a diff with the `include_unmodified` flag. If
1450    /// you do not want unmodified records in the final result, pas this flag to
1451    /// have them removed.
1452    pub fn remove_unmodified(&mut self, remove: bool) -> &mut DiffFindOptions {
1453        self.flag(raw::GIT_DIFF_FIND_REMOVE_UNMODIFIED, remove)
1454    }
1455
1456    /// Similarity to consider a file renamed (default 50)
1457    pub fn rename_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1458        self.raw.rename_threshold = thresh;
1459        self
1460    }
1461
1462    /// Similarity of modified to be eligible rename source (default 50)
1463    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    /// Similarity to consider a file copy (default 50)
1469    pub fn copy_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1470        self.raw.copy_threshold = thresh;
1471        self
1472    }
1473
1474    /// Similarity to split modify into delete/add pair (default 60)
1475    pub fn break_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1476        self.raw.break_rewrite_threshold = thresh;
1477        self
1478    }
1479
1480    /// Maximum similarity sources to examine for a file (somewhat like
1481    /// git-diff's `-l` option or `diff.renameLimit` config)
1482    ///
1483    /// Defaults to 200
1484    pub fn rename_limit(&mut self, limit: usize) -> &mut DiffFindOptions {
1485        self.raw.rename_limit = limit as size_t;
1486        self
1487    }
1488
1489    // TODO: expose git_diff_similarity_metric
1490
1491    /// Acquire a pointer to the underlying raw options.
1492    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    /// Creates a new set of email options,
1505    /// initialized to the default values
1506    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    /// Exclude `[PATCH]` from the subject header
1527    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    /// Creates a new set of patchid options,
1537    /// initialized to the default values
1538    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        // Verified with a node implementation of deflate, might be worth
1644        // adding a deflate lib to do this inline here.
1645        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                &[], // NOTE: Have no parents to ensure stable hash
1780            )
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}