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