Skip to content

Commit 1a7c34b

Browse files
feat: implements mailmap feature (#115)
Co-authored-by: seokju-na <seokju.me@gmail.com>
1 parent 802dab2 commit 1a7c34b

21 files changed

+504
-2
lines changed

index.d.ts

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,33 @@ export interface IndexUpdateAllOptions {
656656
*/
657657
onMatch?: (args: IndexOnMatchCallbackArgs) => number
658658
}
659+
export interface AddMailmapEntryData {
660+
realName?: string
661+
realEmail?: string
662+
replaceName?: string
663+
replaceEmail: string
664+
}
665+
/**
666+
* Create a mailmap from the contents of a string.
667+
*
668+
* The format of the string should follow the rules of the mailmap file:
669+
* ```
670+
* # Comment line (ignored)
671+
* Seokju Me <seokju.me@toss.im> Seokju Na <seokju.me@gmail.com>
672+
* ```
673+
*
674+
* @param {string} content - Content of the mailmap file
675+
* @returns A new mailmap object
676+
* @throws An error if operation failed
677+
*
678+
* @category Mailmap
679+
*
680+
* @signature
681+
* ```ts
682+
* function createMailmapFromBuffer(content: string): Mailmap;
683+
* ```
684+
*/
685+
export declare function createMailmapFromBuffer(content: string): Mailmap
659686
/**
660687
* - `Any` : Any kind of git object
661688
* - `Commit` : An object which corresponds to a git commit
@@ -2050,6 +2077,38 @@ export declare class Commit {
20502077
* @returns `GitObject` that casted from this commit.
20512078
*/
20522079
asObject(): GitObject
2080+
/**
2081+
* Get the author of this commit, using the mailmap to map it to the canonical name and email.
2082+
*
2083+
* @category Commit/Methods
2084+
*
2085+
* @signature
2086+
* ```ts
2087+
* class Commit {
2088+
* authorWithMailmap(mailmap: Mailmap): Signature;
2089+
* }
2090+
* ```
2091+
*
2092+
* @param {Mailmap} mailmap - The mailmap to use for mapping
2093+
* @returns Author signature of this commit with mapping applied
2094+
*/
2095+
authorWithMailmap(mailmap: Mailmap): Signature
2096+
/**
2097+
* Get the committer of this commit, using the mailmap to map it to the canonical name and email.
2098+
*
2099+
* @category Commit/Methods
2100+
*
2101+
* @signature
2102+
* ```ts
2103+
* class Commit {
2104+
* committerWithMailmap(mailmap: Mailmap): Signature;
2105+
* }
2106+
* ```
2107+
*
2108+
* @param {Mailmap} mailmap - The mailmap to use for mapping
2109+
* @returns Committer signature of this commit with mapping applied
2110+
*/
2111+
committerWithMailmap(mailmap: Mailmap): Signature
20532112
}
20542113
/** An iterator over the `ConfigEntry` values of a config. */
20552114
export declare class ConfigEntries {
@@ -3138,6 +3197,54 @@ export declare class Index {
31383197
export declare class IndexEntries {
31393198
[Symbol.iterator](): Iterator<IndexEntry, void, void>
31403199
}
3200+
/** A wrapper around git2::Mailmap providing Node.js bindings */
3201+
export declare class Mailmap {
3202+
/**
3203+
* Add a new Mailmap entry.
3204+
*
3205+
* Maps an author/committer (specified by `replace_name` and `replace_email`)
3206+
* to the specified real name and email. The `replace_email` is required but
3207+
* the other parameters can be null.
3208+
*
3209+
* If both `replace_name` and `replace_email` are provided, then the entry will
3210+
* apply to those who match both. If only `replace_name` is provided,
3211+
* it will apply to anyone with that name, regardless of email. If only
3212+
* `replace_email` is provided, it will apply to anyone with that email,
3213+
* regardless of name.
3214+
*
3215+
* @param {AddMailmapEntryData} entry - The mailmap entry data.
3216+
* @returns {void}
3217+
* @throws An error if the operation failed.
3218+
*
3219+
* @category Mailmap/Methods
3220+
*
3221+
* @signature
3222+
* ```ts
3223+
* class Mailmap {
3224+
* addEntry(entry: AddMailmapEntryData): void;
3225+
* }
3226+
* ```
3227+
*/
3228+
addEntry(entry: AddMailmapEntryData): void
3229+
/**
3230+
* Resolve a signature to its canonical form using a mailmap.
3231+
*
3232+
* Returns a new signature with the canonical name and email.
3233+
*
3234+
* @param {SignaturePayload} signature - Signature to resolve
3235+
* @returns The resolved signature with canonical name and email
3236+
*
3237+
* @category Mailmap/Methods
3238+
*
3239+
* @signature
3240+
* ```ts
3241+
* class Mailmap {
3242+
* resolveSignature(signature: SignaturePayload): Signature;
3243+
* }
3244+
* ```
3245+
*/
3246+
resolveSignature(signature: SignaturePayload): Signature
3247+
}
31413248
/**
31423249
* A class to represent a git [object][1].
31433250
*
@@ -4007,7 +4114,7 @@ export declare class Repository {
40074114
* @signature
40084115
* ```ts
40094116
* class Repository {
4010-
* addIgnoreRule(rules: string): boolean;
4117+
* addIgnoreRule(rules: string): void;
40114118
* }
40124119
* ```
40134120
*
@@ -4092,6 +4199,21 @@ export declare class Repository {
40924199
* @returns The index file for this repository.
40934200
*/
40944201
index(): Index
4202+
/**
4203+
* Gets this repository's mailmap.
4204+
*
4205+
* @category Repository/Methods
4206+
*
4207+
* @signature
4208+
* ```ts
4209+
* class Repository {
4210+
* mailmap(): Mailmap | null;
4211+
* }
4212+
* ```
4213+
*
4214+
* @returns The mailmap object if it exists, null otherwise
4215+
*/
4216+
mailmap(): Mailmap | null
40954217
/**
40964218
* Lookup a reference to one of the objects in a repository.
40974219
*

index.js

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub mod diff;
99
mod error;
1010
pub mod ignore;
1111
pub mod index;
12+
pub mod mailmap;
1213
pub mod object;
1314
pub mod oid;
1415
pub mod reference;

src/mailmap.rs

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
use crate::commit::Commit;
2+
use crate::repository::Repository;
3+
use crate::signature::{Signature, SignaturePayload};
4+
use napi::bindgen_prelude::*;
5+
use napi_derive::napi;
6+
use std::ops::{Deref, DerefMut};
7+
8+
pub(crate) enum MailmapInner {
9+
Repo(SharedReference<Repository, git2::Mailmap>),
10+
Owned(git2::Mailmap),
11+
}
12+
13+
impl Deref for MailmapInner {
14+
type Target = git2::Mailmap;
15+
16+
fn deref(&self) -> &Self::Target {
17+
match self {
18+
Self::Repo(repo) => repo.deref(),
19+
Self::Owned(mailmap) => mailmap,
20+
}
21+
}
22+
}
23+
24+
impl DerefMut for MailmapInner {
25+
fn deref_mut(&mut self) -> &mut Self::Target {
26+
match self {
27+
Self::Repo(repo) => repo.deref_mut(),
28+
Self::Owned(mailmap) => mailmap,
29+
}
30+
}
31+
}
32+
33+
#[napi(object)]
34+
pub struct AddMailmapEntryData {
35+
pub real_name: Option<String>,
36+
pub real_email: Option<String>,
37+
pub replace_name: Option<String>,
38+
pub replace_email: String,
39+
}
40+
41+
/// A wrapper around git2::Mailmap providing Node.js bindings
42+
#[napi]
43+
pub struct Mailmap {
44+
pub(crate) inner: MailmapInner,
45+
}
46+
47+
#[napi]
48+
impl Mailmap {
49+
/// Add a new Mailmap entry.
50+
///
51+
/// Maps an author/committer (specified by `replace_name` and `replace_email`)
52+
/// to the specified real name and email. The `replace_email` is required but
53+
/// the other parameters can be null.
54+
///
55+
/// If both `replace_name` and `replace_email` are provided, then the entry will
56+
/// apply to those who match both. If only `replace_name` is provided,
57+
/// it will apply to anyone with that name, regardless of email. If only
58+
/// `replace_email` is provided, it will apply to anyone with that email,
59+
/// regardless of name.
60+
///
61+
/// @param {AddMailmapEntryData} entry - The mailmap entry data.
62+
/// @returns {void}
63+
/// @throws An error if the operation failed.
64+
///
65+
/// @category Mailmap/Methods
66+
///
67+
/// @signature
68+
/// ```ts
69+
/// class Mailmap {
70+
/// addEntry(entry: AddMailmapEntryData): void;
71+
/// }
72+
/// ```
73+
#[napi]
74+
pub fn add_entry(&mut self, entry: AddMailmapEntryData) -> crate::Result<()> {
75+
self.inner.add_entry(
76+
entry.real_name.as_deref(),
77+
entry.real_email.as_deref(),
78+
entry.replace_name.as_deref(),
79+
&entry.replace_email,
80+
)?;
81+
Ok(())
82+
}
83+
84+
/// Resolve a signature to its canonical form using a mailmap.
85+
///
86+
/// Returns a new signature with the canonical name and email.
87+
///
88+
/// @param {SignaturePayload} signature - Signature to resolve
89+
/// @returns The resolved signature with canonical name and email
90+
/// @throws An error if the operation failed.
91+
///
92+
/// @category Mailmap/Methods
93+
///
94+
/// @signature
95+
/// ```ts
96+
/// class Mailmap {
97+
/// resolveSignature(signature: SignaturePayload): Signature;
98+
/// }
99+
/// ```
100+
#[napi]
101+
pub fn resolve_signature(&self, signature: SignaturePayload) -> crate::Result<Signature> {
102+
let git_signature = git2::Signature::try_from(Signature::try_from(signature)?)?;
103+
104+
let resolved = match &self.inner {
105+
MailmapInner::Repo(repo) => repo.resolve_signature(&git_signature)?,
106+
MailmapInner::Owned(owned) => owned.resolve_signature(&git_signature)?,
107+
};
108+
109+
Signature::try_from(resolved)
110+
}
111+
}
112+
113+
#[napi]
114+
impl Commit {
115+
#[napi]
116+
/// Get the author of this commit, using the mailmap to map it to the canonical name and email.
117+
///
118+
/// @category Commit/Methods
119+
///
120+
/// @signature
121+
/// ```ts
122+
/// class Commit {
123+
/// authorWithMailmap(mailmap: Mailmap): Signature;
124+
/// }
125+
/// ```
126+
///
127+
/// @param {Mailmap} mailmap - The mailmap to use for mapping
128+
/// @returns Author signature of this commit with mapping applied
129+
/// @throws An error if the operation failed.
130+
pub fn author_with_mailmap(&self, mailmap: &Mailmap) -> crate::Result<Signature> {
131+
let git_signature = self.inner.author_with_mailmap(&mailmap.inner)?;
132+
let signature = Signature::try_from(git_signature)?;
133+
Ok(signature)
134+
}
135+
136+
#[napi]
137+
/// Get the committer of this commit, using the mailmap to map it to the canonical name and email.
138+
///
139+
/// @category Commit/Methods
140+
///
141+
/// @signature
142+
/// ```ts
143+
/// class Commit {
144+
/// committerWithMailmap(mailmap: Mailmap): Signature;
145+
/// }
146+
/// ```
147+
///
148+
/// @param {Mailmap} mailmap - The mailmap to use for mapping
149+
/// @returns Committer signature of this commit with mapping applied
150+
/// @throws An error if the operation failed.
151+
pub fn committer_with_mailmap(&self, mailmap: &Mailmap) -> crate::Result<Signature> {
152+
let git_signature = self.inner.committer_with_mailmap(&mailmap.inner)?;
153+
let signature = Signature::try_from(git_signature)?;
154+
Ok(signature)
155+
}
156+
}
157+
158+
#[napi]
159+
impl Repository {
160+
/// Gets this repository's mailmap.
161+
///
162+
/// @category Repository/Methods
163+
///
164+
/// @signature
165+
/// ```ts
166+
/// class Repository {
167+
/// mailmap(): Mailmap | null;
168+
/// }
169+
/// ```
170+
///
171+
/// @returns The mailmap object if it exists, null otherwise
172+
#[napi]
173+
pub fn mailmap(&self, this: Reference<Repository>, env: Env) -> Option<Mailmap> {
174+
let inner = this
175+
.share_with(env, |repo| {
176+
repo.inner.mailmap().map_err(|e| crate::Error::from(e).into())
177+
})
178+
.ok()?;
179+
180+
Some(Mailmap {
181+
inner: MailmapInner::Repo(inner),
182+
})
183+
}
184+
}
185+
186+
/// Create a mailmap from the contents of a string.
187+
///
188+
/// The format of the string should follow the rules of the mailmap file:
189+
/// ```
190+
/// # Comment line (ignored)
191+
/// Seokju Me <seokju.me@toss.im> Seokju Na <seokju.me@gmail.com>
192+
/// ```
193+
///
194+
/// @param {string} content - Content of the mailmap file
195+
/// @returns A new mailmap object
196+
/// @throws An error if operation failed
197+
///
198+
/// @category Mailmap
199+
///
200+
/// @signature
201+
/// ```ts
202+
/// function createMailmapFromBuffer(content: string): Mailmap;
203+
/// ```
204+
#[napi]
205+
pub fn create_mailmap_from_buffer(content: String) -> crate::Result<Mailmap> {
206+
match git2::Mailmap::from_buffer(&content) {
207+
Ok(mailmap) => Ok(Mailmap {
208+
inner: MailmapInner::Owned(mailmap),
209+
}),
210+
Err(e) => Err(e.into()),
211+
}
212+
}

0 commit comments

Comments
 (0)