Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions libgit2-sys/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1560,6 +1560,15 @@ pub struct git_merge_file_options {
pub marker_size: c_ushort,
}

#[repr(C)]
pub struct git_merge_file_input {
pub version: c_uint,
pub ptr: *const c_char,
pub size: size_t,
pub path: *const c_char,
pub mode: c_uint,
}

#[repr(C)]
pub struct git_merge_file_result {
pub automergeable: c_uint,
Expand Down Expand Up @@ -3674,6 +3683,7 @@ extern "C" {
their_tree: *const git_tree,
opts: *const git_merge_options,
) -> c_int;
pub fn git_merge_file_input_init(opts: *mut git_merge_file_input, version: c_uint) -> c_int;
pub fn git_merge_file_options_init(opts: *mut git_merge_file_options, version: c_uint)
-> c_int;
pub fn git_repository_state_cleanup(repo: *mut git_repository) -> c_int;
Expand Down Expand Up @@ -3833,6 +3843,14 @@ extern "C" {
opts: *const git_merge_file_options,
) -> c_int;

pub fn git_merge_file(
out: *mut git_merge_file_result,
ancestor: *const git_merge_file_input,
ours: *const git_merge_file_input,
theirs: *const git_merge_file_input,
opts: *const git_merge_file_options,
) -> c_int;

pub fn git_merge_file_result_free(file_result: *mut git_merge_file_result);

// pathspec
Expand Down
4 changes: 3 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@ pub use crate::index::{
pub use crate::indexer::{Indexer, IndexerProgress, Progress};
pub use crate::mailmap::Mailmap;
pub use crate::mempack::Mempack;
pub use crate::merge::{AnnotatedCommit, MergeFileOptions, MergeFileResult, MergeOptions};
pub use crate::merge::{
merge_file, AnnotatedCommit, MergeFileInput, MergeFileOptions, MergeFileResult, MergeOptions,
};
pub use crate::message::{
message_prettify, message_trailers_bytes, message_trailers_strs, MessageTrailersBytes,
MessageTrailersBytesIterator, MessageTrailersStrs, MessageTrailersStrsIterator,
Expand Down
149 changes: 146 additions & 3 deletions src/merge.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use libc::{c_uint, c_ushort};
use libc::{c_char, c_uint, c_ushort, size_t};
use std::ffi::CString;
use std::marker;
use std::mem;
Expand All @@ -7,8 +7,7 @@ use std::str;

use crate::call::Convert;
use crate::util::Binding;
use crate::IntoCString;
use crate::{raw, Commit, FileFavor, Oid};
use crate::{raw, Commit, Error, FileFavor, FileMode, IntoCString, Oid};

/// A structure to represent an annotated commit, the input to merge and rebase.
///
Expand Down Expand Up @@ -412,3 +411,147 @@ impl std::fmt::Debug for MergeFileResult {
ds.finish()
}
}

/// Input structure for merging a file.
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> {
/// Creates a new, empty merge file input.
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
}

/// Sets the content of the file.
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
}

/// Sets the path of the file.
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
}

/// Sets the mode of the file.
pub fn mode(&mut self, mode: Option<FileMode>) -> &mut MergeFileInput<'a> {
self.raw.mode = mode.map_or(0, u32::from);
self
}
}

/// Merges three versions of a file: an ancestor version, our version, and their version.
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(), Some("file"));
assert_eq!(
String::from_utf8_lossy(merge_file_result.content()).to_string(),
r"<<<<<<< ours
foo
||||||| ancestor
base
=======
bar
>>>>>>> theirs
",
);
}
}