1 use libc::{c_char, c_int, c_void, size_t};
2 use std::ffi::CString;
3 use std::marker;
4 use std::mem;
5 use std::ops::Range;
6 use std::path::Path;
7 use std::ptr;
8 use std::slice;
9
10 use crate::util::{self, Binding};
11 use crate::{panic, raw, Buf, Delta, DiffFormat, Error, Oid, Repository};
12 use crate::{DiffStatsFormat, IntoCString};
13
14 /// The diff object that contains all individual file deltas.
15 ///
16 /// This is an opaque structure which will be allocated by one of the diff
17 /// generator functions on the `Repository` structure (e.g. `diff_tree_to_tree`
18 /// or other `diff_*` functions).
19 pub struct Diff<'repo> {
20 raw: *mut raw::git_diff,
21 _marker: marker::PhantomData<&'repo Repository>,
22 }
23
24 unsafe impl<'repo> Send for Diff<'repo> {}
25
26 /// Description of changes to one entry.
27 pub struct DiffDelta<'a> {
28 raw: *mut raw::git_diff_delta,
29 _marker: marker::PhantomData<&'a raw::git_diff_delta>,
30 }
31
32 /// Description of one side of a delta.
33 ///
34 /// Although this is called a "file" it could represent a file, a symbolic
35 /// link, a submodule commit id, or even a tree (although that only happens if
36 /// you are tracking type changes or ignored/untracked directories).
37 pub struct DiffFile<'a> {
38 raw: *const raw::git_diff_file,
39 _marker: marker::PhantomData<&'a raw::git_diff_file>,
40 }
41
42 /// Structure describing options about how the diff should be executed.
43 pub struct DiffOptions {
44 pathspec: Vec<CString>,
45 pathspec_ptrs: Vec<*const c_char>,
46 old_prefix: Option<CString>,
47 new_prefix: Option<CString>,
48 raw: raw::git_diff_options,
49 }
50
51 /// Control behavior of rename and copy detection
52 pub struct DiffFindOptions {
53 raw: raw::git_diff_find_options,
54 }
55
56 /// An iterator over the diffs in a delta
57 pub struct Deltas<'diff> {
58 range: Range<usize>,
59 diff: &'diff Diff<'diff>,
60 }
61
62 /// Structure describing a line (or data span) of a diff.
63 pub struct DiffLine<'a> {
64 raw: *const raw::git_diff_line,
65 _marker: marker::PhantomData<&'a raw::git_diff_line>,
66 }
67
68 /// Structure describing a hunk of a diff.
69 pub struct DiffHunk<'a> {
70 raw: *const raw::git_diff_hunk,
71 _marker: marker::PhantomData<&'a raw::git_diff_hunk>,
72 }
73
74 /// Structure describing a hunk of a diff.
75 pub struct DiffStats {
76 raw: *mut raw::git_diff_stats,
77 }
78
79 /// Structure describing the binary contents of a diff.
80 pub struct DiffBinary<'a> {
81 raw: *const raw::git_diff_binary,
82 _marker: marker::PhantomData<&'a raw::git_diff_binary>,
83 }
84
85 /// The contents of one of the files in a binary diff.
86 pub struct DiffBinaryFile<'a> {
87 raw: *const raw::git_diff_binary_file,
88 _marker: marker::PhantomData<&'a raw::git_diff_binary_file>,
89 }
90
91 /// When producing a binary diff, the binary data returned will be
92 /// either the deflated full ("literal") contents of the file, or
93 /// the deflated binary delta between the two sides (whichever is
94 /// smaller).
95 #[derive(Copy, Clone, Debug)]
96 pub enum DiffBinaryKind {
97 /// There is no binary delta
98 None,
99 /// The binary data is the literal contents of the file
100 Literal,
101 /// The binary data is the delta from one side to the other
102 Delta,
103 }
104
105 type PrintCb<'a> = dyn FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool + 'a;
106
107 pub type FileCb<'a> = dyn FnMut(DiffDelta<'_>, f32) -> bool + 'a;
108 pub type BinaryCb<'a> = dyn FnMut(DiffDelta<'_>, DiffBinary<'_>) -> bool + 'a;
109 pub type HunkCb<'a> = dyn FnMut(DiffDelta<'_>, DiffHunk<'_>) -> bool + 'a;
110 pub type LineCb<'a> = dyn FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool + 'a;
111
112 struct ForeachCallbacks<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h> {
113 file: &'a mut FileCb<'b>,
114 binary: Option<&'c mut BinaryCb<'d>>,
115 hunk: Option<&'e mut HunkCb<'f>>,
116 line: Option<&'g mut LineCb<'h>>,
117 }
118
119 impl<'repo> Diff<'repo> {
120 /// Merge one diff into another.
121 ///
122 /// This merges items from the "from" list into the "self" list. The
123 /// resulting diff will have all items that appear in either list.
124 /// If an item appears in both lists, then it will be "merged" to appear
125 /// as if the old version was from the "onto" list and the new version
126 /// is from the "from" list (with the exception that if the item has a
127 /// pending DELETE in the middle, then it will show as deleted).
merge(&mut self, from: &Diff<'repo>) -> Result<(), Error>128 pub fn merge(&mut self, from: &Diff<'repo>) -> Result<(), Error> {
129 unsafe {
130 try_call!(raw::git_diff_merge(self.raw, &*from.raw));
131 }
132 Ok(())
133 }
134
135 /// Returns an iterator over the deltas in this diff.
deltas(&self) -> Deltas<'_>136 pub fn deltas(&self) -> Deltas<'_> {
137 let num_deltas = unsafe { raw::git_diff_num_deltas(&*self.raw) };
138 Deltas {
139 range: 0..(num_deltas as usize),
140 diff: self,
141 }
142 }
143
144 /// Return the diff delta for an entry in the diff list.
get_delta(&self, i: usize) -> Option<DiffDelta<'_>>145 pub fn get_delta(&self, i: usize) -> Option<DiffDelta<'_>> {
146 unsafe {
147 let ptr = raw::git_diff_get_delta(&*self.raw, i as size_t);
148 Binding::from_raw_opt(ptr as *mut _)
149 }
150 }
151
152 /// Check if deltas are sorted case sensitively or insensitively.
is_sorted_icase(&self) -> bool153 pub fn is_sorted_icase(&self) -> bool {
154 unsafe { raw::git_diff_is_sorted_icase(&*self.raw) == 1 }
155 }
156
157 /// Iterate over a diff generating formatted text output.
158 ///
159 /// Returning `false` from the callback will terminate the iteration and
160 /// return an error from this function.
print<F>(&self, format: DiffFormat, mut cb: F) -> Result<(), Error> where F: FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool,161 pub fn print<F>(&self, format: DiffFormat, mut cb: F) -> Result<(), Error>
162 where
163 F: FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool,
164 {
165 let mut cb: &mut PrintCb<'_> = &mut cb;
166 let ptr = &mut cb as *mut _;
167 unsafe {
168 try_call!(raw::git_diff_print(
169 self.raw,
170 format,
171 print_cb,
172 ptr as *mut _
173 ));
174 Ok(())
175 }
176 }
177
178 /// Loop over all deltas in a diff issuing callbacks.
179 ///
180 /// Returning `false` from any callback will terminate the iteration and
181 /// return an error from this function.
foreach( &self, file_cb: &mut FileCb<'_>, binary_cb: Option<&mut BinaryCb<'_>>, hunk_cb: Option<&mut HunkCb<'_>>, line_cb: Option<&mut LineCb<'_>>, ) -> Result<(), Error>182 pub fn foreach(
183 &self,
184 file_cb: &mut FileCb<'_>,
185 binary_cb: Option<&mut BinaryCb<'_>>,
186 hunk_cb: Option<&mut HunkCb<'_>>,
187 line_cb: Option<&mut LineCb<'_>>,
188 ) -> Result<(), Error> {
189 let mut cbs = ForeachCallbacks {
190 file: file_cb,
191 binary: binary_cb,
192 hunk: hunk_cb,
193 line: line_cb,
194 };
195 let ptr = &mut cbs as *mut _;
196 unsafe {
197 let binary_cb_c = if cbs.binary.is_some() {
198 Some(binary_cb_c as raw::git_diff_binary_cb)
199 } else {
200 None
201 };
202 let hunk_cb_c = if cbs.hunk.is_some() {
203 Some(hunk_cb_c as raw::git_diff_hunk_cb)
204 } else {
205 None
206 };
207 let line_cb_c = if cbs.line.is_some() {
208 Some(line_cb_c as raw::git_diff_line_cb)
209 } else {
210 None
211 };
212 try_call!(raw::git_diff_foreach(
213 self.raw,
214 file_cb_c,
215 binary_cb_c,
216 hunk_cb_c,
217 line_cb_c,
218 ptr as *mut _
219 ));
220 Ok(())
221 }
222 }
223
224 /// Accumulate diff statistics for all patches.
stats(&self) -> Result<DiffStats, Error>225 pub fn stats(&self) -> Result<DiffStats, Error> {
226 let mut ret = ptr::null_mut();
227 unsafe {
228 try_call!(raw::git_diff_get_stats(&mut ret, self.raw));
229 Ok(Binding::from_raw(ret))
230 }
231 }
232
233 /// Transform a diff marking file renames, copies, etc.
234 ///
235 /// This modifies a diff in place, replacing old entries that look like
236 /// renames or copies with new entries reflecting those changes. This also
237 /// will, if requested, break modified files into add/remove pairs if the
238 /// amount of change is above a threshold.
find_similar(&mut self, opts: Option<&mut DiffFindOptions>) -> Result<(), Error>239 pub fn find_similar(&mut self, opts: Option<&mut DiffFindOptions>) -> Result<(), Error> {
240 let opts = opts.map(|opts| &opts.raw);
241 unsafe {
242 try_call!(raw::git_diff_find_similar(self.raw, opts));
243 }
244 Ok(())
245 }
246
247 // TODO: num_deltas_of_type, format_email, find_similar
248 }
249
print_cb( delta: *const raw::git_diff_delta, hunk: *const raw::git_diff_hunk, line: *const raw::git_diff_line, data: *mut c_void, ) -> c_int250 pub extern "C" fn print_cb(
251 delta: *const raw::git_diff_delta,
252 hunk: *const raw::git_diff_hunk,
253 line: *const raw::git_diff_line,
254 data: *mut c_void,
255 ) -> c_int {
256 unsafe {
257 let delta = Binding::from_raw(delta as *mut _);
258 let hunk = Binding::from_raw_opt(hunk);
259 let line = Binding::from_raw(line);
260
261 let r = panic::wrap(|| {
262 let data = data as *mut &mut PrintCb<'_>;
263 (*data)(delta, hunk, line)
264 });
265 if r == Some(true) {
266 0
267 } else {
268 -1
269 }
270 }
271 }
272
file_cb_c( delta: *const raw::git_diff_delta, progress: f32, data: *mut c_void, ) -> c_int273 extern "C" fn file_cb_c(
274 delta: *const raw::git_diff_delta,
275 progress: f32,
276 data: *mut c_void,
277 ) -> c_int {
278 unsafe {
279 let delta = Binding::from_raw(delta as *mut _);
280
281 let r = panic::wrap(|| {
282 let cbs = data as *mut ForeachCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
283 ((*cbs).file)(delta, progress)
284 });
285 if r == Some(true) {
286 0
287 } else {
288 -1
289 }
290 }
291 }
292
binary_cb_c( delta: *const raw::git_diff_delta, binary: *const raw::git_diff_binary, data: *mut c_void, ) -> c_int293 extern "C" fn binary_cb_c(
294 delta: *const raw::git_diff_delta,
295 binary: *const raw::git_diff_binary,
296 data: *mut c_void,
297 ) -> c_int {
298 unsafe {
299 let delta = Binding::from_raw(delta as *mut _);
300 let binary = Binding::from_raw(binary);
301
302 let r = panic::wrap(|| {
303 let cbs = data as *mut ForeachCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
304 match (*cbs).binary {
305 Some(ref mut cb) => cb(delta, binary),
306 None => false,
307 }
308 });
309 if r == Some(true) {
310 0
311 } else {
312 -1
313 }
314 }
315 }
316
hunk_cb_c( delta: *const raw::git_diff_delta, hunk: *const raw::git_diff_hunk, data: *mut c_void, ) -> c_int317 extern "C" fn hunk_cb_c(
318 delta: *const raw::git_diff_delta,
319 hunk: *const raw::git_diff_hunk,
320 data: *mut c_void,
321 ) -> c_int {
322 unsafe {
323 let delta = Binding::from_raw(delta as *mut _);
324 let hunk = Binding::from_raw(hunk);
325
326 let r = panic::wrap(|| {
327 let cbs = data as *mut ForeachCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
328 match (*cbs).hunk {
329 Some(ref mut cb) => cb(delta, hunk),
330 None => false,
331 }
332 });
333 if r == Some(true) {
334 0
335 } else {
336 -1
337 }
338 }
339 }
340
line_cb_c( delta: *const raw::git_diff_delta, hunk: *const raw::git_diff_hunk, line: *const raw::git_diff_line, data: *mut c_void, ) -> c_int341 extern "C" fn line_cb_c(
342 delta: *const raw::git_diff_delta,
343 hunk: *const raw::git_diff_hunk,
344 line: *const raw::git_diff_line,
345 data: *mut c_void,
346 ) -> c_int {
347 unsafe {
348 let delta = Binding::from_raw(delta as *mut _);
349 let hunk = Binding::from_raw_opt(hunk);
350 let line = Binding::from_raw(line);
351
352 let r = panic::wrap(|| {
353 let cbs = data as *mut ForeachCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
354 match (*cbs).line {
355 Some(ref mut cb) => cb(delta, hunk, line),
356 None => false,
357 }
358 });
359 if r == Some(true) {
360 0
361 } else {
362 -1
363 }
364 }
365 }
366
367 impl<'repo> Binding for Diff<'repo> {
368 type Raw = *mut raw::git_diff;
from_raw(raw: *mut raw::git_diff) -> Diff<'repo>369 unsafe fn from_raw(raw: *mut raw::git_diff) -> Diff<'repo> {
370 Diff {
371 raw: raw,
372 _marker: marker::PhantomData,
373 }
374 }
raw(&self) -> *mut raw::git_diff375 fn raw(&self) -> *mut raw::git_diff {
376 self.raw
377 }
378 }
379
380 impl<'repo> Drop for Diff<'repo> {
drop(&mut self)381 fn drop(&mut self) {
382 unsafe { raw::git_diff_free(self.raw) }
383 }
384 }
385
386 impl<'a> DiffDelta<'a> {
387 // TODO: expose when diffs are more exposed
388 // pub fn similarity(&self) -> u16 {
389 // unsafe { (*self.raw).similarity }
390 // }
391
392 /// Returns the number of files in this delta.
nfiles(&self) -> u16393 pub fn nfiles(&self) -> u16 {
394 unsafe { (*self.raw).nfiles }
395 }
396
397 /// Returns the status of this entry
398 ///
399 /// For more information, see `Delta`'s documentation
status(&self) -> Delta400 pub fn status(&self) -> Delta {
401 match unsafe { (*self.raw).status } {
402 raw::GIT_DELTA_UNMODIFIED => Delta::Unmodified,
403 raw::GIT_DELTA_ADDED => Delta::Added,
404 raw::GIT_DELTA_DELETED => Delta::Deleted,
405 raw::GIT_DELTA_MODIFIED => Delta::Modified,
406 raw::GIT_DELTA_RENAMED => Delta::Renamed,
407 raw::GIT_DELTA_COPIED => Delta::Copied,
408 raw::GIT_DELTA_IGNORED => Delta::Ignored,
409 raw::GIT_DELTA_UNTRACKED => Delta::Untracked,
410 raw::GIT_DELTA_TYPECHANGE => Delta::Typechange,
411 raw::GIT_DELTA_UNREADABLE => Delta::Unreadable,
412 raw::GIT_DELTA_CONFLICTED => Delta::Conflicted,
413 n => panic!("unknown diff status: {}", n),
414 }
415 }
416
417 /// Return the file which represents the "from" side of the diff.
418 ///
419 /// What side this means depends on the function that was used to generate
420 /// the diff and will be documented on the function itself.
old_file(&self) -> DiffFile<'a>421 pub fn old_file(&self) -> DiffFile<'a> {
422 unsafe { Binding::from_raw(&(*self.raw).old_file as *const _) }
423 }
424
425 /// Return the file which represents the "to" side of the diff.
426 ///
427 /// What side this means depends on the function that was used to generate
428 /// the diff and will be documented on the function itself.
new_file(&self) -> DiffFile<'a>429 pub fn new_file(&self) -> DiffFile<'a> {
430 unsafe { Binding::from_raw(&(*self.raw).new_file as *const _) }
431 }
432 }
433
434 impl<'a> Binding for DiffDelta<'a> {
435 type Raw = *mut raw::git_diff_delta;
from_raw(raw: *mut raw::git_diff_delta) -> DiffDelta<'a>436 unsafe fn from_raw(raw: *mut raw::git_diff_delta) -> DiffDelta<'a> {
437 DiffDelta {
438 raw: raw,
439 _marker: marker::PhantomData,
440 }
441 }
raw(&self) -> *mut raw::git_diff_delta442 fn raw(&self) -> *mut raw::git_diff_delta {
443 self.raw
444 }
445 }
446
447 impl<'a> DiffFile<'a> {
448 /// Returns the Oid of this item.
449 ///
450 /// If this entry represents an absent side of a diff (e.g. the `old_file`
451 /// of a `Added` delta), then the oid returned will be zeroes.
id(&self) -> Oid452 pub fn id(&self) -> Oid {
453 unsafe { Binding::from_raw(&(*self.raw).id as *const _) }
454 }
455
456 /// Returns the path, in bytes, of the entry relative to the working
457 /// directory of the repository.
path_bytes(&self) -> Option<&'a [u8]>458 pub fn path_bytes(&self) -> Option<&'a [u8]> {
459 static FOO: () = ();
460 unsafe { crate::opt_bytes(&FOO, (*self.raw).path) }
461 }
462
463 /// Returns the path of the entry relative to the working directory of the
464 /// repository.
path(&self) -> Option<&'a Path>465 pub fn path(&self) -> Option<&'a Path> {
466 self.path_bytes().map(util::bytes2path)
467 }
468
469 /// Returns the size of this entry, in bytes
size(&self) -> u64470 pub fn size(&self) -> u64 {
471 unsafe { (*self.raw).size as u64 }
472 }
473
474 // TODO: expose flags/mode
475 }
476
477 impl<'a> Binding for DiffFile<'a> {
478 type Raw = *const raw::git_diff_file;
from_raw(raw: *const raw::git_diff_file) -> DiffFile<'a>479 unsafe fn from_raw(raw: *const raw::git_diff_file) -> DiffFile<'a> {
480 DiffFile {
481 raw: raw,
482 _marker: marker::PhantomData,
483 }
484 }
raw(&self) -> *const raw::git_diff_file485 fn raw(&self) -> *const raw::git_diff_file {
486 self.raw
487 }
488 }
489
490 impl Default for DiffOptions {
default() -> Self491 fn default() -> Self {
492 Self::new()
493 }
494 }
495
496 impl DiffOptions {
497 /// Creates a new set of empty diff options.
498 ///
499 /// All flags and other options are defaulted to false or their otherwise
500 /// zero equivalents.
new() -> DiffOptions501 pub fn new() -> DiffOptions {
502 let mut opts = DiffOptions {
503 pathspec: Vec::new(),
504 pathspec_ptrs: Vec::new(),
505 raw: unsafe { mem::zeroed() },
506 old_prefix: None,
507 new_prefix: None,
508 };
509 assert_eq!(unsafe { raw::git_diff_init_options(&mut opts.raw, 1) }, 0);
510 opts
511 }
512
flag(&mut self, opt: i32, val: bool) -> &mut DiffOptions513 fn flag(&mut self, opt: i32, val: bool) -> &mut DiffOptions {
514 let opt = opt as u32;
515 if val {
516 self.raw.flags |= opt;
517 } else {
518 self.raw.flags &= !opt;
519 }
520 self
521 }
522
523 /// Flag indicating whether the sides of the diff will be reversed.
reverse(&mut self, reverse: bool) -> &mut DiffOptions524 pub fn reverse(&mut self, reverse: bool) -> &mut DiffOptions {
525 self.flag(raw::GIT_DIFF_REVERSE, reverse)
526 }
527
528 /// Flag indicating whether ignored files are included.
include_ignored(&mut self, include: bool) -> &mut DiffOptions529 pub fn include_ignored(&mut self, include: bool) -> &mut DiffOptions {
530 self.flag(raw::GIT_DIFF_INCLUDE_IGNORED, include)
531 }
532
533 /// Flag indicating whether ignored directories are traversed deeply or not.
recurse_ignored_dirs(&mut self, recurse: bool) -> &mut DiffOptions534 pub fn recurse_ignored_dirs(&mut self, recurse: bool) -> &mut DiffOptions {
535 self.flag(raw::GIT_DIFF_RECURSE_IGNORED_DIRS, recurse)
536 }
537
538 /// Flag indicating whether untracked files are in the diff
include_untracked(&mut self, include: bool) -> &mut DiffOptions539 pub fn include_untracked(&mut self, include: bool) -> &mut DiffOptions {
540 self.flag(raw::GIT_DIFF_INCLUDE_UNTRACKED, include)
541 }
542
543 /// Flag indicating whether untracked directories are deeply traversed or
544 /// not.
recurse_untracked_dirs(&mut self, recurse: bool) -> &mut DiffOptions545 pub fn recurse_untracked_dirs(&mut self, recurse: bool) -> &mut DiffOptions {
546 self.flag(raw::GIT_DIFF_RECURSE_UNTRACKED_DIRS, recurse)
547 }
548
549 /// Flag indicating whether unmodified files are in the diff.
include_unmodified(&mut self, include: bool) -> &mut DiffOptions550 pub fn include_unmodified(&mut self, include: bool) -> &mut DiffOptions {
551 self.flag(raw::GIT_DIFF_INCLUDE_UNMODIFIED, include)
552 }
553
554 /// If entrabled, then Typechange delta records are generated.
include_typechange(&mut self, include: bool) -> &mut DiffOptions555 pub fn include_typechange(&mut self, include: bool) -> &mut DiffOptions {
556 self.flag(raw::GIT_DIFF_INCLUDE_TYPECHANGE, include)
557 }
558
559 /// Event with `include_typechange`, the tree treturned generally shows a
560 /// deleted blow. This flag correctly labels the tree transitions as a
561 /// typechange record with the `new_file`'s mode set to tree.
562 ///
563 /// Note that the tree SHA will not be available.
include_typechange_trees(&mut self, include: bool) -> &mut DiffOptions564 pub fn include_typechange_trees(&mut self, include: bool) -> &mut DiffOptions {
565 self.flag(raw::GIT_DIFF_INCLUDE_TYPECHANGE_TREES, include)
566 }
567
568 /// Flag indicating whether file mode changes are ignored.
ignore_filemode(&mut self, ignore: bool) -> &mut DiffOptions569 pub fn ignore_filemode(&mut self, ignore: bool) -> &mut DiffOptions {
570 self.flag(raw::GIT_DIFF_IGNORE_FILEMODE, ignore)
571 }
572
573 /// Flag indicating whether all submodules should be treated as unmodified.
ignore_submodules(&mut self, ignore: bool) -> &mut DiffOptions574 pub fn ignore_submodules(&mut self, ignore: bool) -> &mut DiffOptions {
575 self.flag(raw::GIT_DIFF_IGNORE_SUBMODULES, ignore)
576 }
577
578 /// Flag indicating whether case insensitive filenames should be used.
ignore_case(&mut self, ignore: bool) -> &mut DiffOptions579 pub fn ignore_case(&mut self, ignore: bool) -> &mut DiffOptions {
580 self.flag(raw::GIT_DIFF_IGNORE_CASE, ignore)
581 }
582
583 /// If pathspecs are specified, this flag means that they should be applied
584 /// as an exact match instead of a fnmatch pattern.
disable_pathspec_match(&mut self, disable: bool) -> &mut DiffOptions585 pub fn disable_pathspec_match(&mut self, disable: bool) -> &mut DiffOptions {
586 self.flag(raw::GIT_DIFF_DISABLE_PATHSPEC_MATCH, disable)
587 }
588
589 /// Disable updating the `binary` flag in delta records. This is useful when
590 /// iterating over a diff if you don't need hunk and data callbacks and want
591 /// to avoid having to load a file completely.
skip_binary_check(&mut self, skip: bool) -> &mut DiffOptions592 pub fn skip_binary_check(&mut self, skip: bool) -> &mut DiffOptions {
593 self.flag(raw::GIT_DIFF_SKIP_BINARY_CHECK, skip)
594 }
595
596 /// When diff finds an untracked directory, to match the behavior of core
597 /// Git, it scans the contents for ignored and untracked files. If all
598 /// contents are ignored, then the directory is ignored; if any contents are
599 /// not ignored, then the directory is untracked. This is extra work that
600 /// may not matter in many cases.
601 ///
602 /// This flag turns off that scan and immediately labels an untracked
603 /// directory as untracked (changing the behavior to not match core git).
enable_fast_untracked_dirs(&mut self, enable: bool) -> &mut DiffOptions604 pub fn enable_fast_untracked_dirs(&mut self, enable: bool) -> &mut DiffOptions {
605 self.flag(raw::GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS, enable)
606 }
607
608 /// When diff finds a file in the working directory with stat information
609 /// different from the index, but the OID ends up being the same, write the
610 /// correct stat information into the index. Note: without this flag, diff
611 /// will always leave the index untouched.
update_index(&mut self, update: bool) -> &mut DiffOptions612 pub fn update_index(&mut self, update: bool) -> &mut DiffOptions {
613 self.flag(raw::GIT_DIFF_UPDATE_INDEX, update)
614 }
615
616 /// Include unreadable files in the diff
include_unreadable(&mut self, include: bool) -> &mut DiffOptions617 pub fn include_unreadable(&mut self, include: bool) -> &mut DiffOptions {
618 self.flag(raw::GIT_DIFF_INCLUDE_UNREADABLE, include)
619 }
620
621 /// Include unreadable files in the diff
include_unreadable_as_untracked(&mut self, include: bool) -> &mut DiffOptions622 pub fn include_unreadable_as_untracked(&mut self, include: bool) -> &mut DiffOptions {
623 self.flag(raw::GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED, include)
624 }
625
626 /// Treat all files as text, disabling binary attributes and detection.
force_text(&mut self, force: bool) -> &mut DiffOptions627 pub fn force_text(&mut self, force: bool) -> &mut DiffOptions {
628 self.flag(raw::GIT_DIFF_FORCE_TEXT, force)
629 }
630
631 /// Treat all files as binary, disabling text diffs
force_binary(&mut self, force: bool) -> &mut DiffOptions632 pub fn force_binary(&mut self, force: bool) -> &mut DiffOptions {
633 self.flag(raw::GIT_DIFF_FORCE_TEXT, force)
634 }
635
636 /// Ignore all whitespace
ignore_whitespace(&mut self, ignore: bool) -> &mut DiffOptions637 pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut DiffOptions {
638 self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE, ignore)
639 }
640
641 /// Ignore changes in the amount of whitespace
ignore_whitespace_change(&mut self, ignore: bool) -> &mut DiffOptions642 pub fn ignore_whitespace_change(&mut self, ignore: bool) -> &mut DiffOptions {
643 self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE_CHANGE, ignore)
644 }
645
646 /// Ignore whitespace at tend of line
ignore_whitespace_eol(&mut self, ignore: bool) -> &mut DiffOptions647 pub fn ignore_whitespace_eol(&mut self, ignore: bool) -> &mut DiffOptions {
648 self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE_EOL, ignore)
649 }
650
651 /// When generating patch text, include the content of untracked files.
652 ///
653 /// This automatically turns on `include_untracked` but it does not turn on
654 /// `recurse_untracked_dirs`. Add that flag if you want the content of every
655 /// single untracked file.
show_untracked_content(&mut self, show: bool) -> &mut DiffOptions656 pub fn show_untracked_content(&mut self, show: bool) -> &mut DiffOptions {
657 self.flag(raw::GIT_DIFF_SHOW_UNTRACKED_CONTENT, show)
658 }
659
660 /// When generating output, include the names of unmodified files if they
661 /// are included in the `Diff`. Normally these are skipped in the formats
662 /// that list files (e.g. name-only, name-status, raw). Even with this these
663 /// will not be included in the patch format.
show_unmodified(&mut self, show: bool) -> &mut DiffOptions664 pub fn show_unmodified(&mut self, show: bool) -> &mut DiffOptions {
665 self.flag(raw::GIT_DIFF_SHOW_UNMODIFIED, show)
666 }
667
668 /// Use the "patience diff" algorithm
patience(&mut self, patience: bool) -> &mut DiffOptions669 pub fn patience(&mut self, patience: bool) -> &mut DiffOptions {
670 self.flag(raw::GIT_DIFF_PATIENCE, patience)
671 }
672
673 /// Take extra time to find the minimal diff
minimal(&mut self, minimal: bool) -> &mut DiffOptions674 pub fn minimal(&mut self, minimal: bool) -> &mut DiffOptions {
675 self.flag(raw::GIT_DIFF_MINIMAL, minimal)
676 }
677
678 /// Include the necessary deflate/delta information so that `git-apply` can
679 /// apply given diff information to binary files.
show_binary(&mut self, show: bool) -> &mut DiffOptions680 pub fn show_binary(&mut self, show: bool) -> &mut DiffOptions {
681 self.flag(raw::GIT_DIFF_SHOW_BINARY, show)
682 }
683
684 /// Use a heuristic that takes indentation and whitespace into account
685 /// which generally can produce better diffs when dealing with ambiguous
686 /// diff hunks.
indent_heuristic(&mut self, heuristic: bool) -> &mut DiffOptions687 pub fn indent_heuristic(&mut self, heuristic: bool) -> &mut DiffOptions {
688 self.flag(raw::GIT_DIFF_INDENT_HEURISTIC, heuristic)
689 }
690
691 /// Set the number of unchanged lines that define the boundary of a hunk
692 /// (and to display before and after).
693 ///
694 /// The default value for this is 3.
context_lines(&mut self, lines: u32) -> &mut DiffOptions695 pub fn context_lines(&mut self, lines: u32) -> &mut DiffOptions {
696 self.raw.context_lines = lines;
697 self
698 }
699
700 /// Set the maximum number of unchanged lines between hunk boundaries before
701 /// the hunks will be merged into one.
702 ///
703 /// The default value for this is 0.
interhunk_lines(&mut self, lines: u32) -> &mut DiffOptions704 pub fn interhunk_lines(&mut self, lines: u32) -> &mut DiffOptions {
705 self.raw.interhunk_lines = lines;
706 self
707 }
708
709 /// The default value for this is `core.abbrev` or 7 if unset.
id_abbrev(&mut self, abbrev: u16) -> &mut DiffOptions710 pub fn id_abbrev(&mut self, abbrev: u16) -> &mut DiffOptions {
711 self.raw.id_abbrev = abbrev;
712 self
713 }
714
715 /// Maximum size (in bytes) above which a blob will be marked as binary
716 /// automatically.
717 ///
718 /// A negative value will disable this entirely.
719 ///
720 /// The default value for this is 512MB.
max_size(&mut self, size: i64) -> &mut DiffOptions721 pub fn max_size(&mut self, size: i64) -> &mut DiffOptions {
722 self.raw.max_size = size as raw::git_off_t;
723 self
724 }
725
726 /// The virtual "directory" to prefix old file names with in hunk headers.
727 ///
728 /// The default value for this is "a".
old_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions729 pub fn old_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions {
730 self.old_prefix = Some(t.into_c_string().unwrap());
731 self
732 }
733
734 /// The virtual "directory" to prefix new file names with in hunk headers.
735 ///
736 /// The default value for this is "b".
new_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions737 pub fn new_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions {
738 self.new_prefix = Some(t.into_c_string().unwrap());
739 self
740 }
741
742 /// Add to the array of paths/fnmatch patterns to constrain the diff.
pathspec<T: IntoCString>(&mut self, pathspec: T) -> &mut DiffOptions743 pub fn pathspec<T: IntoCString>(&mut self, pathspec: T) -> &mut DiffOptions {
744 let s = pathspec.into_c_string().unwrap();
745 self.pathspec_ptrs.push(s.as_ptr());
746 self.pathspec.push(s);
747 self
748 }
749
750 /// Acquire a pointer to the underlying raw options.
751 ///
752 /// This function is unsafe as the pointer is only valid so long as this
753 /// structure is not moved, modified, or used elsewhere.
raw(&mut self) -> *const raw::git_diff_options754 pub unsafe fn raw(&mut self) -> *const raw::git_diff_options {
755 self.raw.old_prefix = self
756 .old_prefix
757 .as_ref()
758 .map(|s| s.as_ptr())
759 .unwrap_or(ptr::null());
760 self.raw.new_prefix = self
761 .new_prefix
762 .as_ref()
763 .map(|s| s.as_ptr())
764 .unwrap_or(ptr::null());
765 self.raw.pathspec.count = self.pathspec_ptrs.len() as size_t;
766 self.raw.pathspec.strings = self.pathspec_ptrs.as_ptr() as *mut _;
767 &self.raw as *const _
768 }
769
770 // TODO: expose ignore_submodules, notify_cb/notify_payload
771 }
772
773 impl<'diff> Iterator for Deltas<'diff> {
774 type Item = DiffDelta<'diff>;
next(&mut self) -> Option<DiffDelta<'diff>>775 fn next(&mut self) -> Option<DiffDelta<'diff>> {
776 self.range.next().and_then(|i| self.diff.get_delta(i))
777 }
size_hint(&self) -> (usize, Option<usize>)778 fn size_hint(&self) -> (usize, Option<usize>) {
779 self.range.size_hint()
780 }
781 }
782 impl<'diff> DoubleEndedIterator for Deltas<'diff> {
next_back(&mut self) -> Option<DiffDelta<'diff>>783 fn next_back(&mut self) -> Option<DiffDelta<'diff>> {
784 self.range.next_back().and_then(|i| self.diff.get_delta(i))
785 }
786 }
787 impl<'diff> ExactSizeIterator for Deltas<'diff> {}
788
789 impl<'a> DiffLine<'a> {
790 /// Line number in old file or `None` for added line
old_lineno(&self) -> Option<u32>791 pub fn old_lineno(&self) -> Option<u32> {
792 match unsafe { (*self.raw).old_lineno } {
793 n if n < 0 => None,
794 n => Some(n as u32),
795 }
796 }
797
798 /// Line number in new file or `None` for deleted line
new_lineno(&self) -> Option<u32>799 pub fn new_lineno(&self) -> Option<u32> {
800 match unsafe { (*self.raw).new_lineno } {
801 n if n < 0 => None,
802 n => Some(n as u32),
803 }
804 }
805
806 /// Number of newline characters in content
num_lines(&self) -> u32807 pub fn num_lines(&self) -> u32 {
808 unsafe { (*self.raw).num_lines as u32 }
809 }
810
811 /// Offset in the original file to the content
content_offset(&self) -> i64812 pub fn content_offset(&self) -> i64 {
813 unsafe { (*self.raw).content_offset as i64 }
814 }
815
816 /// Content of this line as bytes.
content(&self) -> &[u8]817 pub fn content(&self) -> &[u8] {
818 unsafe {
819 slice::from_raw_parts(
820 (*self.raw).content as *const u8,
821 (*self.raw).content_len as usize,
822 )
823 }
824 }
825
826 /// Sigil showing the origin of this `DiffLine`.
827 ///
828 /// * ` ` - Line context
829 /// * `+` - Line addition
830 /// * `-` - Line deletion
831 /// * `=` - Context (End of file)
832 /// * `>` - Add (End of file)
833 /// * `<` - Remove (End of file)
834 /// * `F` - File header
835 /// * `H` - Hunk header
836 /// * `B` - Line binary
origin(&self) -> char837 pub fn origin(&self) -> char {
838 match unsafe { (*self.raw).origin as raw::git_diff_line_t } {
839 raw::GIT_DIFF_LINE_CONTEXT => ' ',
840 raw::GIT_DIFF_LINE_ADDITION => '+',
841 raw::GIT_DIFF_LINE_DELETION => '-',
842 raw::GIT_DIFF_LINE_CONTEXT_EOFNL => '=',
843 raw::GIT_DIFF_LINE_ADD_EOFNL => '>',
844 raw::GIT_DIFF_LINE_DEL_EOFNL => '<',
845 raw::GIT_DIFF_LINE_FILE_HDR => 'F',
846 raw::GIT_DIFF_LINE_HUNK_HDR => 'H',
847 raw::GIT_DIFF_LINE_BINARY => 'B',
848 _ => ' ',
849 }
850 }
851 }
852
853 impl<'a> Binding for DiffLine<'a> {
854 type Raw = *const raw::git_diff_line;
from_raw(raw: *const raw::git_diff_line) -> DiffLine<'a>855 unsafe fn from_raw(raw: *const raw::git_diff_line) -> DiffLine<'a> {
856 DiffLine {
857 raw: raw,
858 _marker: marker::PhantomData,
859 }
860 }
raw(&self) -> *const raw::git_diff_line861 fn raw(&self) -> *const raw::git_diff_line {
862 self.raw
863 }
864 }
865
866 impl<'a> DiffHunk<'a> {
867 /// Starting line number in old_file
old_start(&self) -> u32868 pub fn old_start(&self) -> u32 {
869 unsafe { (*self.raw).old_start as u32 }
870 }
871
872 /// Number of lines in old_file
old_lines(&self) -> u32873 pub fn old_lines(&self) -> u32 {
874 unsafe { (*self.raw).old_lines as u32 }
875 }
876
877 /// Starting line number in new_file
new_start(&self) -> u32878 pub fn new_start(&self) -> u32 {
879 unsafe { (*self.raw).new_start as u32 }
880 }
881
882 /// Number of lines in new_file
new_lines(&self) -> u32883 pub fn new_lines(&self) -> u32 {
884 unsafe { (*self.raw).new_lines as u32 }
885 }
886
887 /// Header text
header(&self) -> &[u8]888 pub fn header(&self) -> &[u8] {
889 unsafe {
890 slice::from_raw_parts(
891 (*self.raw).header.as_ptr() as *const u8,
892 (*self.raw).header_len as usize,
893 )
894 }
895 }
896 }
897
898 impl<'a> Binding for DiffHunk<'a> {
899 type Raw = *const raw::git_diff_hunk;
from_raw(raw: *const raw::git_diff_hunk) -> DiffHunk<'a>900 unsafe fn from_raw(raw: *const raw::git_diff_hunk) -> DiffHunk<'a> {
901 DiffHunk {
902 raw: raw,
903 _marker: marker::PhantomData,
904 }
905 }
raw(&self) -> *const raw::git_diff_hunk906 fn raw(&self) -> *const raw::git_diff_hunk {
907 self.raw
908 }
909 }
910
911 impl DiffStats {
912 /// Get the total number of files chaned in a diff.
files_changed(&self) -> usize913 pub fn files_changed(&self) -> usize {
914 unsafe { raw::git_diff_stats_files_changed(&*self.raw) as usize }
915 }
916
917 /// Get the total number of insertions in a diff
insertions(&self) -> usize918 pub fn insertions(&self) -> usize {
919 unsafe { raw::git_diff_stats_insertions(&*self.raw) as usize }
920 }
921
922 /// Get the total number of deletions in a diff
deletions(&self) -> usize923 pub fn deletions(&self) -> usize {
924 unsafe { raw::git_diff_stats_deletions(&*self.raw) as usize }
925 }
926
927 /// Print diff statistics to a Buf
to_buf(&self, format: DiffStatsFormat, width: usize) -> Result<Buf, Error>928 pub fn to_buf(&self, format: DiffStatsFormat, width: usize) -> Result<Buf, Error> {
929 let buf = Buf::new();
930 unsafe {
931 try_call!(raw::git_diff_stats_to_buf(
932 buf.raw(),
933 self.raw,
934 format.bits(),
935 width as size_t
936 ));
937 }
938 Ok(buf)
939 }
940 }
941
942 impl Binding for DiffStats {
943 type Raw = *mut raw::git_diff_stats;
944
from_raw(raw: *mut raw::git_diff_stats) -> DiffStats945 unsafe fn from_raw(raw: *mut raw::git_diff_stats) -> DiffStats {
946 DiffStats { raw: raw }
947 }
raw(&self) -> *mut raw::git_diff_stats948 fn raw(&self) -> *mut raw::git_diff_stats {
949 self.raw
950 }
951 }
952
953 impl Drop for DiffStats {
drop(&mut self)954 fn drop(&mut self) {
955 unsafe { raw::git_diff_stats_free(self.raw) }
956 }
957 }
958
959 impl<'a> DiffBinary<'a> {
960 /// Returns whether there is data in this binary structure or not.
961 ///
962 /// If this is `true`, then this was produced and included binary content.
963 /// If this is `false` then this was generated knowing only that a binary
964 /// file changed but without providing the data, probably from a patch that
965 /// said `Binary files a/file.txt and b/file.txt differ`.
contains_data(&self) -> bool966 pub fn contains_data(&self) -> bool {
967 unsafe { (*self.raw).contains_data == 1 }
968 }
969
970 /// The contents of the old file.
old_file(&self) -> DiffBinaryFile<'a>971 pub fn old_file(&self) -> DiffBinaryFile<'a> {
972 unsafe { Binding::from_raw(&(*self.raw).old_file as *const _) }
973 }
974
975 /// The contents of the new file.
new_file(&self) -> DiffBinaryFile<'a>976 pub fn new_file(&self) -> DiffBinaryFile<'a> {
977 unsafe { Binding::from_raw(&(*self.raw).new_file as *const _) }
978 }
979 }
980
981 impl<'a> Binding for DiffBinary<'a> {
982 type Raw = *const raw::git_diff_binary;
from_raw(raw: *const raw::git_diff_binary) -> DiffBinary<'a>983 unsafe fn from_raw(raw: *const raw::git_diff_binary) -> DiffBinary<'a> {
984 DiffBinary {
985 raw: raw,
986 _marker: marker::PhantomData,
987 }
988 }
raw(&self) -> *const raw::git_diff_binary989 fn raw(&self) -> *const raw::git_diff_binary {
990 self.raw
991 }
992 }
993
994 impl<'a> DiffBinaryFile<'a> {
995 /// The type of binary data for this file
kind(&self) -> DiffBinaryKind996 pub fn kind(&self) -> DiffBinaryKind {
997 unsafe { Binding::from_raw((*self.raw).kind) }
998 }
999
1000 /// The binary data, deflated
data(&self) -> &[u8]1001 pub fn data(&self) -> &[u8] {
1002 unsafe {
1003 slice::from_raw_parts((*self.raw).data as *const u8, (*self.raw).datalen as usize)
1004 }
1005 }
1006
1007 /// The length of the binary data after inflation
inflated_len(&self) -> usize1008 pub fn inflated_len(&self) -> usize {
1009 unsafe { (*self.raw).inflatedlen as usize }
1010 }
1011 }
1012
1013 impl<'a> Binding for DiffBinaryFile<'a> {
1014 type Raw = *const raw::git_diff_binary_file;
from_raw(raw: *const raw::git_diff_binary_file) -> DiffBinaryFile<'a>1015 unsafe fn from_raw(raw: *const raw::git_diff_binary_file) -> DiffBinaryFile<'a> {
1016 DiffBinaryFile {
1017 raw: raw,
1018 _marker: marker::PhantomData,
1019 }
1020 }
raw(&self) -> *const raw::git_diff_binary_file1021 fn raw(&self) -> *const raw::git_diff_binary_file {
1022 self.raw
1023 }
1024 }
1025
1026 impl Binding for DiffBinaryKind {
1027 type Raw = raw::git_diff_binary_t;
from_raw(raw: raw::git_diff_binary_t) -> DiffBinaryKind1028 unsafe fn from_raw(raw: raw::git_diff_binary_t) -> DiffBinaryKind {
1029 match raw {
1030 raw::GIT_DIFF_BINARY_NONE => DiffBinaryKind::None,
1031 raw::GIT_DIFF_BINARY_LITERAL => DiffBinaryKind::Literal,
1032 raw::GIT_DIFF_BINARY_DELTA => DiffBinaryKind::Delta,
1033 _ => panic!("Unknown git diff binary kind"),
1034 }
1035 }
raw(&self) -> raw::git_diff_binary_t1036 fn raw(&self) -> raw::git_diff_binary_t {
1037 match *self {
1038 DiffBinaryKind::None => raw::GIT_DIFF_BINARY_NONE,
1039 DiffBinaryKind::Literal => raw::GIT_DIFF_BINARY_LITERAL,
1040 DiffBinaryKind::Delta => raw::GIT_DIFF_BINARY_DELTA,
1041 }
1042 }
1043 }
1044
1045 impl Default for DiffFindOptions {
default() -> Self1046 fn default() -> Self {
1047 Self::new()
1048 }
1049 }
1050
1051 impl DiffFindOptions {
1052 /// Creates a new set of empty diff find options.
1053 ///
1054 /// All flags and other options are defaulted to false or their otherwise
1055 /// zero equivalents.
new() -> DiffFindOptions1056 pub fn new() -> DiffFindOptions {
1057 let mut opts = DiffFindOptions {
1058 raw: unsafe { mem::zeroed() },
1059 };
1060 assert_eq!(
1061 unsafe { raw::git_diff_find_init_options(&mut opts.raw, 1) },
1062 0
1063 );
1064 opts
1065 }
1066
flag(&mut self, opt: u32, val: bool) -> &mut DiffFindOptions1067 fn flag(&mut self, opt: u32, val: bool) -> &mut DiffFindOptions {
1068 if val {
1069 self.raw.flags |= opt;
1070 } else {
1071 self.raw.flags &= !opt;
1072 }
1073 self
1074 }
1075
1076 /// Reset all flags back to their unset state, indicating that
1077 /// `diff.renames` should be used instead. This is overridden once any flag
1078 /// is set.
by_config(&mut self) -> &mut DiffFindOptions1079 pub fn by_config(&mut self) -> &mut DiffFindOptions {
1080 self.flag(0xffffffff, false)
1081 }
1082
1083 /// Look for renames?
renames(&mut self, find: bool) -> &mut DiffFindOptions1084 pub fn renames(&mut self, find: bool) -> &mut DiffFindOptions {
1085 self.flag(raw::GIT_DIFF_FIND_RENAMES, find)
1086 }
1087
1088 /// Consider old side of modified for renames?
renames_from_rewrites(&mut self, find: bool) -> &mut DiffFindOptions1089 pub fn renames_from_rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
1090 self.flag(raw::GIT_DIFF_FIND_RENAMES_FROM_REWRITES, find)
1091 }
1092
1093 /// Look for copies?
copies(&mut self, find: bool) -> &mut DiffFindOptions1094 pub fn copies(&mut self, find: bool) -> &mut DiffFindOptions {
1095 self.flag(raw::GIT_DIFF_FIND_COPIES, find)
1096 }
1097
1098 /// Consider unmodified as copy sources?
1099 ///
1100 /// For this to work correctly, use `include_unmodified` when the initial
1101 /// diff is being generated.
copies_from_unmodified(&mut self, find: bool) -> &mut DiffFindOptions1102 pub fn copies_from_unmodified(&mut self, find: bool) -> &mut DiffFindOptions {
1103 self.flag(raw::GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED, find)
1104 }
1105
1106 /// Mark significant rewrites for split.
rewrites(&mut self, find: bool) -> &mut DiffFindOptions1107 pub fn rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
1108 self.flag(raw::GIT_DIFF_FIND_REWRITES, find)
1109 }
1110
1111 /// Actually split large rewrites into delete/add pairs
break_rewrites(&mut self, find: bool) -> &mut DiffFindOptions1112 pub fn break_rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
1113 self.flag(raw::GIT_DIFF_BREAK_REWRITES, find)
1114 }
1115
1116 #[doc(hidden)]
break_rewries(&mut self, find: bool) -> &mut DiffFindOptions1117 pub fn break_rewries(&mut self, find: bool) -> &mut DiffFindOptions {
1118 self.break_rewrites(find)
1119 }
1120
1121 /// Find renames/copies for untracked items in working directory.
1122 ///
1123 /// For this to work correctly use the `include_untracked` option when the
1124 /// initial diff is being generated.
for_untracked(&mut self, find: bool) -> &mut DiffFindOptions1125 pub fn for_untracked(&mut self, find: bool) -> &mut DiffFindOptions {
1126 self.flag(raw::GIT_DIFF_FIND_FOR_UNTRACKED, find)
1127 }
1128
1129 /// Turn on all finding features.
all(&mut self, find: bool) -> &mut DiffFindOptions1130 pub fn all(&mut self, find: bool) -> &mut DiffFindOptions {
1131 self.flag(raw::GIT_DIFF_FIND_ALL, find)
1132 }
1133
1134 /// Measure similarity ignoring leading whitespace (default)
ignore_leading_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions1135 pub fn ignore_leading_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions {
1136 self.flag(raw::GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE, ignore)
1137 }
1138
1139 /// Measure similarity ignoring all whitespace
ignore_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions1140 pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions {
1141 self.flag(raw::GIT_DIFF_FIND_IGNORE_WHITESPACE, ignore)
1142 }
1143
1144 /// Measure similarity including all data
dont_ignore_whitespace(&mut self, dont: bool) -> &mut DiffFindOptions1145 pub fn dont_ignore_whitespace(&mut self, dont: bool) -> &mut DiffFindOptions {
1146 self.flag(raw::GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE, dont)
1147 }
1148
1149 /// Measure similarity only by comparing SHAs (fast and cheap)
exact_match_only(&mut self, exact: bool) -> &mut DiffFindOptions1150 pub fn exact_match_only(&mut self, exact: bool) -> &mut DiffFindOptions {
1151 self.flag(raw::GIT_DIFF_FIND_EXACT_MATCH_ONLY, exact)
1152 }
1153
1154 /// Do not break rewrites unless they contribute to a rename.
1155 ///
1156 /// Normally, `break_rewrites` and `rewrites` will measure the
1157 /// self-similarity of modified files and split the ones that have changed a
1158 /// lot into a delete/add pair. Then the sides of that pair will be
1159 /// considered candidates for rename and copy detection
1160 ///
1161 /// If you add this flag in and the split pair is not used for an actual
1162 /// rename or copy, then the modified record will be restored to a regular
1163 /// modified record instead of being split.
break_rewrites_for_renames_only(&mut self, b: bool) -> &mut DiffFindOptions1164 pub fn break_rewrites_for_renames_only(&mut self, b: bool) -> &mut DiffFindOptions {
1165 self.flag(raw::GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY, b)
1166 }
1167
1168 /// Remove any unmodified deltas after find_similar is done.
1169 ///
1170 /// Using `copies_from_unmodified` to emulate the `--find-copies-harder`
1171 /// behavior requires building a diff with the `include_unmodified` flag. If
1172 /// you do not want unmodified records in the final result, pas this flag to
1173 /// have them removed.
remove_unmodified(&mut self, remove: bool) -> &mut DiffFindOptions1174 pub fn remove_unmodified(&mut self, remove: bool) -> &mut DiffFindOptions {
1175 self.flag(raw::GIT_DIFF_FIND_REMOVE_UNMODIFIED, remove)
1176 }
1177
1178 /// Similarity to consider a file renamed (default 50)
rename_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions1179 pub fn rename_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1180 self.raw.rename_threshold = thresh;
1181 self
1182 }
1183
1184 /// Similarity of modified to be glegible rename source (default 50)
rename_from_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions1185 pub fn rename_from_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1186 self.raw.rename_from_rewrite_threshold = thresh;
1187 self
1188 }
1189
1190 /// Similarity to consider a file copy (default 50)
copy_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions1191 pub fn copy_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1192 self.raw.copy_threshold = thresh;
1193 self
1194 }
1195
1196 /// Similarity to split modify into delete/add pair (default 60)
break_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions1197 pub fn break_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1198 self.raw.break_rewrite_threshold = thresh;
1199 self
1200 }
1201
1202 /// Maximum similarity sources to examine for a file (somewhat like
1203 /// git-diff's `-l` option or `diff.renameLimit` config)
1204 ///
1205 /// Defaults to 200
rename_limit(&mut self, limit: usize) -> &mut DiffFindOptions1206 pub fn rename_limit(&mut self, limit: usize) -> &mut DiffFindOptions {
1207 self.raw.rename_limit = limit as size_t;
1208 self
1209 }
1210
1211 // TODO: expose git_diff_similarity_metric
1212 }
1213
1214 #[cfg(test)]
1215 mod tests {
1216 use crate::DiffOptions;
1217 use std::borrow::Borrow;
1218 use std::fs::File;
1219 use std::io::Write;
1220 use std::path::Path;
1221
1222 #[test]
smoke()1223 fn smoke() {
1224 let (_td, repo) = crate::test::repo_init();
1225 let diff = repo.diff_tree_to_workdir(None, None).unwrap();
1226 assert_eq!(diff.deltas().len(), 0);
1227 let stats = diff.stats().unwrap();
1228 assert_eq!(stats.insertions(), 0);
1229 assert_eq!(stats.deletions(), 0);
1230 assert_eq!(stats.files_changed(), 0);
1231 }
1232
1233 #[test]
foreach_smoke()1234 fn foreach_smoke() {
1235 let (_td, repo) = crate::test::repo_init();
1236 let diff = t!(repo.diff_tree_to_workdir(None, None));
1237 let mut count = 0;
1238 t!(diff.foreach(
1239 &mut |_file, _progress| {
1240 count = count + 1;
1241 true
1242 },
1243 None,
1244 None,
1245 None
1246 ));
1247 assert_eq!(count, 0);
1248 }
1249
1250 #[test]
foreach_file_only()1251 fn foreach_file_only() {
1252 let path = Path::new("foo");
1253 let (td, repo) = crate::test::repo_init();
1254 t!(t!(File::create(&td.path().join(path))).write_all(b"bar"));
1255 let mut opts = DiffOptions::new();
1256 opts.include_untracked(true);
1257 let diff = t!(repo.diff_tree_to_workdir(None, Some(&mut opts)));
1258 let mut count = 0;
1259 let mut result = None;
1260 t!(diff.foreach(
1261 &mut |file, _progress| {
1262 count = count + 1;
1263 result = file.new_file().path().map(ToOwned::to_owned);
1264 true
1265 },
1266 None,
1267 None,
1268 None
1269 ));
1270 assert_eq!(result.as_ref().map(Borrow::borrow), Some(path));
1271 assert_eq!(count, 1);
1272 }
1273
1274 #[test]
foreach_file_and_hunk()1275 fn foreach_file_and_hunk() {
1276 let path = Path::new("foo");
1277 let (td, repo) = crate::test::repo_init();
1278 t!(t!(File::create(&td.path().join(path))).write_all(b"bar"));
1279 let mut index = t!(repo.index());
1280 t!(index.add_path(path));
1281 let mut opts = DiffOptions::new();
1282 opts.include_untracked(true);
1283 let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
1284 let mut new_lines = 0;
1285 t!(diff.foreach(
1286 &mut |_file, _progress| { true },
1287 None,
1288 Some(&mut |_file, hunk| {
1289 new_lines = hunk.new_lines();
1290 true
1291 }),
1292 None
1293 ));
1294 assert_eq!(new_lines, 1);
1295 }
1296
1297 #[test]
foreach_all_callbacks()1298 fn foreach_all_callbacks() {
1299 let fib = vec![0, 1, 1, 2, 3, 5, 8];
1300 // Verified with a node implementation of deflate, might be worth
1301 // adding a deflate lib to do this inline here.
1302 let deflated_fib = vec![120, 156, 99, 96, 100, 100, 98, 102, 229, 0, 0, 0, 53, 0, 21];
1303 let foo_path = Path::new("foo");
1304 let bin_path = Path::new("bin");
1305 let (td, repo) = crate::test::repo_init();
1306 t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n"));
1307 t!(t!(File::create(&td.path().join(bin_path))).write_all(&fib));
1308 let mut index = t!(repo.index());
1309 t!(index.add_path(foo_path));
1310 t!(index.add_path(bin_path));
1311 let mut opts = DiffOptions::new();
1312 opts.include_untracked(true).show_binary(true);
1313 let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
1314 let mut bin_content = None;
1315 let mut new_lines = 0;
1316 let mut line_content = None;
1317 t!(diff.foreach(
1318 &mut |_file, _progress| { true },
1319 Some(&mut |_file, binary| {
1320 bin_content = Some(binary.new_file().data().to_owned());
1321 true
1322 }),
1323 Some(&mut |_file, hunk| {
1324 new_lines = hunk.new_lines();
1325 true
1326 }),
1327 Some(&mut |_file, _hunk, line| {
1328 line_content = String::from_utf8(line.content().into()).ok();
1329 true
1330 })
1331 ));
1332 assert_eq!(bin_content, Some(deflated_fib));
1333 assert_eq!(new_lines, 1);
1334 assert_eq!(line_content, Some("bar\n".to_string()));
1335 }
1336 }
1337