Skip to content

Commit 2572c23

Browse files
MikeJerredehuss
authored andcommitted
feat(merge): add merge_file_from_index
1 parent 10771e4 commit 2572c23

File tree

4 files changed

+435
-7
lines changed

4 files changed

+435
-7
lines changed

libgit2-sys/lib.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// This is required to link libz when libssh2-sys is not included.
55
extern crate libz_sys as libz;
66

7-
use libc::{c_char, c_int, c_uchar, c_uint, c_void, size_t};
7+
use libc::{c_char, c_int, c_uchar, c_uint, c_ushort, c_void, size_t};
88
#[cfg(feature = "ssh")]
99
use libssh2_sys as libssh2;
1010
use std::ffi::CStr;
@@ -1361,6 +1361,32 @@ pub struct git_merge_options {
13611361
pub file_flags: u32,
13621362
}
13631363

1364+
#[repr(C)]
1365+
pub struct git_merge_file_options {
1366+
pub version: c_uint,
1367+
pub ancestor_label: *const c_char,
1368+
pub our_label: *const c_char,
1369+
pub their_label: *const c_char,
1370+
pub favor: git_merge_file_favor_t,
1371+
pub flags: u32,
1372+
pub marker_size: c_ushort,
1373+
}
1374+
1375+
#[repr(C)]
1376+
#[derive(Copy)]
1377+
pub struct git_merge_file_result {
1378+
pub automergeable: c_uint,
1379+
pub path: *const c_char,
1380+
pub mode: c_uint,
1381+
pub ptr: *const c_char,
1382+
pub len: size_t,
1383+
}
1384+
impl Clone for git_merge_file_result {
1385+
fn clone(&self) -> git_merge_file_result {
1386+
*self
1387+
}
1388+
}
1389+
13641390
git_enum! {
13651391
pub enum git_merge_flag_t {
13661392
GIT_MERGE_FIND_RENAMES = 1 << 0,
@@ -1390,6 +1416,8 @@ git_enum! {
13901416
GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL = 1 << 5,
13911417
GIT_MERGE_FILE_DIFF_PATIENCE = 1 << 6,
13921418
GIT_MERGE_FILE_DIFF_MINIMAL = 1 << 7,
1419+
GIT_MERGE_FILE_STYLE_ZDIFF3 = 1 << 8,
1420+
GIT_MERGE_FILE_ACCEPT_CONFLICTS = 1 << 9,
13931421
}
13941422
}
13951423

@@ -3395,6 +3423,7 @@ extern "C" {
33953423
their_tree: *const git_tree,
33963424
opts: *const git_merge_options,
33973425
) -> c_int;
3426+
pub fn git_merge_file_options_init(opts: *mut git_merge_file_options, version: c_uint) -> c_int;
33983427
pub fn git_repository_state_cleanup(repo: *mut git_repository) -> c_int;
33993428

34003429
// merge analysis
@@ -3543,6 +3572,17 @@ extern "C" {
35433572
input_array: *const git_oid,
35443573
) -> c_int;
35453574

3575+
pub fn git_merge_file_from_index(
3576+
out: *mut git_merge_file_result,
3577+
repo: *mut git_repository,
3578+
ancestor: *const git_index_entry,
3579+
ours: *const git_index_entry,
3580+
theirs: *const git_index_entry,
3581+
opts: *const git_merge_file_options,
3582+
) -> c_int;
3583+
3584+
pub fn git_merge_file_result_free(file_result: *mut git_merge_file_result);
3585+
35463586
// pathspec
35473587
pub fn git_pathspec_free(ps: *mut git_pathspec);
35483588
pub fn git_pathspec_match_diff(

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ pub use crate::index::{
101101
pub use crate::indexer::{Indexer, IndexerProgress, Progress};
102102
pub use crate::mailmap::Mailmap;
103103
pub use crate::mempack::Mempack;
104-
pub use crate::merge::{AnnotatedCommit, MergeOptions};
104+
pub use crate::merge::{AnnotatedCommit, MergeOptions, MergeFileOptions, MergeFileResult};
105105
pub use crate::message::{
106106
message_prettify, message_trailers_bytes, message_trailers_strs, MessageTrailersBytes,
107107
MessageTrailersBytesIterator, MessageTrailersStrs, MessageTrailersStrsIterator,

src/merge.rs

Lines changed: 223 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
use libc::c_uint;
1+
use libc::{c_uint, c_ushort};
2+
use std::ffi::CString;
23
use std::marker;
34
use std::mem;
5+
use std::ptr;
46
use std::str;
57

68
use crate::call::Convert;
79
use crate::util::Binding;
810
use crate::{raw, Commit, FileFavor, Oid};
11+
use crate::IntoCString;
912

1013
/// A structure to represent an annotated commit, the input to merge and rebase.
1114
///
@@ -22,6 +25,20 @@ pub struct MergeOptions {
2225
raw: raw::git_merge_options,
2326
}
2427

28+
/// Options for merging a file.
29+
pub 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.
37+
pub struct MergeFileResult<'repo> {
38+
raw: raw::git_merge_file_result,
39+
_marker: marker::PhantomData<&'repo str>,
40+
}
41+
2542
impl<'repo> AnnotatedCommit<'repo> {
2643
/// Gets the commit ID that the given git_annotated_commit refers to
2744
pub fn id(&self) -> Oid {
@@ -192,3 +209,208 @@ impl<'repo> Drop for AnnotatedCommit<'repo> {
192209
unsafe { raw::git_annotated_commit_free(self.raw) }
193210
}
194211
}
212+
213+
impl Default for MergeFileOptions {
214+
fn default() -> Self {
215+
Self::new()
216+
}
217+
}
218+
219+
impl MergeFileOptions {
220+
/// Creates a default set of merge file options.
221+
pub fn new() -> MergeFileOptions {
222+
let mut opts = MergeFileOptions {
223+
ancestor_label: None,
224+
our_label: None,
225+
their_label: None,
226+
raw: unsafe { mem::zeroed() },
227+
};
228+
assert_eq!(unsafe { raw::git_merge_file_options_init(&mut opts.raw, 1) }, 0);
229+
opts
230+
}
231+
232+
/// Label for the ancestor file side of the conflict which will be prepended
233+
/// to labels in diff3-format merge files.
234+
pub fn ancestor_label<T: IntoCString>(&mut self, t: T) -> &mut MergeFileOptions {
235+
self.ancestor_label = Some(t.into_c_string().unwrap());
236+
237+
self.raw.ancestor_label = self
238+
.ancestor_label
239+
.as_ref()
240+
.map(|s| s.as_ptr())
241+
.unwrap_or(ptr::null());
242+
243+
self
244+
}
245+
246+
/// Label for our file side of the conflict which will be prepended to labels
247+
/// in merge files.
248+
pub fn our_label<T: IntoCString>(&mut self, t: T) -> &mut MergeFileOptions {
249+
self.our_label = Some(t.into_c_string().unwrap());
250+
251+
self.raw.our_label = self
252+
.our_label
253+
.as_ref()
254+
.map(|s| s.as_ptr())
255+
.unwrap_or(ptr::null());
256+
257+
self
258+
}
259+
260+
/// Label for their file side of the conflict which will be prepended to labels
261+
/// in merge files.
262+
pub fn their_label<T: IntoCString>(&mut self, t: T) -> &mut MergeFileOptions {
263+
self.their_label = Some(t.into_c_string().unwrap());
264+
265+
self.raw.their_label = self
266+
.their_label
267+
.as_ref()
268+
.map(|s| s.as_ptr())
269+
.unwrap_or(ptr::null());
270+
271+
self
272+
}
273+
274+
/// Specify a side to favor for resolving conflicts
275+
pub fn favor(&mut self, favor: FileFavor) -> &mut MergeFileOptions {
276+
self.raw.favor = favor.convert();
277+
self
278+
}
279+
280+
fn flag(&mut self, opt: u32, val: bool) -> &mut MergeFileOptions {
281+
if val {
282+
self.raw.flags |= opt;
283+
} else {
284+
self.raw.flags &= !opt;
285+
}
286+
self
287+
}
288+
289+
/// Create standard conflicted merge files
290+
pub fn style_standard(&mut self, standard: bool) -> &mut MergeFileOptions {
291+
self.flag(raw::GIT_MERGE_FILE_STYLE_MERGE as u32, standard)
292+
}
293+
294+
/// Create diff3-style file
295+
pub fn style_diff3(&mut self, diff3: bool) -> &mut MergeFileOptions {
296+
self.flag(raw::GIT_MERGE_FILE_STYLE_DIFF3 as u32, diff3)
297+
}
298+
299+
/// Condense non-alphanumeric regions for simplified diff file
300+
pub fn simplify_alnum(&mut self, simplify: bool) -> &mut MergeFileOptions {
301+
self.flag(raw::GIT_MERGE_FILE_SIMPLIFY_ALNUM as u32, simplify)
302+
}
303+
304+
/// Ignore all whitespace
305+
pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut MergeFileOptions {
306+
self.flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE as u32, ignore)
307+
}
308+
309+
/// Ignore changes in amount of whitespace
310+
pub fn ignore_whitespace_change(&mut self, ignore: bool) -> &mut MergeFileOptions {
311+
self.flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE as u32, ignore)
312+
}
313+
314+
/// Ignore whitespace at end of line
315+
pub fn ignore_whitespace_eol(&mut self, ignore: bool) -> &mut MergeFileOptions {
316+
self.flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL as u32, ignore)
317+
}
318+
319+
/// Use the "patience diff" algorithm
320+
pub fn patience(&mut self, patience: bool) -> &mut MergeFileOptions {
321+
self.flag(raw::GIT_MERGE_FILE_DIFF_PATIENCE as u32, patience)
322+
}
323+
324+
/// Take extra time to find minimal diff
325+
pub fn minimal(&mut self, minimal: bool) -> &mut MergeFileOptions {
326+
self.flag(raw::GIT_MERGE_FILE_DIFF_MINIMAL as u32, minimal)
327+
}
328+
329+
/// Create zdiff3 ("zealous diff3")-style files
330+
pub fn style_zdiff3(&mut self, zdiff3: bool) -> &mut MergeFileOptions {
331+
self.flag(raw::GIT_MERGE_FILE_STYLE_ZDIFF3 as u32, zdiff3)
332+
}
333+
334+
/// Do not produce file conflicts when common regions have changed
335+
pub fn accept_conflicts(&mut self, accept: bool) -> &mut MergeFileOptions {
336+
self.flag(raw::GIT_MERGE_FILE_ACCEPT_CONFLICTS as u32, accept)
337+
}
338+
339+
/// The size of conflict markers (eg, "<<<<<<<"). Default is 7.
340+
pub fn marker_size(&mut self, size: u16) -> &mut MergeFileOptions {
341+
self.raw.marker_size = size as c_ushort;
342+
self
343+
}
344+
345+
/// Acquire a pointer to the underlying raw options.
346+
pub unsafe fn raw(&mut self) -> *const raw::git_merge_file_options {
347+
&self.raw as *const _
348+
}
349+
}
350+
351+
impl<'repo> MergeFileResult<'repo> {
352+
/// True if the output was automerged, false if the output contains
353+
/// conflict markers.
354+
pub fn is_automergeable(&self) -> bool {
355+
self.raw.automergeable > 0
356+
}
357+
358+
/// The path that the resultant merge file should use.
359+
///
360+
/// returns `None` if a filename conflict would occur,
361+
/// or if the path is not valid utf-8
362+
pub fn path(&self) -> Option<&str> {
363+
self.path_bytes().and_then(|bytes| str::from_utf8(bytes).ok())
364+
}
365+
366+
/// Gets the path as a byte slice.
367+
pub fn path_bytes(&self) -> Option<&[u8]> {
368+
unsafe { crate::opt_bytes(self, self.raw.path) }
369+
}
370+
371+
/// The mode that the resultant merge file should use.
372+
pub fn mode(&self) -> u32 {
373+
self.raw.mode as u32
374+
}
375+
376+
/// The contents of the merge.
377+
pub fn content(&self) -> &'repo [u8] {
378+
unsafe {
379+
std::slice::from_raw_parts(
380+
self.raw.ptr as *const u8,
381+
self.raw.len as usize,
382+
)
383+
}
384+
}
385+
}
386+
387+
impl<'repo> Binding for MergeFileResult<'repo> {
388+
type Raw = raw::git_merge_file_result;
389+
unsafe fn from_raw(raw: raw::git_merge_file_result) -> MergeFileResult<'repo> {
390+
MergeFileResult {
391+
raw,
392+
_marker: marker::PhantomData,
393+
}
394+
}
395+
fn raw(&self) -> raw::git_merge_file_result {
396+
self.raw
397+
}
398+
}
399+
400+
impl<'repo> Drop for MergeFileResult<'repo> {
401+
fn drop(&mut self) {
402+
unsafe { raw::git_merge_file_result_free(&mut self.raw) }
403+
}
404+
}
405+
406+
impl<'repo> std::fmt::Display for MergeFileResult<'repo> {
407+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
408+
let mut ds = f.debug_struct("MergeFileResult");
409+
if let Some(path) = &self.path() {
410+
ds.field("path", path);
411+
}
412+
ds.field("automergeable", &self.is_automergeable());
413+
ds.field("mode", &self.mode());
414+
ds.finish()
415+
}
416+
}

0 commit comments

Comments
 (0)