Skip to content

Commit 258d3b6

Browse files
authored
Merge pull request #1062 from MikeJerred/master
Add function: git_merge_file_from_index
2 parents 10771e4 + d1b40aa commit 258d3b6

File tree

5 files changed

+433
-7
lines changed

5 files changed

+433
-7
lines changed

libgit2-sys/lib.rs

+36-1
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,26 @@ 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+
pub struct git_merge_file_result {
1377+
pub automergeable: c_uint,
1378+
pub path: *const c_char,
1379+
pub mode: c_uint,
1380+
pub ptr: *const c_char,
1381+
pub len: size_t,
1382+
}
1383+
13641384
git_enum! {
13651385
pub enum git_merge_flag_t {
13661386
GIT_MERGE_FIND_RENAMES = 1 << 0,
@@ -1390,6 +1410,8 @@ git_enum! {
13901410
GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL = 1 << 5,
13911411
GIT_MERGE_FILE_DIFF_PATIENCE = 1 << 6,
13921412
GIT_MERGE_FILE_DIFF_MINIMAL = 1 << 7,
1413+
GIT_MERGE_FILE_STYLE_ZDIFF3 = 1 << 8,
1414+
GIT_MERGE_FILE_ACCEPT_CONFLICTS = 1 << 9,
13931415
}
13941416
}
13951417

@@ -3395,6 +3417,8 @@ extern "C" {
33953417
their_tree: *const git_tree,
33963418
opts: *const git_merge_options,
33973419
) -> c_int;
3420+
pub fn git_merge_file_options_init(opts: *mut git_merge_file_options, version: c_uint)
3421+
-> c_int;
33983422
pub fn git_repository_state_cleanup(repo: *mut git_repository) -> c_int;
33993423

34003424
// merge analysis
@@ -3543,6 +3567,17 @@ extern "C" {
35433567
input_array: *const git_oid,
35443568
) -> c_int;
35453569

3570+
pub fn git_merge_file_from_index(
3571+
out: *mut git_merge_file_result,
3572+
repo: *mut git_repository,
3573+
ancestor: *const git_index_entry,
3574+
ours: *const git_index_entry,
3575+
theirs: *const git_index_entry,
3576+
opts: *const git_merge_file_options,
3577+
) -> c_int;
3578+
3579+
pub fn git_merge_file_result_free(file_result: *mut git_merge_file_result);
3580+
35463581
// pathspec
35473582
pub fn git_pathspec_free(ps: *mut git_pathspec);
35483583
pub fn git_pathspec_match_diff(

src/index.rs

+46
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,52 @@ impl Index {
656656
}
657657
}
658658

659+
impl IndexEntry {
660+
/// Create a raw index entry.
661+
///
662+
/// The returned `raw::git_index_entry` contains a pointer to a `CString` path, which is also
663+
/// returned because it's lifetime must exceed the lifetime of the `raw::git_index_entry`.
664+
pub(crate) unsafe fn to_raw(&self) -> Result<(raw::git_index_entry, CString), Error> {
665+
let path = CString::new(&self.path[..])?;
666+
667+
// libgit2 encodes the length of the path in the lower bits of the
668+
// `flags` entry, so mask those out and recalculate here to ensure we
669+
// don't corrupt anything.
670+
let mut flags = self.flags & !raw::GIT_INDEX_ENTRY_NAMEMASK;
671+
672+
if self.path.len() < raw::GIT_INDEX_ENTRY_NAMEMASK as usize {
673+
flags |= self.path.len() as u16;
674+
} else {
675+
flags |= raw::GIT_INDEX_ENTRY_NAMEMASK;
676+
}
677+
678+
unsafe {
679+
let raw = raw::git_index_entry {
680+
dev: self.dev,
681+
ino: self.ino,
682+
mode: self.mode,
683+
uid: self.uid,
684+
gid: self.gid,
685+
file_size: self.file_size,
686+
id: *self.id.raw(),
687+
flags,
688+
flags_extended: self.flags_extended,
689+
path: path.as_ptr(),
690+
mtime: raw::git_index_time {
691+
seconds: self.mtime.seconds(),
692+
nanoseconds: self.mtime.nanoseconds(),
693+
},
694+
ctime: raw::git_index_time {
695+
seconds: self.ctime.seconds(),
696+
nanoseconds: self.ctime.nanoseconds(),
697+
},
698+
};
699+
700+
Ok((raw, path))
701+
}
702+
}
703+
}
704+
659705
impl Binding for Index {
660706
type Raw = *mut raw::git_index;
661707
unsafe fn from_raw(raw: *mut raw::git_index) -> Index {

src/lib.rs

+1-1
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, MergeFileOptions, MergeFileResult, MergeOptions};
105105
pub use crate::message::{
106106
message_prettify, message_trailers_bytes, message_trailers_strs, MessageTrailersBytes,
107107
MessageTrailersBytesIterator, MessageTrailersStrs, MessageTrailersStrsIterator,

src/merge.rs

+221-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
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;
10+
use crate::IntoCString;
811
use crate::{raw, Commit, FileFavor, Oid};
912

1013
/// A structure to represent an annotated commit, the input to merge and rebase.
@@ -22,6 +25,19 @@ 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 {
38+
raw: raw::git_merge_file_result,
39+
}
40+
2541
impl<'repo> AnnotatedCommit<'repo> {
2642
/// Gets the commit ID that the given git_annotated_commit refers to
2743
pub fn id(&self) -> Oid {
@@ -192,3 +208,207 @@ impl<'repo> Drop for AnnotatedCommit<'repo> {
192208
unsafe { raw::git_annotated_commit_free(self.raw) }
193209
}
194210
}
211+
212+
impl Default for MergeFileOptions {
213+
fn default() -> Self {
214+
Self::new()
215+
}
216+
}
217+
218+
impl 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+
356+
impl 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+
388+
impl 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+
398+
impl Drop for MergeFileResult {
399+
fn drop(&mut self) {
400+
unsafe { raw::git_merge_file_result_free(&mut self.raw) }
401+
}
402+
}
403+
404+
impl 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+
}

0 commit comments

Comments
 (0)