1 use libc
::{c_char, c_int, c_void, size_t}
;
3 use std
::iter
::FusedIterator
;
11 use crate::util
::{self, Binding}
;
12 use crate::{panic, raw, Buf, Delta, DiffFormat, Error, FileMode, Oid, Repository}
;
13 use crate::{DiffFlags, DiffStatsFormat, IntoCString}
;
15 /// The diff object that contains all individual file deltas.
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).
20 pub struct Diff
<'repo
> {
21 raw
: *mut raw
::git_diff
,
22 _marker
: marker
::PhantomData
<&'repo Repository
>,
25 unsafe impl<'repo
> Send
for Diff
<'repo
> {}
27 /// Description of changes to one entry.
28 pub struct DiffDelta
<'a
> {
29 raw
: *mut raw
::git_diff_delta
,
30 _marker
: marker
::PhantomData
<&'a raw
::git_diff_delta
>,
33 /// Description of one side of a delta.
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).
38 pub struct DiffFile
<'a
> {
39 raw
: *const raw
::git_diff_file
,
40 _marker
: marker
::PhantomData
<&'a raw
::git_diff_file
>,
43 /// Structure describing options about how the diff should be executed.
44 pub 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
,
52 /// Control behavior of rename and copy detection
53 pub struct DiffFindOptions
{
54 raw
: raw
::git_diff_find_options
,
57 /// Control behavior of formatting emails
58 pub struct DiffFormatEmailOptions
{
59 raw
: raw
::git_diff_format_email_options
,
62 /// Control behavior of formatting emails
63 pub struct DiffPatchidOptions
{
64 raw
: raw
::git_diff_patchid_options
,
67 /// An iterator over the diffs in a delta
68 pub struct Deltas
<'diff
> {
70 diff
: &'diff Diff
<'diff
>,
73 /// Structure describing a line (or data span) of a diff.
74 pub struct DiffLine
<'a
> {
75 raw
: *const raw
::git_diff_line
,
76 _marker
: marker
::PhantomData
<&'a raw
::git_diff_line
>,
79 /// Structure describing a hunk of a diff.
80 pub struct DiffHunk
<'a
> {
81 raw
: *const raw
::git_diff_hunk
,
82 _marker
: marker
::PhantomData
<&'a raw
::git_diff_hunk
>,
85 /// Structure describing a hunk of a diff.
86 pub struct DiffStats
{
87 raw
: *mut raw
::git_diff_stats
,
90 /// Structure describing the binary contents of a diff.
91 pub struct DiffBinary
<'a
> {
92 raw
: *const raw
::git_diff_binary
,
93 _marker
: marker
::PhantomData
<&'a raw
::git_diff_binary
>,
96 /// The contents of one of the files in a binary diff.
97 pub struct DiffBinaryFile
<'a
> {
98 raw
: *const raw
::git_diff_binary_file
,
99 _marker
: marker
::PhantomData
<&'a raw
::git_diff_binary_file
>,
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
106 #[derive(Copy, Clone, Debug)]
107 pub enum DiffBinaryKind
{
108 /// There is no binary delta
110 /// The binary data is the literal contents of the file
112 /// The binary data is the delta from one side to the other
116 type PrintCb
<'a
> = dyn FnMut(DiffDelta
<'_
>, Option
<DiffHunk
<'_
>>, DiffLine
<'_
>) -> bool
+ 'a
;
118 pub type FileCb
<'a
> = dyn FnMut(DiffDelta
<'_
>, f32) -> bool
+ 'a
;
119 pub type BinaryCb
<'a
> = dyn FnMut(DiffDelta
<'_
>, DiffBinary
<'_
>) -> bool
+ 'a
;
120 pub type HunkCb
<'a
> = dyn FnMut(DiffDelta
<'_
>, DiffHunk
<'_
>) -> bool
+ 'a
;
121 pub type LineCb
<'a
> = dyn FnMut(DiffDelta
<'_
>, Option
<DiffHunk
<'_
>>, DiffLine
<'_
>) -> bool
+ 'a
;
123 pub 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
>>,
130 impl<'repo
> Diff
<'repo
> {
131 /// Merge one diff into another.
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
> {
141 try_call
!(raw
::git_diff_merge(self.raw
, &*from
.raw
));
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) }
;
150 range
: 0..(num_deltas
as usize),
155 /// Return the diff delta for an entry in the diff list.
156 pub fn get_delta(&self, i
: usize) -> Option
<DiffDelta
<'_
>> {
158 let ptr
= raw
::git_diff_get_delta(&*self.raw
, i
as size_t
);
159 Binding
::from_raw_opt(ptr
as *mut _
)
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 }
168 /// Iterate over a diff generating formatted text output.
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
>
174 F
: FnMut(DiffDelta
<'_
>, Option
<DiffHunk
<'_
>>, DiffLine
<'_
>) -> bool
,
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
);
180 try_call
!(raw
::git_diff_print(self.raw
, format
, print
, ptr
as *mut _
));
185 /// Loop over all deltas in a diff issuing callbacks.
187 /// Returning `false` from any callback will terminate the iteration and
188 /// return an error from this function.
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
{
202 let ptr
= &mut cbs
as *mut _
;
204 let binary_cb_c
: raw
::git_diff_binary_cb
= if cbs
.binary
.is_some() {
209 let hunk_cb_c
: raw
::git_diff_hunk_cb
= if cbs
.hunk
.is_some() {
214 let line_cb_c
: raw
::git_diff_line_cb
= if cbs
.line
.is_some() {
219 let file_cb
: raw
::git_diff_file_cb
= Some(file_cb_c
);
220 try_call
!(raw
::git_diff_foreach(
232 /// Accumulate diff statistics for all patches.
233 pub fn stats(&self) -> Result
<DiffStats
, Error
> {
234 let mut ret
= ptr
::null_mut();
236 try_call
!(raw
::git_diff_get_stats(&mut ret
, self.raw
));
237 Ok(Binding
::from_raw(ret
))
241 /// Transform a diff marking file renames, copies, etc.
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
);
250 try_call
!(raw
::git_diff_find_similar(self.raw
, opts
));
255 /// Create an e-mail ready patch from a diff.
257 /// Matches the format created by `git format-patch`
259 #[deprecated(note = "refactored to `Email::from_diff` to match upstream")]
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();
285 try_call
!(raw
::git_diff_format_email(buf
.raw(), self.raw
, &*raw_opts
));
290 /// Create an 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
],
296 try_call
!(raw
::git_diff_patchid(
299 opts
.map(|o
| &mut o
.raw
)
301 Ok(Binding
::from_raw(&raw
as *const _
))
305 // TODO: num_deltas_of_type, find_similar
308 /// Read the contents of a git patch file into a `git_diff` object.
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
> {
317 let mut diff
: *mut raw
::git_diff
= std
::ptr
::null_mut();
319 // NOTE: Doesn't depend on repo, so lifetime can be 'static
320 try_call
!(raw
::git_diff_from_buffer(
322 buffer
.as_ptr() as *const c_char
,
325 Ok(Diff
::from_raw(diff
))
330 pub 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
,
337 let delta
= Binding
::from_raw(delta
as *mut _
);
338 let hunk
= Binding
::from_raw_opt(hunk
);
339 let line
= Binding
::from_raw(line
);
341 let r
= panic
::wrap(|| {
342 let data
= data
as *mut &mut PrintCb
<'_
>;
343 (*data
)(delta
, hunk
, line
)
353 pub extern "C" fn file_cb_c(
354 delta
: *const raw
::git_diff_delta
,
359 let delta
= Binding
::from_raw(delta
as *mut _
);
361 let r
= panic
::wrap(|| {
362 let cbs
= data
as *mut DiffCallbacks
<'_
, '_
, '_
, '_
, '_
, '_
, '_
, '_
>;
364 Some(ref mut cb
) => cb(delta
, progress
),
376 pub extern "C" fn binary_cb_c(
377 delta
: *const raw
::git_diff_delta
,
378 binary
: *const raw
::git_diff_binary
,
382 let delta
= Binding
::from_raw(delta
as *mut _
);
383 let binary
= Binding
::from_raw(binary
);
385 let r
= panic
::wrap(|| {
386 let cbs
= data
as *mut DiffCallbacks
<'_
, '_
, '_
, '_
, '_
, '_
, '_
, '_
>;
387 match (*cbs
).binary
{
388 Some(ref mut cb
) => cb(delta
, binary
),
400 pub extern "C" fn hunk_cb_c(
401 delta
: *const raw
::git_diff_delta
,
402 hunk
: *const raw
::git_diff_hunk
,
406 let delta
= Binding
::from_raw(delta
as *mut _
);
407 let hunk
= Binding
::from_raw(hunk
);
409 let r
= panic
::wrap(|| {
410 let cbs
= data
as *mut DiffCallbacks
<'_
, '_
, '_
, '_
, '_
, '_
, '_
, '_
>;
412 Some(ref mut cb
) => cb(delta
, hunk
),
424 pub 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
,
431 let delta
= Binding
::from_raw(delta
as *mut _
);
432 let hunk
= Binding
::from_raw_opt(hunk
);
433 let line
= Binding
::from_raw(line
);
435 let r
= panic
::wrap(|| {
436 let cbs
= data
as *mut DiffCallbacks
<'_
, '_
, '_
, '_
, '_
, '_
, '_
, '_
>;
438 Some(ref mut cb
) => cb(delta
, hunk
, line
),
450 impl<'repo
> Binding
for Diff
<'repo
> {
451 type Raw
= *mut raw
::git_diff
;
452 unsafe fn from_raw(raw
: *mut raw
::git_diff
) -> Diff
<'repo
> {
455 _marker
: marker
::PhantomData
,
458 fn raw(&self) -> *mut raw
::git_diff
{
463 impl<'repo
> Drop
for Diff
<'repo
> {
465 unsafe { raw::git_diff_free(self.raw) }
469 impl<'a
> DiffDelta
<'a
> {
470 /// Returns the flags on the delta.
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();
477 #[cfg(target_env = "msvc")]
478 fn as_u32(flag
: i32) -> u32 {
481 #[cfg(not(target_env = "msvc"))]
482 fn as_u32(flag
: u32) -> u32 {
486 if (flags
& as_u32(raw
::GIT_DIFF_FLAG_BINARY
)) != 0 {
487 result
|= DiffFlags
::BINARY
;
489 if (flags
& as_u32(raw
::GIT_DIFF_FLAG_NOT_BINARY
)) != 0 {
490 result
|= DiffFlags
::NOT_BINARY
;
492 if (flags
& as_u32(raw
::GIT_DIFF_FLAG_VALID_ID
)) != 0 {
493 result
|= DiffFlags
::VALID_ID
;
495 if (flags
& as_u32(raw
::GIT_DIFF_FLAG_EXISTS
)) != 0 {
496 result
|= DiffFlags
::EXISTS
;
501 // TODO: expose when diffs are more exposed
502 // pub fn similarity(&self) -> u16 {
503 // unsafe { (*self.raw).similarity }
506 /// Returns the number of files in this delta.
507 pub fn nfiles(&self) -> u16 {
508 unsafe { (*self.raw).nfiles }
511 /// Returns the status of this entry
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
),
531 /// Return the file which represents the "from" side of the diff.
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 _) }
539 /// Return the file which represents the "to" side of the diff.
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 _) }
548 impl<'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
> {
553 _marker
: marker
::PhantomData
,
556 fn raw(&self) -> *mut raw
::git_diff_delta
{
561 impl<'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())
572 impl<'a
> DiffFile
<'a
> {
573 /// Returns the Oid of this item.
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 _) }
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]> {
585 unsafe { crate::opt_bytes(&FOO, (*self.raw).path) }
588 /// Returns the path of the entry relative to the working directory of the
590 pub fn path(&self) -> Option
<&'a Path
> {
591 self.path_bytes().map(util
::bytes2path
)
594 /// Returns the size of this entry, in bytes
595 pub fn size(&self) -> u64 {
596 unsafe { (*self.raw).size as u64 }
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 }
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 }
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 }
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 }
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
),
634 impl<'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
> {
639 _marker
: marker
::PhantomData
,
642 fn raw(&self) -> *const raw
::git_diff_file
{
647 impl<'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
);
654 if let Some(path
) = &self.path() {
655 ds
.field("path", path
);
657 ds
.field("size", &self.size()).finish()
661 impl Default
for DiffOptions
{
662 fn default() -> Self {
668 /// Creates a new set of empty diff options.
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() }
,
680 assert_eq
!(unsafe { raw::git_diff_init_options(&mut opts.raw, 1) }
, 0);
684 fn flag(&mut self, opt
: raw
::git_diff_option_t
, val
: bool
) -> &mut DiffOptions
{
685 let opt
= opt
as u32;
687 self.raw
.flags
|= opt
;
689 self.raw
.flags
&= !opt
;
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
)
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
)
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
)
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
)
714 /// Flag indicating whether untracked directories are traversed deeply or
716 pub fn recurse_untracked_dirs(&mut self, recurse
: bool
) -> &mut DiffOptions
{
717 self.flag(raw
::GIT_DIFF_RECURSE_UNTRACKED_DIRS
, recurse
)
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
)
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
)
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.
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
)
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
)
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
)
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
)
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
)
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
)
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.
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
)
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
)
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
)
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
)
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
)
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
)
807 /// Ignore all whitespace
808 pub fn ignore_whitespace(&mut self, ignore
: bool
) -> &mut DiffOptions
{
809 self.flag(raw
::GIT_DIFF_IGNORE_WHITESPACE
, ignore
)
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
)
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
)
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
)
827 /// When generating patch text, include the content of untracked files.
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
)
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
)
844 /// Use the "patience diff" algorithm
845 pub fn patience(&mut self, patience
: bool
) -> &mut DiffOptions
{
846 self.flag(raw
::GIT_DIFF_PATIENCE
, patience
)
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
)
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
)
860 /// Use a heuristic that takes indentation and whitespace into account
861 /// which generally can produce better diffs when dealing with ambiguous
863 pub fn indent_heuristic(&mut self, heuristic
: bool
) -> &mut DiffOptions
{
864 self.flag(raw
::GIT_DIFF_INDENT_HEURISTIC
, heuristic
)
867 /// Set the number of unchanged lines that define the boundary of a hunk
868 /// (and to display before and after).
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
;
876 /// Set the maximum number of unchanged lines between hunk boundaries before
877 /// the hunks will be merged into one.
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
;
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
;
891 /// Maximum size (in bytes) above which a blob will be marked as binary
894 /// A negative value will disable this entirely.
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
;
902 /// The virtual "directory" to prefix old file names with in hunk headers.
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());
910 /// The virtual "directory" to prefix new file names with in hunk headers.
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());
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
);
926 /// Acquire a pointer to the underlying raw options.
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
935 .unwrap_or(ptr
::null());
936 self.raw
.new_prefix
= self
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 _
946 // TODO: expose ignore_submodules, notify_cb/notify_payload
949 impl<'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
))
954 fn size_hint(&self) -> (usize, Option
<usize>) {
955 self.range
.size_hint()
958 impl<'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
))
963 impl<'diff
> FusedIterator
for Deltas
<'diff
> {}
965 impl<'diff
> ExactSizeIterator
for Deltas
<'diff
> {}
967 /// Line origin constants.
968 #[derive(Copy, Clone, Debug, PartialEq)]
969 pub enum DiffLineType
{
970 /// These values will be sent to `git_diff_line_cb` along with the line
976 /// Both files have no LF at end
978 /// Old has no LF at end, new does
980 /// Old has LF at end, new does not
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`.
987 /// For "Binary files x and y differ"
991 impl Binding
for DiffLineType
{
992 type Raw
= raw
::git_diff_line_t
;
993 unsafe fn from_raw(raw
: raw
::git_diff_line_t
) -> Self {
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"),
1007 fn raw(&self) -> raw
::git_diff_line_t
{
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
,
1022 impl<'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 }
{
1027 n
=> Some(n
as u32),
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 }
{
1035 n
=> Some(n
as u32),
1039 /// Number of newline characters in content
1040 pub fn num_lines(&self) -> u32 {
1041 unsafe { (*self.raw).num_lines as u32 }
1044 /// Offset in the original file to the content
1045 pub fn content_offset(&self) -> i64 {
1046 unsafe { (*self.raw).content_offset as i64 }
1049 /// Content of this line as bytes.
1050 pub fn content(&self) -> &'a
[u8] {
1052 slice
::from_raw_parts(
1053 (*self.raw
).content
as *const u8,
1054 (*self.raw
).content_len
as usize,
1059 /// origin of this `DiffLine`.
1061 pub fn origin_value(&self) -> DiffLineType
{
1062 unsafe { Binding::from_raw((*self.raw).origin as raw::git_diff_line_t) }
1065 /// Sigil showing the origin of this `DiffLine`.
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'
,
1092 impl<'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
> {
1097 _marker
: marker
::PhantomData
,
1100 fn raw(&self) -> *const raw
::git_diff_line
{
1105 impl<'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
);
1111 if let Some(new_lineno
) = &self.new_lineno() {
1112 ds
.field("new_lineno", new_lineno
);
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())
1122 impl<'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 }
1128 /// Number of lines in old_file
1129 pub fn old_lines(&self) -> u32 {
1130 unsafe { (*self.raw).old_lines as u32 }
1133 /// Starting line number in new_file
1134 pub fn new_start(&self) -> u32 {
1135 unsafe { (*self.raw).new_start as u32 }
1138 /// Number of lines in new_file
1139 pub fn new_lines(&self) -> u32 {
1140 unsafe { (*self.raw).new_lines as u32 }
1144 pub fn header(&self) -> &'a
[u8] {
1146 slice
::from_raw_parts(
1147 (*self.raw
).header
.as_ptr() as *const u8,
1148 (*self.raw
).header_len
as usize,
1154 impl<'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
> {
1159 _marker
: marker
::PhantomData
,
1162 fn raw(&self) -> *const raw
::git_diff_hunk
{
1167 impl<'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())
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 }
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 }
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 }
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();
1199 try_call
!(raw
::git_diff_stats_to_buf(
1210 impl Binding
for DiffStats
{
1211 type Raw
= *mut raw
::git_diff_stats
;
1213 unsafe fn from_raw(raw
: *mut raw
::git_diff_stats
) -> DiffStats
{
1216 fn raw(&self) -> *mut raw
::git_diff_stats
{
1221 impl Drop
for DiffStats
{
1222 fn drop(&mut self) {
1223 unsafe { raw::git_diff_stats_free(self.raw) }
1227 impl 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())
1237 impl<'a
> DiffBinary
<'a
> {
1238 /// Returns whether there is data in this binary structure or not.
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 }
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 _) }
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 _) }
1259 impl<'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
> {
1264 _marker
: marker
::PhantomData
,
1267 fn raw(&self) -> *const raw
::git_diff_binary
{
1272 impl<'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) }
1278 /// The binary data, deflated
1279 pub fn data(&self) -> &[u8] {
1281 slice
::from_raw_parts((*self.raw
).data
as *const u8, (*self.raw
).datalen
as usize)
1285 /// The length of the binary data after inflation
1286 pub fn inflated_len(&self) -> usize {
1287 unsafe { (*self.raw).inflatedlen as usize }
1291 impl<'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
> {
1296 _marker
: marker
::PhantomData
,
1299 fn raw(&self) -> *const raw
::git_diff_binary_file
{
1304 impl Binding
for DiffBinaryKind
{
1305 type Raw
= raw
::git_diff_binary_t
;
1306 unsafe fn from_raw(raw
: raw
::git_diff_binary_t
) -> DiffBinaryKind
{
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"),
1314 fn raw(&self) -> raw
::git_diff_binary_t
{
1316 DiffBinaryKind
::None
=> raw
::GIT_DIFF_BINARY_NONE
,
1317 DiffBinaryKind
::Literal
=> raw
::GIT_DIFF_BINARY_LITERAL
,
1318 DiffBinaryKind
::Delta
=> raw
::GIT_DIFF_BINARY_DELTA
,
1323 impl Default
for DiffFindOptions
{
1324 fn default() -> Self {
1329 impl DiffFindOptions
{
1330 /// Creates a new set of empty diff find options.
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() }
,
1339 unsafe { raw::git_diff_find_init_options(&mut opts.raw, 1) }
,
1345 fn flag(&mut self, opt
: u32, val
: bool
) -> &mut DiffFindOptions
{
1347 self.raw
.flags
|= opt
;
1349 self.raw
.flags
&= !opt
;
1354 /// Reset all flags back to their unset state, indicating that
1355 /// `diff.renames` should be used instead. This is overridden once any flag
1357 pub fn by_config(&mut self) -> &mut DiffFindOptions
{
1358 self.flag(0xffffffff, false)
1361 /// Look for renames?
1362 pub fn renames(&mut self, find
: bool
) -> &mut DiffFindOptions
{
1363 self.flag(raw
::GIT_DIFF_FIND_RENAMES
, find
)
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
)
1371 /// Look for copies?
1372 pub fn copies(&mut self, find
: bool
) -> &mut DiffFindOptions
{
1373 self.flag(raw
::GIT_DIFF_FIND_COPIES
, find
)
1376 /// Consider unmodified as copy sources?
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
)
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
)
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
)
1395 pub fn break_rewries(&mut self, find
: bool
) -> &mut DiffFindOptions
{
1396 self.break_rewrites(find
)
1399 /// Find renames/copies for untracked items in working directory.
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
)
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
)
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
)
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
)
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
)
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
)
1432 /// Do not break rewrites unless they contribute to a rename.
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
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
)
1446 /// Remove any unmodified deltas after find_similar is done.
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
)
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
;
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
;
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
;
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
;
1480 /// Maximum similarity sources to examine for a file (somewhat like
1481 /// git-diff's `-l` option or `diff.renameLimit` config)
1484 pub fn rename_limit(&mut self, limit
: usize) -> &mut DiffFindOptions
{
1485 self.raw
.rename_limit
= limit
as size_t
;
1489 // TODO: expose git_diff_similarity_metric
1491 /// Acquire a pointer to the underlying raw options.
1492 pub unsafe fn raw(&mut self) -> *const raw
::git_diff_find_options
{
1497 impl Default
for DiffFormatEmailOptions
{
1498 fn default() -> Self {
1503 impl 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() }
,
1511 unsafe { raw::git_diff_format_email_options_init(&mut opts.raw, 1) }
,
1517 fn flag(&mut self, opt
: u32, val
: bool
) -> &mut Self {
1519 self.raw
.flags
|= opt
;
1521 self.raw
.flags
&= !opt
;
1526 /// Exclude `[PATCH]` from the subject header
1527 pub fn exclude_subject_patch_header(&mut self, should_exclude
: bool
) -> &mut Self {
1529 raw
::GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER
,
1535 impl 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() }
,
1544 raw
::git_diff_patchid_options_init(
1546 raw
::GIT_DIFF_PATCHID_OPTIONS_VERSION
,
1557 use crate::{DiffLineType, DiffOptions, Oid, Signature, Time}
;
1558 use std
::borrow
::Borrow
;
1561 use std
::path
::Path
;
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());
1577 fn foreach_smoke() {
1578 let (_td
, repo
) = crate::test
::repo_init();
1579 let diff
= t
!(repo
.diff_tree_to_workdir(None
, None
));
1582 &mut |_file
, _progress
| {
1590 assert_eq
!(count
, 0);
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
)));
1602 let mut result
= None
;
1604 &mut |file
, _progress
| {
1606 result
= file
.new_file().path().map(ToOwned
::to_owned
);
1613 assert_eq
!(result
.as_ref().map(Borrow
::borrow
), Some(path
));
1614 assert_eq
!(count
, 1);
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;
1629 &mut |_file
, _progress
| { true }
,
1631 Some(&mut |_file
, hunk
| {
1632 new_lines
= hunk
.new_lines();
1637 assert_eq
!(new_lines
, 1);
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
;
1661 &mut |_file
, _progress
| { true }
,
1662 Some(&mut |_file
, binary
| {
1663 bin_content
= Some(binary
.new_file().data().to_owned());
1666 Some(&mut |_file
, hunk
| {
1667 new_lines
= hunk
.new_lines();
1670 Some(&mut |_file
, _hunk
, line
| {
1671 line_content
= String
::from_utf8(line
.content().into()).ok();
1675 assert_eq
!(bin_content
, Some(deflated_fib
));
1676 assert_eq
!(new_lines
, 1);
1677 assert_eq
!(line_content
, Some("bar\n".to_string()));
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",
1691 " file1.txt | 8 +++++---\n",
1692 " 1 file changed, 5 insertions(+), 3 deletions(-)\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",
1721 const ORIGINAL_FILE
: &str = concat
!(
1738 const UPDATED_FILE
: &str = concat
!(
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();
1762 .insert("file1.txt", original_file
, FILE_MODE
)
1764 let original_tree
= original_tree
.write().unwrap();
1765 let mut updated_tree
= repo
.treebuilder(None
).unwrap();
1767 .insert("file1.txt", updated_file
, FILE_MODE
)
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
1778 &repo
.find_tree(updated_tree
).unwrap(),
1779 &[], // NOTE: Have no parents to ensure stable hash
1782 let updated_commit
= repo
.find_commit(updated_commit
).unwrap();
1785 Some(&repo
.find_tree(original_tree
).unwrap()),
1786 Some(&repo
.find_tree(updated_tree
).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();
1794 actual_email
.starts_with(EXPECTED_EMAIL_START
),
1795 "Unexpected email:\n{}",
1798 let mut remaining_lines
= actual_email
[EXPECTED_EMAIL_START
.len()..].lines();
1799 let version_line
= remaining_lines
.next();
1801 version_line
.unwrap().starts_with("libgit2"),
1802 "Invalid version line: {:?}",
1805 while let Some(line
) = remaining_lines
.next() {
1806 assert_eq
!(line
.trim(), "")
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();
1822 &mut |_file
, _progress
| { true }
,
1825 Some(&mut |_file
, _hunk
, line
| {
1826 origin_values
.push(line
.origin_value());
1830 assert_eq
!(origin_values
.len(), 1);
1831 assert_eq
!(origin_values
[0], DiffLineType
::Addition
);
1835 fn foreach_exits_with_euser() {
1836 let foo_path
= Path
::new("foo");
1837 let bar_path
= Path
::new("foo");
1839 let (td
, repo
) = crate::test
::repo_init();
1840 t
!(t
!(File
::create(&td
.path().join(foo_path
))).write_all(b
"bar\n"));
1842 let mut index
= t
!(repo
.index());
1843 t
!(index
.add_path(foo_path
));
1844 t
!(index
.add_path(bar_path
));
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
)));
1851 let result
= diff
.foreach(
1852 &mut |_file
, _progress
| {
1861 assert_eq
!(result
.unwrap_err().code(), crate::ErrorCode
::User
);