155 f = author.find('<') |
156 f = author.find('<') |
156 if f != -1: |
157 if f != -1: |
157 return author[:f].strip(' "').replace('\\"', '"') |
158 return author[:f].strip(' "').replace('\\"', '"') |
158 f = author.find('@') |
159 f = author.find('@') |
159 return author[:f].replace('.', ' ') |
160 return author[:f].replace('.', ' ') |
|
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 ) |
160 |
291 |
161 _correctauthorformat = remod.compile(br'^[^<]+\s\<[^<>]+@[^<>]+\>$') |
292 _correctauthorformat = remod.compile(br'^[^<]+\s\<[^<>]+@[^<>]+\>$') |
162 |
293 |
163 def isauthorwellformed(author): |
294 def isauthorwellformed(author): |
164 '''Return True if the author field is well formed |
295 '''Return True if the author field is well formed |