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}