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