Skip to content

Commit d5a56e9

Browse files
Add bindings for git email create (#847)
Add bindings for `git_email_create_from_diff()` and `git_email_create_from_commit()`. Deprecate `git_diff_format_email()` to reflect upstream changes.
1 parent fe55127 commit d5a56e9

File tree

4 files changed

+235
-0
lines changed

4 files changed

+235
-0
lines changed

Diff for: libgit2-sys/lib.rs

+41
Original file line numberDiff line numberDiff line change
@@ -1992,6 +1992,28 @@ pub struct git_message_trailer_array {
19921992
pub _trailer_block: *mut c_char,
19931993
}
19941994

1995+
#[repr(C)]
1996+
pub struct git_email_create_options {
1997+
pub version: c_uint,
1998+
pub flags: u32,
1999+
pub diff_opts: git_diff_options,
2000+
pub diff_find_opts: git_diff_find_options,
2001+
pub subject_prefix: *const c_char,
2002+
pub start_number: usize,
2003+
pub reroll_number: usize,
2004+
}
2005+
2006+
pub const GIT_EMAIL_CREATE_OPTIONS_VERSION: c_uint = 1;
2007+
2008+
git_enum! {
2009+
pub enum git_email_create_flags_t {
2010+
GIT_EMAIL_CREATE_DEFAULT = 0,
2011+
GIT_EMAIL_CREATE_OMIT_NUMBERS = 1 << 0,
2012+
GIT_EMAIL_CREATE_ALWAYS_NUMBER = 1 << 1,
2013+
GIT_EMAIL_CREATE_NO_RENAMES = 1 << 2,
2014+
}
2015+
}
2016+
19952017
extern "C" {
19962018
// threads
19972019
pub fn git_libgit2_init() -> c_int;
@@ -4106,6 +4128,25 @@ extern "C" {
41064128
replace_email: *const c_char,
41074129
) -> c_int;
41084130

4131+
// email
4132+
pub fn git_email_create_from_diff(
4133+
out: *mut git_buf,
4134+
diff: *mut git_diff,
4135+
patch_idx: usize,
4136+
patch_count: usize,
4137+
commit_id: *const git_oid,
4138+
summary: *const c_char,
4139+
body: *const c_char,
4140+
author: *const git_signature,
4141+
given_opts: *const git_email_create_options,
4142+
) -> c_int;
4143+
4144+
pub fn git_email_create_from_commit(
4145+
out: *mut git_buf,
4146+
commit: *mut git_commit,
4147+
given_opts: *const git_email_create_options,
4148+
) -> c_int;
4149+
41094150
pub fn git_trace_set(level: git_trace_level_t, cb: git_trace_cb) -> c_int;
41104151
}
41114152

Diff for: src/diff.rs

+9
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,8 @@ impl<'repo> Diff<'repo> {
254254
/// Create an e-mail ready patch from a diff.
255255
///
256256
/// Matches the format created by `git format-patch`
257+
#[doc(hidden)]
258+
#[deprecated(note = "refactored to `Email::from_diff` to match upstream")]
257259
pub fn format_email(
258260
&mut self,
259261
patch_no: usize,
@@ -277,6 +279,7 @@ impl<'repo> Diff<'repo> {
277279
raw_opts.body = message.as_ptr() as *const _;
278280
raw_opts.author = commit.author().raw();
279281
let buf = Buf::new();
282+
#[allow(deprecated)]
280283
unsafe {
281284
try_call!(raw::git_diff_format_email(buf.raw(), self.raw, &*raw_opts));
282285
}
@@ -1480,6 +1483,11 @@ impl DiffFindOptions {
14801483
}
14811484

14821485
// TODO: expose git_diff_similarity_metric
1486+
1487+
/// Acquire a pointer to the underlying raw options.
1488+
pub unsafe fn raw(&mut self) -> *const raw::git_diff_find_options {
1489+
&self.raw
1490+
}
14831491
}
14841492

14851493
impl Default for DiffFormatEmailOptions {
@@ -1775,6 +1783,7 @@ mod tests {
17751783
None,
17761784
)
17771785
.unwrap();
1786+
#[allow(deprecated)]
17781787
let actual_email = diff.format_email(1, 1, &updated_commit, None).unwrap();
17791788
let actual_email = actual_email.as_str().unwrap();
17801789
assert!(

Diff for: src/email.rs

+183
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
use std::ffi::CString;
2+
use std::{mem, ptr};
3+
4+
use crate::util::Binding;
5+
use crate::{raw, Buf, Commit, DiffFindOptions, DiffOptions, Error, IntoCString};
6+
use crate::{Diff, Oid, Signature};
7+
8+
/// A structure to represent patch in mbox format for sending via email
9+
pub struct Email {
10+
buf: Buf,
11+
}
12+
13+
/// Options for controlling the formatting of the generated e-mail.
14+
pub struct EmailCreateOptions {
15+
diff_options: DiffOptions,
16+
diff_find_options: DiffFindOptions,
17+
subject_prefix: Option<CString>,
18+
raw: raw::git_email_create_options,
19+
}
20+
21+
impl Default for EmailCreateOptions {
22+
fn default() -> Self {
23+
// Defaults options created in corresponding to `GIT_EMAIL_CREATE_OPTIONS_INIT`
24+
let default_options = raw::git_email_create_options {
25+
version: raw::GIT_EMAIL_CREATE_OPTIONS_VERSION,
26+
flags: raw::GIT_EMAIL_CREATE_DEFAULT as u32,
27+
diff_opts: unsafe { mem::zeroed() },
28+
diff_find_opts: unsafe { mem::zeroed() },
29+
subject_prefix: ptr::null(),
30+
start_number: 1,
31+
reroll_number: 0,
32+
};
33+
let mut diff_options = DiffOptions::new();
34+
diff_options.show_binary(true).context_lines(3);
35+
Self {
36+
diff_options,
37+
diff_find_options: DiffFindOptions::new(),
38+
subject_prefix: None,
39+
raw: default_options,
40+
}
41+
}
42+
}
43+
44+
impl EmailCreateOptions {
45+
/// Creates a new set of email create options
46+
///
47+
/// By default, options include rename detection and binary
48+
/// diffs to match `git format-patch`.
49+
pub fn new() -> Self {
50+
Self::default()
51+
}
52+
53+
fn flag(&mut self, opt: raw::git_email_create_flags_t, val: bool) -> &mut Self {
54+
let opt = opt as u32;
55+
if val {
56+
self.raw.flags |= opt;
57+
} else {
58+
self.raw.flags &= !opt;
59+
}
60+
self
61+
}
62+
63+
/// Flag indicating whether patch numbers are included in the subject prefix.
64+
pub fn omit_numbers(&mut self, omit: bool) -> &mut Self {
65+
self.flag(raw::GIT_EMAIL_CREATE_OMIT_NUMBERS, omit)
66+
}
67+
68+
/// Flag indicating whether numbers included in the subject prefix even when
69+
/// the patch is for a single commit (1/1).
70+
pub fn always_number(&mut self, always: bool) -> &mut Self {
71+
self.flag(raw::GIT_EMAIL_CREATE_ALWAYS_NUMBER, always)
72+
}
73+
74+
/// Flag indicating whether rename or similarity detection are ignored.
75+
pub fn ignore_renames(&mut self, ignore: bool) -> &mut Self {
76+
self.flag(raw::GIT_EMAIL_CREATE_NO_RENAMES, ignore)
77+
}
78+
79+
/// Get mutable access to `DiffOptions` that are used for creating diffs.
80+
pub fn diff_options(&mut self) -> &mut DiffOptions {
81+
&mut self.diff_options
82+
}
83+
84+
/// Get mutable access to `DiffFindOptions` that are used for finding
85+
/// similarities within diffs.
86+
pub fn diff_find_options(&mut self) -> &mut DiffFindOptions {
87+
&mut self.diff_find_options
88+
}
89+
90+
/// Set the subject prefix
91+
///
92+
/// The default value for this is "PATCH". If set to an empty string ("")
93+
/// then only the patch numbers will be shown in the prefix.
94+
/// If the subject_prefix is empty and patch numbers are not being shown,
95+
/// the prefix will be omitted entirely.
96+
pub fn subject_prefix<T: IntoCString>(&mut self, t: T) -> &mut Self {
97+
self.subject_prefix = Some(t.into_c_string().unwrap());
98+
self
99+
}
100+
101+
/// Set the starting patch number; this cannot be 0.
102+
///
103+
/// The default value for this is 1.
104+
pub fn start_number(&mut self, number: usize) -> &mut Self {
105+
self.raw.start_number = number;
106+
self
107+
}
108+
109+
/// Set the "re-roll" number.
110+
///
111+
/// The default value for this is 0 (no re-roll).
112+
pub fn reroll_number(&mut self, number: usize) -> &mut Self {
113+
self.raw.reroll_number = number;
114+
self
115+
}
116+
117+
/// Acquire a pointer to the underlying raw options.
118+
///
119+
/// This function is unsafe as the pointer is only valid so long as this
120+
/// structure is not moved, modified, or used elsewhere.
121+
unsafe fn raw(&mut self) -> *const raw::git_email_create_options {
122+
self.raw.subject_prefix = self
123+
.subject_prefix
124+
.as_ref()
125+
.map(|s| s.as_ptr())
126+
.unwrap_or(ptr::null());
127+
self.raw.diff_opts = ptr::read(self.diff_options.raw());
128+
self.raw.diff_find_opts = ptr::read(self.diff_find_options.raw());
129+
&self.raw as *const _
130+
}
131+
}
132+
133+
impl Email {
134+
/// Returns a byte slice with stored e-mail patch in. `Email` could be
135+
/// created by one of the `from_*` functions.
136+
pub fn as_slice(&self) -> &[u8] {
137+
&self.buf
138+
}
139+
140+
/// Create a diff for a commit in mbox format for sending via email.
141+
pub fn from_diff<T: IntoCString>(
142+
diff: &Diff<'_>,
143+
patch_idx: usize,
144+
patch_count: usize,
145+
commit_id: &Oid,
146+
summary: T,
147+
body: T,
148+
author: &Signature<'_>,
149+
opts: &mut EmailCreateOptions,
150+
) -> Result<Self, Error> {
151+
let buf = Buf::new();
152+
let summary = summary.into_c_string()?;
153+
let body = body.into_c_string()?;
154+
unsafe {
155+
try_call!(raw::git_email_create_from_diff(
156+
buf.raw(),
157+
Binding::raw(diff),
158+
patch_idx,
159+
patch_count,
160+
Binding::raw(commit_id),
161+
summary.as_ptr(),
162+
body.as_ptr(),
163+
Binding::raw(author),
164+
opts.raw()
165+
));
166+
Ok(Self { buf })
167+
}
168+
}
169+
170+
/// Create a diff for a commit in mbox format for sending via email.
171+
/// The commit must not be a merge commit.
172+
pub fn from_commit(commit: &Commit<'_>, opts: &mut EmailCreateOptions) -> Result<Self, Error> {
173+
let buf = Buf::new();
174+
unsafe {
175+
try_call!(raw::git_email_create_from_commit(
176+
buf.raw(),
177+
commit.raw(),
178+
opts.raw()
179+
));
180+
Ok(Self { buf })
181+
}
182+
}
183+
}

Diff for: src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ pub use crate::describe::{Describe, DescribeFormatOptions, DescribeOptions};
9393
pub use crate::diff::{Deltas, Diff, DiffDelta, DiffFile, DiffOptions};
9494
pub use crate::diff::{DiffBinary, DiffBinaryFile, DiffBinaryKind};
9595
pub use crate::diff::{DiffFindOptions, DiffHunk, DiffLine, DiffLineType, DiffStats};
96+
pub use crate::email::{Email, EmailCreateOptions};
9697
pub use crate::error::Error;
9798
pub use crate::index::{
9899
Index, IndexConflict, IndexConflicts, IndexEntries, IndexEntry, IndexMatchedPath,
@@ -675,6 +676,7 @@ mod config;
675676
mod cred;
676677
mod describe;
677678
mod diff;
679+
mod email;
678680
mod error;
679681
mod index;
680682
mod indexer;

0 commit comments

Comments
 (0)