Show More
@@ -0,0 +1,67 b'' | |||||
|
1 | Create a repo and add some commits | |||
|
2 | ||||
|
3 | $ hg init mm | |||
|
4 | $ cd mm | |||
|
5 | $ echo "Test content" > testfile1 | |||
|
6 | $ hg add testfile1 | |||
|
7 | $ hg commit -m "First commit" -u "Proper <commit@m.c>" | |||
|
8 | $ echo "Test content 2" > testfile2 | |||
|
9 | $ hg add testfile2 | |||
|
10 | $ hg commit -m "Second commit" -u "Commit Name 2 <commit2@m.c>" | |||
|
11 | $ echo "Test content 3" > testfile3 | |||
|
12 | $ hg add testfile3 | |||
|
13 | $ hg commit -m "Third commit" -u "Commit Name 3 <commit3@m.c>" | |||
|
14 | $ echo "Test content 4" > testfile4 | |||
|
15 | $ hg add testfile4 | |||
|
16 | $ hg commit -m "Fourth commit" -u "Commit Name 4 <commit4@m.c>" | |||
|
17 | ||||
|
18 | Add a .mailmap file with each possible entry type plus comments | |||
|
19 | $ cat > .mailmap << EOF | |||
|
20 | > # Comment shouldn't break anything | |||
|
21 | > <proper@m.c> <commit@m.c> # Should update email only | |||
|
22 | > Proper Name 2 <commit2@m.c> # Should update name only | |||
|
23 | > Proper Name 3 <proper@m.c> <commit3@m.c> # Should update name, email due to email | |||
|
24 | > Proper Name 4 <proper@m.c> Commit Name 4 <commit4@m.c> # Should update name, email due to name, email | |||
|
25 | > EOF | |||
|
26 | $ hg add .mailmap | |||
|
27 | $ hg commit -m "Add mailmap file" -u "Testuser <test123@m.c>" | |||
|
28 | ||||
|
29 | Output of commits should be normal without filter | |||
|
30 | $ hg log -T "{author}\n" -r "all()" | |||
|
31 | Proper <commit@m.c> | |||
|
32 | Commit Name 2 <commit2@m.c> | |||
|
33 | Commit Name 3 <commit3@m.c> | |||
|
34 | Commit Name 4 <commit4@m.c> | |||
|
35 | Testuser <test123@m.c> | |||
|
36 | ||||
|
37 | Output of commits with filter shows their mailmap values | |||
|
38 | $ hg log -T "{mailmap(author)}\n" -r "all()" | |||
|
39 | Proper <proper@m.c> | |||
|
40 | Proper Name 2 <commit2@m.c> | |||
|
41 | Proper Name 3 <proper@m.c> | |||
|
42 | Proper Name 4 <proper@m.c> | |||
|
43 | Testuser <test123@m.c> | |||
|
44 | ||||
|
45 | Add new mailmap entry for testuser | |||
|
46 | $ cat >> .mailmap << EOF | |||
|
47 | > <newmmentry@m.c> <test123@m.c> | |||
|
48 | > EOF | |||
|
49 | ||||
|
50 | Output of commits with filter shows their updated mailmap values | |||
|
51 | $ hg log -T "{mailmap(author)}\n" -r "all()" | |||
|
52 | Proper <proper@m.c> | |||
|
53 | Proper Name 2 <commit2@m.c> | |||
|
54 | Proper Name 3 <proper@m.c> | |||
|
55 | Proper Name 4 <proper@m.c> | |||
|
56 | Testuser <newmmentry@m.c> | |||
|
57 | ||||
|
58 | A commit with improperly formatted user field should not break the filter | |||
|
59 | $ echo "some more test content" > testfile1 | |||
|
60 | $ hg commit -m "Commit with improper user field" -u "Improper user" | |||
|
61 | $ hg log -T "{mailmap(author)}\n" -r "all()" | |||
|
62 | Proper <proper@m.c> | |||
|
63 | Proper Name 2 <commit2@m.c> | |||
|
64 | Proper Name 3 <proper@m.c> | |||
|
65 | Proper Name 4 <proper@m.c> | |||
|
66 | Testuser <newmmentry@m.c> | |||
|
67 | Improper user |
@@ -26,7 +26,10 b' from . import (' | |||||
26 | templateutil, |
|
26 | templateutil, | |
27 | util, |
|
27 | util, | |
28 | ) |
|
28 | ) | |
29 |
from .utils import |
|
29 | from .utils import ( | |
|
30 | dateutil, | |||
|
31 | stringutil, | |||
|
32 | ) | |||
30 |
|
33 | |||
31 | evalrawexp = templateutil.evalrawexp |
|
34 | evalrawexp = templateutil.evalrawexp | |
32 | evalfuncarg = templateutil.evalfuncarg |
|
35 | evalfuncarg = templateutil.evalfuncarg | |
@@ -166,6 +169,24 b' def formatnode(context, mapping, args):' | |||||
166 | return node |
|
169 | return node | |
167 | return templatefilters.short(node) |
|
170 | return templatefilters.short(node) | |
168 |
|
171 | |||
|
172 | @templatefunc('mailmap(author)') | |||
|
173 | def mailmap(context, mapping, args): | |||
|
174 | """Return the author, updated according to the value | |||
|
175 | set in the .mailmap file""" | |||
|
176 | if len(args) != 1: | |||
|
177 | raise error.ParseError(_("mailmap expects one argument")) | |||
|
178 | ||||
|
179 | author = evalfuncarg(context, mapping, args[0]) | |||
|
180 | ||||
|
181 | cache = context.resource(mapping, 'cache') | |||
|
182 | repo = context.resource(mapping, 'repo') | |||
|
183 | ||||
|
184 | if 'mailmap' not in cache: | |||
|
185 | data = repo.wvfs.tryread('.mailmap') | |||
|
186 | cache['mailmap'] = stringutil.parsemailmap(data) | |||
|
187 | ||||
|
188 | return stringutil.mapname(cache['mailmap'], author) or author | |||
|
189 | ||||
169 | @templatefunc('pad(text, width[, fillchar=\' \'[, left=False]])', |
|
190 | @templatefunc('pad(text, width[, fillchar=\' \'[, left=False]])', | |
170 | argspec='text width fillchar left') |
|
191 | argspec='text width fillchar left') | |
171 | def pad(context, mapping, args): |
|
192 | def pad(context, mapping, args): |
@@ -14,6 +14,7 b' import re as remod' | |||||
14 | import textwrap |
|
14 | import textwrap | |
15 |
|
15 | |||
16 | from ..i18n import _ |
|
16 | from ..i18n import _ | |
|
17 | from ..thirdparty import attr | |||
17 |
|
18 | |||
18 | from .. import ( |
|
19 | from .. import ( | |
19 | encoding, |
|
20 | encoding, | |
@@ -158,6 +159,136 b' def person(author):' | |||||
158 | f = author.find('@') |
|
159 | f = author.find('@') | |
159 | return author[:f].replace('.', ' ') |
|
160 | return author[:f].replace('.', ' ') | |
160 |
|
161 | |||
|
162 | @attr.s(hash=True) | |||
|
163 | class mailmapping(object): | |||
|
164 | '''Represents a username/email key or value in | |||
|
165 | a mailmap file''' | |||
|
166 | email = attr.ib() | |||
|
167 | name = attr.ib(default=None) | |||
|
168 | ||||
|
169 | def parsemailmap(mailmapcontent): | |||
|
170 | """Parses data in the .mailmap format | |||
|
171 | ||||
|
172 | >>> mmdata = b"\\n".join([ | |||
|
173 | ... b'# Comment', | |||
|
174 | ... b'Name <commit1@email.xx>', | |||
|
175 | ... b'<name@email.xx> <commit2@email.xx>', | |||
|
176 | ... b'Name <proper@email.xx> <commit3@email.xx>', | |||
|
177 | ... b'Name <proper@email.xx> Commit <commit4@email.xx>', | |||
|
178 | ... ]) | |||
|
179 | >>> mm = parsemailmap(mmdata) | |||
|
180 | >>> for key in sorted(mm.keys()): | |||
|
181 | ... print(key) | |||
|
182 | mailmapping(email='commit1@email.xx', name=None) | |||
|
183 | mailmapping(email='commit2@email.xx', name=None) | |||
|
184 | mailmapping(email='commit3@email.xx', name=None) | |||
|
185 | mailmapping(email='commit4@email.xx', name='Commit') | |||
|
186 | >>> for val in sorted(mm.values()): | |||
|
187 | ... print(val) | |||
|
188 | mailmapping(email='commit1@email.xx', name='Name') | |||
|
189 | mailmapping(email='name@email.xx', name=None) | |||
|
190 | mailmapping(email='proper@email.xx', name='Name') | |||
|
191 | mailmapping(email='proper@email.xx', name='Name') | |||
|
192 | """ | |||
|
193 | mailmap = {} | |||
|
194 | ||||
|
195 | if mailmapcontent is None: | |||
|
196 | return mailmap | |||
|
197 | ||||
|
198 | for line in mailmapcontent.splitlines(): | |||
|
199 | ||||
|
200 | # Don't bother checking the line if it is a comment or | |||
|
201 | # is an improperly formed author field | |||
|
202 | if line.lstrip().startswith('#') or any(c not in line for c in '<>@'): | |||
|
203 | continue | |||
|
204 | ||||
|
205 | # name, email hold the parsed emails and names for each line | |||
|
206 | # name_builder holds the words in a persons name | |||
|
207 | name, email = [], [] | |||
|
208 | namebuilder = [] | |||
|
209 | ||||
|
210 | for element in line.split(): | |||
|
211 | if element.startswith('#'): | |||
|
212 | # If we reach a comment in the mailmap file, move on | |||
|
213 | break | |||
|
214 | ||||
|
215 | elif element.startswith('<') and element.endswith('>'): | |||
|
216 | # We have found an email. | |||
|
217 | # Parse it, and finalize any names from earlier | |||
|
218 | email.append(element[1:-1]) # Slice off the "<>" | |||
|
219 | ||||
|
220 | if namebuilder: | |||
|
221 | name.append(' '.join(namebuilder)) | |||
|
222 | namebuilder = [] | |||
|
223 | ||||
|
224 | # Break if we have found a second email, any other | |||
|
225 | # data does not fit the spec for .mailmap | |||
|
226 | if len(email) > 1: | |||
|
227 | break | |||
|
228 | ||||
|
229 | else: | |||
|
230 | # We have found another word in the committers name | |||
|
231 | namebuilder.append(element) | |||
|
232 | ||||
|
233 | mailmapkey = mailmapping( | |||
|
234 | email=email[-1], | |||
|
235 | name=name[-1] if len(name) == 2 else None, | |||
|
236 | ) | |||
|
237 | ||||
|
238 | mailmap[mailmapkey] = mailmapping( | |||
|
239 | email=email[0], | |||
|
240 | name=name[0] if name else None, | |||
|
241 | ) | |||
|
242 | ||||
|
243 | return mailmap | |||
|
244 | ||||
|
245 | def mapname(mailmap, author): | |||
|
246 | """Returns the author field according to the mailmap cache, or | |||
|
247 | the original author field. | |||
|
248 | ||||
|
249 | >>> mmdata = b"\\n".join([ | |||
|
250 | ... b'# Comment', | |||
|
251 | ... b'Name <commit1@email.xx>', | |||
|
252 | ... b'<name@email.xx> <commit2@email.xx>', | |||
|
253 | ... b'Name <proper@email.xx> <commit3@email.xx>', | |||
|
254 | ... b'Name <proper@email.xx> Commit <commit4@email.xx>', | |||
|
255 | ... ]) | |||
|
256 | >>> m = parsemailmap(mmdata) | |||
|
257 | >>> mapname(m, b'Commit <commit1@email.xx>') | |||
|
258 | 'Name <commit1@email.xx>' | |||
|
259 | >>> mapname(m, b'Name <commit2@email.xx>') | |||
|
260 | 'Name <name@email.xx>' | |||
|
261 | >>> mapname(m, b'Commit <commit3@email.xx>') | |||
|
262 | 'Name <proper@email.xx>' | |||
|
263 | >>> mapname(m, b'Commit <commit4@email.xx>') | |||
|
264 | 'Name <proper@email.xx>' | |||
|
265 | >>> mapname(m, b'Unknown Name <unknown@email.com>') | |||
|
266 | 'Unknown Name <unknown@email.com>' | |||
|
267 | """ | |||
|
268 | # If the author field coming in isn't in the correct format, | |||
|
269 | # or the mailmap is empty just return the original author field | |||
|
270 | if not isauthorwellformed(author) or not mailmap: | |||
|
271 | return author | |||
|
272 | ||||
|
273 | # Turn the user name into a mailmaptup | |||
|
274 | commit = mailmapping(name=person(author), email=email(author)) | |||
|
275 | ||||
|
276 | try: | |||
|
277 | # Try and use both the commit email and name as the key | |||
|
278 | proper = mailmap[commit] | |||
|
279 | ||||
|
280 | except KeyError: | |||
|
281 | # If the lookup fails, use just the email as the key instead | |||
|
282 | # We call this commit2 as not to erase original commit fields | |||
|
283 | commit2 = mailmapping(email=commit.email) | |||
|
284 | proper = mailmap.get(commit2, mailmapping(None, None)) | |||
|
285 | ||||
|
286 | # Return the author field with proper values filled in | |||
|
287 | return '%s <%s>' % ( | |||
|
288 | proper.name if proper.name else commit.name, | |||
|
289 | proper.email if proper.email else commit.email, | |||
|
290 | ) | |||
|
291 | ||||
161 | _correctauthorformat = remod.compile(br'^[^<]+\s\<[^<>]+@[^<>]+\>$') |
|
292 | _correctauthorformat = remod.compile(br'^[^<]+\s\<[^<>]+@[^<>]+\>$') | |
162 |
|
293 | |||
163 | def isauthorwellformed(author): |
|
294 | def isauthorwellformed(author): |
General Comments 0
You need to be logged in to leave comments.
Login now