use libc::{c_char, c_uint, c_ushort, size_t};
use std::ffi::CString;
use std::marker;
use std::mem;
use std::ptr;
use std::str;
use crate::call::Convert;
use crate::util::Binding;
use crate::{raw, Commit, Error, FileFavor, FileMode, IntoCString, Oid};
pub struct AnnotatedCommit<'repo> {
raw: *mut raw::git_annotated_commit,
_marker: marker::PhantomData<Commit<'repo>>,
}
pub struct MergeOptions {
raw: raw::git_merge_options,
}
pub struct MergeFileOptions {
ancestor_label: Option<CString>,
our_label: Option<CString>,
their_label: Option<CString>,
raw: raw::git_merge_file_options,
}
pub struct MergeFileResult {
raw: raw::git_merge_file_result,
}
impl<'repo> AnnotatedCommit<'repo> {
pub fn id(&self) -> Oid {
unsafe { Binding::from_raw(raw::git_annotated_commit_id(self.raw)) }
}
pub fn refname(&self) -> Result<&str, Error> {
str::from_utf8(self.refname_bytes()).map_err(|e| e.into())
}
pub fn refname_bytes(&self) -> &[u8] {
unsafe { crate::opt_bytes(self, raw::git_annotated_commit_ref(&*self.raw)).unwrap() }
}
}
impl Default for MergeOptions {
fn default() -> Self {
Self::new()
}
}
impl MergeOptions {
pub fn new() -> MergeOptions {
let mut opts = MergeOptions {
raw: unsafe { mem::zeroed() },
};
assert_eq!(unsafe { raw::git_merge_init_options(&mut opts.raw, 1) }, 0);
opts
}
fn flag(&mut self, opt: u32, val: bool) -> &mut MergeOptions {
if val {
self.raw.flags |= opt;
} else {
self.raw.flags &= !opt;
}
self
}
pub fn find_renames(&mut self, find: bool) -> &mut MergeOptions {
self.flag(raw::GIT_MERGE_FIND_RENAMES as u32, find)
}
pub fn fail_on_conflict(&mut self, fail: bool) -> &mut MergeOptions {
self.flag(raw::GIT_MERGE_FAIL_ON_CONFLICT as u32, fail)
}
pub fn skip_reuc(&mut self, skip: bool) -> &mut MergeOptions {
self.flag(raw::GIT_MERGE_SKIP_REUC as u32, skip)
}
pub fn no_recursive(&mut self, disable: bool) -> &mut MergeOptions {
self.flag(raw::GIT_MERGE_NO_RECURSIVE as u32, disable)
}
pub fn rename_threshold(&mut self, thresh: u32) -> &mut MergeOptions {
self.raw.rename_threshold = thresh;
self
}
pub fn target_limit(&mut self, limit: u32) -> &mut MergeOptions {
self.raw.target_limit = limit as c_uint;
self
}
pub fn recursion_limit(&mut self, limit: u32) -> &mut MergeOptions {
self.raw.recursion_limit = limit as c_uint;
self
}
pub fn file_favor(&mut self, favor: FileFavor) -> &mut MergeOptions {
self.raw.file_favor = favor.convert();
self
}
fn file_flag(&mut self, opt: u32, val: bool) -> &mut MergeOptions {
if val {
self.raw.file_flags |= opt;
} else {
self.raw.file_flags &= !opt;
}
self
}
pub fn standard_style(&mut self, standard: bool) -> &mut MergeOptions {
self.file_flag(raw::GIT_MERGE_FILE_STYLE_MERGE as u32, standard)
}
pub fn diff3_style(&mut self, diff3: bool) -> &mut MergeOptions {
self.file_flag(raw::GIT_MERGE_FILE_STYLE_DIFF3 as u32, diff3)
}
pub fn simplify_alnum(&mut self, simplify: bool) -> &mut MergeOptions {
self.file_flag(raw::GIT_MERGE_FILE_SIMPLIFY_ALNUM as u32, simplify)
}
pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut MergeOptions {
self.file_flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE as u32, ignore)
}
pub fn ignore_whitespace_change(&mut self, ignore: bool) -> &mut MergeOptions {
self.file_flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE as u32, ignore)
}
pub fn ignore_whitespace_eol(&mut self, ignore: bool) -> &mut MergeOptions {
self.file_flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL as u32, ignore)
}
pub fn patience(&mut self, patience: bool) -> &mut MergeOptions {
self.file_flag(raw::GIT_MERGE_FILE_DIFF_PATIENCE as u32, patience)
}
pub fn minimal(&mut self, minimal: bool) -> &mut MergeOptions {
self.file_flag(raw::GIT_MERGE_FILE_DIFF_MINIMAL as u32, minimal)
}
pub unsafe fn raw(&self) -> *const raw::git_merge_options {
&self.raw as *const _
}
}
impl<'repo> Binding for AnnotatedCommit<'repo> {
type Raw = *mut raw::git_annotated_commit;
unsafe fn from_raw(raw: *mut raw::git_annotated_commit) -> AnnotatedCommit<'repo> {
AnnotatedCommit {
raw,
_marker: marker::PhantomData,
}
}
fn raw(&self) -> *mut raw::git_annotated_commit {
self.raw
}
}
impl<'repo> Drop for AnnotatedCommit<'repo> {
fn drop(&mut self) {
unsafe { raw::git_annotated_commit_free(self.raw) }
}
}
impl Default for MergeFileOptions {
fn default() -> Self {
Self::new()
}
}
impl MergeFileOptions {
pub fn new() -> MergeFileOptions {
let mut opts = MergeFileOptions {
ancestor_label: None,
our_label: None,
their_label: None,
raw: unsafe { mem::zeroed() },
};
assert_eq!(
unsafe { raw::git_merge_file_options_init(&mut opts.raw, 1) },
0
);
opts
}
pub fn ancestor_label<T: IntoCString>(&mut self, t: T) -> &mut MergeFileOptions {
self.ancestor_label = Some(t.into_c_string().unwrap());
self.raw.ancestor_label = self
.ancestor_label
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(ptr::null());
self
}
pub fn our_label<T: IntoCString>(&mut self, t: T) -> &mut MergeFileOptions {
self.our_label = Some(t.into_c_string().unwrap());
self.raw.our_label = self
.our_label
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(ptr::null());
self
}
pub fn their_label<T: IntoCString>(&mut self, t: T) -> &mut MergeFileOptions {
self.their_label = Some(t.into_c_string().unwrap());
self.raw.their_label = self
.their_label
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(ptr::null());
self
}
pub fn favor(&mut self, favor: FileFavor) -> &mut MergeFileOptions {
self.raw.favor = favor.convert();
self
}
fn flag(&mut self, opt: raw::git_merge_file_flag_t, val: bool) -> &mut MergeFileOptions {
if val {
self.raw.flags |= opt as u32;
} else {
self.raw.flags &= !opt as u32;
}
self
}
pub fn style_standard(&mut self, standard: bool) -> &mut MergeFileOptions {
self.flag(raw::GIT_MERGE_FILE_STYLE_MERGE, standard)
}
pub fn style_diff3(&mut self, diff3: bool) -> &mut MergeFileOptions {
self.flag(raw::GIT_MERGE_FILE_STYLE_DIFF3, diff3)
}
pub fn simplify_alnum(&mut self, simplify: bool) -> &mut MergeFileOptions {
self.flag(raw::GIT_MERGE_FILE_SIMPLIFY_ALNUM, simplify)
}
pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut MergeFileOptions {
self.flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE, ignore)
}
pub fn ignore_whitespace_change(&mut self, ignore: bool) -> &mut MergeFileOptions {
self.flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE, ignore)
}
pub fn ignore_whitespace_eol(&mut self, ignore: bool) -> &mut MergeFileOptions {
self.flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL, ignore)
}
pub fn patience(&mut self, patience: bool) -> &mut MergeFileOptions {
self.flag(raw::GIT_MERGE_FILE_DIFF_PATIENCE, patience)
}
pub fn minimal(&mut self, minimal: bool) -> &mut MergeFileOptions {
self.flag(raw::GIT_MERGE_FILE_DIFF_MINIMAL, minimal)
}
pub fn style_zdiff3(&mut self, zdiff3: bool) -> &mut MergeFileOptions {
self.flag(raw::GIT_MERGE_FILE_STYLE_ZDIFF3, zdiff3)
}
pub fn accept_conflicts(&mut self, accept: bool) -> &mut MergeFileOptions {
self.flag(raw::GIT_MERGE_FILE_ACCEPT_CONFLICTS, accept)
}
pub fn marker_size(&mut self, size: u16) -> &mut MergeFileOptions {
self.raw.marker_size = size as c_ushort;
self
}
pub(crate) unsafe fn raw(&mut self) -> *const raw::git_merge_file_options {
&self.raw
}
}
impl MergeFileResult {
pub fn is_automergeable(&self) -> bool {
self.raw.automergeable > 0
}
pub fn path(&self) -> Result<Option<&str>, Error> {
match self.path_bytes() {
Some(pb) => str::from_utf8(pb).map(|s| Some(s)).map_err(|e| e.into()),
None => Ok(None),
}
}
pub fn path_bytes(&self) -> Option<&[u8]> {
unsafe { crate::opt_bytes(self, self.raw.path) }
}
pub fn mode(&self) -> u32 {
self.raw.mode as u32
}
pub fn content(&self) -> &[u8] {
unsafe { std::slice::from_raw_parts(self.raw.ptr as *const u8, self.raw.len as usize) }
}
}
impl Binding for MergeFileResult {
type Raw = raw::git_merge_file_result;
unsafe fn from_raw(raw: raw::git_merge_file_result) -> MergeFileResult {
MergeFileResult { raw }
}
fn raw(&self) -> raw::git_merge_file_result {
unimplemented!()
}
}
impl Drop for MergeFileResult {
fn drop(&mut self) {
unsafe { raw::git_merge_file_result_free(&mut self.raw) }
}
}
impl std::fmt::Debug for MergeFileResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut ds = f.debug_struct("MergeFileResult");
if let Ok(Some(path)) = &self.path() {
ds.field("path", path);
}
ds.field("automergeable", &self.is_automergeable());
ds.field("mode", &self.mode());
ds.finish()
}
}
pub struct MergeFileInput<'a> {
raw: raw::git_merge_file_input,
path: Option<CString>,
content: Option<&'a [u8]>,
}
impl Default for MergeFileInput<'_> {
fn default() -> Self {
Self::new()
}
}
impl Binding for MergeFileInput<'_> {
type Raw = *const raw::git_merge_file_input;
unsafe fn from_raw(_raw: *const raw::git_merge_file_input) -> MergeFileInput<'static> {
panic!("unimplemened")
}
fn raw(&self) -> *const raw::git_merge_file_input {
&self.raw as *const _
}
}
impl<'a> MergeFileInput<'a> {
pub fn new() -> MergeFileInput<'a> {
let mut input = MergeFileInput {
raw: unsafe { mem::zeroed() },
path: None,
content: None,
};
assert_eq!(
unsafe { raw::git_merge_file_input_init(&mut input.raw, 1) },
0
);
input
}
pub fn content(&mut self, content: &'a [u8]) -> &mut MergeFileInput<'a> {
self.content = Some(content);
self.raw.ptr = content.as_ptr() as *const c_char;
self.raw.size = content.len() as size_t;
self
}
pub fn path<T: IntoCString>(&mut self, t: T) -> &mut MergeFileInput<'a> {
self.path = Some(t.into_c_string().unwrap());
self.raw.path = self
.path
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(ptr::null());
self
}
pub fn mode(&mut self, mode: Option<FileMode>) -> &mut MergeFileInput<'a> {
self.raw.mode = mode.map_or(0, u32::from);
self
}
}
pub fn merge_file(
ancestor: &MergeFileInput<'_>,
ours: &MergeFileInput<'_>,
theirs: &MergeFileInput<'_>,
opts: Option<&mut MergeFileOptions>,
) -> Result<MergeFileResult, Error> {
unsafe {
let ancestor = ancestor.raw();
let ours = ours.raw();
let theirs = theirs.raw();
let mut ret = mem::zeroed();
try_call!(raw::git_merge_file(
&mut ret,
ancestor,
ours,
theirs,
opts.map(|o| o.raw()).unwrap_or(ptr::null())
));
Ok(Binding::from_raw(ret))
}
}
#[cfg(test)]
mod tests {
use crate::{MergeFileInput, MergeFileOptions};
use std::path::Path;
#[test]
fn smoke_merge_file() {
let file_path = Path::new("file");
let base = {
let mut input = MergeFileInput::new();
input.content(b"base").path(file_path);
input
};
let ours = {
let mut input = MergeFileInput::new();
input.content(b"foo").path(file_path);
input
};
let theirs = {
let mut input = MergeFileInput::new();
input.content(b"bar").path(file_path);
input
};
let mut opts = MergeFileOptions::new();
opts.ancestor_label("ancestor");
opts.our_label("ours");
opts.their_label("theirs");
opts.style_diff3(true);
let merge_file_result = crate::merge_file(&base, &ours, &theirs, Some(&mut opts)).unwrap();
assert!(!merge_file_result.is_automergeable());
assert_eq!(merge_file_result.path(), Ok(Some("file")));
assert_eq!(
String::from_utf8_lossy(merge_file_result.content()).to_string(),
r"<<<<<<< ours
foo
||||||| ancestor
base
=======
bar
>>>>>>> theirs
",
);
}
}