mercurial/utils/stringutil.py
changeset 37210 2a2ce93e12f4
parent 37155 fb7140f1d09d
child 37245 54b896f195d1
equal deleted inserted replaced
37209:2208149c4b8e 37210:2a2ce93e12f4
    12 import codecs
    12 import codecs
    13 import re as remod
    13 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,
    20     error,
    21     error,
    21     pycompat,
    22     pycompat,
   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