1 use libc::{c_int, c_void};
2 use std::marker::PhantomData;
3 use std::path::Path;
4 use std::ptr;
5 
6 use crate::diff::{print_cb, LineCb};
7 use crate::util::{into_opt_c_string, Binding};
8 use crate::{raw, Blob, Buf, Diff, DiffDelta, DiffHunk, DiffLine, DiffOptions, Error};
9 
10 /// A structure representing the text changes in a single diff delta.
11 ///
12 /// This is an opaque structure.
13 pub struct Patch<'buffers> {
14     raw: *mut raw::git_patch,
15     buffers: PhantomData<&'buffers ()>,
16 }
17 
18 unsafe impl<'buffers> Send for Patch<'buffers> {}
19 
20 impl<'buffers> Binding for Patch<'buffers> {
21     type Raw = *mut raw::git_patch;
from_raw(raw: Self::Raw) -> Self22     unsafe fn from_raw(raw: Self::Raw) -> Self {
23         Patch {
24             raw,
25             buffers: PhantomData,
26         }
27     }
raw(&self) -> Self::Raw28     fn raw(&self) -> Self::Raw {
29         self.raw
30     }
31 }
32 
33 impl<'buffers> Drop for Patch<'buffers> {
drop(&mut self)34     fn drop(&mut self) {
35         unsafe { raw::git_patch_free(self.raw) }
36     }
37 }
38 
39 impl<'buffers> Patch<'buffers> {
40     /// Return a Patch for one file in a Diff.
41     ///
42     /// Returns Ok(None) for an unchanged or binary file.
from_diff(diff: &Diff<'buffers>, idx: usize) -> Result<Option<Self>, Error>43     pub fn from_diff(diff: &Diff<'buffers>, idx: usize) -> Result<Option<Self>, Error> {
44         let mut ret = ptr::null_mut();
45         unsafe {
46             try_call!(raw::git_patch_from_diff(&mut ret, diff.raw(), idx));
47             Ok(Binding::from_raw_opt(ret))
48         }
49     }
50 
51     /// Generate a Patch by diffing two blobs.
from_blobs( old_blob: &Blob<'buffers>, old_path: Option<&Path>, new_blob: &Blob<'buffers>, new_path: Option<&Path>, opts: Option<&mut DiffOptions>, ) -> Result<Self, Error>52     pub fn from_blobs(
53         old_blob: &Blob<'buffers>,
54         old_path: Option<&Path>,
55         new_blob: &Blob<'buffers>,
56         new_path: Option<&Path>,
57         opts: Option<&mut DiffOptions>,
58     ) -> Result<Self, Error> {
59         let mut ret = ptr::null_mut();
60         let old_path = into_opt_c_string(old_path)?;
61         let new_path = into_opt_c_string(new_path)?;
62         unsafe {
63             try_call!(raw::git_patch_from_blobs(
64                 &mut ret,
65                 old_blob.raw(),
66                 old_path,
67                 new_blob.raw(),
68                 new_path,
69                 opts.map(|s| s.raw())
70             ));
71             Ok(Binding::from_raw(ret))
72         }
73     }
74 
75     /// Generate a Patch by diffing a blob and a buffer.
from_blob_and_buffer( old_blob: &Blob<'buffers>, old_path: Option<&Path>, new_buffer: &'buffers [u8], new_path: Option<&Path>, opts: Option<&mut DiffOptions>, ) -> Result<Self, Error>76     pub fn from_blob_and_buffer(
77         old_blob: &Blob<'buffers>,
78         old_path: Option<&Path>,
79         new_buffer: &'buffers [u8],
80         new_path: Option<&Path>,
81         opts: Option<&mut DiffOptions>,
82     ) -> Result<Self, Error> {
83         let mut ret = ptr::null_mut();
84         let old_path = into_opt_c_string(old_path)?;
85         let new_path = into_opt_c_string(new_path)?;
86         unsafe {
87             try_call!(raw::git_patch_from_blob_and_buffer(
88                 &mut ret,
89                 old_blob.raw(),
90                 old_path,
91                 new_buffer.as_ptr() as *const c_void,
92                 new_buffer.len(),
93                 new_path,
94                 opts.map(|s| s.raw())
95             ));
96             Ok(Binding::from_raw(ret))
97         }
98     }
99 
100     /// Generate a Patch by diffing two buffers.
from_buffers( old_buffer: &'buffers [u8], old_path: Option<&Path>, new_buffer: &'buffers [u8], new_path: Option<&Path>, opts: Option<&mut DiffOptions>, ) -> Result<Self, Error>101     pub fn from_buffers(
102         old_buffer: &'buffers [u8],
103         old_path: Option<&Path>,
104         new_buffer: &'buffers [u8],
105         new_path: Option<&Path>,
106         opts: Option<&mut DiffOptions>,
107     ) -> Result<Self, Error> {
108         crate::init();
109         let mut ret = ptr::null_mut();
110         let old_path = into_opt_c_string(old_path)?;
111         let new_path = into_opt_c_string(new_path)?;
112         unsafe {
113             try_call!(raw::git_patch_from_buffers(
114                 &mut ret,
115                 old_buffer.as_ptr() as *const c_void,
116                 old_buffer.len(),
117                 old_path,
118                 new_buffer.as_ptr() as *const c_void,
119                 new_buffer.len(),
120                 new_path,
121                 opts.map(|s| s.raw())
122             ));
123             Ok(Binding::from_raw(ret))
124         }
125     }
126 
127     /// Get the DiffDelta associated with the Patch.
delta(&self) -> DiffDelta<'buffers>128     pub fn delta(&self) -> DiffDelta<'buffers> {
129         unsafe { Binding::from_raw(raw::git_patch_get_delta(self.raw) as *mut _) }
130     }
131 
132     /// Get the number of hunks in the Patch.
num_hunks(&self) -> usize133     pub fn num_hunks(&self) -> usize {
134         unsafe { raw::git_patch_num_hunks(self.raw) }
135     }
136 
137     /// Get the number of lines of context, additions, and deletions in the Patch.
line_stats(&self) -> Result<(usize, usize, usize), Error>138     pub fn line_stats(&self) -> Result<(usize, usize, usize), Error> {
139         let mut context = 0;
140         let mut additions = 0;
141         let mut deletions = 0;
142         unsafe {
143             try_call!(raw::git_patch_line_stats(
144                 &mut context,
145                 &mut additions,
146                 &mut deletions,
147                 self.raw
148             ));
149         }
150         Ok((context, additions, deletions))
151     }
152 
153     /// Get a DiffHunk and its total line count from the Patch.
hunk(&self, hunk_idx: usize) -> Result<(DiffHunk<'buffers>, usize), Error>154     pub fn hunk(&self, hunk_idx: usize) -> Result<(DiffHunk<'buffers>, usize), Error> {
155         let mut ret = ptr::null();
156         let mut lines = 0;
157         unsafe {
158             try_call!(raw::git_patch_get_hunk(
159                 &mut ret, &mut lines, self.raw, hunk_idx
160             ));
161             Ok((Binding::from_raw(ret), lines))
162         }
163     }
164 
165     /// Get the number of lines in a hunk.
num_lines_in_hunk(&self, hunk_idx: usize) -> Result<usize, Error>166     pub fn num_lines_in_hunk(&self, hunk_idx: usize) -> Result<usize, Error> {
167         unsafe { Ok(try_call!(raw::git_patch_num_lines_in_hunk(self.raw, hunk_idx)) as usize) }
168     }
169 
170     /// Get a DiffLine from a hunk of the Patch.
line_in_hunk( &self, hunk_idx: usize, line_of_hunk: usize, ) -> Result<DiffLine<'buffers>, Error>171     pub fn line_in_hunk(
172         &self,
173         hunk_idx: usize,
174         line_of_hunk: usize,
175     ) -> Result<DiffLine<'buffers>, Error> {
176         let mut ret = ptr::null();
177         unsafe {
178             try_call!(raw::git_patch_get_line_in_hunk(
179                 &mut ret,
180                 self.raw,
181                 hunk_idx,
182                 line_of_hunk
183             ));
184             Ok(Binding::from_raw(ret))
185         }
186     }
187 
188     /// Get the size of a Patch's diff data in bytes.
size( &self, include_context: bool, include_hunk_headers: bool, include_file_headers: bool, ) -> usize189     pub fn size(
190         &self,
191         include_context: bool,
192         include_hunk_headers: bool,
193         include_file_headers: bool,
194     ) -> usize {
195         unsafe {
196             raw::git_patch_size(
197                 self.raw,
198                 include_context as c_int,
199                 include_hunk_headers as c_int,
200                 include_file_headers as c_int,
201             )
202         }
203     }
204 
205     /// Print the Patch to text via a callback.
print(&mut self, mut line_cb: &mut LineCb<'_>) -> Result<(), Error>206     pub fn print(&mut self, mut line_cb: &mut LineCb<'_>) -> Result<(), Error> {
207         let ptr = &mut line_cb as *mut _ as *mut c_void;
208         unsafe {
209             let cb: raw::git_diff_line_cb = Some(print_cb);
210             try_call!(raw::git_patch_print(self.raw, cb, ptr));
211             Ok(())
212         }
213     }
214 
215     /// Get the Patch text as a Buf.
to_buf(&mut self) -> Result<Buf, Error>216     pub fn to_buf(&mut self) -> Result<Buf, Error> {
217         let buf = Buf::new();
218         unsafe {
219             try_call!(raw::git_patch_to_buf(buf.raw(), self.raw));
220         }
221         Ok(buf)
222     }
223 }
224 
225 impl<'buffers> std::fmt::Debug for Patch<'buffers> {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error>226     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
227         let mut ds = f.debug_struct("Patch");
228         ds.field("delta", &self.delta())
229             .field("num_hunks", &self.num_hunks());
230         if let Ok(line_stats) = &self.line_stats() {
231             ds.field("line_stats", line_stats);
232         }
233         ds.finish()
234     }
235 }
236