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 bindings for git email create #847

Merged
merged 1 commit into from
Jun 13, 2022
Merged
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
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