Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add function: git_merge_file_from_index #1062

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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
38 changes: 37 additions & 1 deletion libgit2-sys/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// This is required to link libz when libssh2-sys is not included.
extern crate libz_sys as libz;

use libc::{c_char, c_int, c_uchar, c_uint, c_void, size_t};
use libc::{c_char, c_int, c_uchar, c_uint, c_ushort, c_void, size_t};
#[cfg(feature = "ssh")]
use libssh2_sys as libssh2;
use std::ffi::CStr;
Expand Down Expand Up @@ -1350,6 +1350,27 @@ pub struct git_merge_options {
pub file_flags: u32,
}

#[repr(C)]
pub struct git_merge_file_options {
pub version: c_uint,
pub ancestor_label: *const c_char,
pub our_label: *const c_char,
pub their_label: *const c_char,
pub favor: git_merge_file_favor_t,
pub flags: u32,
pub marker_size: c_ushort,
}

#[repr(C)]
#[derive(Clone, Copy)]
pub struct git_merge_file_result {
pub automergeable: c_uint,
pub path: *const c_char,
pub mode: c_uint,
pub ptr: *const c_char,
pub len: size_t,
}

git_enum! {
pub enum git_merge_flag_t {
GIT_MERGE_FIND_RENAMES = 1 << 0,
Expand Down Expand Up @@ -1379,6 +1400,8 @@ git_enum! {
GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL = 1 << 5,
GIT_MERGE_FILE_DIFF_PATIENCE = 1 << 6,
GIT_MERGE_FILE_DIFF_MINIMAL = 1 << 7,
GIT_MERGE_FILE_STYLE_ZDIFF3 = 1 << 8,
GIT_MERGE_FILE_ACCEPT_CONFLICTS = 1 << 9,
}
}

Expand Down Expand Up @@ -3378,6 +3401,8 @@ extern "C" {
their_tree: *const git_tree,
opts: *const git_merge_options,
) -> 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;

// merge analysis
Expand Down Expand Up @@ -3519,6 +3544,17 @@ extern "C" {
input_array: *const git_oid,
) -> c_int;

pub fn git_merge_file_from_index(
out: *mut git_merge_file_result,
repo: *mut git_repository,
ancestor: *const git_index_entry,
ours: *const git_index_entry,
theirs: *const git_index_entry,
opts: *const git_merge_file_options,
) -> c_int;

pub fn git_merge_file_result_free(file_result: *mut git_merge_file_result);

// pathspec
pub fn git_pathspec_free(ps: *mut git_pathspec);
pub fn git_pathspec_match_diff(
Expand Down
46 changes: 46 additions & 0 deletions src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,52 @@ impl Index {
}
}

impl IndexEntry {
/// Create a raw index entry.
///
/// The returned `raw::git_index_entry` contains a pointer to a `CString` path, which is also
/// returned because it's lifetime must exceed the lifetime of the `raw::git_index_entry`.
pub fn to_raw(&self) -> Result<(raw::git_index_entry, CString), Error> {
let path = CString::new(&self.path[..])?;

// libgit2 encodes the length of the path in the lower bits of the
// `flags` entry, so mask those out and recalculate here to ensure we
// don't corrupt anything.
let mut flags = self.flags & !raw::GIT_INDEX_ENTRY_NAMEMASK;

if self.path.len() < raw::GIT_INDEX_ENTRY_NAMEMASK as usize {
flags |= self.path.len() as u16;
} else {
flags |= raw::GIT_INDEX_ENTRY_NAMEMASK;
}

unsafe {
let raw = raw::git_index_entry {
dev: self.dev,
ino: self.ino,
mode: self.mode,
uid: self.uid,
gid: self.gid,
file_size: self.file_size,
id: *self.id.raw(),
flags,
flags_extended: self.flags_extended,
path: path.as_ptr(),
mtime: raw::git_index_time {
seconds: self.mtime.seconds(),
nanoseconds: self.mtime.nanoseconds(),
},
ctime: raw::git_index_time {
seconds: self.ctime.seconds(),
nanoseconds: self.ctime.nanoseconds(),
},
};

Ok((raw, path))
}
}
}

impl Binding for Index {
type Raw = *mut raw::git_index;
unsafe fn from_raw(raw: *mut raw::git_index) -> Index {
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ 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, MergeOptions};
pub use crate::merge::{AnnotatedCommit, MergeFileOptions, MergeFileResult, MergeOptions};
pub use crate::message::{
message_prettify, message_trailers_bytes, message_trailers_strs, MessageTrailersBytes,
MessageTrailersBytesIterator, MessageTrailersStrs, MessageTrailersStrsIterator,
Expand Down
223 changes: 222 additions & 1 deletion src/merge.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use libc::c_uint;
use libc::{c_uint, c_ushort};
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::IntoCString;
use crate::{raw, Commit, FileFavor, Oid};

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

/// Options for merging a file.
pub struct MergeFileOptions {
ancestor_label: Option<CString>,
our_label: Option<CString>,
their_label: Option<CString>,
raw: raw::git_merge_file_options,
}

/// Information about file-level merging.
pub struct MergeFileResult<'repo> {
raw: raw::git_merge_file_result,
_marker: marker::PhantomData<&'repo str>,
}

impl<'repo> AnnotatedCommit<'repo> {
/// Gets the commit ID that the given git_annotated_commit refers to
pub fn id(&self) -> Oid {
Expand Down Expand Up @@ -192,3 +209,207 @@ impl<'repo> Drop for AnnotatedCommit<'repo> {
unsafe { raw::git_annotated_commit_free(self.raw) }
}
}

impl Default for MergeFileOptions {
fn default() -> Self {
Self::new()
}
}

impl MergeFileOptions {
/// Creates a default set of merge file options.
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
}

/// Label for the ancestor file side of the conflict which will be prepended
/// to labels in diff3-format merge files.
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
}

/// Label for our file side of the conflict which will be prepended to labels
/// in merge files.
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
}

/// Label for their file side of the conflict which will be prepended to labels
/// in merge files.
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
}

/// Specify a side to favor for resolving conflicts
pub fn favor(&mut self, favor: FileFavor) -> &mut MergeFileOptions {
self.raw.favor = favor.convert();
self
}

fn flag(&mut self, opt: u32, val: bool) -> &mut MergeFileOptions {
if val {
self.raw.flags |= opt;
} else {
self.raw.flags &= !opt;
}
self
}

/// Create standard conflicted merge files
pub fn style_standard(&mut self, standard: bool) -> &mut MergeFileOptions {
self.flag(raw::GIT_MERGE_FILE_STYLE_MERGE as u32, standard)
}

/// Create diff3-style file
pub fn style_diff3(&mut self, diff3: bool) -> &mut MergeFileOptions {
self.flag(raw::GIT_MERGE_FILE_STYLE_DIFF3 as u32, diff3)
}

/// Condense non-alphanumeric regions for simplified diff file
pub fn simplify_alnum(&mut self, simplify: bool) -> &mut MergeFileOptions {
self.flag(raw::GIT_MERGE_FILE_SIMPLIFY_ALNUM as u32, simplify)
}

/// Ignore all whitespace
pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut MergeFileOptions {
self.flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE as u32, ignore)
}

/// Ignore changes in amount of whitespace
pub fn ignore_whitespace_change(&mut self, ignore: bool) -> &mut MergeFileOptions {
self.flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE as u32, ignore)
}

/// Ignore whitespace at end of line
pub fn ignore_whitespace_eol(&mut self, ignore: bool) -> &mut MergeFileOptions {
self.flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL as u32, ignore)
}

/// Use the "patience diff" algorithm
pub fn patience(&mut self, patience: bool) -> &mut MergeFileOptions {
self.flag(raw::GIT_MERGE_FILE_DIFF_PATIENCE as u32, patience)
}

/// Take extra time to find minimal diff
pub fn minimal(&mut self, minimal: bool) -> &mut MergeFileOptions {
self.flag(raw::GIT_MERGE_FILE_DIFF_MINIMAL as u32, minimal)
}

/// Create zdiff3 ("zealous diff3")-style files
pub fn style_zdiff3(&mut self, zdiff3: bool) -> &mut MergeFileOptions {
self.flag(raw::GIT_MERGE_FILE_STYLE_ZDIFF3 as u32, zdiff3)
}

/// Do not produce file conflicts when common regions have changed
pub fn accept_conflicts(&mut self, accept: bool) -> &mut MergeFileOptions {
self.flag(raw::GIT_MERGE_FILE_ACCEPT_CONFLICTS as u32, accept)
}

/// The size of conflict markers (eg, "<<<<<<<"). Default is 7.
pub fn marker_size(&mut self, size: u16) -> &mut MergeFileOptions {
self.raw.marker_size = size as c_ushort;
self
}

/// Acquire a pointer to the underlying raw options.
pub unsafe fn raw(&mut self) -> *const raw::git_merge_file_options {
&self.raw as *const _
}
}

impl<'repo> MergeFileResult<'repo> {
/// True if the output was automerged, false if the output contains
/// conflict markers.
pub fn is_automergeable(&self) -> bool {
self.raw.automergeable > 0
}

/// The path that the resultant merge file should use.
///
/// returns `None` if a filename conflict would occur,
/// or if the path is not valid utf-8
pub fn path(&self) -> Option<&str> {
self.path_bytes()
.and_then(|bytes| str::from_utf8(bytes).ok())
}

/// Gets the path as a byte slice.
pub fn path_bytes(&self) -> Option<&[u8]> {
unsafe { crate::opt_bytes(self, self.raw.path) }
}

/// The mode that the resultant merge file should use.
pub fn mode(&self) -> u32 {
self.raw.mode as u32
}

/// The contents of the merge.
pub fn content(&self) -> &'repo [u8] {
unsafe { std::slice::from_raw_parts(self.raw.ptr as *const u8, self.raw.len as usize) }
}
}

impl<'repo> Binding for MergeFileResult<'repo> {
type Raw = raw::git_merge_file_result;
unsafe fn from_raw(raw: raw::git_merge_file_result) -> MergeFileResult<'repo> {
MergeFileResult {
raw,
_marker: marker::PhantomData,
}
}
fn raw(&self) -> raw::git_merge_file_result {
self.raw
}
}

impl<'repo> Drop for MergeFileResult<'repo> {
fn drop(&mut self) {
unsafe { raw::git_merge_file_result_free(&mut self.raw) }
}
}

impl<'repo> std::fmt::Debug for MergeFileResult<'repo> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut ds = f.debug_struct("MergeFileResult");
if let Some(path) = &self.path() {
ds.field("path", path);
}
ds.field("automergeable", &self.is_automergeable());
ds.field("mode", &self.mode());
ds.finish()
}
}
Loading