Skip to content

Commit

Permalink
Add bindings for git email create (#847)
Browse files Browse the repository at this point in the history
Add bindings for `git_email_create_from_diff()` and
`git_email_create_from_commit()`. Deprecate `git_diff_format_email()` to
reflect upstream changes.
  • Loading branch information
f0rget-the-sad authored Jun 13, 2022
1 parent fe55127 commit d5a56e9
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 0 deletions.
41 changes: 41 additions & 0 deletions libgit2-sys/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1992,6 +1992,28 @@ pub struct git_message_trailer_array {
pub _trailer_block: *mut c_char,
}

#[repr(C)]
pub struct git_email_create_options {
pub version: c_uint,
pub flags: u32,
pub diff_opts: git_diff_options,
pub diff_find_opts: git_diff_find_options,
pub subject_prefix: *const c_char,
pub start_number: usize,
pub reroll_number: usize,
}

pub const GIT_EMAIL_CREATE_OPTIONS_VERSION: c_uint = 1;

git_enum! {
pub enum git_email_create_flags_t {
GIT_EMAIL_CREATE_DEFAULT = 0,
GIT_EMAIL_CREATE_OMIT_NUMBERS = 1 << 0,
GIT_EMAIL_CREATE_ALWAYS_NUMBER = 1 << 1,
GIT_EMAIL_CREATE_NO_RENAMES = 1 << 2,
}
}

extern "C" {
// threads
pub fn git_libgit2_init() -> c_int;
Expand Down Expand Up @@ -4106,6 +4128,25 @@ extern "C" {
replace_email: *const c_char,
) -> c_int;

// email
pub fn git_email_create_from_diff(
out: *mut git_buf,
diff: *mut git_diff,
patch_idx: usize,
patch_count: usize,
commit_id: *const git_oid,
summary: *const c_char,
body: *const c_char,
author: *const git_signature,
given_opts: *const git_email_create_options,
) -> c_int;

pub fn git_email_create_from_commit(
out: *mut git_buf,
commit: *mut git_commit,
given_opts: *const git_email_create_options,
) -> c_int;

pub fn git_trace_set(level: git_trace_level_t, cb: git_trace_cb) -> c_int;
}

Expand Down
9 changes: 9 additions & 0 deletions src/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@ impl<'repo> Diff<'repo> {
/// Create an e-mail ready patch from a diff.
///
/// Matches the format created by `git format-patch`
#[doc(hidden)]
#[deprecated(note = "refactored to `Email::from_diff` to match upstream")]
pub fn format_email(
&mut self,
patch_no: usize,
Expand All @@ -277,6 +279,7 @@ impl<'repo> Diff<'repo> {
raw_opts.body = message.as_ptr() as *const _;
raw_opts.author = commit.author().raw();
let buf = Buf::new();
#[allow(deprecated)]
unsafe {
try_call!(raw::git_diff_format_email(buf.raw(), self.raw, &*raw_opts));
}
Expand Down Expand Up @@ -1480,6 +1483,11 @@ impl DiffFindOptions {
}

// TODO: expose git_diff_similarity_metric

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

impl Default for DiffFormatEmailOptions {
Expand Down Expand Up @@ -1775,6 +1783,7 @@ mod tests {
None,
)
.unwrap();
#[allow(deprecated)]
let actual_email = diff.format_email(1, 1, &updated_commit, None).unwrap();
let actual_email = actual_email.as_str().unwrap();
assert!(
Expand Down
183 changes: 183 additions & 0 deletions src/email.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
use std::ffi::CString;
use std::{mem, ptr};

use crate::util::Binding;
use crate::{raw, Buf, Commit, DiffFindOptions, DiffOptions, Error, IntoCString};
use crate::{Diff, Oid, Signature};

/// A structure to represent patch in mbox format for sending via email
pub struct Email {
buf: Buf,
}

/// Options for controlling the formatting of the generated e-mail.
pub struct EmailCreateOptions {
diff_options: DiffOptions,
diff_find_options: DiffFindOptions,
subject_prefix: Option<CString>,
raw: raw::git_email_create_options,
}

impl Default for EmailCreateOptions {
fn default() -> Self {
// Defaults options created in corresponding to `GIT_EMAIL_CREATE_OPTIONS_INIT`
let default_options = raw::git_email_create_options {
version: raw::GIT_EMAIL_CREATE_OPTIONS_VERSION,
flags: raw::GIT_EMAIL_CREATE_DEFAULT as u32,
diff_opts: unsafe { mem::zeroed() },
diff_find_opts: unsafe { mem::zeroed() },
subject_prefix: ptr::null(),
start_number: 1,
reroll_number: 0,
};
let mut diff_options = DiffOptions::new();
diff_options.show_binary(true).context_lines(3);
Self {
diff_options,
diff_find_options: DiffFindOptions::new(),
subject_prefix: None,
raw: default_options,
}
}
}

impl EmailCreateOptions {
/// Creates a new set of email create options
///
/// By default, options include rename detection and binary
/// diffs to match `git format-patch`.
pub fn new() -> Self {
Self::default()
}

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

/// Flag indicating whether patch numbers are included in the subject prefix.
pub fn omit_numbers(&mut self, omit: bool) -> &mut Self {
self.flag(raw::GIT_EMAIL_CREATE_OMIT_NUMBERS, omit)
}

/// Flag indicating whether numbers included in the subject prefix even when
/// the patch is for a single commit (1/1).
pub fn always_number(&mut self, always: bool) -> &mut Self {
self.flag(raw::GIT_EMAIL_CREATE_ALWAYS_NUMBER, always)
}

/// Flag indicating whether rename or similarity detection are ignored.
pub fn ignore_renames(&mut self, ignore: bool) -> &mut Self {
self.flag(raw::GIT_EMAIL_CREATE_NO_RENAMES, ignore)
}

/// Get mutable access to `DiffOptions` that are used for creating diffs.
pub fn diff_options(&mut self) -> &mut DiffOptions {
&mut self.diff_options
}

/// Get mutable access to `DiffFindOptions` that are used for finding
/// similarities within diffs.
pub fn diff_find_options(&mut self) -> &mut DiffFindOptions {
&mut self.diff_find_options
}

/// Set the subject prefix
///
/// The default value for this is "PATCH". If set to an empty string ("")
/// then only the patch numbers will be shown in the prefix.
/// If the subject_prefix is empty and patch numbers are not being shown,
/// the prefix will be omitted entirely.
pub fn subject_prefix<T: IntoCString>(&mut self, t: T) -> &mut Self {
self.subject_prefix = Some(t.into_c_string().unwrap());
self
}

/// Set the starting patch number; this cannot be 0.
///
/// The default value for this is 1.
pub fn start_number(&mut self, number: usize) -> &mut Self {
self.raw.start_number = number;
self
}

/// Set the "re-roll" number.
///
/// The default value for this is 0 (no re-roll).
pub fn reroll_number(&mut self, number: usize) -> &mut Self {
self.raw.reroll_number = number;
self
}

/// Acquire a pointer to the underlying raw options.
///
/// This function is unsafe as the pointer is only valid so long as this
/// structure is not moved, modified, or used elsewhere.
unsafe fn raw(&mut self) -> *const raw::git_email_create_options {
self.raw.subject_prefix = self
.subject_prefix
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(ptr::null());
self.raw.diff_opts = ptr::read(self.diff_options.raw());
self.raw.diff_find_opts = ptr::read(self.diff_find_options.raw());
&self.raw as *const _
}
}

impl Email {
/// Returns a byte slice with stored e-mail patch in. `Email` could be
/// created by one of the `from_*` functions.
pub fn as_slice(&self) -> &[u8] {
&self.buf
}

/// Create a diff for a commit in mbox format for sending via email.
pub fn from_diff<T: IntoCString>(
diff: &Diff<'_>,
patch_idx: usize,
patch_count: usize,
commit_id: &Oid,
summary: T,
body: T,
author: &Signature<'_>,
opts: &mut EmailCreateOptions,
) -> Result<Self, Error> {
let buf = Buf::new();
let summary = summary.into_c_string()?;
let body = body.into_c_string()?;
unsafe {
try_call!(raw::git_email_create_from_diff(
buf.raw(),
Binding::raw(diff),
patch_idx,
patch_count,
Binding::raw(commit_id),
summary.as_ptr(),
body.as_ptr(),
Binding::raw(author),
opts.raw()
));
Ok(Self { buf })
}
}

/// Create a diff for a commit in mbox format for sending via email.
/// The commit must not be a merge commit.
pub fn from_commit(commit: &Commit<'_>, opts: &mut EmailCreateOptions) -> Result<Self, Error> {
let buf = Buf::new();
unsafe {
try_call!(raw::git_email_create_from_commit(
buf.raw(),
commit.raw(),
opts.raw()
));
Ok(Self { buf })
}
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ pub use crate::describe::{Describe, DescribeFormatOptions, DescribeOptions};
pub use crate::diff::{Deltas, Diff, DiffDelta, DiffFile, DiffOptions};
pub use crate::diff::{DiffBinary, DiffBinaryFile, DiffBinaryKind};
pub use crate::diff::{DiffFindOptions, DiffHunk, DiffLine, DiffLineType, DiffStats};
pub use crate::email::{Email, EmailCreateOptions};
pub use crate::error::Error;
pub use crate::index::{
Index, IndexConflict, IndexConflicts, IndexEntries, IndexEntry, IndexMatchedPath,
Expand Down Expand Up @@ -675,6 +676,7 @@ mod config;
mod cred;
mod describe;
mod diff;
mod email;
mod error;
mod index;
mod indexer;
Expand Down

0 comments on commit d5a56e9

Please sign in to comment.