Skip to content

Commit 7912c90

Browse files
authored
Add bindings for mailmap (#691)
* Add several bindings for mailmap Add bindings for - git_mailmap_free - git_mailmap_from_repository - git_mailmap_resolve_signature - git_commit_author_with_mailmap - git_commit_committer_with_mailmap This is half the functions in the "mailmap" group and the two convenience functions in the "commit" group, which is enough to make mailmap support useful. I omitted git_mailmap_resolve because of the slightly awkward function signature. I also omitted - git_mailmap_new - git_mailmap_from_buffer - git_mailmap_add_entry which support in-memory manipulation of a mailmap representation: there appears to be no way to write the representation to disk, neither directly nor indirectly, so those functions seem a bit pointless. * Add more mailmap bindings Add bindings for - git_mailmap_new - git_mailmap_from_buffer - git_mailmap_add_entry to support in-memory manipulation of a mailmap representation. Commit c5fe47d (Add several bindings for mailmap, 2021-03-26) notes that these seem silly on account of offering no way to write the representation back to disk. However, they are easy to support and effectively enable an out-of-tree .mailmap file.
1 parent 8e24a2e commit 7912c90

File tree

5 files changed

+304
-2
lines changed

5 files changed

+304
-2
lines changed

libgit2-sys/lib.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ pub enum git_odb_stream {}
9090
pub enum git_odb_object {}
9191
pub enum git_worktree {}
9292
pub enum git_transaction {}
93+
pub enum git_mailmap {}
9394

9495
#[repr(C)]
9596
pub struct git_revspec {
@@ -2598,7 +2599,17 @@ extern "C" {
25982599

25992600
// commit
26002601
pub fn git_commit_author(commit: *const git_commit) -> *const git_signature;
2602+
pub fn git_commit_author_with_mailmap(
2603+
out: *mut *mut git_signature,
2604+
commit: *const git_commit,
2605+
mailmap: *const git_mailmap,
2606+
) -> c_int;
26012607
pub fn git_commit_committer(commit: *const git_commit) -> *const git_signature;
2608+
pub fn git_commit_committer_with_mailmap(
2609+
out: *mut *mut git_signature,
2610+
commit: *const git_commit,
2611+
mailmap: *const git_mailmap,
2612+
) -> c_int;
26022613
pub fn git_commit_free(commit: *mut git_commit);
26032614
pub fn git_commit_id(commit: *const git_commit) -> *const git_oid;
26042615
pub fn git_commit_lookup(
@@ -3941,6 +3952,31 @@ extern "C" {
39413952
pub fn git_transaction_remove(tx: *mut git_transaction, refname: *const c_char) -> c_int;
39423953
pub fn git_transaction_commit(tx: *mut git_transaction) -> c_int;
39433954
pub fn git_transaction_free(tx: *mut git_transaction);
3955+
3956+
// Mailmap
3957+
pub fn git_mailmap_new(out: *mut *mut git_mailmap) -> c_int;
3958+
pub fn git_mailmap_from_buffer(
3959+
out: *mut *mut git_mailmap,
3960+
buf: *const c_char,
3961+
len: size_t,
3962+
) -> c_int;
3963+
pub fn git_mailmap_from_repository(
3964+
out: *mut *mut git_mailmap,
3965+
repo: *mut git_repository,
3966+
) -> c_int;
3967+
pub fn git_mailmap_free(mm: *mut git_mailmap);
3968+
pub fn git_mailmap_resolve_signature(
3969+
out: *mut *mut git_signature,
3970+
mm: *const git_mailmap,
3971+
sig: *const git_signature,
3972+
) -> c_int;
3973+
pub fn git_mailmap_add_entry(
3974+
mm: *mut git_mailmap,
3975+
real_name: *const c_char,
3976+
real_email: *const c_char,
3977+
replace_name: *const c_char,
3978+
replace_email: *const c_char,
3979+
) -> c_int;
39443980
}
39453981

39463982
pub fn init() {

src/commit.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::ptr;
66
use std::str;
77

88
use crate::util::Binding;
9-
use crate::{raw, signature, Buf, Error, IntoCString, Object, Oid, Signature, Time, Tree};
9+
use crate::{raw, signature, Buf, Error, IntoCString, Mailmap, Object, Oid, Signature, Time, Tree};
1010

1111
/// A structure to represent a git [commit][1]
1212
///
@@ -183,6 +183,20 @@ impl<'repo> Commit<'repo> {
183183
}
184184
}
185185

186+
/// Get the author of this commit, using the mailmap to map names and email
187+
/// addresses to canonical real names and email addresses.
188+
pub fn author_with_mailmap(&self, mailmap: &Mailmap) -> Result<Signature<'static>, Error> {
189+
let mut ret = ptr::null_mut();
190+
unsafe {
191+
try_call!(raw::git_commit_author_with_mailmap(
192+
&mut ret,
193+
&*self.raw,
194+
&*mailmap.raw()
195+
));
196+
Ok(Binding::from_raw(ret))
197+
}
198+
}
199+
186200
/// Get the committer of this commit.
187201
pub fn committer(&self) -> Signature<'_> {
188202
unsafe {
@@ -191,6 +205,20 @@ impl<'repo> Commit<'repo> {
191205
}
192206
}
193207

208+
/// Get the committer of this commit, using the mailmap to map names and email
209+
/// addresses to canonical real names and email addresses.
210+
pub fn committer_with_mailmap(&self, mailmap: &Mailmap) -> Result<Signature<'static>, Error> {
211+
let mut ret = ptr::null_mut();
212+
unsafe {
213+
try_call!(raw::git_commit_committer_with_mailmap(
214+
&mut ret,
215+
&*self.raw,
216+
&*mailmap.raw()
217+
));
218+
Ok(Binding::from_raw(ret))
219+
}
220+
}
221+
194222
/// Amend this existing commit with all non-`None` values
195223
///
196224
/// This creates a new commit that is exactly the same as the old commit,

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ pub use crate::index::{
9898
Index, IndexConflict, IndexConflicts, IndexEntries, IndexEntry, IndexMatchedPath,
9999
};
100100
pub use crate::indexer::{IndexerProgress, Progress};
101+
pub use crate::mailmap::Mailmap;
101102
pub use crate::mempack::Mempack;
102103
pub use crate::merge::{AnnotatedCommit, MergeOptions};
103104
pub use crate::message::{message_prettify, DEFAULT_COMMENT_CHAR};
@@ -660,6 +661,7 @@ mod diff;
660661
mod error;
661662
mod index;
662663
mod indexer;
664+
mod mailmap;
663665
mod mempack;
664666
mod merge;
665667
mod message;

src/mailmap.rs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
use std::ffi::CString;
2+
use std::ptr;
3+
4+
use crate::util::Binding;
5+
use crate::{raw, Error, Signature};
6+
7+
/// A structure to represent a repository's .mailmap file.
8+
///
9+
/// The representation cannot be written to disk.
10+
pub struct Mailmap {
11+
raw: *mut raw::git_mailmap,
12+
}
13+
14+
impl Binding for Mailmap {
15+
type Raw = *mut raw::git_mailmap;
16+
17+
unsafe fn from_raw(ptr: *mut raw::git_mailmap) -> Mailmap {
18+
Mailmap { raw: ptr }
19+
}
20+
21+
fn raw(&self) -> *mut raw::git_mailmap {
22+
self.raw
23+
}
24+
}
25+
26+
impl Drop for Mailmap {
27+
fn drop(&mut self) {
28+
unsafe {
29+
raw::git_mailmap_free(self.raw);
30+
}
31+
}
32+
}
33+
34+
impl Mailmap {
35+
/// Creates an empty, in-memory mailmap object.
36+
pub fn new() -> Result<Mailmap, Error> {
37+
crate::init();
38+
let mut ret = ptr::null_mut();
39+
unsafe {
40+
try_call!(raw::git_mailmap_new(&mut ret));
41+
Ok(Binding::from_raw(ret))
42+
}
43+
}
44+
45+
/// Creates an in-memory mailmap object representing the given buffer.
46+
pub fn from_buffer(buf: &str) -> Result<Mailmap, Error> {
47+
crate::init();
48+
let mut ret = ptr::null_mut();
49+
let len = buf.len();
50+
let buf = CString::new(buf)?;
51+
unsafe {
52+
try_call!(raw::git_mailmap_from_buffer(&mut ret, buf, len));
53+
Ok(Binding::from_raw(ret))
54+
}
55+
}
56+
57+
/// Adds a new entry to this in-memory mailmap object.
58+
pub fn add_entry(
59+
&mut self,
60+
real_name: Option<&str>,
61+
real_email: Option<&str>,
62+
replace_name: Option<&str>,
63+
replace_email: &str,
64+
) -> Result<(), Error> {
65+
let real_name = crate::opt_cstr(real_name)?;
66+
let real_email = crate::opt_cstr(real_email)?;
67+
let replace_name = crate::opt_cstr(replace_name)?;
68+
let replace_email = CString::new(replace_email)?;
69+
unsafe {
70+
try_call!(raw::git_mailmap_add_entry(
71+
self.raw,
72+
real_name,
73+
real_email,
74+
replace_name,
75+
replace_email
76+
));
77+
Ok(())
78+
}
79+
}
80+
81+
/// Resolves a signature to its real name and email address.
82+
pub fn resolve_signature(&self, sig: &Signature<'_>) -> Result<Signature<'static>, Error> {
83+
let mut ret = ptr::null_mut();
84+
unsafe {
85+
try_call!(raw::git_mailmap_resolve_signature(
86+
&mut ret,
87+
&*self.raw,
88+
sig.raw()
89+
));
90+
Ok(Binding::from_raw(ret))
91+
}
92+
}
93+
}
94+
95+
#[cfg(test)]
96+
mod tests {
97+
use super::*;
98+
99+
#[test]
100+
fn smoke() {
101+
let sig_name = "name";
102+
let sig_email = "email";
103+
let sig = t!(Signature::now(sig_name, sig_email));
104+
105+
let mut mm = t!(Mailmap::new());
106+
107+
let mailmapped_sig = t!(mm.resolve_signature(&sig));
108+
assert_eq!(mailmapped_sig.name(), Some(sig_name));
109+
assert_eq!(mailmapped_sig.email(), Some(sig_email));
110+
111+
t!(mm.add_entry(None, None, None, sig_email));
112+
t!(mm.add_entry(
113+
Some("real name"),
114+
Some("real@email"),
115+
Some(sig_name),
116+
sig_email,
117+
));
118+
119+
let mailmapped_sig = t!(mm.resolve_signature(&sig));
120+
assert_eq!(mailmapped_sig.name(), Some("real name"));
121+
assert_eq!(mailmapped_sig.email(), Some("real@email"));
122+
}
123+
124+
#[test]
125+
fn from_buffer() {
126+
let buf = "<prøper@emæil> <email>";
127+
let mm = t!(Mailmap::from_buffer(&buf));
128+
129+
let sig = t!(Signature::now("name", "email"));
130+
let mailmapped_sig = t!(mm.resolve_signature(&sig));
131+
assert_eq!(mailmapped_sig.name(), Some("name"));
132+
assert_eq!(mailmapped_sig.email(), Some("prøper@emæil"));
133+
}
134+
}

src/repo.rs

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::build::{CheckoutBuilder, RepoBuilder};
1111
use crate::diff::{
1212
binary_cb_c, file_cb_c, hunk_cb_c, line_cb_c, BinaryCb, DiffCallbacks, FileCb, HunkCb, LineCb,
1313
};
14+
use crate::mailmap::Mailmap;
1415
use crate::oid_array::OidArray;
1516
use crate::stash::{stash_cb, StashApplyOptions, StashCbData};
1617
use crate::string_array::StringArray;
@@ -2962,6 +2963,15 @@ impl Repository {
29622963
Ok(Binding::from_raw(raw))
29632964
}
29642965
}
2966+
2967+
/// Gets this repository's mailmap.
2968+
pub fn mailmap(&self) -> Result<Mailmap, Error> {
2969+
let mut ret = ptr::null_mut();
2970+
unsafe {
2971+
try_call!(raw::git_mailmap_from_repository(&mut ret, self.raw));
2972+
Ok(Binding::from_raw(ret))
2973+
}
2974+
}
29652975
}
29662976

29672977
impl Binding for Repository {
@@ -3144,7 +3154,9 @@ impl RepositoryInitOptions {
31443154
mod tests {
31453155
use crate::build::CheckoutBuilder;
31463156
use crate::CherrypickOptions;
3147-
use crate::{ObjectType, Oid, Repository, ResetType, SubmoduleIgnore, SubmoduleUpdate};
3157+
use crate::{
3158+
ObjectType, Oid, Repository, ResetType, Signature, SubmoduleIgnore, SubmoduleUpdate,
3159+
};
31483160
use std::ffi::OsStr;
31493161
use std::fs;
31503162
use std::path::Path;
@@ -3836,4 +3848,94 @@ mod tests {
38363848

38373849
Ok(())
38383850
}
3851+
3852+
#[test]
3853+
fn smoke_mailmap_from_repository() {
3854+
let (_td, repo) = crate::test::repo_init();
3855+
3856+
let commit = {
3857+
let head = t!(repo.head()).target().unwrap();
3858+
t!(repo.find_commit(head))
3859+
};
3860+
3861+
// This is our baseline for HEAD.
3862+
let author = commit.author();
3863+
let committer = commit.committer();
3864+
assert_eq!(author.name(), Some("name"));
3865+
assert_eq!(author.email(), Some("email"));
3866+
assert_eq!(committer.name(), Some("name"));
3867+
assert_eq!(committer.email(), Some("email"));
3868+
3869+
// There is no .mailmap file in the test repo so all signature identities are equal.
3870+
let mailmap = t!(repo.mailmap());
3871+
let mailmapped_author = t!(commit.author_with_mailmap(&mailmap));
3872+
let mailmapped_committer = t!(commit.committer_with_mailmap(&mailmap));
3873+
assert_eq!(mailmapped_author.name(), author.name());
3874+
assert_eq!(mailmapped_author.email(), author.email());
3875+
assert_eq!(mailmapped_committer.name(), committer.name());
3876+
assert_eq!(mailmapped_committer.email(), committer.email());
3877+
3878+
let commit = {
3879+
// - Add a .mailmap file to the repository.
3880+
// - Commit with a signature identity different from the author's.
3881+
// - Include entries for both author and committer to prove we call
3882+
// the right raw functions.
3883+
let mailmap_file = Path::new(".mailmap");
3884+
let p = Path::new(repo.workdir().unwrap()).join(&mailmap_file);
3885+
t!(fs::write(
3886+
p,
3887+
r#"
3888+
Author Name <author.proper@email> name <email>
3889+
Committer Name <committer.proper@email> <committer@email>"#,
3890+
));
3891+
let mut index = t!(repo.index());
3892+
t!(index.add_path(&mailmap_file));
3893+
let id_mailmap = t!(index.write_tree());
3894+
let tree_mailmap = t!(repo.find_tree(id_mailmap));
3895+
3896+
let head = t!(repo.commit(
3897+
Some("HEAD"),
3898+
&author,
3899+
t!(&Signature::now("committer", "committer@email")),
3900+
"Add mailmap",
3901+
&tree_mailmap,
3902+
&[&commit],
3903+
));
3904+
t!(repo.find_commit(head))
3905+
};
3906+
3907+
// Sanity check that we're working with the right commit and that its
3908+
// author and committer identities differ.
3909+
let author = commit.author();
3910+
let committer = commit.committer();
3911+
assert_ne!(author.name(), committer.name());
3912+
assert_ne!(author.email(), committer.email());
3913+
assert_eq!(author.name(), Some("name"));
3914+
assert_eq!(author.email(), Some("email"));
3915+
assert_eq!(committer.name(), Some("committer"));
3916+
assert_eq!(committer.email(), Some("committer@email"));
3917+
3918+
// Fetch the newly added .mailmap from the repository.
3919+
let mailmap = t!(repo.mailmap());
3920+
let mailmapped_author = t!(commit.author_with_mailmap(&mailmap));
3921+
let mailmapped_committer = t!(commit.committer_with_mailmap(&mailmap));
3922+
3923+
let mm_resolve_author = t!(mailmap.resolve_signature(&author));
3924+
let mm_resolve_committer = t!(mailmap.resolve_signature(&committer));
3925+
3926+
// Mailmap Signature lifetime is independent of Commit lifetime.
3927+
drop(author);
3928+
drop(committer);
3929+
drop(commit);
3930+
3931+
// author_with_mailmap() + committer_with_mailmap() work
3932+
assert_eq!(mailmapped_author.name(), Some("Author Name"));
3933+
assert_eq!(mailmapped_author.email(), Some("author.proper@email"));
3934+
assert_eq!(mailmapped_committer.name(), Some("Committer Name"));
3935+
assert_eq!(mailmapped_committer.email(), Some("committer.proper@email"));
3936+
3937+
// resolve_signature() works
3938+
assert_eq!(mm_resolve_author.email(), mailmapped_author.email());
3939+
assert_eq!(mm_resolve_committer.email(), mailmapped_committer.email());
3940+
}
38393941
}

0 commit comments

Comments
 (0)