git2/
patch.rs

1use libc::{c_int, c_void};
2use std::marker::PhantomData;
3use std::path::Path;
4use std::ptr;
5
6use crate::diff::{print_cb, LineCb};
7use crate::util::{into_opt_c_string, Binding};
8use 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.
13pub struct Patch<'buffers> {
14    raw: *mut raw::git_patch,
15    buffers: PhantomData<&'buffers ()>,
16}
17
18unsafe impl<'buffers> Send for Patch<'buffers> {}
19
20impl<'buffers> Binding for Patch<'buffers> {
21    type Raw = *mut raw::git_patch;
22    unsafe fn from_raw(raw: Self::Raw) -> Self {
23        Patch {
24            raw,
25            buffers: PhantomData,
26        }
27    }
28    fn raw(&self) -> Self::Raw {
29        self.raw
30    }
31}
32
33impl<'buffers> Drop for Patch<'buffers> {
34    fn drop(&mut self) {
35        unsafe { raw::git_patch_free(self.raw) }
36    }
37}
38
39impl<'buffers> Patch<'buffers> {
40    /// Return a Patch for one file in a Diff.
41    ///
42    /// Returns Ok(None) for an unchanged or binary file.
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.
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.
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.
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.
128    pub fn delta<'a>(&'a self) -> DiffDelta<'a> {
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.
133    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.
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.
154    pub fn hunk<'a>(&'a self, hunk_idx: usize) -> Result<(DiffHunk<'a>, 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.
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.
171    pub fn line_in_hunk<'a>(
172        &'a self,
173        hunk_idx: usize,
174        line_of_hunk: usize,
175    ) -> Result<DiffLine<'a>, 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.
189    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.
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.
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
225impl<'buffers> std::fmt::Debug for Patch<'buffers> {
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}