git2/
merge.rs

1use libc::{c_uint, c_ushort};
2use std::ffi::CString;
3use std::marker;
4use std::mem;
5use std::ptr;
6use std::str;
7
8use crate::call::Convert;
9use crate::util::Binding;
10use crate::IntoCString;
11use crate::{raw, Commit, FileFavor, Oid};
12
13/// A structure to represent an annotated commit, the input to merge and rebase.
14///
15/// An annotated commit contains information about how it was looked up, which
16/// may be useful for functions like merge or rebase to provide context to the
17/// operation.
18pub struct AnnotatedCommit<'repo> {
19    raw: *mut raw::git_annotated_commit,
20    _marker: marker::PhantomData<Commit<'repo>>,
21}
22
23/// Options to specify when merging.
24pub struct MergeOptions {
25    raw: raw::git_merge_options,
26}
27
28/// Options for merging a file.
29pub struct MergeFileOptions {
30    ancestor_label: Option<CString>,
31    our_label: Option<CString>,
32    their_label: Option<CString>,
33    raw: raw::git_merge_file_options,
34}
35
36/// Information about file-level merging.
37pub struct MergeFileResult {
38    raw: raw::git_merge_file_result,
39}
40
41impl<'repo> AnnotatedCommit<'repo> {
42    /// Gets the commit ID that the given git_annotated_commit refers to
43    pub fn id(&self) -> Oid {
44        unsafe { Binding::from_raw(raw::git_annotated_commit_id(self.raw)) }
45    }
46
47    /// Get the refname that the given git_annotated_commit refers to
48    ///
49    /// Returns None if it is not valid utf8
50    pub fn refname(&self) -> Option<&str> {
51        str::from_utf8(self.refname_bytes()).ok()
52    }
53
54    /// Get the refname that the given git_annotated_commit refers to.
55    pub fn refname_bytes(&self) -> &[u8] {
56        unsafe { crate::opt_bytes(self, raw::git_annotated_commit_ref(&*self.raw)).unwrap() }
57    }
58}
59
60impl Default for MergeOptions {
61    fn default() -> Self {
62        Self::new()
63    }
64}
65
66impl MergeOptions {
67    /// Creates a default set of merge options.
68    pub fn new() -> MergeOptions {
69        let mut opts = MergeOptions {
70            raw: unsafe { mem::zeroed() },
71        };
72        assert_eq!(unsafe { raw::git_merge_init_options(&mut opts.raw, 1) }, 0);
73        opts
74    }
75
76    fn flag(&mut self, opt: u32, val: bool) -> &mut MergeOptions {
77        if val {
78            self.raw.flags |= opt;
79        } else {
80            self.raw.flags &= !opt;
81        }
82        self
83    }
84
85    /// Detect file renames
86    pub fn find_renames(&mut self, find: bool) -> &mut MergeOptions {
87        self.flag(raw::GIT_MERGE_FIND_RENAMES as u32, find)
88    }
89
90    /// If a conflict occurs, exit immediately instead of attempting to continue
91    /// resolving conflicts
92    pub fn fail_on_conflict(&mut self, fail: bool) -> &mut MergeOptions {
93        self.flag(raw::GIT_MERGE_FAIL_ON_CONFLICT as u32, fail)
94    }
95
96    /// Do not write the REUC extension on the generated index
97    pub fn skip_reuc(&mut self, skip: bool) -> &mut MergeOptions {
98        self.flag(raw::GIT_MERGE_FAIL_ON_CONFLICT as u32, skip)
99    }
100
101    /// If the commits being merged have multiple merge bases, do not build a
102    /// recursive merge base (by merging the multiple merge bases), instead
103    /// simply use the first base.
104    pub fn no_recursive(&mut self, disable: bool) -> &mut MergeOptions {
105        self.flag(raw::GIT_MERGE_NO_RECURSIVE as u32, disable)
106    }
107
108    /// Similarity to consider a file renamed (default 50)
109    pub fn rename_threshold(&mut self, thresh: u32) -> &mut MergeOptions {
110        self.raw.rename_threshold = thresh;
111        self
112    }
113
114    ///  Maximum similarity sources to examine for renames (default 200).
115    /// If the number of rename candidates (add / delete pairs) is greater
116    /// than this value, inexact rename detection is aborted. This setting
117    /// overrides the `merge.renameLimit` configuration value.
118    pub fn target_limit(&mut self, limit: u32) -> &mut MergeOptions {
119        self.raw.target_limit = limit as c_uint;
120        self
121    }
122
123    /// Maximum number of times to merge common ancestors to build a
124    /// virtual merge base when faced with criss-cross merges.  When
125    /// this limit is reached, the next ancestor will simply be used
126    /// instead of attempting to merge it.  The default is unlimited.
127    pub fn recursion_limit(&mut self, limit: u32) -> &mut MergeOptions {
128        self.raw.recursion_limit = limit as c_uint;
129        self
130    }
131
132    /// Specify a side to favor for resolving conflicts
133    pub fn file_favor(&mut self, favor: FileFavor) -> &mut MergeOptions {
134        self.raw.file_favor = favor.convert();
135        self
136    }
137
138    fn file_flag(&mut self, opt: u32, val: bool) -> &mut MergeOptions {
139        if val {
140            self.raw.file_flags |= opt;
141        } else {
142            self.raw.file_flags &= !opt;
143        }
144        self
145    }
146
147    /// Create standard conflicted merge files
148    pub fn standard_style(&mut self, standard: bool) -> &mut MergeOptions {
149        self.file_flag(raw::GIT_MERGE_FILE_STYLE_MERGE as u32, standard)
150    }
151
152    /// Create diff3-style file
153    pub fn diff3_style(&mut self, diff3: bool) -> &mut MergeOptions {
154        self.file_flag(raw::GIT_MERGE_FILE_STYLE_DIFF3 as u32, diff3)
155    }
156
157    /// Condense non-alphanumeric regions for simplified diff file
158    pub fn simplify_alnum(&mut self, simplify: bool) -> &mut MergeOptions {
159        self.file_flag(raw::GIT_MERGE_FILE_SIMPLIFY_ALNUM as u32, simplify)
160    }
161
162    /// Ignore all whitespace
163    pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut MergeOptions {
164        self.file_flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE as u32, ignore)
165    }
166
167    /// Ignore changes in amount of whitespace
168    pub fn ignore_whitespace_change(&mut self, ignore: bool) -> &mut MergeOptions {
169        self.file_flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE as u32, ignore)
170    }
171
172    /// Ignore whitespace at end of line
173    pub fn ignore_whitespace_eol(&mut self, ignore: bool) -> &mut MergeOptions {
174        self.file_flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL as u32, ignore)
175    }
176
177    /// Use the "patience diff" algorithm
178    pub fn patience(&mut self, patience: bool) -> &mut MergeOptions {
179        self.file_flag(raw::GIT_MERGE_FILE_DIFF_PATIENCE as u32, patience)
180    }
181
182    /// Take extra time to find minimal diff
183    pub fn minimal(&mut self, minimal: bool) -> &mut MergeOptions {
184        self.file_flag(raw::GIT_MERGE_FILE_DIFF_MINIMAL as u32, minimal)
185    }
186
187    /// Acquire a pointer to the underlying raw options.
188    pub unsafe fn raw(&self) -> *const raw::git_merge_options {
189        &self.raw as *const _
190    }
191}
192
193impl<'repo> Binding for AnnotatedCommit<'repo> {
194    type Raw = *mut raw::git_annotated_commit;
195    unsafe fn from_raw(raw: *mut raw::git_annotated_commit) -> AnnotatedCommit<'repo> {
196        AnnotatedCommit {
197            raw,
198            _marker: marker::PhantomData,
199        }
200    }
201    fn raw(&self) -> *mut raw::git_annotated_commit {
202        self.raw
203    }
204}
205
206impl<'repo> Drop for AnnotatedCommit<'repo> {
207    fn drop(&mut self) {
208        unsafe { raw::git_annotated_commit_free(self.raw) }
209    }
210}
211
212impl Default for MergeFileOptions {
213    fn default() -> Self {
214        Self::new()
215    }
216}
217
218impl MergeFileOptions {
219    /// Creates a default set of merge file options.
220    pub fn new() -> MergeFileOptions {
221        let mut opts = MergeFileOptions {
222            ancestor_label: None,
223            our_label: None,
224            their_label: None,
225            raw: unsafe { mem::zeroed() },
226        };
227        assert_eq!(
228            unsafe { raw::git_merge_file_options_init(&mut opts.raw, 1) },
229            0
230        );
231        opts
232    }
233
234    /// Label for the ancestor file side of the conflict which will be prepended
235    /// to labels in diff3-format merge files.
236    pub fn ancestor_label<T: IntoCString>(&mut self, t: T) -> &mut MergeFileOptions {
237        self.ancestor_label = Some(t.into_c_string().unwrap());
238
239        self.raw.ancestor_label = self
240            .ancestor_label
241            .as_ref()
242            .map(|s| s.as_ptr())
243            .unwrap_or(ptr::null());
244
245        self
246    }
247
248    /// Label for our file side of the conflict which will be prepended to labels
249    /// in merge files.
250    pub fn our_label<T: IntoCString>(&mut self, t: T) -> &mut MergeFileOptions {
251        self.our_label = Some(t.into_c_string().unwrap());
252
253        self.raw.our_label = self
254            .our_label
255            .as_ref()
256            .map(|s| s.as_ptr())
257            .unwrap_or(ptr::null());
258
259        self
260    }
261
262    /// Label for their file side of the conflict which will be prepended to labels
263    /// in merge files.
264    pub fn their_label<T: IntoCString>(&mut self, t: T) -> &mut MergeFileOptions {
265        self.their_label = Some(t.into_c_string().unwrap());
266
267        self.raw.their_label = self
268            .their_label
269            .as_ref()
270            .map(|s| s.as_ptr())
271            .unwrap_or(ptr::null());
272
273        self
274    }
275
276    /// Specify a side to favor for resolving conflicts
277    pub fn favor(&mut self, favor: FileFavor) -> &mut MergeFileOptions {
278        self.raw.favor = favor.convert();
279        self
280    }
281
282    fn flag(&mut self, opt: raw::git_merge_file_flag_t, val: bool) -> &mut MergeFileOptions {
283        if val {
284            self.raw.flags |= opt as u32;
285        } else {
286            self.raw.flags &= !opt as u32;
287        }
288        self
289    }
290
291    /// Create standard conflicted merge files
292    pub fn style_standard(&mut self, standard: bool) -> &mut MergeFileOptions {
293        self.flag(raw::GIT_MERGE_FILE_STYLE_MERGE, standard)
294    }
295
296    /// Create diff3-style file
297    pub fn style_diff3(&mut self, diff3: bool) -> &mut MergeFileOptions {
298        self.flag(raw::GIT_MERGE_FILE_STYLE_DIFF3, diff3)
299    }
300
301    /// Condense non-alphanumeric regions for simplified diff file
302    pub fn simplify_alnum(&mut self, simplify: bool) -> &mut MergeFileOptions {
303        self.flag(raw::GIT_MERGE_FILE_SIMPLIFY_ALNUM, simplify)
304    }
305
306    /// Ignore all whitespace
307    pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut MergeFileOptions {
308        self.flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE, ignore)
309    }
310
311    /// Ignore changes in amount of whitespace
312    pub fn ignore_whitespace_change(&mut self, ignore: bool) -> &mut MergeFileOptions {
313        self.flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE, ignore)
314    }
315
316    /// Ignore whitespace at end of line
317    pub fn ignore_whitespace_eol(&mut self, ignore: bool) -> &mut MergeFileOptions {
318        self.flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL, ignore)
319    }
320
321    /// Use the "patience diff" algorithm
322    pub fn patience(&mut self, patience: bool) -> &mut MergeFileOptions {
323        self.flag(raw::GIT_MERGE_FILE_DIFF_PATIENCE, patience)
324    }
325
326    /// Take extra time to find minimal diff
327    pub fn minimal(&mut self, minimal: bool) -> &mut MergeFileOptions {
328        self.flag(raw::GIT_MERGE_FILE_DIFF_MINIMAL, minimal)
329    }
330
331    /// Create zdiff3 ("zealous diff3")-style files
332    pub fn style_zdiff3(&mut self, zdiff3: bool) -> &mut MergeFileOptions {
333        self.flag(raw::GIT_MERGE_FILE_STYLE_ZDIFF3, zdiff3)
334    }
335
336    /// Do not produce file conflicts when common regions have changed
337    pub fn accept_conflicts(&mut self, accept: bool) -> &mut MergeFileOptions {
338        self.flag(raw::GIT_MERGE_FILE_ACCEPT_CONFLICTS, accept)
339    }
340
341    /// The size of conflict markers (eg, "<<<<<<<"). Default is 7.
342    pub fn marker_size(&mut self, size: u16) -> &mut MergeFileOptions {
343        self.raw.marker_size = size as c_ushort;
344        self
345    }
346
347    /// Acquire a pointer to the underlying raw options.
348    ///
349    /// # Safety
350    /// The pointer used here (or its contents) should not outlive self.
351    pub(crate) unsafe fn raw(&mut self) -> *const raw::git_merge_file_options {
352        &self.raw
353    }
354}
355
356impl MergeFileResult {
357    /// True if the output was automerged, false if the output contains
358    /// conflict markers.
359    pub fn is_automergeable(&self) -> bool {
360        self.raw.automergeable > 0
361    }
362
363    /// The path that the resultant merge file should use.
364    ///
365    /// returns `None` if a filename conflict would occur,
366    /// or if the path is not valid utf-8
367    pub fn path(&self) -> Option<&str> {
368        self.path_bytes()
369            .and_then(|bytes| str::from_utf8(bytes).ok())
370    }
371
372    /// Gets the path as a byte slice.
373    pub fn path_bytes(&self) -> Option<&[u8]> {
374        unsafe { crate::opt_bytes(self, self.raw.path) }
375    }
376
377    /// The mode that the resultant merge file should use.
378    pub fn mode(&self) -> u32 {
379        self.raw.mode as u32
380    }
381
382    /// The contents of the merge.
383    pub fn content(&self) -> &[u8] {
384        unsafe { std::slice::from_raw_parts(self.raw.ptr as *const u8, self.raw.len as usize) }
385    }
386}
387
388impl Binding for MergeFileResult {
389    type Raw = raw::git_merge_file_result;
390    unsafe fn from_raw(raw: raw::git_merge_file_result) -> MergeFileResult {
391        MergeFileResult { raw }
392    }
393    fn raw(&self) -> raw::git_merge_file_result {
394        unimplemented!()
395    }
396}
397
398impl Drop for MergeFileResult {
399    fn drop(&mut self) {
400        unsafe { raw::git_merge_file_result_free(&mut self.raw) }
401    }
402}
403
404impl std::fmt::Debug for MergeFileResult {
405    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
406        let mut ds = f.debug_struct("MergeFileResult");
407        if let Some(path) = &self.path() {
408            ds.field("path", path);
409        }
410        ds.field("automergeable", &self.is_automergeable());
411        ds.field("mode", &self.mode());
412        ds.finish()
413    }
414}