##// END OF EJS Templates
templatefuncs: add mailmap template function...
Connor Sheehan -
r37227:2a2ce93e default
parent child Browse files
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 dateutil
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