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: 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: 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: 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 /// When generating patch text, include the content of untracked files.
818 ///
819 /// This automatically turns on `include_untracked` but it does not turn on
820 /// `recurse_untracked_dirs`. Add that flag if you want the content of every
821 /// single untracked file.
show_untracked_content(&mut self, show: bool) -> &mut DiffOptions822 pub fn show_untracked_content(&mut self, show: bool) -> &mut DiffOptions {
823 self.flag(raw::GIT_DIFF_SHOW_UNTRACKED_CONTENT, show)
824 }
825
826 /// When generating output, include the names of unmodified files if they
827 /// are included in the `Diff`. Normally these are skipped in the formats
828 /// that list files (e.g. name-only, name-status, raw). Even with this these
829 /// will not be included in the patch format.
show_unmodified(&mut self, show: bool) -> &mut DiffOptions830 pub fn show_unmodified(&mut self, show: bool) -> &mut DiffOptions {
831 self.flag(raw::GIT_DIFF_SHOW_UNMODIFIED, show)
832 }
833
834 /// Use the "patience diff" algorithm
patience(&mut self, patience: bool) -> &mut DiffOptions835 pub fn patience(&mut self, patience: bool) -> &mut DiffOptions {
836 self.flag(raw::GIT_DIFF_PATIENCE, patience)
837 }
838
839 /// Take extra time to find the minimal diff
minimal(&mut self, minimal: bool) -> &mut DiffOptions840 pub fn minimal(&mut self, minimal: bool) -> &mut DiffOptions {
841 self.flag(raw::GIT_DIFF_MINIMAL, minimal)
842 }
843
844 /// Include the necessary deflate/delta information so that `git-apply` can
845 /// apply given diff information to binary files.
show_binary(&mut self, show: bool) -> &mut DiffOptions846 pub fn show_binary(&mut self, show: bool) -> &mut DiffOptions {
847 self.flag(raw::GIT_DIFF_SHOW_BINARY, show)
848 }
849
850 /// Use a heuristic that takes indentation and whitespace into account
851 /// which generally can produce better diffs when dealing with ambiguous
852 /// diff hunks.
indent_heuristic(&mut self, heuristic: bool) -> &mut DiffOptions853 pub fn indent_heuristic(&mut self, heuristic: bool) -> &mut DiffOptions {
854 self.flag(raw::GIT_DIFF_INDENT_HEURISTIC, heuristic)
855 }
856
857 /// Set the number of unchanged lines that define the boundary of a hunk
858 /// (and to display before and after).
859 ///
860 /// The default value for this is 3.
context_lines(&mut self, lines: u32) -> &mut DiffOptions861 pub fn context_lines(&mut self, lines: u32) -> &mut DiffOptions {
862 self.raw.context_lines = lines;
863 self
864 }
865
866 /// Set the maximum number of unchanged lines between hunk boundaries before
867 /// the hunks will be merged into one.
868 ///
869 /// The default value for this is 0.
interhunk_lines(&mut self, lines: u32) -> &mut DiffOptions870 pub fn interhunk_lines(&mut self, lines: u32) -> &mut DiffOptions {
871 self.raw.interhunk_lines = lines;
872 self
873 }
874
875 /// The default value for this is `core.abbrev` or 7 if unset.
id_abbrev(&mut self, abbrev: u16) -> &mut DiffOptions876 pub fn id_abbrev(&mut self, abbrev: u16) -> &mut DiffOptions {
877 self.raw.id_abbrev = abbrev;
878 self
879 }
880
881 /// Maximum size (in bytes) above which a blob will be marked as binary
882 /// automatically.
883 ///
884 /// A negative value will disable this entirely.
885 ///
886 /// The default value for this is 512MB.
max_size(&mut self, size: i64) -> &mut DiffOptions887 pub fn max_size(&mut self, size: i64) -> &mut DiffOptions {
888 self.raw.max_size = size as raw::git_off_t;
889 self
890 }
891
892 /// The virtual "directory" to prefix old file names with in hunk headers.
893 ///
894 /// The default value for this is "a".
old_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions895 pub fn old_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions {
896 self.old_prefix = Some(t.into_c_string().unwrap());
897 self
898 }
899
900 /// The virtual "directory" to prefix new file names with in hunk headers.
901 ///
902 /// The default value for this is "b".
new_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions903 pub fn new_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions {
904 self.new_prefix = Some(t.into_c_string().unwrap());
905 self
906 }
907
908 /// Add to the array of paths/fnmatch patterns to constrain the diff.
pathspec<T: IntoCString>(&mut self, pathspec: T) -> &mut DiffOptions909 pub fn pathspec<T: IntoCString>(&mut self, pathspec: T) -> &mut DiffOptions {
910 let s = util::cstring_to_repo_path(pathspec).unwrap();
911 self.pathspec_ptrs.push(s.as_ptr());
912 self.pathspec.push(s);
913 self
914 }
915
916 /// Acquire a pointer to the underlying raw options.
917 ///
918 /// This function is unsafe as the pointer is only valid so long as this
919 /// structure is not moved, modified, or used elsewhere.
raw(&mut self) -> *const raw::git_diff_options920 pub unsafe fn raw(&mut self) -> *const raw::git_diff_options {
921 self.raw.old_prefix = self
922 .old_prefix
923 .as_ref()
924 .map(|s| s.as_ptr())
925 .unwrap_or(ptr::null());
926 self.raw.new_prefix = self
927 .new_prefix
928 .as_ref()
929 .map(|s| s.as_ptr())
930 .unwrap_or(ptr::null());
931 self.raw.pathspec.count = self.pathspec_ptrs.len() as size_t;
932 self.raw.pathspec.strings = self.pathspec_ptrs.as_ptr() as *mut _;
933 &self.raw as *const _
934 }
935
936 // TODO: expose ignore_submodules, notify_cb/notify_payload
937 }
938
939 impl<'diff> Iterator for Deltas<'diff> {
940 type Item = DiffDelta<'diff>;
next(&mut self) -> Option<DiffDelta<'diff>>941 fn next(&mut self) -> Option<DiffDelta<'diff>> {
942 self.range.next().and_then(|i| self.diff.get_delta(i))
943 }
size_hint(&self) -> (usize, Option<usize>)944 fn size_hint(&self) -> (usize, Option<usize>) {
945 self.range.size_hint()
946 }
947 }
948 impl<'diff> DoubleEndedIterator for Deltas<'diff> {
next_back(&mut self) -> Option<DiffDelta<'diff>>949 fn next_back(&mut self) -> Option<DiffDelta<'diff>> {
950 self.range.next_back().and_then(|i| self.diff.get_delta(i))
951 }
952 }
953 impl<'diff> ExactSizeIterator for Deltas<'diff> {}
954
955 /// Line origin constants.
956 #[derive(Copy, Clone, Debug, PartialEq)]
957 pub enum DiffLineType {
958 /// These values will be sent to `git_diff_line_cb` along with the line
959 Context,
960 ///
961 Addition,
962 ///
963 Deletion,
964 /// Both files have no LF at end
965 ContextEOFNL,
966 /// Old has no LF at end, new does
967 AddEOFNL,
968 /// Old has LF at end, new does not
969 DeleteEOFNL,
970 /// The following values will only be sent to a `git_diff_line_cb` when
971 /// the content of a diff is being formatted through `git_diff_print`.
972 FileHeader,
973 ///
974 HunkHeader,
975 /// For "Binary files x and y differ"
976 Binary,
977 }
978
979 impl Binding for DiffLineType {
980 type Raw = raw::git_diff_line_t;
from_raw(raw: raw::git_diff_line_t) -> Self981 unsafe fn from_raw(raw: raw::git_diff_line_t) -> Self {
982 match raw {
983 raw::GIT_DIFF_LINE_CONTEXT => DiffLineType::Context,
984 raw::GIT_DIFF_LINE_ADDITION => DiffLineType::Addition,
985 raw::GIT_DIFF_LINE_DELETION => DiffLineType::Deletion,
986 raw::GIT_DIFF_LINE_CONTEXT_EOFNL => DiffLineType::ContextEOFNL,
987 raw::GIT_DIFF_LINE_ADD_EOFNL => DiffLineType::AddEOFNL,
988 raw::GIT_DIFF_LINE_DEL_EOFNL => DiffLineType::DeleteEOFNL,
989 raw::GIT_DIFF_LINE_FILE_HDR => DiffLineType::FileHeader,
990 raw::GIT_DIFF_LINE_HUNK_HDR => DiffLineType::HunkHeader,
991 raw::GIT_DIFF_LINE_BINARY => DiffLineType::Binary,
992 _ => panic!("Unknown git diff line type"),
993 }
994 }
raw(&self) -> raw::git_diff_line_t995 fn raw(&self) -> raw::git_diff_line_t {
996 match *self {
997 DiffLineType::Context => raw::GIT_DIFF_LINE_CONTEXT,
998 DiffLineType::Addition => raw::GIT_DIFF_LINE_ADDITION,
999 DiffLineType::Deletion => raw::GIT_DIFF_LINE_DELETION,
1000 DiffLineType::ContextEOFNL => raw::GIT_DIFF_LINE_CONTEXT_EOFNL,
1001 DiffLineType::AddEOFNL => raw::GIT_DIFF_LINE_ADD_EOFNL,
1002 DiffLineType::DeleteEOFNL => raw::GIT_DIFF_LINE_DEL_EOFNL,
1003 DiffLineType::FileHeader => raw::GIT_DIFF_LINE_FILE_HDR,
1004 DiffLineType::HunkHeader => raw::GIT_DIFF_LINE_HUNK_HDR,
1005 DiffLineType::Binary => raw::GIT_DIFF_LINE_BINARY,
1006 }
1007 }
1008 }
1009
1010 impl<'a> DiffLine<'a> {
1011 /// Line number in old file or `None` for added line
old_lineno(&self) -> Option<u32>1012 pub fn old_lineno(&self) -> Option<u32> {
1013 match unsafe { (*self.raw).old_lineno } {
1014 n if n < 0 => None,
1015 n => Some(n as u32),
1016 }
1017 }
1018
1019 /// Line number in new file or `None` for deleted line
new_lineno(&self) -> Option<u32>1020 pub fn new_lineno(&self) -> Option<u32> {
1021 match unsafe { (*self.raw).new_lineno } {
1022 n if n < 0 => None,
1023 n => Some(n as u32),
1024 }
1025 }
1026
1027 /// Number of newline characters in content
num_lines(&self) -> u321028 pub fn num_lines(&self) -> u32 {
1029 unsafe { (*self.raw).num_lines as u32 }
1030 }
1031
1032 /// Offset in the original file to the content
content_offset(&self) -> i641033 pub fn content_offset(&self) -> i64 {
1034 unsafe { (*self.raw).content_offset as i64 }
1035 }
1036
1037 /// Content of this line as bytes.
content(&self) -> &'a [u8]1038 pub fn content(&self) -> &'a [u8] {
1039 unsafe {
1040 slice::from_raw_parts(
1041 (*self.raw).content as *const u8,
1042 (*self.raw).content_len as usize,
1043 )
1044 }
1045 }
1046
1047 /// origin of this `DiffLine`.
1048 ///
origin_value(&self) -> DiffLineType1049 pub fn origin_value(&self) -> DiffLineType {
1050 unsafe { Binding::from_raw((*self.raw).origin as raw::git_diff_line_t) }
1051 }
1052
1053 /// Sigil showing the origin of this `DiffLine`.
1054 ///
1055 /// * ` ` - Line context
1056 /// * `+` - Line addition
1057 /// * `-` - Line deletion
1058 /// * `=` - Context (End of file)
1059 /// * `>` - Add (End of file)
1060 /// * `<` - Remove (End of file)
1061 /// * `F` - File header
1062 /// * `H` - Hunk header
1063 /// * `B` - Line binary
origin(&self) -> char1064 pub fn origin(&self) -> char {
1065 match unsafe { (*self.raw).origin as raw::git_diff_line_t } {
1066 raw::GIT_DIFF_LINE_CONTEXT => ' ',
1067 raw::GIT_DIFF_LINE_ADDITION => '+',
1068 raw::GIT_DIFF_LINE_DELETION => '-',
1069 raw::GIT_DIFF_LINE_CONTEXT_EOFNL => '=',
1070 raw::GIT_DIFF_LINE_ADD_EOFNL => '>',
1071 raw::GIT_DIFF_LINE_DEL_EOFNL => '<',
1072 raw::GIT_DIFF_LINE_FILE_HDR => 'F',
1073 raw::GIT_DIFF_LINE_HUNK_HDR => 'H',
1074 raw::GIT_DIFF_LINE_BINARY => 'B',
1075 _ => ' ',
1076 }
1077 }
1078 }
1079
1080 impl<'a> Binding for DiffLine<'a> {
1081 type Raw = *const raw::git_diff_line;
from_raw(raw: *const raw::git_diff_line) -> DiffLine<'a>1082 unsafe fn from_raw(raw: *const raw::git_diff_line) -> DiffLine<'a> {
1083 DiffLine {
1084 raw: raw,
1085 _marker: marker::PhantomData,
1086 }
1087 }
raw(&self) -> *const raw::git_diff_line1088 fn raw(&self) -> *const raw::git_diff_line {
1089 self.raw
1090 }
1091 }
1092
1093 impl<'a> std::fmt::Debug for DiffLine<'a> {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error>1094 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
1095 let mut ds = f.debug_struct("DiffLine");
1096 if let Some(old_lineno) = &self.old_lineno() {
1097 ds.field("old_lineno", old_lineno);
1098 }
1099 if let Some(new_lineno) = &self.new_lineno() {
1100 ds.field("new_lineno", new_lineno);
1101 }
1102 ds.field("num_lines", &self.num_lines())
1103 .field("content_offset", &self.content_offset())
1104 .field("content", &self.content())
1105 .field("origin", &self.origin())
1106 .finish()
1107 }
1108 }
1109
1110 impl<'a> DiffHunk<'a> {
1111 /// Starting line number in old_file
old_start(&self) -> u321112 pub fn old_start(&self) -> u32 {
1113 unsafe { (*self.raw).old_start as u32 }
1114 }
1115
1116 /// Number of lines in old_file
old_lines(&self) -> u321117 pub fn old_lines(&self) -> u32 {
1118 unsafe { (*self.raw).old_lines as u32 }
1119 }
1120
1121 /// Starting line number in new_file
new_start(&self) -> u321122 pub fn new_start(&self) -> u32 {
1123 unsafe { (*self.raw).new_start as u32 }
1124 }
1125
1126 /// Number of lines in new_file
new_lines(&self) -> u321127 pub fn new_lines(&self) -> u32 {
1128 unsafe { (*self.raw).new_lines as u32 }
1129 }
1130
1131 /// Header text
header(&self) -> &'a [u8]1132 pub fn header(&self) -> &'a [u8] {
1133 unsafe {
1134 slice::from_raw_parts(
1135 (*self.raw).header.as_ptr() as *const u8,
1136 (*self.raw).header_len as usize,
1137 )
1138 }
1139 }
1140 }
1141
1142 impl<'a> Binding for DiffHunk<'a> {
1143 type Raw = *const raw::git_diff_hunk;
from_raw(raw: *const raw::git_diff_hunk) -> DiffHunk<'a>1144 unsafe fn from_raw(raw: *const raw::git_diff_hunk) -> DiffHunk<'a> {
1145 DiffHunk {
1146 raw: raw,
1147 _marker: marker::PhantomData,
1148 }
1149 }
raw(&self) -> *const raw::git_diff_hunk1150 fn raw(&self) -> *const raw::git_diff_hunk {
1151 self.raw
1152 }
1153 }
1154
1155 impl<'a> std::fmt::Debug for DiffHunk<'a> {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error>1156 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
1157 f.debug_struct("DiffHunk")
1158 .field("old_start", &self.old_start())
1159 .field("old_lines", &self.old_lines())
1160 .field("new_start", &self.new_start())
1161 .field("new_lines", &self.new_lines())
1162 .field("header", &self.header())
1163 .finish()
1164 }
1165 }
1166
1167 impl DiffStats {
1168 /// Get the total number of files chaned in a diff.
files_changed(&self) -> usize1169 pub fn files_changed(&self) -> usize {
1170 unsafe { raw::git_diff_stats_files_changed(&*self.raw) as usize }
1171 }
1172
1173 /// Get the total number of insertions in a diff
insertions(&self) -> usize1174 pub fn insertions(&self) -> usize {
1175 unsafe { raw::git_diff_stats_insertions(&*self.raw) as usize }
1176 }
1177
1178 /// Get the total number of deletions in a diff
deletions(&self) -> usize1179 pub fn deletions(&self) -> usize {
1180 unsafe { raw::git_diff_stats_deletions(&*self.raw) as usize }
1181 }
1182
1183 /// Print diff statistics to a Buf
to_buf(&self, format: DiffStatsFormat, width: usize) -> Result<Buf, Error>1184 pub fn to_buf(&self, format: DiffStatsFormat, width: usize) -> Result<Buf, Error> {
1185 let buf = Buf::new();
1186 unsafe {
1187 try_call!(raw::git_diff_stats_to_buf(
1188 buf.raw(),
1189 self.raw,
1190 format.bits(),
1191 width as size_t
1192 ));
1193 }
1194 Ok(buf)
1195 }
1196 }
1197
1198 impl Binding for DiffStats {
1199 type Raw = *mut raw::git_diff_stats;
1200
from_raw(raw: *mut raw::git_diff_stats) -> DiffStats1201 unsafe fn from_raw(raw: *mut raw::git_diff_stats) -> DiffStats {
1202 DiffStats { raw: raw }
1203 }
raw(&self) -> *mut raw::git_diff_stats1204 fn raw(&self) -> *mut raw::git_diff_stats {
1205 self.raw
1206 }
1207 }
1208
1209 impl Drop for DiffStats {
drop(&mut self)1210 fn drop(&mut self) {
1211 unsafe { raw::git_diff_stats_free(self.raw) }
1212 }
1213 }
1214
1215 impl std::fmt::Debug for DiffStats {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error>1216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
1217 f.debug_struct("DiffStats")
1218 .field("files_changed", &self.files_changed())
1219 .field("insertions", &self.insertions())
1220 .field("deletions", &self.deletions())
1221 .finish()
1222 }
1223 }
1224
1225 impl<'a> DiffBinary<'a> {
1226 /// Returns whether there is data in this binary structure or not.
1227 ///
1228 /// If this is `true`, then this was produced and included binary content.
1229 /// If this is `false` then this was generated knowing only that a binary
1230 /// file changed but without providing the data, probably from a patch that
1231 /// said `Binary files a/file.txt and b/file.txt differ`.
contains_data(&self) -> bool1232 pub fn contains_data(&self) -> bool {
1233 unsafe { (*self.raw).contains_data == 1 }
1234 }
1235
1236 /// The contents of the old file.
old_file(&self) -> DiffBinaryFile<'a>1237 pub fn old_file(&self) -> DiffBinaryFile<'a> {
1238 unsafe { Binding::from_raw(&(*self.raw).old_file as *const _) }
1239 }
1240
1241 /// The contents of the new file.
new_file(&self) -> DiffBinaryFile<'a>1242 pub fn new_file(&self) -> DiffBinaryFile<'a> {
1243 unsafe { Binding::from_raw(&(*self.raw).new_file as *const _) }
1244 }
1245 }
1246
1247 impl<'a> Binding for DiffBinary<'a> {
1248 type Raw = *const raw::git_diff_binary;
from_raw(raw: *const raw::git_diff_binary) -> DiffBinary<'a>1249 unsafe fn from_raw(raw: *const raw::git_diff_binary) -> DiffBinary<'a> {
1250 DiffBinary {
1251 raw: raw,
1252 _marker: marker::PhantomData,
1253 }
1254 }
raw(&self) -> *const raw::git_diff_binary1255 fn raw(&self) -> *const raw::git_diff_binary {
1256 self.raw
1257 }
1258 }
1259
1260 impl<'a> DiffBinaryFile<'a> {
1261 /// The type of binary data for this file
kind(&self) -> DiffBinaryKind1262 pub fn kind(&self) -> DiffBinaryKind {
1263 unsafe { Binding::from_raw((*self.raw).kind) }
1264 }
1265
1266 /// The binary data, deflated
data(&self) -> &[u8]1267 pub fn data(&self) -> &[u8] {
1268 unsafe {
1269 slice::from_raw_parts((*self.raw).data as *const u8, (*self.raw).datalen as usize)
1270 }
1271 }
1272
1273 /// The length of the binary data after inflation
inflated_len(&self) -> usize1274 pub fn inflated_len(&self) -> usize {
1275 unsafe { (*self.raw).inflatedlen as usize }
1276 }
1277 }
1278
1279 impl<'a> Binding for DiffBinaryFile<'a> {
1280 type Raw = *const raw::git_diff_binary_file;
from_raw(raw: *const raw::git_diff_binary_file) -> DiffBinaryFile<'a>1281 unsafe fn from_raw(raw: *const raw::git_diff_binary_file) -> DiffBinaryFile<'a> {
1282 DiffBinaryFile {
1283 raw: raw,
1284 _marker: marker::PhantomData,
1285 }
1286 }
raw(&self) -> *const raw::git_diff_binary_file1287 fn raw(&self) -> *const raw::git_diff_binary_file {
1288 self.raw
1289 }
1290 }
1291
1292 impl Binding for DiffBinaryKind {
1293 type Raw = raw::git_diff_binary_t;
from_raw(raw: raw::git_diff_binary_t) -> DiffBinaryKind1294 unsafe fn from_raw(raw: raw::git_diff_binary_t) -> DiffBinaryKind {
1295 match raw {
1296 raw::GIT_DIFF_BINARY_NONE => DiffBinaryKind::None,
1297 raw::GIT_DIFF_BINARY_LITERAL => DiffBinaryKind::Literal,
1298 raw::GIT_DIFF_BINARY_DELTA => DiffBinaryKind::Delta,
1299 _ => panic!("Unknown git diff binary kind"),
1300 }
1301 }
raw(&self) -> raw::git_diff_binary_t1302 fn raw(&self) -> raw::git_diff_binary_t {
1303 match *self {
1304 DiffBinaryKind::None => raw::GIT_DIFF_BINARY_NONE,
1305 DiffBinaryKind::Literal => raw::GIT_DIFF_BINARY_LITERAL,
1306 DiffBinaryKind::Delta => raw::GIT_DIFF_BINARY_DELTA,
1307 }
1308 }
1309 }
1310
1311 impl Default for DiffFindOptions {
default() -> Self1312 fn default() -> Self {
1313 Self::new()
1314 }
1315 }
1316
1317 impl DiffFindOptions {
1318 /// Creates a new set of empty diff find options.
1319 ///
1320 /// All flags and other options are defaulted to false or their otherwise
1321 /// zero equivalents.
new() -> DiffFindOptions1322 pub fn new() -> DiffFindOptions {
1323 let mut opts = DiffFindOptions {
1324 raw: unsafe { mem::zeroed() },
1325 };
1326 assert_eq!(
1327 unsafe { raw::git_diff_find_init_options(&mut opts.raw, 1) },
1328 0
1329 );
1330 opts
1331 }
1332
flag(&mut self, opt: u32, val: bool) -> &mut DiffFindOptions1333 fn flag(&mut self, opt: u32, val: bool) -> &mut DiffFindOptions {
1334 if val {
1335 self.raw.flags |= opt;
1336 } else {
1337 self.raw.flags &= !opt;
1338 }
1339 self
1340 }
1341
1342 /// Reset all flags back to their unset state, indicating that
1343 /// `diff.renames` should be used instead. This is overridden once any flag
1344 /// is set.
by_config(&mut self) -> &mut DiffFindOptions1345 pub fn by_config(&mut self) -> &mut DiffFindOptions {
1346 self.flag(0xffffffff, false)
1347 }
1348
1349 /// Look for renames?
renames(&mut self, find: bool) -> &mut DiffFindOptions1350 pub fn renames(&mut self, find: bool) -> &mut DiffFindOptions {
1351 self.flag(raw::GIT_DIFF_FIND_RENAMES, find)
1352 }
1353
1354 /// Consider old side of modified for renames?
renames_from_rewrites(&mut self, find: bool) -> &mut DiffFindOptions1355 pub fn renames_from_rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
1356 self.flag(raw::GIT_DIFF_FIND_RENAMES_FROM_REWRITES, find)
1357 }
1358
1359 /// Look for copies?
copies(&mut self, find: bool) -> &mut DiffFindOptions1360 pub fn copies(&mut self, find: bool) -> &mut DiffFindOptions {
1361 self.flag(raw::GIT_DIFF_FIND_COPIES, find)
1362 }
1363
1364 /// Consider unmodified as copy sources?
1365 ///
1366 /// For this to work correctly, use `include_unmodified` when the initial
1367 /// diff is being generated.
copies_from_unmodified(&mut self, find: bool) -> &mut DiffFindOptions1368 pub fn copies_from_unmodified(&mut self, find: bool) -> &mut DiffFindOptions {
1369 self.flag(raw::GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED, find)
1370 }
1371
1372 /// Mark significant rewrites for split.
rewrites(&mut self, find: bool) -> &mut DiffFindOptions1373 pub fn rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
1374 self.flag(raw::GIT_DIFF_FIND_REWRITES, find)
1375 }
1376
1377 /// Actually split large rewrites into delete/add pairs
break_rewrites(&mut self, find: bool) -> &mut DiffFindOptions1378 pub fn break_rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
1379 self.flag(raw::GIT_DIFF_BREAK_REWRITES, find)
1380 }
1381
1382 #[doc(hidden)]
break_rewries(&mut self, find: bool) -> &mut DiffFindOptions1383 pub fn break_rewries(&mut self, find: bool) -> &mut DiffFindOptions {
1384 self.break_rewrites(find)
1385 }
1386
1387 /// Find renames/copies for untracked items in working directory.
1388 ///
1389 /// For this to work correctly use the `include_untracked` option when the
1390 /// initial diff is being generated.
for_untracked(&mut self, find: bool) -> &mut DiffFindOptions1391 pub fn for_untracked(&mut self, find: bool) -> &mut DiffFindOptions {
1392 self.flag(raw::GIT_DIFF_FIND_FOR_UNTRACKED, find)
1393 }
1394
1395 /// Turn on all finding features.
all(&mut self, find: bool) -> &mut DiffFindOptions1396 pub fn all(&mut self, find: bool) -> &mut DiffFindOptions {
1397 self.flag(raw::GIT_DIFF_FIND_ALL, find)
1398 }
1399
1400 /// Measure similarity ignoring leading whitespace (default)
ignore_leading_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions1401 pub fn ignore_leading_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions {
1402 self.flag(raw::GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE, ignore)
1403 }
1404
1405 /// Measure similarity ignoring all whitespace
ignore_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions1406 pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions {
1407 self.flag(raw::GIT_DIFF_FIND_IGNORE_WHITESPACE, ignore)
1408 }
1409
1410 /// Measure similarity including all data
dont_ignore_whitespace(&mut self, dont: bool) -> &mut DiffFindOptions1411 pub fn dont_ignore_whitespace(&mut self, dont: bool) -> &mut DiffFindOptions {
1412 self.flag(raw::GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE, dont)
1413 }
1414
1415 /// Measure similarity only by comparing SHAs (fast and cheap)
exact_match_only(&mut self, exact: bool) -> &mut DiffFindOptions1416 pub fn exact_match_only(&mut self, exact: bool) -> &mut DiffFindOptions {
1417 self.flag(raw::GIT_DIFF_FIND_EXACT_MATCH_ONLY, exact)
1418 }
1419
1420 /// Do not break rewrites unless they contribute to a rename.
1421 ///
1422 /// Normally, `break_rewrites` and `rewrites` will measure the
1423 /// self-similarity of modified files and split the ones that have changed a
1424 /// lot into a delete/add pair. Then the sides of that pair will be
1425 /// considered candidates for rename and copy detection
1426 ///
1427 /// If you add this flag in and the split pair is not used for an actual
1428 /// rename or copy, then the modified record will be restored to a regular
1429 /// modified record instead of being split.
break_rewrites_for_renames_only(&mut self, b: bool) -> &mut DiffFindOptions1430 pub fn break_rewrites_for_renames_only(&mut self, b: bool) -> &mut DiffFindOptions {
1431 self.flag(raw::GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY, b)
1432 }
1433
1434 /// Remove any unmodified deltas after find_similar is done.
1435 ///
1436 /// Using `copies_from_unmodified` to emulate the `--find-copies-harder`
1437 /// behavior requires building a diff with the `include_unmodified` flag. If
1438 /// you do not want unmodified records in the final result, pas this flag to
1439 /// have them removed.
remove_unmodified(&mut self, remove: bool) -> &mut DiffFindOptions1440 pub fn remove_unmodified(&mut self, remove: bool) -> &mut DiffFindOptions {
1441 self.flag(raw::GIT_DIFF_FIND_REMOVE_UNMODIFIED, remove)
1442 }
1443
1444 /// Similarity to consider a file renamed (default 50)
rename_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions1445 pub fn rename_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1446 self.raw.rename_threshold = thresh;
1447 self
1448 }
1449
1450 /// Similarity of modified to be glegible rename source (default 50)
rename_from_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions1451 pub fn rename_from_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1452 self.raw.rename_from_rewrite_threshold = thresh;
1453 self
1454 }
1455
1456 /// Similarity to consider a file copy (default 50)
copy_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions1457 pub fn copy_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1458 self.raw.copy_threshold = thresh;
1459 self
1460 }
1461
1462 /// Similarity to split modify into delete/add pair (default 60)
break_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions1463 pub fn break_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
1464 self.raw.break_rewrite_threshold = thresh;
1465 self
1466 }
1467
1468 /// Maximum similarity sources to examine for a file (somewhat like
1469 /// git-diff's `-l` option or `diff.renameLimit` config)
1470 ///
1471 /// Defaults to 200
rename_limit(&mut self, limit: usize) -> &mut DiffFindOptions1472 pub fn rename_limit(&mut self, limit: usize) -> &mut DiffFindOptions {
1473 self.raw.rename_limit = limit as size_t;
1474 self
1475 }
1476
1477 // TODO: expose git_diff_similarity_metric
1478 }
1479
1480 impl Default for DiffFormatEmailOptions {
default() -> Self1481 fn default() -> Self {
1482 Self::new()
1483 }
1484 }
1485
1486 impl DiffFormatEmailOptions {
1487 /// Creates a new set of email options,
1488 /// initialized to the default values
new() -> Self1489 pub fn new() -> Self {
1490 let mut opts = DiffFormatEmailOptions {
1491 raw: unsafe { mem::zeroed() },
1492 };
1493 assert_eq!(
1494 unsafe { raw::git_diff_format_email_options_init(&mut opts.raw, 1) },
1495 0
1496 );
1497 opts
1498 }
1499
flag(&mut self, opt: u32, val: bool) -> &mut Self1500 fn flag(&mut self, opt: u32, val: bool) -> &mut Self {
1501 if val {
1502 self.raw.flags |= opt;
1503 } else {
1504 self.raw.flags &= !opt;
1505 }
1506 self
1507 }
1508
1509 /// Exclude `[PATCH]` from the subject header
exclude_subject_patch_header(&mut self, should_exclude: bool) -> &mut Self1510 pub fn exclude_subject_patch_header(&mut self, should_exclude: bool) -> &mut Self {
1511 self.flag(
1512 raw::GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER,
1513 should_exclude,
1514 )
1515 }
1516 }
1517
1518 impl DiffPatchidOptions {
1519 /// Creates a new set of patchid options,
1520 /// initialized to the default values
new() -> Self1521 pub fn new() -> Self {
1522 let mut opts = DiffPatchidOptions {
1523 raw: unsafe { mem::zeroed() },
1524 };
1525 assert_eq!(
1526 unsafe {
1527 raw::git_diff_patchid_options_init(
1528 &mut opts.raw,
1529 raw::GIT_DIFF_PATCHID_OPTIONS_VERSION,
1530 )
1531 },
1532 0
1533 );
1534 opts
1535 }
1536 }
1537
1538 #[cfg(test)]
1539 mod tests {
1540 use crate::{DiffLineType, DiffOptions, Oid, Signature, Time};
1541 use std::borrow::Borrow;
1542 use std::fs::File;
1543 use std::io::Write;
1544 use std::path::Path;
1545
1546 #[test]
smoke()1547 fn smoke() {
1548 let (_td, repo) = crate::test::repo_init();
1549 let diff = repo.diff_tree_to_workdir(None, None).unwrap();
1550 assert_eq!(diff.deltas().len(), 0);
1551 let stats = diff.stats().unwrap();
1552 assert_eq!(stats.insertions(), 0);
1553 assert_eq!(stats.deletions(), 0);
1554 assert_eq!(stats.files_changed(), 0);
1555 let patchid = diff.patchid(None).unwrap();
1556 assert_ne!(patchid, Oid::zero());
1557 }
1558
1559 #[test]
foreach_smoke()1560 fn foreach_smoke() {
1561 let (_td, repo) = crate::test::repo_init();
1562 let diff = t!(repo.diff_tree_to_workdir(None, None));
1563 let mut count = 0;
1564 t!(diff.foreach(
1565 &mut |_file, _progress| {
1566 count = count + 1;
1567 true
1568 },
1569 None,
1570 None,
1571 None
1572 ));
1573 assert_eq!(count, 0);
1574 }
1575
1576 #[test]
foreach_file_only()1577 fn foreach_file_only() {
1578 let path = Path::new("foo");
1579 let (td, repo) = crate::test::repo_init();
1580 t!(t!(File::create(&td.path().join(path))).write_all(b"bar"));
1581 let mut opts = DiffOptions::new();
1582 opts.include_untracked(true);
1583 let diff = t!(repo.diff_tree_to_workdir(None, Some(&mut opts)));
1584 let mut count = 0;
1585 let mut result = None;
1586 t!(diff.foreach(
1587 &mut |file, _progress| {
1588 count = count + 1;
1589 result = file.new_file().path().map(ToOwned::to_owned);
1590 true
1591 },
1592 None,
1593 None,
1594 None
1595 ));
1596 assert_eq!(result.as_ref().map(Borrow::borrow), Some(path));
1597 assert_eq!(count, 1);
1598 }
1599
1600 #[test]
foreach_file_and_hunk()1601 fn foreach_file_and_hunk() {
1602 let path = Path::new("foo");
1603 let (td, repo) = crate::test::repo_init();
1604 t!(t!(File::create(&td.path().join(path))).write_all(b"bar"));
1605 let mut index = t!(repo.index());
1606 t!(index.add_path(path));
1607 let mut opts = DiffOptions::new();
1608 opts.include_untracked(true);
1609 let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
1610 let mut new_lines = 0;
1611 t!(diff.foreach(
1612 &mut |_file, _progress| { true },
1613 None,
1614 Some(&mut |_file, hunk| {
1615 new_lines = hunk.new_lines();
1616 true
1617 }),
1618 None
1619 ));
1620 assert_eq!(new_lines, 1);
1621 }
1622
1623 #[test]
foreach_all_callbacks()1624 fn foreach_all_callbacks() {
1625 let fib = vec![0, 1, 1, 2, 3, 5, 8];
1626 // Verified with a node implementation of deflate, might be worth
1627 // adding a deflate lib to do this inline here.
1628 let deflated_fib = vec![120, 156, 99, 96, 100, 100, 98, 102, 229, 0, 0, 0, 53, 0, 21];
1629 let foo_path = Path::new("foo");
1630 let bin_path = Path::new("bin");
1631 let (td, repo) = crate::test::repo_init();
1632 t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n"));
1633 t!(t!(File::create(&td.path().join(bin_path))).write_all(&fib));
1634 let mut index = t!(repo.index());
1635 t!(index.add_path(foo_path));
1636 t!(index.add_path(bin_path));
1637 let mut opts = DiffOptions::new();
1638 opts.include_untracked(true).show_binary(true);
1639 let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
1640 let mut bin_content = None;
1641 let mut new_lines = 0;
1642 let mut line_content = None;
1643 t!(diff.foreach(
1644 &mut |_file, _progress| { true },
1645 Some(&mut |_file, binary| {
1646 bin_content = Some(binary.new_file().data().to_owned());
1647 true
1648 }),
1649 Some(&mut |_file, hunk| {
1650 new_lines = hunk.new_lines();
1651 true
1652 }),
1653 Some(&mut |_file, _hunk, line| {
1654 line_content = String::from_utf8(line.content().into()).ok();
1655 true
1656 })
1657 ));
1658 assert_eq!(bin_content, Some(deflated_fib));
1659 assert_eq!(new_lines, 1);
1660 assert_eq!(line_content, Some("bar\n".to_string()));
1661 }
1662
1663 #[test]
format_email_simple()1664 fn format_email_simple() {
1665 let (_td, repo) = crate::test::repo_init();
1666 const COMMIT_MESSAGE: &str = "Modify some content";
1667 const EXPECTED_EMAIL_START: &str = concat!(
1668 "From f1234fb0588b6ed670779a34ba5c51ef962f285f Mon Sep 17 00:00:00 2001\n",
1669 "From: Techcable <dummy@dummy.org>\n",
1670 "Date: Tue, 11 Jan 1972 17:46:40 +0000\n",
1671 "Subject: [PATCH] Modify some content\n",
1672 "\n",
1673 "---\n",
1674 " file1.txt | 8 +++++---\n",
1675 " 1 file changed, 5 insertions(+), 3 deletions(-)\n",
1676 "\n",
1677 "diff --git a/file1.txt b/file1.txt\n",
1678 "index 94aaae8..af8f41d 100644\n",
1679 "--- a/file1.txt\n",
1680 "+++ b/file1.txt\n",
1681 "@@ -1,15 +1,17 @@\n",
1682 " file1.txt\n",
1683 " file1.txt\n",
1684 "+_file1.txt_\n",
1685 " file1.txt\n",
1686 " file1.txt\n",
1687 " file1.txt\n",
1688 " file1.txt\n",
1689 "+\n",
1690 "+\n",
1691 " file1.txt\n",
1692 " file1.txt\n",
1693 " file1.txt\n",
1694 " file1.txt\n",
1695 " file1.txt\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 "--\n"
1703 );
1704 const ORIGINAL_FILE: &str = concat!(
1705 "file1.txt\n",
1706 "file1.txt\n",
1707 "file1.txt\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 "file1.txt\n"
1720 );
1721 const UPDATED_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 "\n",
1730 "\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 "_file1.txt_\n",
1738 "file1.txt\n"
1739 );
1740 const FILE_MODE: i32 = 0o100644;
1741 let original_file = repo.blob(ORIGINAL_FILE.as_bytes()).unwrap();
1742 let updated_file = repo.blob(UPDATED_FILE.as_bytes()).unwrap();
1743 let mut original_tree = repo.treebuilder(None).unwrap();
1744 original_tree
1745 .insert("file1.txt", original_file, FILE_MODE)
1746 .unwrap();
1747 let original_tree = original_tree.write().unwrap();
1748 let mut updated_tree = repo.treebuilder(None).unwrap();
1749 updated_tree
1750 .insert("file1.txt", updated_file, FILE_MODE)
1751 .unwrap();
1752 let updated_tree = updated_tree.write().unwrap();
1753 let time = Time::new(64_000_000, 0);
1754 let author = Signature::new("Techcable", "dummy@dummy.org", &time).unwrap();
1755 let updated_commit = repo
1756 .commit(
1757 None,
1758 &author,
1759 &author,
1760 COMMIT_MESSAGE,
1761 &repo.find_tree(updated_tree).unwrap(),
1762 &[], // NOTE: Have no parents to ensure stable hash
1763 )
1764 .unwrap();
1765 let updated_commit = repo.find_commit(updated_commit).unwrap();
1766 let mut diff = repo
1767 .diff_tree_to_tree(
1768 Some(&repo.find_tree(original_tree).unwrap()),
1769 Some(&repo.find_tree(updated_tree).unwrap()),
1770 None,
1771 )
1772 .unwrap();
1773 let actual_email = diff.format_email(1, 1, &updated_commit, None).unwrap();
1774 let actual_email = actual_email.as_str().unwrap();
1775 assert!(
1776 actual_email.starts_with(EXPECTED_EMAIL_START),
1777 "Unexpected email:\n{}",
1778 actual_email
1779 );
1780 let mut remaining_lines = actual_email[EXPECTED_EMAIL_START.len()..].lines();
1781 let version_line = remaining_lines.next();
1782 assert!(
1783 version_line.unwrap().starts_with("libgit2"),
1784 "Invalid version line: {:?}",
1785 version_line
1786 );
1787 while let Some(line) = remaining_lines.next() {
1788 assert_eq!(line.trim(), "")
1789 }
1790 }
1791
1792 #[test]
foreach_diff_line_origin_value()1793 fn foreach_diff_line_origin_value() {
1794 let foo_path = Path::new("foo");
1795 let (td, repo) = crate::test::repo_init();
1796 t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n"));
1797 let mut index = t!(repo.index());
1798 t!(index.add_path(foo_path));
1799 let mut opts = DiffOptions::new();
1800 opts.include_untracked(true);
1801 let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
1802 let mut origin_values: Vec<DiffLineType> = Vec::new();
1803 t!(diff.foreach(
1804 &mut |_file, _progress| { true },
1805 None,
1806 None,
1807 Some(&mut |_file, _hunk, line| {
1808 origin_values.push(line.origin_value());
1809 true
1810 })
1811 ));
1812 assert_eq!(origin_values.len(), 1);
1813 assert_eq!(origin_values[0], DiffLineType::Addition);
1814 }
1815
1816 #[test]
foreach_exits_with_euser()1817 fn foreach_exits_with_euser() {
1818 let foo_path = Path::new("foo");
1819 let bar_path = Path::new("foo");
1820
1821 let (td, repo) = crate::test::repo_init();
1822 t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n"));
1823
1824 let mut index = t!(repo.index());
1825 t!(index.add_path(foo_path));
1826 t!(index.add_path(bar_path));
1827
1828 let mut opts = DiffOptions::new();
1829 opts.include_untracked(true);
1830 let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
1831
1832 let mut calls = 0;
1833 let result = diff.foreach(
1834 &mut |_file, _progress| {
1835 calls += 1;
1836 false
1837 },
1838 None,
1839 None,
1840 None,
1841 );
1842
1843 assert_eq!(result.unwrap_err().code(), crate::ErrorCode::User);
1844 }
1845 }
1846