]> git.proxmox.com Git - rustc.git/blob - vendor/git2/src/diff.rs
New upstream version 1.74.1+dfsg1
[rustc.git] / vendor / git2 / src / diff.rs
1 use libc::{c_char, c_int, c_void, size_t};
2 use std::ffi::CString;
3 use std::iter::FusedIterator;
4 use std::marker;
5 use std::mem;
6 use std::ops::Range;
7 use std::path::Path;
8 use std::ptr;
9 use std::slice;
10
11 use crate::util::{self, Binding};
12 use crate::{panic, raw, Buf, Delta, DiffFormat, Error, FileMode, Oid, Repository};
13 use crate::{DiffFlags, DiffStatsFormat, IntoCString};
14
15 /// The diff object that contains all individual file deltas.
16 ///
17 /// This is an opaque structure which will be allocated by one of the diff
18 /// generator functions on the `Repository` structure (e.g. `diff_tree_to_tree`
19 /// or other `diff_*` functions).
20 pub struct Diff<'repo> {
21 raw: *mut raw::git_diff,
22 _marker: marker::PhantomData<&'repo Repository>,
23 }
24
25 unsafe impl<'repo> Send for Diff<'repo> {}
26
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>,
31 }
32
33 /// Description of one side of a delta.
34 ///
35 /// Although this is called a "file" it could represent a file, a symbolic
36 /// link, a submodule commit id, or even a tree (although that only happens if
37 /// you are tracking type changes or ignored/untracked directories).
38 pub struct DiffFile<'a> {
39 raw: *const raw::git_diff_file,
40 _marker: marker::PhantomData<&'a raw::git_diff_file>,
41 }
42
43 /// Structure describing options about how the diff should be executed.
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,
50 }
51
52 /// Control behavior of rename and copy detection
53 pub struct DiffFindOptions {
54 raw: raw::git_diff_find_options,
55 }
56
57 /// Control behavior of formatting emails
58 pub struct DiffFormatEmailOptions {
59 raw: raw::git_diff_format_email_options,
60 }
61
62 /// Control behavior of formatting emails
63 pub struct DiffPatchidOptions {
64 raw: raw::git_diff_patchid_options,
65 }
66
67 /// An iterator over the diffs in a delta
68 pub struct Deltas<'diff> {
69 range: Range<usize>,
70 diff: &'diff Diff<'diff>,
71 }
72
73 /// Structure describing a line (or data span) of a diff.
74 pub struct DiffLine<'a> {
75 raw: *const raw::git_diff_line,
76 _marker: marker::PhantomData<&'a raw::git_diff_line>,
77 }
78
79 /// Structure describing a hunk of a diff.
80 pub struct DiffHunk<'a> {
81 raw: *const raw::git_diff_hunk,
82 _marker: marker::PhantomData<&'a raw::git_diff_hunk>,
83 }
84
85 /// Structure describing a hunk of a diff.
86 pub struct DiffStats {
87 raw: *mut raw::git_diff_stats,
88 }
89
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>,
94 }
95
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>,
100 }
101
102 /// When producing a binary diff, the binary data returned will be
103 /// either the deflated full ("literal") contents of the file, or
104 /// the deflated binary delta between the two sides (whichever is
105 /// smaller).
106 #[derive(Copy, Clone, Debug)]
107 pub enum DiffBinaryKind {
108 /// There is no binary delta
109 None,
110 /// The binary data is the literal contents of the file
111 Literal,
112 /// The binary data is the delta from one side to the other
113 Delta,
114 }
115
116 type PrintCb<'a> = dyn FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool + 'a;
117
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;
122
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>>,
128 }
129
130 impl<'repo> Diff<'repo> {
131 /// Merge one diff into another.
132 ///
133 /// This merges items from the "from" list into the "self" list. The
134 /// resulting diff will have all items that appear in either list.
135 /// If an item appears in both lists, then it will be "merged" to appear
136 /// as if the old version was from the "onto" list and the new version
137 /// is from the "from" list (with the exception that if the item has a
138 /// pending DELETE in the middle, then it will show as deleted).
139 pub fn merge(&mut self, from: &Diff<'repo>) -> Result<(), Error> {
140 unsafe {
141 try_call!(raw::git_diff_merge(self.raw, &*from.raw));
142 }
143 Ok(())
144 }
145
146 /// Returns an iterator over the deltas in this diff.
147 pub fn deltas(&self) -> Deltas<'_> {
148 let num_deltas = unsafe { raw::git_diff_num_deltas(&*self.raw) };
149 Deltas {
150 range: 0..(num_deltas as usize),
151 diff: self,
152 }
153 }
154
155 /// Return the diff delta for an entry in the diff list.
156 pub fn get_delta(&self, i: usize) -> Option<DiffDelta<'_>> {
157 unsafe {
158 let ptr = raw::git_diff_get_delta(&*self.raw, i as size_t);
159 Binding::from_raw_opt(ptr as *mut _)
160 }
161 }
162
163 /// Check if deltas are sorted case sensitively or insensitively.
164 pub fn is_sorted_icase(&self) -> bool {
165 unsafe { raw::git_diff_is_sorted_icase(&*self.raw) == 1 }
166 }
167
168 /// Iterate over a diff generating formatted text output.
169 ///
170 /// Returning `false` from the callback will terminate the iteration and
171 /// return an error from this function.
172 pub fn print<F>(&self, format: DiffFormat, mut cb: F) -> Result<(), Error>
173 where
174 F: FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool,
175 {
176 let mut cb: &mut PrintCb<'_> = &mut cb;
177 let ptr = &mut cb as *mut _;
178 let print: raw::git_diff_line_cb = Some(print_cb);
179 unsafe {
180 try_call!(raw::git_diff_print(self.raw, format, print, ptr as *mut _));
181 Ok(())
182 }
183 }
184
185 /// Loop over all deltas in a diff issuing callbacks.
186 ///
187 /// Returning `false` from any callback will terminate the iteration and
188 /// return an error from this function.
189 pub fn foreach(
190 &self,
191 file_cb: &mut FileCb<'_>,
192 binary_cb: Option<&mut BinaryCb<'_>>,
193 hunk_cb: Option<&mut HunkCb<'_>>,
194 line_cb: Option<&mut LineCb<'_>>,
195 ) -> Result<(), Error> {
196 let mut cbs = DiffCallbacks {
197 file: Some(file_cb),
198 binary: binary_cb,
199 hunk: hunk_cb,
200 line: line_cb,
201 };
202 let ptr = &mut cbs as *mut _;
203 unsafe {
204 let binary_cb_c: raw::git_diff_binary_cb = if cbs.binary.is_some() {
205 Some(binary_cb_c)
206 } else {
207 None
208 };
209 let hunk_cb_c: raw::git_diff_hunk_cb = if cbs.hunk.is_some() {
210 Some(hunk_cb_c)
211 } else {
212 None
213 };
214 let line_cb_c: raw::git_diff_line_cb = if cbs.line.is_some() {
215 Some(line_cb_c)
216 } else {
217 None
218 };
219 let file_cb: raw::git_diff_file_cb = Some(file_cb_c);
220 try_call!(raw::git_diff_foreach(
221 self.raw,
222 file_cb,
223 binary_cb_c,
224 hunk_cb_c,
225 line_cb_c,
226 ptr as *mut _
227 ));
228 Ok(())
229 }
230 }
231
232 /// Accumulate diff statistics for all patches.
233 pub fn stats(&self) -> Result<DiffStats, Error> {
234 let mut ret = ptr::null_mut();
235 unsafe {
236 try_call!(raw::git_diff_get_stats(&mut ret, self.raw));
237 Ok(Binding::from_raw(ret))
238 }
239 }
240
241 /// Transform a diff marking file renames, copies, etc.
242 ///
243 /// This modifies a diff in place, replacing old entries that look like
244 /// renames or copies with new entries reflecting those changes. This also
245 /// will, if requested, break modified files into add/remove pairs if the
246 /// amount of change is above a threshold.
247 pub fn find_similar(&mut self, opts: Option<&mut DiffFindOptions>) -> Result<(), Error> {
248 let opts = opts.map(|opts| &opts.raw);
249 unsafe {
250 try_call!(raw::git_diff_find_similar(self.raw, opts));
251 }
252 Ok(())
253 }
254
255 /// Create an e-mail ready patch from a diff.
256 ///
257 /// Matches the format created by `git format-patch`
258 #[doc(hidden)]
259 #[deprecated(note = "refactored to `Email::from_diff` to match upstream")]
260 pub fn format_email(
261 &mut self,
262 patch_no: usize,
263 total_patches: usize,
264 commit: &crate::Commit<'repo>,
265 opts: Option<&mut DiffFormatEmailOptions>,
266 ) -> Result<Buf, Error> {
267 assert!(patch_no > 0);
268 assert!(patch_no <= total_patches);
269 let mut default = DiffFormatEmailOptions::default();
270 let raw_opts = opts.map_or(&mut default.raw, |opts| &mut opts.raw);
271 let summary = commit.summary_bytes().unwrap();
272 let mut message = commit.message_bytes();
273 assert!(message.starts_with(summary));
274 message = &message[summary.len()..];
275 raw_opts.patch_no = patch_no;
276 raw_opts.total_patches = total_patches;
277 let id = commit.id();
278 raw_opts.id = id.raw();
279 raw_opts.summary = summary.as_ptr() as *const _;
280 raw_opts.body = message.as_ptr() as *const _;
281 raw_opts.author = commit.author().raw();
282 let buf = Buf::new();
283 #[allow(deprecated)]
284 unsafe {
285 try_call!(raw::git_diff_format_email(buf.raw(), self.raw, &*raw_opts));
286 }
287 Ok(buf)
288 }
289
290 /// Create 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],
294 };
295 unsafe {
296 try_call!(raw::git_diff_patchid(
297 &mut raw,
298 self.raw,
299 opts.map(|o| &mut o.raw)
300 ));
301 Ok(Binding::from_raw(&raw as *const _))
302 }
303 }
304
305 // TODO: num_deltas_of_type, find_similar
306 }
307 impl Diff<'static> {
308 /// Read the contents of a git patch file into a `git_diff` object.
309 ///
310 /// The diff object produced is similar to the one that would be
311 /// produced if you actually produced it computationally by comparing
312 /// two trees, however there may be subtle differences. For example,
313 /// a patch file likely contains abbreviated object IDs, so the
314 /// object IDs parsed by this function will also be abbreviated.
315 pub fn from_buffer(buffer: &[u8]) -> Result<Diff<'static>, Error> {
316 crate::init();
317 let mut diff: *mut raw::git_diff = std::ptr::null_mut();
318 unsafe {
319 // NOTE: Doesn't depend on repo, so lifetime can be 'static
320 try_call!(raw::git_diff_from_buffer(
321 &mut diff,
322 buffer.as_ptr() as *const c_char,
323 buffer.len()
324 ));
325 Ok(Diff::from_raw(diff))
326 }
327 }
328 }
329
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,
334 data: *mut c_void,
335 ) -> c_int {
336 unsafe {
337 let delta = Binding::from_raw(delta as *mut _);
338 let hunk = Binding::from_raw_opt(hunk);
339 let line = Binding::from_raw(line);
340
341 let r = panic::wrap(|| {
342 let data = data as *mut &mut PrintCb<'_>;
343 (*data)(delta, hunk, line)
344 });
345 if r == Some(true) {
346 raw::GIT_OK
347 } else {
348 raw::GIT_EUSER
349 }
350 }
351 }
352
353 pub extern "C" fn file_cb_c(
354 delta: *const raw::git_diff_delta,
355 progress: f32,
356 data: *mut c_void,
357 ) -> c_int {
358 unsafe {
359 let delta = Binding::from_raw(delta as *mut _);
360
361 let r = panic::wrap(|| {
362 let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
363 match (*cbs).file {
364 Some(ref mut cb) => cb(delta, progress),
365 None => false,
366 }
367 });
368 if r == Some(true) {
369 raw::GIT_OK
370 } else {
371 raw::GIT_EUSER
372 }
373 }
374 }
375
376 pub extern "C" fn binary_cb_c(
377 delta: *const raw::git_diff_delta,
378 binary: *const raw::git_diff_binary,
379 data: *mut c_void,
380 ) -> c_int {
381 unsafe {
382 let delta = Binding::from_raw(delta as *mut _);
383 let binary = Binding::from_raw(binary);
384
385 let r = panic::wrap(|| {
386 let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
387 match (*cbs).binary {
388 Some(ref mut cb) => cb(delta, binary),
389 None => false,
390 }
391 });
392 if r == Some(true) {
393 raw::GIT_OK
394 } else {
395 raw::GIT_EUSER
396 }
397 }
398 }
399
400 pub extern "C" fn hunk_cb_c(
401 delta: *const raw::git_diff_delta,
402 hunk: *const raw::git_diff_hunk,
403 data: *mut c_void,
404 ) -> c_int {
405 unsafe {
406 let delta = Binding::from_raw(delta as *mut _);
407 let hunk = Binding::from_raw(hunk);
408
409 let r = panic::wrap(|| {
410 let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
411 match (*cbs).hunk {
412 Some(ref mut cb) => cb(delta, hunk),
413 None => false,
414 }
415 });
416 if r == Some(true) {
417 raw::GIT_OK
418 } else {
419 raw::GIT_EUSER
420 }
421 }
422 }
423
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,
428 data: *mut c_void,
429 ) -> c_int {
430 unsafe {
431 let delta = Binding::from_raw(delta as *mut _);
432 let hunk = Binding::from_raw_opt(hunk);
433 let line = Binding::from_raw(line);
434
435 let r = panic::wrap(|| {
436 let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
437 match (*cbs).line {
438 Some(ref mut cb) => cb(delta, hunk, line),
439 None => false,
440 }
441 });
442 if r == Some(true) {
443 raw::GIT_OK
444 } else {
445 raw::GIT_EUSER
446 }
447 }
448 }
449
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> {
453 Diff {
454 raw,
455 _marker: marker::PhantomData,
456 }
457 }
458 fn raw(&self) -> *mut raw::git_diff {
459 self.raw
460 }
461 }
462
463 impl<'repo> Drop for Diff<'repo> {
464 fn drop(&mut self) {
465 unsafe { raw::git_diff_free(self.raw) }
466 }
467 }
468
469 impl<'a> DiffDelta<'a> {
470 /// Returns the flags on the delta.
471 ///
472 /// For more information, see `DiffFlags`'s documentation.
473 pub fn flags(&self) -> DiffFlags {
474 let flags = unsafe { (*self.raw).flags };
475 let mut result = DiffFlags::empty();
476
477 #[cfg(target_env = "msvc")]
478 fn as_u32(flag: i32) -> u32 {
479 flag as u32
480 }
481 #[cfg(not(target_env = "msvc"))]
482 fn as_u32(flag: u32) -> u32 {
483 flag
484 }
485
486 if (flags & as_u32(raw::GIT_DIFF_FLAG_BINARY)) != 0 {
487 result |= DiffFlags::BINARY;
488 }
489 if (flags & as_u32(raw::GIT_DIFF_FLAG_NOT_BINARY)) != 0 {
490 result |= DiffFlags::NOT_BINARY;
491 }
492 if (flags & as_u32(raw::GIT_DIFF_FLAG_VALID_ID)) != 0 {
493 result |= DiffFlags::VALID_ID;
494 }
495 if (flags & as_u32(raw::GIT_DIFF_FLAG_EXISTS)) != 0 {
496 result |= DiffFlags::EXISTS;
497 }
498 result
499 }
500
501 // TODO: expose when diffs are more exposed
502 // pub fn similarity(&self) -> u16 {
503 // unsafe { (*self.raw).similarity }
504 // }
505
506 /// Returns the number of files in this delta.
507 pub fn nfiles(&self) -> u16 {
508 unsafe { (*self.raw).nfiles }
509 }
510
511 /// Returns the status of this entry
512 ///
513 /// For more information, see `Delta`'s documentation
514 pub fn status(&self) -> Delta {
515 match unsafe { (*self.raw).status } {
516 raw::GIT_DELTA_UNMODIFIED => Delta::Unmodified,
517 raw::GIT_DELTA_ADDED => Delta::Added,
518 raw::GIT_DELTA_DELETED => Delta::Deleted,
519 raw::GIT_DELTA_MODIFIED => Delta::Modified,
520 raw::GIT_DELTA_RENAMED => Delta::Renamed,
521 raw::GIT_DELTA_COPIED => Delta::Copied,
522 raw::GIT_DELTA_IGNORED => Delta::Ignored,
523 raw::GIT_DELTA_UNTRACKED => Delta::Untracked,
524 raw::GIT_DELTA_TYPECHANGE => Delta::Typechange,
525 raw::GIT_DELTA_UNREADABLE => Delta::Unreadable,
526 raw::GIT_DELTA_CONFLICTED => Delta::Conflicted,
527 n => panic!("unknown diff status: {}", n),
528 }
529 }
530
531 /// Return the file which represents the "from" side of the diff.
532 ///
533 /// What side this means depends on the function that was used to generate
534 /// the diff and will be documented on the function itself.
535 pub fn old_file(&self) -> DiffFile<'a> {
536 unsafe { Binding::from_raw(&(*self.raw).old_file as *const _) }
537 }
538
539 /// Return the file which represents the "to" side of the diff.
540 ///
541 /// What side this means depends on the function that was used to generate
542 /// the diff and will be documented on the function itself.
543 pub fn new_file(&self) -> DiffFile<'a> {
544 unsafe { Binding::from_raw(&(*self.raw).new_file as *const _) }
545 }
546 }
547
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> {
551 DiffDelta {
552 raw,
553 _marker: marker::PhantomData,
554 }
555 }
556 fn raw(&self) -> *mut raw::git_diff_delta {
557 self.raw
558 }
559 }
560
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())
568 .finish()
569 }
570 }
571
572 impl<'a> DiffFile<'a> {
573 /// Returns the Oid of this item.
574 ///
575 /// If this entry represents an absent side of a diff (e.g. the `old_file`
576 /// of a `Added` delta), then the oid returned will be zeroes.
577 pub fn id(&self) -> Oid {
578 unsafe { Binding::from_raw(&(*self.raw).id as *const _) }
579 }
580
581 /// Returns the path, in bytes, of the entry relative to the working
582 /// directory of the repository.
583 pub fn path_bytes(&self) -> Option<&'a [u8]> {
584 static FOO: () = ();
585 unsafe { crate::opt_bytes(&FOO, (*self.raw).path) }
586 }
587
588 /// Returns the path of the entry relative to the working directory of the
589 /// repository.
590 pub fn path(&self) -> Option<&'a Path> {
591 self.path_bytes().map(util::bytes2path)
592 }
593
594 /// Returns the size of this entry, in bytes
595 pub fn size(&self) -> u64 {
596 unsafe { (*self.raw).size as u64 }
597 }
598
599 /// Returns `true` if file(s) are treated as binary data.
600 pub fn is_binary(&self) -> bool {
601 unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_BINARY as u32 != 0 }
602 }
603
604 /// Returns `true` if file(s) are treated as text data.
605 pub fn is_not_binary(&self) -> bool {
606 unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_NOT_BINARY as u32 != 0 }
607 }
608
609 /// Returns `true` if `id` value is known correct.
610 pub fn is_valid_id(&self) -> bool {
611 unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_VALID_ID as u32 != 0 }
612 }
613
614 /// Returns `true` if file exists at this side of the delta.
615 pub fn exists(&self) -> bool {
616 unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_EXISTS as u32 != 0 }
617 }
618
619 /// Returns file mode.
620 pub fn mode(&self) -> FileMode {
621 match unsafe { (*self.raw).mode.into() } {
622 raw::GIT_FILEMODE_UNREADABLE => FileMode::Unreadable,
623 raw::GIT_FILEMODE_TREE => FileMode::Tree,
624 raw::GIT_FILEMODE_BLOB => FileMode::Blob,
625 raw::GIT_FILEMODE_BLOB_GROUP_WRITABLE => FileMode::BlobGroupWritable,
626 raw::GIT_FILEMODE_BLOB_EXECUTABLE => FileMode::BlobExecutable,
627 raw::GIT_FILEMODE_LINK => FileMode::Link,
628 raw::GIT_FILEMODE_COMMIT => FileMode::Commit,
629 mode => panic!("unknown mode: {}", mode),
630 }
631 }
632 }
633
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> {
637 DiffFile {
638 raw,
639 _marker: marker::PhantomData,
640 }
641 }
642 fn raw(&self) -> *const raw::git_diff_file {
643 self.raw
644 }
645 }
646
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);
653 }
654 if let Some(path) = &self.path() {
655 ds.field("path", path);
656 }
657 ds.field("size", &self.size()).finish()
658 }
659 }
660
661 impl Default for DiffOptions {
662 fn default() -> Self {
663 Self::new()
664 }
665 }
666
667 impl DiffOptions {
668 /// Creates a new set of empty diff options.
669 ///
670 /// All flags and other options are defaulted to false or their otherwise
671 /// zero equivalents.
672 pub fn new() -> DiffOptions {
673 let mut opts = DiffOptions {
674 pathspec: Vec::new(),
675 pathspec_ptrs: Vec::new(),
676 raw: unsafe { mem::zeroed() },
677 old_prefix: None,
678 new_prefix: None,
679 };
680 assert_eq!(unsafe { raw::git_diff_init_options(&mut opts.raw, 1) }, 0);
681 opts
682 }
683
684 fn flag(&mut self, opt: raw::git_diff_option_t, val: bool) -> &mut DiffOptions {
685 let opt = opt as u32;
686 if val {
687 self.raw.flags |= opt;
688 } else {
689 self.raw.flags &= !opt;
690 }
691 self
692 }
693
694 /// Flag indicating whether the sides of the diff will be reversed.
695 pub fn reverse(&mut self, reverse: bool) -> &mut DiffOptions {
696 self.flag(raw::GIT_DIFF_REVERSE, reverse)
697 }
698
699 /// Flag indicating whether ignored files are included.
700 pub fn include_ignored(&mut self, include: bool) -> &mut DiffOptions {
701 self.flag(raw::GIT_DIFF_INCLUDE_IGNORED, include)
702 }
703
704 /// Flag indicating whether ignored directories are traversed deeply or not.
705 pub fn recurse_ignored_dirs(&mut self, recurse: bool) -> &mut DiffOptions {
706 self.flag(raw::GIT_DIFF_RECURSE_IGNORED_DIRS, recurse)
707 }
708
709 /// Flag indicating whether untracked files are in the diff
710 pub fn include_untracked(&mut self, include: bool) -> &mut DiffOptions {
711 self.flag(raw::GIT_DIFF_INCLUDE_UNTRACKED, include)
712 }
713
714 /// Flag indicating whether untracked directories are traversed deeply or
715 /// not.
716 pub fn recurse_untracked_dirs(&mut self, recurse: bool) -> &mut DiffOptions {
717 self.flag(raw::GIT_DIFF_RECURSE_UNTRACKED_DIRS, recurse)
718 }
719
720 /// Flag indicating whether unmodified files are in the diff.
721 pub fn include_unmodified(&mut self, include: bool) -> &mut DiffOptions {
722 self.flag(raw::GIT_DIFF_INCLUDE_UNMODIFIED, include)
723 }
724
725 /// If enabled, then Typechange delta records are generated.
726 pub fn include_typechange(&mut self, include: bool) -> &mut DiffOptions {
727 self.flag(raw::GIT_DIFF_INCLUDE_TYPECHANGE, include)
728 }
729
730 /// Event with `include_typechange`, the tree returned generally shows a
731 /// deleted blob. This flag correctly labels the tree transitions as a
732 /// typechange record with the `new_file`'s mode set to tree.
733 ///
734 /// Note that the tree SHA will not be available.
735 pub fn include_typechange_trees(&mut self, include: bool) -> &mut DiffOptions {
736 self.flag(raw::GIT_DIFF_INCLUDE_TYPECHANGE_TREES, include)
737 }
738
739 /// Flag indicating whether file mode changes are ignored.
740 pub fn ignore_filemode(&mut self, ignore: bool) -> &mut DiffOptions {
741 self.flag(raw::GIT_DIFF_IGNORE_FILEMODE, ignore)
742 }
743
744 /// Flag indicating whether all submodules should be treated as unmodified.
745 pub fn ignore_submodules(&mut self, ignore: bool) -> &mut DiffOptions {
746 self.flag(raw::GIT_DIFF_IGNORE_SUBMODULES, ignore)
747 }
748
749 /// Flag indicating whether case insensitive filenames should be used.
750 pub fn ignore_case(&mut self, ignore: bool) -> &mut DiffOptions {
751 self.flag(raw::GIT_DIFF_IGNORE_CASE, ignore)
752 }
753
754 /// If pathspecs are specified, this flag means that they should be applied
755 /// as an exact match instead of a fnmatch pattern.
756 pub fn disable_pathspec_match(&mut self, disable: bool) -> &mut DiffOptions {
757 self.flag(raw::GIT_DIFF_DISABLE_PATHSPEC_MATCH, disable)
758 }
759
760 /// Disable updating the `binary` flag in delta records. This is useful when
761 /// iterating over a diff if you don't need hunk and data callbacks and want
762 /// to avoid having to load a file completely.
763 pub fn skip_binary_check(&mut self, skip: bool) -> &mut DiffOptions {
764 self.flag(raw::GIT_DIFF_SKIP_BINARY_CHECK, skip)
765 }
766
767 /// When diff finds an untracked directory, to match the behavior of core
768 /// Git, it scans the contents for ignored and untracked files. If all
769 /// contents are ignored, then the directory is ignored; if any contents are
770 /// not ignored, then the directory is untracked. This is extra work that
771 /// may not matter in many cases.
772 ///
773 /// This flag turns off that scan and immediately labels an untracked
774 /// directory as untracked (changing the behavior to not match core git).
775 pub fn enable_fast_untracked_dirs(&mut self, enable: bool) -> &mut DiffOptions {
776 self.flag(raw::GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS, enable)
777 }
778
779 /// When diff finds a file in the working directory with stat information
780 /// different from the index, but the OID ends up being the same, write the
781 /// correct stat information into the index. Note: without this flag, diff
782 /// will always leave the index untouched.
783 pub fn update_index(&mut self, update: bool) -> &mut DiffOptions {
784 self.flag(raw::GIT_DIFF_UPDATE_INDEX, update)
785 }
786
787 /// Include unreadable files in the diff
788 pub fn include_unreadable(&mut self, include: bool) -> &mut DiffOptions {
789 self.flag(raw::GIT_DIFF_INCLUDE_UNREADABLE, include)
790 }
791
792 /// Include unreadable files in the diff as untracked files
793 pub fn include_unreadable_as_untracked(&mut self, include: bool) -> &mut DiffOptions {
794 self.flag(raw::GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED, include)
795 }
796
797 /// Treat all files as text, disabling binary attributes and detection.
798 pub fn force_text(&mut self, force: bool) -> &mut DiffOptions {
799 self.flag(raw::GIT_DIFF_FORCE_TEXT, force)
800 }
801
802 /// Treat all files as binary, disabling text diffs
803 pub fn force_binary(&mut self, force: bool) -> &mut DiffOptions {
804 self.flag(raw::GIT_DIFF_FORCE_BINARY, force)
805 }
806
807 /// Ignore all whitespace
808 pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut DiffOptions {
809 self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE, ignore)
810 }
811
812 /// Ignore changes in the amount of whitespace
813 pub fn ignore_whitespace_change(&mut self, ignore: bool) -> &mut DiffOptions {
814 self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE_CHANGE, ignore)
815 }
816
817 /// Ignore whitespace at the end of line
818 pub fn ignore_whitespace_eol(&mut self, ignore: bool) -> &mut DiffOptions {
819 self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE_EOL, ignore)
820 }
821
822 /// Ignore blank lines
823 pub fn ignore_blank_lines(&mut self, ignore: bool) -> &mut DiffOptions {
824 self.flag(raw::GIT_DIFF_IGNORE_BLANK_LINES, ignore)
825 }
826
827 /// When generating patch text, include the content of untracked files.
828 ///
829 /// This automatically turns on `include_untracked` but it does not turn on
830 /// `recurse_untracked_dirs`. Add that flag if you want the content of every
831 /// single untracked file.
832 pub fn show_untracked_content(&mut self, show: bool) -> &mut DiffOptions {
833 self.flag(raw::GIT_DIFF_SHOW_UNTRACKED_CONTENT, show)
834 }
835
836 /// When generating output, include the names of unmodified files if they
837 /// are included in the `Diff`. Normally these are skipped in the formats
838 /// that list files (e.g. name-only, name-status, raw). Even with this these
839 /// will not be included in the patch format.
840 pub fn show_unmodified(&mut self, show: bool) -> &mut DiffOptions {
841 self.flag(raw::GIT_DIFF_SHOW_UNMODIFIED, show)
842 }
843
844 /// Use the "patience diff" algorithm
845 pub fn patience(&mut self, patience: bool) -> &mut DiffOptions {
846 self.flag(raw::GIT_DIFF_PATIENCE, patience)
847 }
848
849 /// Take extra time to find the minimal diff
850 pub fn minimal(&mut self, minimal: bool) -> &mut DiffOptions {
851 self.flag(raw::GIT_DIFF_MINIMAL, minimal)
852 }
853
854 /// Include the necessary deflate/delta information so that `git-apply` can
855 /// apply given diff information to binary files.
856 pub fn show_binary(&mut self, show: bool) -> &mut DiffOptions {
857 self.flag(raw::GIT_DIFF_SHOW_BINARY, show)
858 }
859
860 /// Use a heuristic that takes indentation and whitespace into account
861 /// which generally can produce better diffs when dealing with ambiguous
862 /// diff hunks.
863 pub fn indent_heuristic(&mut self, heuristic: bool) -> &mut DiffOptions {
864 self.flag(raw::GIT_DIFF_INDENT_HEURISTIC, heuristic)
865 }
866
867 /// Set the number of unchanged lines that define the boundary of a hunk
868 /// (and to display before and after).
869 ///
870 /// The default value for this is 3.
871 pub fn context_lines(&mut self, lines: u32) -> &mut DiffOptions {
872 self.raw.context_lines = lines;
873 self
874 }
875
876 /// Set the maximum number of unchanged lines between hunk boundaries before
877 /// the hunks will be merged into one.
878 ///
879 /// The default value for this is 0.
880 pub fn interhunk_lines(&mut self, lines: u32) -> &mut DiffOptions {
881 self.raw.interhunk_lines = lines;
882 self
883 }
884
885 /// The default value for this is `core.abbrev` or 7 if unset.
886 pub fn id_abbrev(&mut self, abbrev: u16) -> &mut DiffOptions {
887 self.raw.id_abbrev = abbrev;
888 self
889 }
890
891 /// Maximum size (in bytes) above which a blob will be marked as binary
892 /// automatically.
893 ///
894 /// A negative value will disable this entirely.
895 ///
896 /// The default value for this is 512MB.
897 pub fn max_size(&mut self, size: i64) -> &mut DiffOptions {
898 self.raw.max_size = size as raw::git_off_t;
899 self
900 }
901
902 /// The virtual "directory" to prefix old file names with in hunk headers.
903 ///
904 /// The default value for this is "a".
905 pub fn old_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions {
906 self.old_prefix = Some(t.into_c_string().unwrap());
907 self
908 }
909
910 /// The virtual "directory" to prefix new file names with in hunk headers.
911 ///
912 /// The default value for this is "b".
913 pub fn new_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions {
914 self.new_prefix = Some(t.into_c_string().unwrap());
915 self
916 }
917
918 /// Add to the array of paths/fnmatch patterns to constrain the diff.
919 pub fn pathspec<T: IntoCString>(&mut self, pathspec: T) -> &mut DiffOptions {
920 let s = util::cstring_to_repo_path(pathspec).unwrap();
921 self.pathspec_ptrs.push(s.as_ptr());
922 self.pathspec.push(s);
923 self
924 }
925
926 /// Acquire a pointer to the underlying raw options.
927 ///
928 /// This function is unsafe as the pointer is only valid so long as this
929 /// structure is not moved, modified, or used elsewhere.
930 pub unsafe fn raw(&mut self) -> *const raw::git_diff_options {
931 self.raw.old_prefix = self
932 .old_prefix
933 .as_ref()
934 .map(|s| s.as_ptr())
935 .unwrap_or(ptr::null());
936 self.raw.new_prefix = self
937 .new_prefix
938 .as_ref()
939 .map(|s| s.as_ptr())
940 .unwrap_or(ptr::null());
941 self.raw.pathspec.count = self.pathspec_ptrs.len() as size_t;
942 self.raw.pathspec.strings = self.pathspec_ptrs.as_ptr() as *mut _;
943 &self.raw as *const _
944 }
945
946 // TODO: expose ignore_submodules, notify_cb/notify_payload
947 }
948
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))
953 }
954 fn size_hint(&self) -> (usize, Option<usize>) {
955 self.range.size_hint()
956 }
957 }
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))
961 }
962 }
963 impl<'diff> FusedIterator for Deltas<'diff> {}
964
965 impl<'diff> ExactSizeIterator for Deltas<'diff> {}
966
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
971 Context,
972 ///
973 Addition,
974 ///
975 Deletion,
976 /// Both files have no LF at end
977 ContextEOFNL,
978 /// Old has no LF at end, new does
979 AddEOFNL,
980 /// Old has LF at end, new does not
981 DeleteEOFNL,
982 /// The following values will only be sent to a `git_diff_line_cb` when
983 /// the content of a diff is being formatted through `git_diff_print`.
984 FileHeader,
985 ///
986 HunkHeader,
987 /// For "Binary files x and y differ"
988 Binary,
989 }
990
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 {
994 match raw {
995 raw::GIT_DIFF_LINE_CONTEXT => DiffLineType::Context,
996 raw::GIT_DIFF_LINE_ADDITION => DiffLineType::Addition,
997 raw::GIT_DIFF_LINE_DELETION => DiffLineType::Deletion,
998 raw::GIT_DIFF_LINE_CONTEXT_EOFNL => DiffLineType::ContextEOFNL,
999 raw::GIT_DIFF_LINE_ADD_EOFNL => DiffLineType::AddEOFNL,
1000 raw::GIT_DIFF_LINE_DEL_EOFNL => DiffLineType::DeleteEOFNL,
1001 raw::GIT_DIFF_LINE_FILE_HDR => DiffLineType::FileHeader,
1002 raw::GIT_DIFF_LINE_HUNK_HDR => DiffLineType::HunkHeader,
1003 raw::GIT_DIFF_LINE_BINARY => DiffLineType::Binary,
1004 _ => panic!("Unknown git diff line type"),
1005 }
1006 }
1007 fn raw(&self) -> raw::git_diff_line_t {
1008 match *self {
1009 DiffLineType::Context => raw::GIT_DIFF_LINE_CONTEXT,
1010 DiffLineType::Addition => raw::GIT_DIFF_LINE_ADDITION,
1011 DiffLineType::Deletion => raw::GIT_DIFF_LINE_DELETION,
1012 DiffLineType::ContextEOFNL => raw::GIT_DIFF_LINE_CONTEXT_EOFNL,
1013 DiffLineType::AddEOFNL => raw::GIT_DIFF_LINE_ADD_EOFNL,
1014 DiffLineType::DeleteEOFNL => raw::GIT_DIFF_LINE_DEL_EOFNL,
1015 DiffLineType::FileHeader => raw::GIT_DIFF_LINE_FILE_HDR,
1016 DiffLineType::HunkHeader => raw::GIT_DIFF_LINE_HUNK_HDR,
1017 DiffLineType::Binary => raw::GIT_DIFF_LINE_BINARY,
1018 }
1019 }
1020 }
1021
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 } {
1026 n if n < 0 => None,
1027 n => Some(n as u32),
1028 }
1029 }
1030
1031 /// Line number in new file or `None` for deleted line
1032 pub fn new_lineno(&self) -> Option<u32> {
1033 match unsafe { (*self.raw).new_lineno } {
1034 n if n < 0 => None,
1035 n => Some(n as u32),
1036 }
1037 }
1038
1039 /// Number of newline characters in content
1040 pub fn num_lines(&self) -> u32 {
1041 unsafe { (*self.raw).num_lines as u32 }
1042 }
1043
1044 /// Offset in the original file to the content
1045 pub fn content_offset(&self) -> i64 {
1046 unsafe { (*self.raw).content_offset as i64 }
1047 }
1048
1049 /// Content of this line as bytes.
1050 pub fn content(&self) -> &'a [u8] {
1051 unsafe {
1052 slice::from_raw_parts(
1053 (*self.raw).content as *const u8,
1054 (*self.raw).content_len as usize,
1055 )
1056 }
1057 }
1058
1059 /// origin of this `DiffLine`.
1060 ///
1061 pub fn origin_value(&self) -> DiffLineType {
1062 unsafe { Binding::from_raw((*self.raw).origin as raw::git_diff_line_t) }
1063 }
1064
1065 /// Sigil showing the origin of this `DiffLine`.
1066 ///
1067 /// * ` ` - Line context
1068 /// * `+` - Line addition
1069 /// * `-` - Line deletion
1070 /// * `=` - Context (End of file)
1071 /// * `>` - Add (End of file)
1072 /// * `<` - Remove (End of file)
1073 /// * `F` - File header
1074 /// * `H` - Hunk header
1075 /// * `B` - Line binary
1076 pub fn origin(&self) -> char {
1077 match unsafe { (*self.raw).origin as raw::git_diff_line_t } {
1078 raw::GIT_DIFF_LINE_CONTEXT => ' ',
1079 raw::GIT_DIFF_LINE_ADDITION => '+',
1080 raw::GIT_DIFF_LINE_DELETION => '-',
1081 raw::GIT_DIFF_LINE_CONTEXT_EOFNL => '=',
1082 raw::GIT_DIFF_LINE_ADD_EOFNL => '>',
1083 raw::GIT_DIFF_LINE_DEL_EOFNL => '<',
1084 raw::GIT_DIFF_LINE_FILE_HDR => 'F',
1085 raw::GIT_DIFF_LINE_HUNK_HDR => 'H',
1086 raw::GIT_DIFF_LINE_BINARY => 'B',
1087 _ => ' ',
1088 }
1089 }
1090 }
1091
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> {
1095 DiffLine {
1096 raw,
1097 _marker: marker::PhantomData,
1098 }
1099 }
1100 fn raw(&self) -> *const raw::git_diff_line {
1101 self.raw
1102 }
1103 }
1104
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);
1110 }
1111 if let Some(new_lineno) = &self.new_lineno() {
1112 ds.field("new_lineno", new_lineno);
1113 }
1114 ds.field("num_lines", &self.num_lines())
1115 .field("content_offset", &self.content_offset())
1116 .field("content", &self.content())
1117 .field("origin", &self.origin())
1118 .finish()
1119 }
1120 }
1121
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 }
1126 }
1127
1128 /// Number of lines in old_file
1129 pub fn old_lines(&self) -> u32 {
1130 unsafe { (*self.raw).old_lines as u32 }
1131 }
1132
1133 /// Starting line number in new_file
1134 pub fn new_start(&self) -> u32 {
1135 unsafe { (*self.raw).new_start as u32 }
1136 }
1137
1138 /// Number of lines in new_file
1139 pub fn new_lines(&self) -> u32 {
1140 unsafe { (*self.raw).new_lines as u32 }
1141 }
1142
1143 /// Header text
1144 pub fn header(&self) -> &'a [u8] {
1145 unsafe {
1146 slice::from_raw_parts(
1147 (*self.raw).header.as_ptr() as *const u8,
1148 (*self.raw).header_len as usize,
1149 )
1150 }
1151 }
1152 }
1153
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> {
1157 DiffHunk {
1158 raw,
1159 _marker: marker::PhantomData,
1160 }
1161 }
1162 fn raw(&self) -> *const raw::git_diff_hunk {
1163 self.raw
1164 }
1165 }
1166
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())
1175 .finish()
1176 }
1177 }
1178
1179 impl DiffStats {
1180 /// Get the total number of files changed in a diff.
1181 pub fn files_changed(&self) -> usize {
1182 unsafe { raw::git_diff_stats_files_changed(&*self.raw) as usize }
1183 }
1184
1185 /// Get the total number of insertions in a diff
1186 pub fn insertions(&self) -> usize {
1187 unsafe { raw::git_diff_stats_insertions(&*self.raw) as usize }
1188 }
1189
1190 /// Get the total number of deletions in a diff
1191 pub fn deletions(&self) -> usize {
1192 unsafe { raw::git_diff_stats_deletions(&*self.raw) as usize }
1193 }
1194
1195 /// Print diff statistics to a Buf
1196 pub fn to_buf(&self, format: DiffStatsFormat, width: usize) -> Result<Buf, Error> {
1197 let buf = Buf::new();
1198 unsafe {
1199 try_call!(raw::git_diff_stats_to_buf(
1200 buf.raw(),
1201 self.raw,
1202 format.bits(),
1203 width as size_t
1204 ));
1205 }
1206 Ok(buf)
1207 }
1208 }
1209
1210 impl Binding for DiffStats {
1211 type Raw = *mut raw::git_diff_stats;
1212
1213 unsafe fn from_raw(raw: *mut raw::git_diff_stats) -> DiffStats {
1214 DiffStats { raw }
1215 }
1216 fn raw(&self) -> *mut raw::git_diff_stats {
1217 self.raw
1218 }
1219 }
1220
1221 impl Drop for DiffStats {
1222 fn drop(&mut self) {
1223 unsafe { raw::git_diff_stats_free(self.raw) }
1224 }
1225 }
1226
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())
1233 .finish()
1234 }
1235 }
1236
1237 impl<'a> DiffBinary<'a> {
1238 /// Returns whether there is data in this binary structure or not.
1239 ///
1240 /// If this is `true`, then this was produced and included binary content.
1241 /// If this is `false` then this was generated knowing only that a binary
1242 /// file changed but without providing the data, probably from a patch that
1243 /// said `Binary files a/file.txt and b/file.txt differ`.
1244 pub fn contains_data(&self) -> bool {
1245 unsafe { (*self.raw).contains_data == 1 }
1246 }
1247
1248 /// The contents of the old file.
1249 pub fn old_file(&self) -> DiffBinaryFile<'a> {
1250 unsafe { Binding::from_raw(&(*self.raw).old_file as *const _) }
1251 }
1252
1253 /// The contents of the new file.
1254 pub fn new_file(&self) -> DiffBinaryFile<'a> {
1255 unsafe { Binding::from_raw(&(*self.raw).new_file as *const _) }
1256 }
1257 }
1258
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> {
1262 DiffBinary {
1263 raw,
1264 _marker: marker::PhantomData,
1265 }
1266 }
1267 fn raw(&self) -> *const raw::git_diff_binary {
1268 self.raw
1269 }
1270 }
1271
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) }
1276 }
1277
1278 /// The binary data, deflated
1279 pub fn data(&self) -> &[u8] {
1280 unsafe {
1281 slice::from_raw_parts((*self.raw).data as *const u8, (*self.raw).datalen as usize)
1282 }
1283 }
1284
1285 /// The length of the binary data after inflation
1286 pub fn inflated_len(&self) -> usize {
1287 unsafe { (*self.raw).inflatedlen as usize }
1288 }
1289 }
1290
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> {
1294 DiffBinaryFile {
1295 raw,
1296 _marker: marker::PhantomData,
1297 }
1298 }
1299 fn raw(&self) -> *const raw::git_diff_binary_file {
1300 self.raw
1301 }
1302 }
1303
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 {
1307 match raw {
1308 raw::GIT_DIFF_BINARY_NONE => DiffBinaryKind::None,
1309 raw::GIT_DIFF_BINARY_LITERAL => DiffBinaryKind::Literal,
1310 raw::GIT_DIFF_BINARY_DELTA => DiffBinaryKind::Delta,
1311 _ => panic!("Unknown git diff binary kind"),
1312 }
1313 }
1314 fn raw(&self) -> raw::git_diff_binary_t {
1315 match *self {
1316 DiffBinaryKind::None => raw::GIT_DIFF_BINARY_NONE,
1317 DiffBinaryKind::Literal => raw::GIT_DIFF_BINARY_LITERAL,
1318 DiffBinaryKind::Delta => raw::GIT_DIFF_BINARY_DELTA,
1319 }
1320 }
1321 }
1322
1323 impl Default for DiffFindOptions {
1324 fn default() -> Self {
1325 Self::new()
1326 }
1327 }
1328
1329 impl DiffFindOptions {
1330 /// Creates a new set of empty diff find options.
1331 ///
1332 /// All flags and other options are defaulted to false or their otherwise
1333 /// zero equivalents.
1334 pub fn new() -> DiffFindOptions {
1335 let mut opts = DiffFindOptions {
1336 raw: unsafe { mem::zeroed() },
1337 };
1338 assert_eq!(
1339 unsafe { raw::git_diff_find_init_options(&mut opts.raw, 1) },
1340 0
1341 );
1342 opts
1343 }
1344
1345 fn flag(&mut self, opt: u32, val: bool) -> &mut DiffFindOptions {
1346 if val {
1347 self.raw.flags |= opt;
1348 } else {
1349 self.raw.flags &= !opt;
1350 }
1351 self
1352 }
1353
1354 /// Reset all flags back to their unset state, indicating that
1355 /// `diff.renames` should be used instead. This is overridden once any flag
1356 /// is set.
1357 pub fn by_config(&mut self) -> &mut DiffFindOptions {
1358 self.flag(0xffffffff, false)
1359 }
1360
1361 /// Look for renames?
1362 pub fn renames(&mut self, find: bool) -> &mut DiffFindOptions {
1363 self.flag(raw::GIT_DIFF_FIND_RENAMES, find)
1364 }
1365
1366 /// Consider old side of modified for renames?
1367 pub fn renames_from_rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
1368 self.flag(raw::GIT_DIFF_FIND_RENAMES_FROM_REWRITES, find)
1369 }
1370
1371 /// Look for copies?
1372 pub fn copies(&mut self, find: bool) -> &mut DiffFindOptions {
1373 self.flag(raw::GIT_DIFF_FIND_COPIES, find)
1374 }
1375
1376 /// Consider unmodified as copy sources?
1377 ///
1378 /// For this to work correctly, use `include_unmodified` when the initial
1379 /// diff is being generated.
1380 pub fn copies_from_unmodified(&mut self, find: bool) -> &mut DiffFindOptions {
1381 self.flag(raw::GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED, find)
1382 }
1383
1384 /// Mark significant rewrites for split.
1385 pub fn rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
1386 self.flag(raw::GIT_DIFF_FIND_REWRITES, find)
1387 }
1388
1389 /// Actually split large rewrites into delete/add pairs
1390 pub fn break_rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
1391 self.flag(raw::GIT_DIFF_BREAK_REWRITES, find)
1392 }
1393
1394 #[doc(hidden)]
1395 pub fn break_rewries(&mut self, find: bool) -> &mut DiffFindOptions {
1396 self.break_rewrites(find)
1397 }
1398
1399 /// Find renames/copies for untracked items in working directory.
1400 ///
1401 /// For this to work correctly use the `include_untracked` option when the
1402 /// initial diff is being generated.
1403 pub fn for_untracked(&mut self, find: bool) -> &mut DiffFindOptions {
1404 self.flag(raw::GIT_DIFF_FIND_FOR_UNTRACKED, find)
1405 }
1406
1407 /// Turn on all finding features.
1408 pub fn all(&mut self, find: bool) -> &mut DiffFindOptions {
1409 self.flag(raw::GIT_DIFF_FIND_ALL, find)
1410 }
1411
1412 /// Measure similarity ignoring leading whitespace (default)
1413 pub fn ignore_leading_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions {
1414 self.flag(raw::GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE, ignore)
1415 }
1416
1417 /// Measure similarity ignoring all whitespace
1418 pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions {
1419 self.flag(raw::GIT_DIFF_FIND_IGNORE_WHITESPACE, ignore)
1420 }
1421
1422 /// Measure similarity including all data
1423 pub fn dont_ignore_whitespace(&mut self, dont: bool) -> &mut DiffFindOptions {
1424 self.flag(raw::GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE, dont)
1425 }
1426
1427 /// Measure similarity only by comparing SHAs (fast and cheap)
1428 pub fn exact_match_only(&mut self, exact: bool) -> &mut DiffFindOptions {
1429 self.flag(raw::GIT_DIFF_FIND_EXACT_MATCH_ONLY, exact)
1430 }
1431
1432 /// Do not break rewrites unless they contribute to a rename.
1433 ///
1434 /// Normally, `break_rewrites` and `rewrites` will measure the
1435 /// self-similarity of modified files and split the ones that have changed a
1436 /// lot into a delete/add pair. Then the sides of that pair will be
1437 /// considered candidates for rename and copy detection
1438 ///
1439 /// If you add this flag in and the split pair is not used for an actual
1440 /// rename or copy, then the modified record will be restored to a regular
1441 /// modified record instead of being split.
1442 pub fn break_rewrites_for_renames_only(&mut self, b: bool) -> &mut DiffFindOptions {
1443 self.flag(raw::GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY, b)
1444 }
1445
1446 /// Remove any unmodified deltas after find_similar is done.
1447 ///
1448 /// Using `copies_from_unmodified` to emulate the `--find-copies-harder`
1449 /// behavior requires building a diff with the `include_unmodified` flag. If
1450 /// you do not want unmodified records in the final result, pas this flag to
1451 /// have them removed.
1452 pub fn remove_unmodified(&mut self, remove: bool) -> &mut DiffFindOptions {
1453 self.flag(raw::GIT_DIFF_FIND_REMOVE_UNMODIFIED, remove)
1454 }
1455
1456 /// Similarity to consider a file renamed (default 50)
1457 pub fn rename_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1458 self.raw.rename_threshold = thresh;
1459 self
1460 }
1461
1462 /// Similarity of modified to be eligible rename source (default 50)
1463 pub fn rename_from_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1464 self.raw.rename_from_rewrite_threshold = thresh;
1465 self
1466 }
1467
1468 /// Similarity to consider a file copy (default 50)
1469 pub fn copy_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1470 self.raw.copy_threshold = thresh;
1471 self
1472 }
1473
1474 /// Similarity to split modify into delete/add pair (default 60)
1475 pub fn break_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1476 self.raw.break_rewrite_threshold = thresh;
1477 self
1478 }
1479
1480 /// Maximum similarity sources to examine for a file (somewhat like
1481 /// git-diff's `-l` option or `diff.renameLimit` config)
1482 ///
1483 /// Defaults to 200
1484 pub fn rename_limit(&mut self, limit: usize) -> &mut DiffFindOptions {
1485 self.raw.rename_limit = limit as size_t;
1486 self
1487 }
1488
1489 // TODO: expose git_diff_similarity_metric
1490
1491 /// Acquire a pointer to the underlying raw options.
1492 pub unsafe fn raw(&mut self) -> *const raw::git_diff_find_options {
1493 &self.raw
1494 }
1495 }
1496
1497 impl Default for DiffFormatEmailOptions {
1498 fn default() -> Self {
1499 Self::new()
1500 }
1501 }
1502
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() },
1509 };
1510 assert_eq!(
1511 unsafe { raw::git_diff_format_email_options_init(&mut opts.raw, 1) },
1512 0
1513 );
1514 opts
1515 }
1516
1517 fn flag(&mut self, opt: u32, val: bool) -> &mut Self {
1518 if val {
1519 self.raw.flags |= opt;
1520 } else {
1521 self.raw.flags &= !opt;
1522 }
1523 self
1524 }
1525
1526 /// Exclude `[PATCH]` from the subject header
1527 pub fn exclude_subject_patch_header(&mut self, should_exclude: bool) -> &mut Self {
1528 self.flag(
1529 raw::GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER,
1530 should_exclude,
1531 )
1532 }
1533 }
1534
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() },
1541 };
1542 assert_eq!(
1543 unsafe {
1544 raw::git_diff_patchid_options_init(
1545 &mut opts.raw,
1546 raw::GIT_DIFF_PATCHID_OPTIONS_VERSION,
1547 )
1548 },
1549 0
1550 );
1551 opts
1552 }
1553 }
1554
1555 #[cfg(test)]
1556 mod tests {
1557 use crate::{DiffLineType, DiffOptions, Oid, Signature, Time};
1558 use std::borrow::Borrow;
1559 use std::fs::File;
1560 use std::io::Write;
1561 use std::path::Path;
1562
1563 #[test]
1564 fn smoke() {
1565 let (_td, repo) = crate::test::repo_init();
1566 let diff = repo.diff_tree_to_workdir(None, None).unwrap();
1567 assert_eq!(diff.deltas().len(), 0);
1568 let stats = diff.stats().unwrap();
1569 assert_eq!(stats.insertions(), 0);
1570 assert_eq!(stats.deletions(), 0);
1571 assert_eq!(stats.files_changed(), 0);
1572 let patchid = diff.patchid(None).unwrap();
1573 assert_ne!(patchid, Oid::zero());
1574 }
1575
1576 #[test]
1577 fn foreach_smoke() {
1578 let (_td, repo) = crate::test::repo_init();
1579 let diff = t!(repo.diff_tree_to_workdir(None, None));
1580 let mut count = 0;
1581 t!(diff.foreach(
1582 &mut |_file, _progress| {
1583 count = count + 1;
1584 true
1585 },
1586 None,
1587 None,
1588 None
1589 ));
1590 assert_eq!(count, 0);
1591 }
1592
1593 #[test]
1594 fn foreach_file_only() {
1595 let path = Path::new("foo");
1596 let (td, repo) = crate::test::repo_init();
1597 t!(t!(File::create(&td.path().join(path))).write_all(b"bar"));
1598 let mut opts = DiffOptions::new();
1599 opts.include_untracked(true);
1600 let diff = t!(repo.diff_tree_to_workdir(None, Some(&mut opts)));
1601 let mut count = 0;
1602 let mut result = None;
1603 t!(diff.foreach(
1604 &mut |file, _progress| {
1605 count = count + 1;
1606 result = file.new_file().path().map(ToOwned::to_owned);
1607 true
1608 },
1609 None,
1610 None,
1611 None
1612 ));
1613 assert_eq!(result.as_ref().map(Borrow::borrow), Some(path));
1614 assert_eq!(count, 1);
1615 }
1616
1617 #[test]
1618 fn foreach_file_and_hunk() {
1619 let path = Path::new("foo");
1620 let (td, repo) = crate::test::repo_init();
1621 t!(t!(File::create(&td.path().join(path))).write_all(b"bar"));
1622 let mut index = t!(repo.index());
1623 t!(index.add_path(path));
1624 let mut opts = DiffOptions::new();
1625 opts.include_untracked(true);
1626 let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
1627 let mut new_lines = 0;
1628 t!(diff.foreach(
1629 &mut |_file, _progress| { true },
1630 None,
1631 Some(&mut |_file, hunk| {
1632 new_lines = hunk.new_lines();
1633 true
1634 }),
1635 None
1636 ));
1637 assert_eq!(new_lines, 1);
1638 }
1639
1640 #[test]
1641 fn foreach_all_callbacks() {
1642 let fib = vec![0, 1, 1, 2, 3, 5, 8];
1643 // Verified with a node implementation of deflate, might be worth
1644 // adding a deflate lib to do this inline here.
1645 let deflated_fib = vec![120, 156, 99, 96, 100, 100, 98, 102, 229, 0, 0, 0, 53, 0, 21];
1646 let foo_path = Path::new("foo");
1647 let bin_path = Path::new("bin");
1648 let (td, repo) = crate::test::repo_init();
1649 t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n"));
1650 t!(t!(File::create(&td.path().join(bin_path))).write_all(&fib));
1651 let mut index = t!(repo.index());
1652 t!(index.add_path(foo_path));
1653 t!(index.add_path(bin_path));
1654 let mut opts = DiffOptions::new();
1655 opts.include_untracked(true).show_binary(true);
1656 let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
1657 let mut bin_content = None;
1658 let mut new_lines = 0;
1659 let mut line_content = None;
1660 t!(diff.foreach(
1661 &mut |_file, _progress| { true },
1662 Some(&mut |_file, binary| {
1663 bin_content = Some(binary.new_file().data().to_owned());
1664 true
1665 }),
1666 Some(&mut |_file, hunk| {
1667 new_lines = hunk.new_lines();
1668 true
1669 }),
1670 Some(&mut |_file, _hunk, line| {
1671 line_content = String::from_utf8(line.content().into()).ok();
1672 true
1673 })
1674 ));
1675 assert_eq!(bin_content, Some(deflated_fib));
1676 assert_eq!(new_lines, 1);
1677 assert_eq!(line_content, Some("bar\n".to_string()));
1678 }
1679
1680 #[test]
1681 fn format_email_simple() {
1682 let (_td, repo) = crate::test::repo_init();
1683 const COMMIT_MESSAGE: &str = "Modify some content";
1684 const EXPECTED_EMAIL_START: &str = concat!(
1685 "From f1234fb0588b6ed670779a34ba5c51ef962f285f Mon Sep 17 00:00:00 2001\n",
1686 "From: Techcable <dummy@dummy.org>\n",
1687 "Date: Tue, 11 Jan 1972 17:46:40 +0000\n",
1688 "Subject: [PATCH] Modify some content\n",
1689 "\n",
1690 "---\n",
1691 " file1.txt | 8 +++++---\n",
1692 " 1 file changed, 5 insertions(+), 3 deletions(-)\n",
1693 "\n",
1694 "diff --git a/file1.txt b/file1.txt\n",
1695 "index 94aaae8..af8f41d 100644\n",
1696 "--- a/file1.txt\n",
1697 "+++ b/file1.txt\n",
1698 "@@ -1,15 +1,17 @@\n",
1699 " file1.txt\n",
1700 " file1.txt\n",
1701 "+_file1.txt_\n",
1702 " file1.txt\n",
1703 " file1.txt\n",
1704 " file1.txt\n",
1705 " file1.txt\n",
1706 "+\n",
1707 "+\n",
1708 " file1.txt\n",
1709 " file1.txt\n",
1710 " file1.txt\n",
1711 " file1.txt\n",
1712 " file1.txt\n",
1713 "-file1.txt\n",
1714 "-file1.txt\n",
1715 "-file1.txt\n",
1716 "+_file1.txt_\n",
1717 "+_file1.txt_\n",
1718 " file1.txt\n",
1719 "--\n"
1720 );
1721 const ORIGINAL_FILE: &str = concat!(
1722 "file1.txt\n",
1723 "file1.txt\n",
1724 "file1.txt\n",
1725 "file1.txt\n",
1726 "file1.txt\n",
1727 "file1.txt\n",
1728 "file1.txt\n",
1729 "file1.txt\n",
1730 "file1.txt\n",
1731 "file1.txt\n",
1732 "file1.txt\n",
1733 "file1.txt\n",
1734 "file1.txt\n",
1735 "file1.txt\n",
1736 "file1.txt\n"
1737 );
1738 const UPDATED_FILE: &str = concat!(
1739 "file1.txt\n",
1740 "file1.txt\n",
1741 "_file1.txt_\n",
1742 "file1.txt\n",
1743 "file1.txt\n",
1744 "file1.txt\n",
1745 "file1.txt\n",
1746 "\n",
1747 "\n",
1748 "file1.txt\n",
1749 "file1.txt\n",
1750 "file1.txt\n",
1751 "file1.txt\n",
1752 "file1.txt\n",
1753 "_file1.txt_\n",
1754 "_file1.txt_\n",
1755 "file1.txt\n"
1756 );
1757 const FILE_MODE: i32 = 0o100644;
1758 let original_file = repo.blob(ORIGINAL_FILE.as_bytes()).unwrap();
1759 let updated_file = repo.blob(UPDATED_FILE.as_bytes()).unwrap();
1760 let mut original_tree = repo.treebuilder(None).unwrap();
1761 original_tree
1762 .insert("file1.txt", original_file, FILE_MODE)
1763 .unwrap();
1764 let original_tree = original_tree.write().unwrap();
1765 let mut updated_tree = repo.treebuilder(None).unwrap();
1766 updated_tree
1767 .insert("file1.txt", updated_file, FILE_MODE)
1768 .unwrap();
1769 let updated_tree = updated_tree.write().unwrap();
1770 let time = Time::new(64_000_000, 0);
1771 let author = Signature::new("Techcable", "dummy@dummy.org", &time).unwrap();
1772 let updated_commit = repo
1773 .commit(
1774 None,
1775 &author,
1776 &author,
1777 COMMIT_MESSAGE,
1778 &repo.find_tree(updated_tree).unwrap(),
1779 &[], // NOTE: Have no parents to ensure stable hash
1780 )
1781 .unwrap();
1782 let updated_commit = repo.find_commit(updated_commit).unwrap();
1783 let mut diff = repo
1784 .diff_tree_to_tree(
1785 Some(&repo.find_tree(original_tree).unwrap()),
1786 Some(&repo.find_tree(updated_tree).unwrap()),
1787 None,
1788 )
1789 .unwrap();
1790 #[allow(deprecated)]
1791 let actual_email = diff.format_email(1, 1, &updated_commit, None).unwrap();
1792 let actual_email = actual_email.as_str().unwrap();
1793 assert!(
1794 actual_email.starts_with(EXPECTED_EMAIL_START),
1795 "Unexpected email:\n{}",
1796 actual_email
1797 );
1798 let mut remaining_lines = actual_email[EXPECTED_EMAIL_START.len()..].lines();
1799 let version_line = remaining_lines.next();
1800 assert!(
1801 version_line.unwrap().starts_with("libgit2"),
1802 "Invalid version line: {:?}",
1803 version_line
1804 );
1805 while let Some(line) = remaining_lines.next() {
1806 assert_eq!(line.trim(), "")
1807 }
1808 }
1809
1810 #[test]
1811 fn foreach_diff_line_origin_value() {
1812 let foo_path = Path::new("foo");
1813 let (td, repo) = crate::test::repo_init();
1814 t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n"));
1815 let mut index = t!(repo.index());
1816 t!(index.add_path(foo_path));
1817 let mut opts = DiffOptions::new();
1818 opts.include_untracked(true);
1819 let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
1820 let mut origin_values: Vec<DiffLineType> = Vec::new();
1821 t!(diff.foreach(
1822 &mut |_file, _progress| { true },
1823 None,
1824 None,
1825 Some(&mut |_file, _hunk, line| {
1826 origin_values.push(line.origin_value());
1827 true
1828 })
1829 ));
1830 assert_eq!(origin_values.len(), 1);
1831 assert_eq!(origin_values[0], DiffLineType::Addition);
1832 }
1833
1834 #[test]
1835 fn foreach_exits_with_euser() {
1836 let foo_path = Path::new("foo");
1837 let bar_path = Path::new("foo");
1838
1839 let (td, repo) = crate::test::repo_init();
1840 t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n"));
1841
1842 let mut index = t!(repo.index());
1843 t!(index.add_path(foo_path));
1844 t!(index.add_path(bar_path));
1845
1846 let mut opts = DiffOptions::new();
1847 opts.include_untracked(true);
1848 let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
1849
1850 let mut calls = 0;
1851 let result = diff.foreach(
1852 &mut |_file, _progress| {
1853 calls += 1;
1854 false
1855 },
1856 None,
1857 None,
1858 None,
1859 );
1860
1861 assert_eq!(result.unwrap_err().code(), crate::ErrorCode::User);
1862 }
1863 }