Skip to content

Commit 21b23cb

Browse files
committed
feat(merge): add merge_file_from_index
1 parent 9a5c970 commit 21b23cb

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;
@@ -1350,6 +1350,32 @@ pub struct git_merge_options {
13501350
pub file_flags: u32,
13511351
}
13521352

1353+
#[repr(C)]
1354+
pub struct git_merge_file_options {
1355+
pub version: c_uint,
1356+
pub ancestor_label: *const c_char,
1357+
pub our_label: *const c_char,
1358+
pub their_label: *const c_char,
1359+
pub favor: git_merge_file_favor_t,
1360+
pub flags: u32,
1361+
pub marker_size: c_ushort,
1362+
}
1363+
1364+
#[repr(C)]
1365+
#[derive(Copy)]
1366+
pub struct git_merge_file_result {
1367+
pub automergeable: c_uint,
1368+
pub path: *const c_char,
1369+
pub mode: c_uint,
1370+
pub ptr: *const c_char,
1371+
pub len: size_t,
1372+
}
1373+
impl Clone for git_merge_file_result {
1374+
fn clone(&self) -> git_merge_file_result {
1375+
*self
1376+
}
1377+
}
1378+
13531379
git_enum! {
13541380
pub enum git_merge_flag_t {
13551381
GIT_MERGE_FIND_RENAMES = 1 << 0,
@@ -1379,6 +1405,8 @@ git_enum! {
13791405
GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL = 1 << 5,
13801406
GIT_MERGE_FILE_DIFF_PATIENCE = 1 << 6,
13811407
GIT_MERGE_FILE_DIFF_MINIMAL = 1 << 7,
1408+
GIT_MERGE_FILE_STYLE_ZDIFF3 = 1 << 8,
1409+
GIT_MERGE_FILE_ACCEPT_CONFLICTS = 1 << 9,
13821410
}
13831411
}
13841412

@@ -3378,6 +3406,7 @@ extern "C" {
33783406
their_tree: *const git_tree,
33793407
opts: *const git_merge_options,
33803408
) -> c_int;
3409+
pub fn git_merge_file_options_init(opts: *mut git_merge_file_options, version: c_uint) -> c_int;
33813410
pub fn git_repository_state_cleanup(repo: *mut git_repository) -> c_int;
33823411

33833412
// merge analysis
@@ -3519,6 +3548,17 @@ extern "C" {
35193548
input_array: *const git_oid,
35203549
) -> c_int;
35213550

3551+
pub fn git_merge_file_from_index(
3552+
out: *mut git_merge_file_result,
3553+
repo: *mut git_repository,
3554+
ancestor: *const git_index_entry,
3555+
ours: *const git_index_entry,
3556+
theirs: *const git_index_entry,
3557+
opts: *const git_merge_file_options,
3558+
) -> c_int;
3559+
3560+
pub fn git_merge_file_result_free(file_result: *mut git_merge_file_result);
3561+
35223562
// pathspec
35233563
pub fn git_pathspec_free(ps: *mut git_pathspec);
35243564
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)