From 63d5313664c96cdfcee345f5c880f9973a3ee1da Mon Sep 17 00:00:00 2001 From: Konstantin Ryabitsev Date: Wed, 31 Aug 2022 15:09:47 -0400 Subject: Refactor how we handle trailers With the addition of b4 trailers it became pretty obvious that the way we originally implemented trailers didn't age well. This refactor does the following: - introduces LoreTrailer class to replace passing trailers as tuples - reimplements trailer-order with strict adherence to chain-of-custody rules - adds tests to most common trailer follow-up/ordering cases Signed-off-by: Konstantin Ryabitsev --- b4/__init__.py | 356 ++++++++++++++------- b4/command.py | 2 - b4/ez.py | 26 +- b4/mbox.py | 20 +- .../trailers-followup-custody-ref-ordered.txt | 41 +++ .../trailers-followup-custody-ref-unordered.txt | 41 +++ tests/samples/trailers-followup-custody.mbox | 65 ++++ .../trailers-followup-single-ref-addlink.txt | 37 +++ .../trailers-followup-single-ref-addmysob.txt | 36 +++ .../trailers-followup-single-ref-copyccs.txt | 39 +++ .../trailers-followup-single-ref-defaults.txt | 35 ++ .../samples/trailers-followup-single-ref-noadd.txt | 33 ++ .../trailers-followup-single-ref-ordered.txt | 39 +++ .../trailers-followup-single-ref-sloppy.txt | 37 +++ tests/samples/trailers-followup-single.mbox | 78 +++++ ...ilers-followup-with-cover-ref-covertrailers.txt | 75 +++++ .../trailers-followup-with-cover-ref-defaults.txt | 73 +++++ tests/samples/trailers-followup-with-cover.mbox | 113 +++++++ tests/samples/trailers-test-extinfo.txt | 29 ++ tests/samples/trailers-test-simple.txt | 24 ++ tests/test___init__.py | 66 +++- 21 files changed, 1112 insertions(+), 153 deletions(-) create mode 100644 tests/samples/trailers-followup-custody-ref-ordered.txt create mode 100644 tests/samples/trailers-followup-custody-ref-unordered.txt create mode 100644 tests/samples/trailers-followup-custody.mbox create mode 100644 tests/samples/trailers-followup-single-ref-addlink.txt create mode 100644 tests/samples/trailers-followup-single-ref-addmysob.txt create mode 100644 tests/samples/trailers-followup-single-ref-copyccs.txt create mode 100644 tests/samples/trailers-followup-single-ref-defaults.txt create mode 100644 tests/samples/trailers-followup-single-ref-noadd.txt create mode 100644 tests/samples/trailers-followup-single-ref-ordered.txt create mode 100644 tests/samples/trailers-followup-single-ref-sloppy.txt create mode 100644 tests/samples/trailers-followup-single.mbox create mode 100644 tests/samples/trailers-followup-with-cover-ref-covertrailers.txt create mode 100644 tests/samples/trailers-followup-with-cover-ref-defaults.txt create mode 100644 tests/samples/trailers-followup-with-cover.mbox create mode 100644 tests/samples/trailers-test-extinfo.txt create mode 100644 tests/samples/trailers-test-simple.txt diff --git a/b4/__init__.py b/b4/__init__.py index 84ef073..e8e31fe 100644 --- a/b4/__init__.py +++ b/b4/__init__.py @@ -304,14 +304,14 @@ class LoreMailbox: continue trailers, mismatches = fmsg.get_trailers(sloppy=sloppytrailers) - for trailer in mismatches: - lser.trailer_mismatches.add((trailer[0], trailer[1], fmsg.fromname, fmsg.fromemail)) + for ltr in mismatches: + lser.trailer_mismatches.add((ltr.name, ltr.value, fmsg.fromname, fmsg.fromemail)) lvl = 1 while True: logger.debug('%sParent: %s', ' ' * lvl, pmsg.full_subject) logger.debug('%sTrailers:', ' ' * lvl) - for trailer in trailers: - logger.debug('%s%s: %s', ' ' * (lvl+1), trailer[0], trailer[1]) + for ltr in trailers: + logger.debug('%s%s: %s', ' ' * (lvl+1), ltr.name, ltr.value) if pmsg.has_diff and not pmsg.reply: # We found the patch for these trailers if pmsg.revision != revision: @@ -330,9 +330,9 @@ class LoreMailbox: break if pmsg.in_reply_to and pmsg.in_reply_to in self.msgid_map: lvl += 1 - for ptrailer in pmsg.trailers: - print(ptrailer) - trailers.append(tuple(ptrailer + (pmsg,))) + for pltr in pmsg.trailers: + pltr.lmsg = pmsg + trailers.append(pltr) pmsg = self.msgid_map[pmsg.in_reply_to] continue break @@ -557,11 +557,13 @@ class LoreSeries: raise KeyError('Cherrypick not in series') if lmsg is not None: + extras = list() if addlink: - lmsg.followup_trailers.append(('Link', linkmask % lmsg.msgid, None, None)) - if addmysob: - lmsg.followup_trailers.append(('Signed-off-by', - '%s <%s>' % (usercfg['name'], usercfg['email']), None, None)) + if linkmask is None: + linkmask = config.get('linkmask') + linkval = linkmask % lmsg.msgid + lltr = LoreTrailer(name='Link', value=linkval) + extras.append(lltr) if attsame and not attcrit: if attmark: @@ -588,7 +590,8 @@ class LoreSeries: add_trailers = True if noaddtrailers: add_trailers = False - msg = lmsg.get_am_message(add_trailers=add_trailers, copyccs=copyccs, allowbadchars=allowbadchars) + msg = lmsg.get_am_message(add_trailers=add_trailers, extras=extras, copyccs=copyccs, + addmysob=addmysob, allowbadchars=allowbadchars) msgs.append(msg) else: logger.error(' ERROR: missing [%s/%s]!', at, self.expected) @@ -832,6 +835,101 @@ class LoreSeries: logger.critical('Cover: %s', outfile) +class LoreTrailer: + type: str + name: str + lname: str + value: str + extinfo: Optional[str] = None + addr: Optional[Tuple[str, str]] = None + lmsg = None + # Small list of recognized utility trailers + _utility: Set[str] = {'fixes', 'link', 'buglink', 'obsoleted-by', 'message-id', 'change-id'} + + def __init__(self, name: Optional[str] = None, value: Optional[str] = None, extinfo: Optional[str] = None, + msg: Optional[email.message.Message] = None): + if name is None: + self.name = 'Signed-off-by' + ucfg = get_user_config() + self.value = '%s <%s>' % (ucfg['name'], ucfg['email']) + self.type = 'person' + self.addr = (ucfg['name'], ucfg['email']) + else: + self.name = name + self.value = value + if name.lower() in self._utility: + self.type = 'utility' + elif re.search(r'\S+@\S+\.\S+', value): + self.type = 'person' + self.addr = email.utils.parseaddr(value) + else: + self.type = 'unknown' + self.lname = self.name.lower() + self.extinfo = extinfo + self.msg = msg + + def as_string(self, omit_extinfo: bool = False) -> str: + ret = f'{self.name}: {self.value}' + if not self.extinfo or omit_extinfo: + return ret + # extinfo can be either be [on the next line], or # at the end + if self.extinfo.lstrip()[0] == '#': + ret += self.extinfo + else: + ret += f'\n{self.extinfo}' + + return ret + + def email_eq(self, cmp_email: str, fuzzy: bool = True) -> bool: + if not self.addr: + return False + our = self.addr[1].lower() + their = cmp_email.lower() + if our == their: + return True + if not fuzzy: + return False + + if '@' not in our or '@' not in their: + return False + + # Strip extended local parts often added by people, e.g.: + # comparing foo@example.com and foo+kernel@example.com should match + our = re.sub(r'\+[^@]+@', '@', our) + their = re.sub(r'\+[^@]+@', '@', their) + if our == their: + return True + + # See if domain part of one of the addresses is a subset of the other one, + # which should match cases like foo@linux.intel.com and foo@intel.com + olocal, odomain = our.split('@', maxsplit=1) + tlocal, tdomain = their.split('@', maxsplit=1) + if olocal != tlocal: + return False + + if (abs(odomain.count('.')-tdomain.count('.')) == 1 + and (odomain.endswith(f'.{tdomain}') or tdomain.endswith(f'.{odomain}'))): + return True + + return False + + def __eq__(self, other): + # We never compare extinfo, we just tack it if we find a match + return self.lname == other.lname and self.value.lower() == other.value.lower() + + def __hash__(self): + return hash(f'{self.lname}: {self.value}') + + def __repr__(self): + out = list() + out.append(' type: %s' % self.type) + out.append(' name: %s' % self.name) + out.append(' value: %s' % self.value) + out.append(' extinfo: %s' % self.extinfo) + + return '\n'.join(out) + + class LoreMessage: def __init__(self, msg): self.msg = msg @@ -971,41 +1069,32 @@ class LoreMessage: trailers, others = LoreMessage.find_trailers(self.body, followup=True) for trailer in trailers: # These are commonly part of patch/commit metadata - badtrailers = ('from', 'author', 'cc', 'to') - if trailer[0].lower() not in badtrailers: + badtrailers = {'from', 'author', 'cc', 'to'} + if trailer.lname not in badtrailers: self.trailers.append(trailer) - def get_trailers(self, sloppy=False): + def get_trailers(self, sloppy: bool = False) -> Tuple[List[LoreTrailer], List[LoreTrailer]]: trailers = list() - mismatches = set() + mismatches = list() - for tname, tvalue, extdata in self.trailers: - if sloppy or tname.lower() in ('fixes', 'obsoleted-by'): - trailers.append((tname, tvalue, extdata, self)) + for ltr in self.trailers: + ltr.lmsg = self + if sloppy or ltr.type != 'person': + trailers.append(ltr) continue - tmatch = False - namedata = email.utils.getaddresses([tvalue])[0] - tfrom = re.sub(r'\+[^@]+@', '@', namedata[1].lower()) - hfrom = re.sub(r'\+[^@]+@', '@', self.fromemail.lower()) - tlname = namedata[0].lower() - hlname = self.fromname.lower() - tchunks = tfrom.split('@') - hchunks = hfrom.split('@') - if tfrom == hfrom: - logger.debug(' trailer exact email match') - tmatch = True - # See if domain part of one of the addresses is a subset of the other one, - # which should match cases like @linux.intel.com and @intel.com - elif (len(tchunks) == 2 and len(hchunks) == 2 - and tchunks[0] == hchunks[0] - and (tchunks[1].find(hchunks[1]) >= 0 or hchunks[1].find(tchunks[1]) >= 0)): - logger.debug(' trailer fuzzy email match') - tmatch = True + if ltr.email_eq(self.fromemail): + logger.debug(' trailer email match') + trailers.append(ltr) + # Does the name match, at least? - elif tlname == hlname: + nmatch = False + tlname = ltr.addr[0].lower() + hlname = self.fromname.lower() + + if tlname == hlname: logger.debug(' trailer exact name match') - tmatch = True + nmatch = True # Finally, see if the header From has a comma in it and try to find all # parts in the trailer name elif hlname.find(',') > 0: @@ -1014,13 +1103,12 @@ class LoreMessage: if hlname.find(nchunk.strip()) < 0: nmatch = False break - if nmatch: - logger.debug(' trailer fuzzy name match') - tmatch = True - if tmatch: - trailers.append((tname, tvalue, extdata, self)) - else: - mismatches.add((tname, tvalue, extdata, self)) + if nmatch: + logger.debug(' trailer fuzzy name match') + trailers.append(ltr) + continue + + mismatches.append(ltr) return trailers, mismatches @@ -1409,7 +1497,7 @@ class LoreMessage: return indexes @staticmethod - def find_trailers(body: str, followup: bool = False) -> Tuple[List[Tuple], List[str]]: + def find_trailers(body: str, followup: bool = False) -> Tuple[List[LoreTrailer], List[str]]: ignores = {'phone', 'email'} headers = {'subject', 'date', 'from'} nonperson = {'fixes', 'subject', 'date', 'link', 'buglink', 'obsoleted-by'} @@ -1434,43 +1522,51 @@ class LoreMessage: line = line.strip('\r') matches = re.search(r'^\s*(\w\S+):\s+(\S.*)', line, flags=re.I) if matches: - groups = list(matches.groups()) + oname, ovalue = list(matches.groups()) # We only accept headers if we haven't seen any non-trailer lines - tname = groups[0].lower() - if tname in ignores: + lname = oname.lower() + if lname in ignores: logger.debug('Ignoring known non-trailer: %s', line) continue - if len(others) and tname in headers: + if len(others) and lname in headers: logger.debug('Ignoring %s (header after other content)', line) continue if followup: - if not tname.isascii(): - logger.debug('Ignoring known non-ascii follow-up trailer: %s', tname) + if not lname.isascii(): + logger.debug('Ignoring known non-ascii follow-up trailer: %s', lname) continue - mperson = re.search(r'\S+@\S+\.\S+', groups[1]) - if not mperson and tname not in nonperson: + mperson = re.search(r'\S+@\S+\.\S+', ovalue) + if not mperson and lname not in nonperson: logger.debug('Ignoring %s (not a recognized non-person trailer)', line) continue + + extinfo = None + mextinfo = re.search(r'(.*\S+)(\s+#[^#]+)$', ovalue) + if mextinfo: + logger.debug('Trailer contains hashtag extinfo: ', line) + # Found extinfo of the hashtag genre + egr = mextinfo.groups() + ovalue = egr[0] + extinfo = egr[1] + was_trailer = True - groups.append(None) - trailers.append(groups) + ltrailer = LoreTrailer(name=oname, value=ovalue, extinfo=extinfo) + trailers.append(ltrailer) continue # Is it an extended info line, e.g.: # Signed-off-by: Foo Foo # [for the foo bits] - if len(line) > 2 and line[0] == '[' and line[-1] == ']' and was_trailer: - trailers[-1][2] = line + if len(line) > 2 and was_trailer and re.search(r'^\s*\[[^]]+]\s*$', line): + trailers[-1].extinfo = line was_trailer = False continue was_trailer = False others.append(line) - # convert to tuples for ease of matching - ttrailers = [tuple(x) for x in trailers] - return ttrailers, others + return trailers, others @staticmethod - def get_body_parts(body): + def get_body_parts(body: str) -> Tuple[List[LoreTrailer], str, List[LoreTrailer], str, str]: # remove any starting/trailing blank lines body = body.replace('\r', '') body = body.strip('\n') @@ -1533,13 +1629,28 @@ class LoreMessage: return githeaders, message, trailers, basement, signature - def fix_trailers(self, copyccs=False, signoff=None): + def fix_trailers(self, extras: Optional[List[LoreTrailer]] = None, + copyccs: bool = False, addmysob: bool = False) -> None: + config = get_main_config() - attpolicy = config['attestation-policy'] bheaders, message, btrailers, basement, signature = LoreMessage.get_body_parts(self.body) - # Now we add mix-in trailers - trailers = btrailers + self.followup_trailers + + sobtr = LoreTrailer() + hasmysob = False + if sobtr in btrailers: + # Our own signoff always moves to the bottom of all trailers + hasmysob = True + btrailers.remove(sobtr) + + new_trailers = self.followup_trailers + if extras: + new_trailers += extras + + if sobtr in new_trailers: + # Our own signoff always moves to the bottom of all trailers + new_trailers.remove(sobtr) + addmysob = True if copyccs: alldests = email.utils.getaddresses([str(x) for x in self.msg.get_all('to', [])]) @@ -1548,66 +1659,77 @@ class LoreMessage: alldests.sort(key=lambda x: x[1].find('@') > 0 and x[1].split('@')[1] + x[1].split('@')[0] or x[1]) for pair in alldests: found = False - for ftr in trailers: - if ftr[1].lower().find(pair[1].lower()) >= 0: + for fltr in btrailers + new_trailers: + if fltr.email_eq(pair[1]): # already present found = True break if not found: if len(pair[0]): - trailers.append(('Cc', f'{pair[0]} <{pair[1]}>', None, None)) # noqa + altr = LoreTrailer(name='Cc', value=f'{pair[0]} <{pair[1]}>') else: - trailers.append(('Cc', pair[1], None, None)) # noqa - - fixtrailers = list() - # If we received a signoff trailer: - # - if it's already present, we move it to the bottom - # - if not already present, we add it - new_signoff = True - for trailer in trailers: - if list(trailer[:3]) in fixtrailers: - # Dupe - continue + altr = LoreTrailer(name='Cc', value=pair[1]) + new_trailers.append(altr) + + torder = config.get('trailer-order') + if torder and torder != '*': + # this only applies to trailers within our chain of custody, so walk existing + # body trailers backwards and stop at the outermost Signed-off-by we find (if any) + for bltr in reversed(btrailers): + if bltr.lname == 'signed-off-by': + break + btrailers.remove(bltr) + new_trailers.insert(0, bltr) + + ordered_trailers = list() + for glob in [x.strip().lower() for x in torder.split(',')]: + if not len(new_trailers): + break + for ltr in list(new_trailers): + if fnmatch.fnmatch(ltr.lname, glob): + ordered_trailers.append(ltr) + new_trailers.remove(ltr) + if len(new_trailers): + # Tack them to the bottom + ordered_trailers += new_trailers + new_trailers = ordered_trailers - if signoff and tuple(trailer[:3]) == tuple(signoff): - # Skip it, we'll add it at the bottom - new_signoff = False - logger.debug(' . %s: %s', signoff[0], signoff[1]) + attpolicy = config['attestation-policy'] + fixtrailers = btrailers + + for ltr in new_trailers: + if ltr in fixtrailers: continue - fixtrailers.append(list(trailer[:3])) - if trailer[:3] not in btrailers: - extra = '' - if trailer[3] is not None: - fmsg = trailer[3] - for attestor in fmsg.attestors: # noqa - if attestor.passing: - extra = ' (%s %s)' % (attestor.checkmark, attestor.trailer) - elif attpolicy in ('hardfail', 'softfail'): - extra = ' (%s %s)' % (attestor.checkmark, attestor.trailer) - if attpolicy == 'hardfail': - import sys - logger.critical('---') - logger.critical('Exiting due to attestation-policy: hardfail') - sys.exit(1) - - logger.info(' + %s: %s%s', trailer[0], trailer[1], extra) - else: - logger.debug(' . %s: %s', trailer[0], trailer[1]) + fixtrailers.append(ltr) + extra = '' + if ltr.lmsg is not None: + for attestor in ltr.lmsg.attestors: + if attestor.passing: + extra = ' (%s %s)' % (attestor.checkmark, attestor.trailer) + elif attpolicy in ('hardfail', 'softfail'): + extra = ' (%s %s)' % (attestor.checkmark, attestor.trailer) + if attpolicy == 'hardfail': + import sys + logger.critical('---') + logger.critical('Exiting due to attestation-policy: hardfail') + sys.exit(1) + + logger.info(' + %s%s', ltr.as_string(omit_extinfo=True), extra) - if signoff: + if addmysob: # Tack on our signoff at the bottom - fixtrailers.append(list(signoff)) - if new_signoff: - logger.info(' + %s: %s', signoff[0], signoff[1]) + fixtrailers.append(sobtr) + if not hasmysob: + logger.info(' + %s', sobtr.as_string(omit_extinfo=True)) # Reconstitute the message self.body = '' if bheaders: - for bheader in bheaders: + for bltr in bheaders: # There is no [extdata] in git headers, so we ignore bheader[2] - self.body += '%s: %s\n' % (bheader[0], bheader[1]) + self.body += bltr.as_string(omit_extinfo=True) + '\n' self.body += '\n' newmessage = '' @@ -1617,20 +1739,18 @@ class LoreMessage: newmessage += '\n' if len(fixtrailers): - for trailer in fixtrailers: - newmessage += '%s: %s\n' % (trailer[0], trailer[1]) - if trailer[2]: - newmessage += '%s\n' % trailer[2] + for ltr in fixtrailers: + newmessage += ltr.as_string() + '\n' self.message = self.subject + '\n\n' + newmessage self.body += newmessage if len(basement): self.body += '---\n' - self.body += basement.rstrip('\r\n') + '\n\n' + self.body += basement.rstrip('\r\n') + '\n' if len(signature): self.body += '-- \n' - self.body += signature.rstrip('\r\n') + '\n\n' + self.body += signature.rstrip('\r\n') + '\n' def get_am_subject(self, indicate_reroll=True): # Return a clean patch subject @@ -1652,9 +1772,9 @@ class LoreMessage: return '[%s] %s' % (' '.join(parts), self.lsubject.subject) - def get_am_message(self, add_trailers=True, copyccs=False, allowbadchars=False): + def get_am_message(self, add_trailers=True, addmysob=False, extras=None, copyccs=False, allowbadchars=False): if add_trailers: - self.fix_trailers(copyccs=copyccs) + self.fix_trailers(copyccs=copyccs, addmysob=addmysob, extras=extras) bbody = self.body.encode() # Look through the body to make sure there aren't any suspicious unicode control flow chars # First, encode into ascii and compare for a quickie utf8 presence test diff --git a/b4/command.py b/b4/command.py index 7eb7700..94ba875 100644 --- a/b4/command.py +++ b/b4/command.py @@ -274,8 +274,6 @@ def cmd(): sp_trl = subparsers.add_parser('trailers', help='Operate on trailers received for mailing list reviews') sp_trl.add_argument('-u', '--update', action='store_true', default=False, help='Update branch commits with latest received trailers') - sp_trl.add_argument('-s', '--signoff', action='store_true', default=False, - help='Add my Signed-off-by trailer, if not already present') sp_trl.add_argument('-S', '--sloppy-trailers', dest='sloppytrailers', action='store_true', default=False, help='Apply trailers without email address match checking') sp_trl.add_argument('-F', '--trailers-from', dest='msgid', diff --git a/b4/ez.py b/b4/ez.py index 8a1b8ed..965ae6e 100644 --- a/b4/ez.py +++ b/b4/ez.py @@ -616,10 +616,6 @@ def update_trailers(cmdargs: argparse.Namespace) -> None: if 'name' not in usercfg or 'email' not in usercfg: logger.critical('CRITICAL: Please set your user.name and user.email') sys.exit(1) - if cmdargs.signoff: - signoff = ('Signed-off-by', f"{usercfg['name']} <{usercfg['email']}>", None) - else: - signoff = None ignore_commits = None # If we are in an b4-prep branch, we start from the beginning of the series @@ -686,12 +682,6 @@ def update_trailers(cmdargs: argparse.Namespace) -> None: subject = msg.get('subject') by_subject[subject] = commit by_patchid[patchid] = commit - parts = b4.LoreMessage.get_body_parts(body) - # Force SOB update - if signoff and (signoff not in parts[2] or (len(signoff) > 1 and parts[2][-1] != signoff)): - updates[commit] = list() - if signoff not in parts[2]: - updates[commit].append(signoff) if cmdargs.msgid: msgid = b4.get_msgid(cmdargs) @@ -764,11 +754,7 @@ def update_trailers(cmdargs: argparse.Namespace) -> None: logger.info(' %s', cmsg.subject) if len(newtrailers): cmsg.followup_trailers = newtrailers - if signoff in newtrailers: - logger.info(' + %s: %s', signoff[0], signoff[1]) - elif signoff: - logger.info(' > %s: %s', signoff[0], signoff[1]) - cmsg.fix_trailers(signoff=signoff) + cmsg.fix_trailers() fred.add(commit, cmsg.message) logger.info('---') args = fr.FilteringOptions.parse_args(['--force', '--quiet', '--refs', f'{start}..']) @@ -1104,12 +1090,10 @@ def cmd_send(cmdargs: argparse.Namespace) -> None: ccdests.append(pair) # add addresses seen in trailers - for trailer in trailers: - if '@' in trailer[1] and '<' in trailer[1]: - for pair in utils.getaddresses([trailer[1]]): - if pair[1] not in seen: - seen.add(pair[1]) - ccdests.append(pair) + for ltr in trailers: + if ltr.addr and ltr.addr[1] not in seen: + seen.add(ltr.addr[1]) + ccdests.append(ltr.addr) excludes = b4.get_excluded_addrs() if cmdargs.not_me_too: diff --git a/b4/mbox.py b/b4/mbox.py index 9a6ea9c..c2e1555 100644 --- a/b4/mbox.py +++ b/b4/mbox.py @@ -121,9 +121,9 @@ def make_am(msgs, cmdargs, msgid): # Only check cover letter or first patch if not lmsg or lmsg.counter > 1: continue - for trailer in list(lmsg.followup_trailers): - if trailer[0].lower() == 'obsoleted-by': - lmsg.followup_trailers.remove(trailer) + for ltr in list(lmsg.followup_trailers): + if ltr.lname == 'obsoleted-by': + lmsg.followup_trailers.remove(ltr) if warned: continue logger.critical('---') @@ -136,10 +136,10 @@ def make_am(msgs, cmdargs, msgid): logger.critical('---') logger.critical('NOTE: Some trailers were sent to the cover letter:') tseen = set() - for trailer in lser.patches[0].followup_trailers: - if tuple(trailer[:2]) not in tseen: - logger.critical(' %s: %s', trailer[0], trailer[1]) - tseen.add(tuple(trailer[:2])) + for ltr in lser.patches[0].followup_trailers: + if ltr not in tseen: + logger.critical(' %s', ltr.as_string(omit_extinfo=True)) + tseen.add(ltr) logger.critical('NOTE: Rerun with -t to apply them to all patches') if len(lser.trailer_mismatches): logger.critical('---') @@ -526,9 +526,9 @@ def get_extra_series(msgs: list, direction: int = 1, wantvers: Optional[int] = N # Does it have an Obsoleted-by: trailer? rmsg = b4.LoreMessage(msg) trailers, mismatches = rmsg.get_trailers() - for tl in trailers: - if tl[0].lower() == 'obsoleted-by': - for chunk in tl[1].split('/'): + for ltr in trailers: + if ltr.lname == 'obsoleted-by': + for chunk in ltr.value.split('/'): if chunk.find('@') > 0 and chunk not in seen_msgids: obsoleted.append(chunk) break diff --git a/tests/samples/trailers-followup-custody-ref-ordered.txt b/tests/samples/trailers-followup-custody-ref-ordered.txt new file mode 100644 index 0000000..383befb --- /dev/null +++ b/tests/samples/trailers-followup-custody-ref-ordered.txt @@ -0,0 +1,41 @@ +From git@z Thu Jan 1 00:00:00 1970 +Subject: [PATCH] Simple test +From: Test Test +Date: Tue, 30 Aug 2022 11:19:07 -0400 +Message-Id: +To: Some List +Cc: Dev Eloper1 , Dev Eloper2 +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +Follow-up trailer collating test. + +Link: https://msgid.link/some@msgid.here +Reviewed-by: Original Reviewer +Signed-off-by: Original Submitter +Cc: Dev Eloper1 +Cc: Dev Eloper2 +Cc: Some List +Fixes: abcdef01234567890 +Link: https://lore.kernel.org/some@msgid.here # bug discussion +Suggested-by: Friendly Suggester +Reviewed-by: Followup Reviewer1 +Tested-by: Followup Reviewer2 +Signed-off-by: Test Override +--- + +diff --git a/b4/junk.py b/b4/junk.py +index 12345678..23456789 100644 +--- a/b4/junk.py +--- b/b4/junk.py +@@@ -1,1 +1,1 @@ def junk(): + + +-junk1 ++junk2 + + +-- +2.wong.fu + diff --git a/tests/samples/trailers-followup-custody-ref-unordered.txt b/tests/samples/trailers-followup-custody-ref-unordered.txt new file mode 100644 index 0000000..4b238da --- /dev/null +++ b/tests/samples/trailers-followup-custody-ref-unordered.txt @@ -0,0 +1,41 @@ +From git@z Thu Jan 1 00:00:00 1970 +Subject: [PATCH] Simple test +From: Test Test +Date: Tue, 30 Aug 2022 11:19:07 -0400 +Message-Id: +To: Some List +Cc: Dev Eloper1 , Dev Eloper2 +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +Follow-up trailer collating test. + +Link: https://msgid.link/some@msgid.here +Reviewed-by: Original Reviewer +Signed-off-by: Original Submitter +Suggested-by: Friendly Suggester +Fixes: abcdef01234567890 +Link: https://lore.kernel.org/some@msgid.here # bug discussion +Reviewed-by: Followup Reviewer1 +Tested-by: Followup Reviewer2 +Cc: Dev Eloper1 +Cc: Dev Eloper2 +Cc: Some List +Signed-off-by: Test Override +--- + +diff --git a/b4/junk.py b/b4/junk.py +index 12345678..23456789 100644 +--- a/b4/junk.py +--- b/b4/junk.py +@@@ -1,1 +1,1 @@ def junk(): + + +-junk1 ++junk2 + + +-- +2.wong.fu + diff --git a/tests/samples/trailers-followup-custody.mbox b/tests/samples/trailers-followup-custody.mbox new file mode 100644 index 0000000..0c4e4b8 --- /dev/null +++ b/tests/samples/trailers-followup-custody.mbox @@ -0,0 +1,65 @@ +From foo@z Thu Jan 1 00:00:00 1970 +From: Test Test +Subject: [PATCH] Simple test +To: Some List +Cc: Dev Eloper1 , + Dev Eloper2 +Date: Tue, 30 Aug 2022 11:19:07 -0400 +Message-Id: + +Follow-up trailer collating test. + +Link: https://msgid.link/some@msgid.here +Reviewed-by: Original Reviewer +Signed-off-by: Original Submitter +Suggested-by: Friendly Suggester +Fixes: abcdef01234567890 +Link: https://lore.kernel.org/some@msgid.here # bug discussion +Signed-off-by: Test Override +--- + +diff --git a/b4/junk.py b/b4/junk.py +index 12345678..23456789 100644 +--- a/b4/junk.py +--- b/b4/junk.py +@@@ -1,1 +1,1 @@ def junk(): + + +-junk1 ++junk2 + + +-- +2.wong.fu + +From foo@z Thu Jan 1 00:00:00 1970 +From: Followup Reviewer1 +Subject: Re: [PATCH] Simple test +Date: Tue, 30 Aug 2022 11:19:07 -0400 +Message-Id: +In-Reply-To: +References: + +> This is a simple trailer parsing test. + +Reviewed-by: Followup Reviewer1 + +-- +My sig + +From foo@z Thu Jan 1 00:00:00 1970 +From: Followup Reviewer2 +Subject: Re: [PATCH] Simple test +Date: Tue, 30 Aug 2022 11:19:07 -0400 +Message-Id: +In-Reply-To: +References: + +>> This is a simple trailer parsing test. +> +> Reviewed-by: Followup Reviewer1 + +Tested-by: Followup Reviewer2 + +-- +My sig diff --git a/tests/samples/trailers-followup-single-ref-addlink.txt b/tests/samples/trailers-followup-single-ref-addlink.txt new file mode 100644 index 0000000..024c842 --- /dev/null +++ b/tests/samples/trailers-followup-single-ref-addlink.txt @@ -0,0 +1,37 @@ +From git@z Thu Jan 1 00:00:00 1970 +Subject: [PATCH] Simple test +From: Test Test +Date: Tue, 30 Aug 2022 11:19:07 -0400 +Message-Id: +To: Some List +Cc: Dev Eloper1 , Dev Eloper2 +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +Follow-up trailer collating test. + +Fixes: abcdef01234567890 +Reviewed-by: Original Reviewer +Link: https://msgid.link/some@msgid.here +Signed-off-by: Original Submitter +Reviewed-by: Followup Reviewer1 +Tested-by: Followup Reviewer2 +Link: https://lore.kernel.org/r/orig-message@example.com +Signed-off-by: Test Override +--- + +diff --git a/b4/junk.py b/b4/junk.py +index 12345678..23456789 100644 +--- a/b4/junk.py +--- b/b4/junk.py +@@@ -1,1 +1,1 @@ def junk(): + + +-junk1 ++junk2 + + +-- +2.wong.fu + diff --git a/tests/samples/trailers-followup-single-ref-addmysob.txt b/tests/samples/trailers-followup-single-ref-addmysob.txt new file mode 100644 index 0000000..9da6c0b --- /dev/null +++ b/tests/samples/trailers-followup-single-ref-addmysob.txt @@ -0,0 +1,36 @@ +From git@z Thu Jan 1 00:00:00 1970 +Subject: [PATCH] Simple test +From: Test Test +Date: Tue, 30 Aug 2022 11:19:07 -0400 +Message-Id: +To: Some List +Cc: Dev Eloper1 , Dev Eloper2 +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +Follow-up trailer collating test. + +Fixes: abcdef01234567890 +Reviewed-by: Original Reviewer +Link: https://msgid.link/some@msgid.here +Signed-off-by: Original Submitter +Reviewed-by: Followup Reviewer1 +Tested-by: Followup Reviewer2 +Signed-off-by: Test Override +--- + +diff --git a/b4/junk.py b/b4/junk.py +index 12345678..23456789 100644 +--- a/b4/junk.py +--- b/b4/junk.py +@@@ -1,1 +1,1 @@ def junk(): + + +-junk1 ++junk2 + + +-- +2.wong.fu + diff --git a/tests/samples/trailers-followup-single-ref-copyccs.txt b/tests/samples/trailers-followup-single-ref-copyccs.txt new file mode 100644 index 0000000..3623462 --- /dev/null +++ b/tests/samples/trailers-followup-single-ref-copyccs.txt @@ -0,0 +1,39 @@ +From git@z Thu Jan 1 00:00:00 1970 +Subject: [PATCH] Simple test +From: Test Test +Date: Tue, 30 Aug 2022 11:19:07 -0400 +Message-Id: +To: Some List +Cc: Dev Eloper1 , Dev Eloper2 +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +Follow-up trailer collating test. + +Fixes: abcdef01234567890 +Reviewed-by: Original Reviewer +Link: https://msgid.link/some@msgid.here +Signed-off-by: Original Submitter +Reviewed-by: Followup Reviewer1 +Tested-by: Followup Reviewer2 +Cc: Dev Eloper1 +Cc: Dev Eloper2 +Cc: Some List +Signed-off-by: Test Override +--- + +diff --git a/b4/junk.py b/b4/junk.py +index 12345678..23456789 100644 +--- a/b4/junk.py +--- b/b4/junk.py +@@@ -1,1 +1,1 @@ def junk(): + + +-junk1 ++junk2 + + +-- +2.wong.fu + diff --git a/tests/samples/trailers-followup-single-ref-defaults.txt b/tests/samples/trailers-followup-single-ref-defaults.txt new file mode 100644 index 0000000..3750c06 --- /dev/null +++ b/tests/samples/trailers-followup-single-ref-defaults.txt @@ -0,0 +1,35 @@ +From git@z Thu Jan 1 00:00:00 1970 +Subject: [PATCH] Simple test +From: Test Test +Date: Tue, 30 Aug 2022 11:19:07 -0400 +Message-Id: +To: Some List +Cc: Dev Eloper1 , Dev Eloper2 +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +Follow-up trailer collating test. + +Fixes: abcdef01234567890 +Reviewed-by: Original Reviewer +Link: https://msgid.link/some@msgid.here +Signed-off-by: Original Submitter +Reviewed-by: Followup Reviewer1 +Tested-by: Followup Reviewer2 +--- + +diff --git a/b4/junk.py b/b4/junk.py +index 12345678..23456789 100644 +--- a/b4/junk.py +--- b/b4/junk.py +@@@ -1,1 +1,1 @@ def junk(): + + +-junk1 ++junk2 + + +-- +2.wong.fu + diff --git a/tests/samples/trailers-followup-single-ref-noadd.txt b/tests/samples/trailers-followup-single-ref-noadd.txt new file mode 100644 index 0000000..80a1011 --- /dev/null +++ b/tests/samples/trailers-followup-single-ref-noadd.txt @@ -0,0 +1,33 @@ +From git@z Thu Jan 1 00:00:00 1970 +Subject: [PATCH] Simple test +From: Test Test +Date: Tue, 30 Aug 2022 11:19:07 -0400 +Message-Id: +To: Some List +Cc: Dev Eloper1 , Dev Eloper2 +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +Follow-up trailer collating test. + +Fixes: abcdef01234567890 +Reviewed-by: Original Reviewer +Link: https://msgid.link/some@msgid.here +Signed-off-by: Original Submitter +--- + +diff --git a/b4/junk.py b/b4/junk.py +index 12345678..23456789 100644 +--- a/b4/junk.py +--- b/b4/junk.py +@@@ -1,1 +1,1 @@ def junk(): + + +-junk1 ++junk2 + + +-- +2.wong.fu + diff --git a/tests/samples/trailers-followup-single-ref-ordered.txt b/tests/samples/trailers-followup-single-ref-ordered.txt new file mode 100644 index 0000000..2084c36 --- /dev/null +++ b/tests/samples/trailers-followup-single-ref-ordered.txt @@ -0,0 +1,39 @@ +From git@z Thu Jan 1 00:00:00 1970 +Subject: [PATCH] Simple test +From: Test Test +Date: Tue, 30 Aug 2022 11:19:07 -0400 +Message-Id: +To: Some List +Cc: Dev Eloper1 , Dev Eloper2 +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +Follow-up trailer collating test. + +Fixes: abcdef01234567890 +Reviewed-by: Original Reviewer +Link: https://msgid.link/some@msgid.here +Signed-off-by: Original Submitter +Cc: Dev Eloper1 +Cc: Dev Eloper2 +Cc: Some List +Tested-by: Followup Reviewer2 +Reviewed-by: Followup Reviewer1 +Signed-off-by: Test Override +--- + +diff --git a/b4/junk.py b/b4/junk.py +index 12345678..23456789 100644 +--- a/b4/junk.py +--- b/b4/junk.py +@@@ -1,1 +1,1 @@ def junk(): + + +-junk1 ++junk2 + + +-- +2.wong.fu + diff --git a/tests/samples/trailers-followup-single-ref-sloppy.txt b/tests/samples/trailers-followup-single-ref-sloppy.txt new file mode 100644 index 0000000..9b6a49d --- /dev/null +++ b/tests/samples/trailers-followup-single-ref-sloppy.txt @@ -0,0 +1,37 @@ +From git@z Thu Jan 1 00:00:00 1970 +Subject: [PATCH] Simple test +From: Test Test +Date: Tue, 30 Aug 2022 11:19:07 -0400 +Message-Id: +To: Some List +Cc: Dev Eloper1 , Dev Eloper2 +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +Follow-up trailer collating test. + +Fixes: abcdef01234567890 +Reviewed-by: Original Reviewer +Link: https://msgid.link/some@msgid.here +Signed-off-by: Original Submitter +Reviewed-by: Followup Reviewer1 +Tested-by: Followup Reviewer2 +Reviewed-by: Mismatched Reviewer1 +Signed-off-by: Test Override +--- + +diff --git a/b4/junk.py b/b4/junk.py +index 12345678..23456789 100644 +--- a/b4/junk.py +--- b/b4/junk.py +@@@ -1,1 +1,1 @@ def junk(): + + +-junk1 ++junk2 + + +-- +2.wong.fu + diff --git a/tests/samples/trailers-followup-single.mbox b/tests/samples/trailers-followup-single.mbox new file mode 100644 index 0000000..b12f6ba --- /dev/null +++ b/tests/samples/trailers-followup-single.mbox @@ -0,0 +1,78 @@ +From foo@z Thu Jan 1 00:00:00 1970 +From: Test Test +Subject: [PATCH] Simple test +To: Some List +Cc: Dev Eloper1 , + Dev Eloper2 +Date: Tue, 30 Aug 2022 11:19:07 -0400 +Message-Id: + +Follow-up trailer collating test. + +Fixes: abcdef01234567890 +Reviewed-by: Original Reviewer +Link: https://msgid.link/some@msgid.here +Signed-off-by: Original Submitter +--- + +diff --git a/b4/junk.py b/b4/junk.py +index 12345678..23456789 100644 +--- a/b4/junk.py +--- b/b4/junk.py +@@@ -1,1 +1,1 @@ def junk(): + + +-junk1 ++junk2 + + +-- +2.wong.fu + +From foo@z Thu Jan 1 00:00:00 1970 +From: Followup Reviewer1 +Subject: Re: [PATCH] Simple test +Date: Tue, 30 Aug 2022 11:19:07 -0400 +Message-Id: +In-Reply-To: +References: + +> This is a simple trailer parsing test. + +Reviewed-by: Followup Reviewer1 + +-- +My sig + +From foo@z Thu Jan 1 00:00:00 1970 +From: Followup Reviewer2 +Subject: Re: [PATCH] Simple test +Date: Tue, 30 Aug 2022 11:19:07 -0400 +Message-Id: +In-Reply-To: +References: + +>> This is a simple trailer parsing test. +> +> Reviewed-by: Followup Reviewer1 + +Tested-by: Followup Reviewer2 + +-- +My sig + +From foo@z Thu Jan 1 00:00:00 1970 +From: Mismatched Reviewer +Subject: Re: [PATCH] Simple test +Date: Tue, 30 Aug 2022 11:19:07 -0400 +Message-Id: +In-Reply-To: +References: + +> This is a simple trailer parsing test. + +Reviewed-by: Mismatched Reviewer1 + +-- +My sig + diff --git a/tests/samples/trailers-followup-with-cover-ref-covertrailers.txt b/tests/samples/trailers-followup-with-cover-ref-covertrailers.txt new file mode 100644 index 0000000..8a503ea --- /dev/null +++ b/tests/samples/trailers-followup-with-cover-ref-covertrailers.txt @@ -0,0 +1,75 @@ +From git@z Thu Jan 1 00:00:00 1970 +Subject: [PATCH v2 1/2] Simple test 1 +From: Test Test +Date: Tue, 30 Aug 2022 11:19:07 -0400 +Message-Id: +To: Some List +Cc: Dev Eloper1 , Dev Eloper2 +In-Reply-To: +References: +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +Follow-up trailer collating test patch 1. + +Fixes: abcdef01234567890 +Reviewed-by: Original Reviewer +Link: https://msgid.link/some@msgid.here +Signed-off-by: Original Submitter +Reviewed-by: Coverletter Reviewer1 +Signed-off-by: Test Override +--- + +diff --git a/b4/junk.py b/b4/junk.py +index 12345678..23456789 100644 +--- a/b4/junk.py +--- b/b4/junk.py +@@@ -1,1 +1,1 @@ def junk(): + + +-junk1 ++junk2 + + +-- +2.wong.fu + +From git@z Thu Jan 1 00:00:00 1970 +Subject: [PATCH v2 2/2] Simple test 2 +From: Test Test +Date: Tue, 30 Aug 2022 11:19:07 -0400 +Message-Id: +To: Some List +Cc: Dev Eloper1 , Dev Eloper2 +In-Reply-To: +References: +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +Follow-up trailer collating test patch 2. + +Fixes: abcdef01234567890 +Reviewed-by: Original Reviewer +Link: https://msgid.link/some@msgid.here +Signed-off-by: Original Submitter +Reviewed-by: Followup Reviewer1 +Reviewed-by: Coverletter Reviewer1 +Signed-off-by: Test Override +--- + +diff --git a/b4/bupkes.py b/b4/bupkes.py +index 12345678..23456789 100644 +--- a/b4/bupkes.py +--- b/b4/bupkes.py +@@@ -1,1 +1,1 @@ def bupkes(): + + +-bupkes1 ++bupkes2 + + +-- +2.wong.fu + diff --git a/tests/samples/trailers-followup-with-cover-ref-defaults.txt b/tests/samples/trailers-followup-with-cover-ref-defaults.txt new file mode 100644 index 0000000..cbd1eb8 --- /dev/null +++ b/tests/samples/trailers-followup-with-cover-ref-defaults.txt @@ -0,0 +1,73 @@ +From git@z Thu Jan 1 00:00:00 1970 +Subject: [PATCH v2 1/2] Simple test 1 +From: Test Test +Date: Tue, 30 Aug 2022 11:19:07 -0400 +Message-Id: +To: Some List +Cc: Dev Eloper1 , Dev Eloper2 +In-Reply-To: +References: +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +Follow-up trailer collating test patch 1. + +Fixes: abcdef01234567890 +Reviewed-by: Original Reviewer +Link: https://msgid.link/some@msgid.here +Signed-off-by: Original Submitter +Signed-off-by: Test Override +--- + +diff --git a/b4/junk.py b/b4/junk.py +index 12345678..23456789 100644 +--- a/b4/junk.py +--- b/b4/junk.py +@@@ -1,1 +1,1 @@ def junk(): + + +-junk1 ++junk2 + + +-- +2.wong.fu + +From git@z Thu Jan 1 00:00:00 1970 +Subject: [PATCH v2 2/2] Simple test 2 +From: Test Test +Date: Tue, 30 Aug 2022 11:19:07 -0400 +Message-Id: +To: Some List +Cc: Dev Eloper1 , Dev Eloper2 +In-Reply-To: +References: +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +Follow-up trailer collating test patch 2. + +Fixes: abcdef01234567890 +Reviewed-by: Original Reviewer +Link: https://msgid.link/some@msgid.here +Signed-off-by: Original Submitter +Reviewed-by: Followup Reviewer1 +Signed-off-by: Test Override +--- + +diff --git a/b4/bupkes.py b/b4/bupkes.py +index 12345678..23456789 100644 +--- a/b4/bupkes.py +--- b/b4/bupkes.py +@@@ -1,1 +1,1 @@ def bupkes(): + + +-bupkes1 ++bupkes2 + + +-- +2.wong.fu + diff --git a/tests/samples/trailers-followup-with-cover.mbox b/tests/samples/trailers-followup-with-cover.mbox new file mode 100644 index 0000000..52c14c4 --- /dev/null +++ b/tests/samples/trailers-followup-with-cover.mbox @@ -0,0 +1,113 @@ +From foo@z Thu Jan 1 00:00:00 1970 +From: Test Test +Subject: [PATCH v2 0/2] Simple cover +To: Some List +Cc: Dev Eloper1 , + Dev Eloper2 +Date: Tue, 30 Aug 2022 11:19:07 -0400 +Message-Id: + +This is a cover letter. It has a diffstat. + +--- +b4/junk.py | 1 - +b4/bupkes.py | 1 - +2 files changed, 2 insertions(+), 2 deletions(-) + + +From foo@z Thu Jan 1 00:00:00 1970 +From: Test Test +Subject: [PATCH v2 1/2] Simple test 1 +To: Some List +Cc: Dev Eloper1 , + Dev Eloper2 +Date: Tue, 30 Aug 2022 11:19:07 -0400 +Message-Id: +In-Reply-To: +References: + +Follow-up trailer collating test patch 1. + +Fixes: abcdef01234567890 +Reviewed-by: Original Reviewer +Link: https://msgid.link/some@msgid.here +Signed-off-by: Original Submitter +--- + +diff --git a/b4/junk.py b/b4/junk.py +index 12345678..23456789 100644 +--- a/b4/junk.py +--- b/b4/junk.py +@@@ -1,1 +1,1 @@ def junk(): + + +-junk1 ++junk2 + + +-- +2.wong.fu + +From foo@z Thu Jan 1 00:00:00 1970 +From: Test Test +Subject: [PATCH v2 2/2] Simple test 2 +To: Some List +Cc: Dev Eloper1 , + Dev Eloper2 +Date: Tue, 30 Aug 2022 11:19:07 -0400 +Message-Id: +In-Reply-To: +References: + +Follow-up trailer collating test patch 2. + +Fixes: abcdef01234567890 +Reviewed-by: Original Reviewer +Link: https://msgid.link/some@msgid.here +Signed-off-by: Original Submitter +--- + +diff --git a/b4/bupkes.py b/b4/bupkes.py +index 12345678..23456789 100644 +--- a/b4/bupkes.py +--- b/b4/bupkes.py +@@@ -1,1 +1,1 @@ def bupkes(): + + +-bupkes1 ++bupkes2 + + +-- +2.wong.fu + +From foo@z Thu Jan 1 00:00:00 1970 +From: Followup Reviewer1 +Subject: Re: [PATCH v2 2/2] Simple test 2 +Date: Tue, 30 Aug 2022 11:19:07 -0400 +Message-Id: +In-Reply-To: +References: + +> This is a simple trailer parsing test. + +Reviewed-by: Followup Reviewer1 + +-- +My sig + +From foo@z Thu Jan 1 00:00:00 1970 +From: Followup Reviewer1 +Subject: Re: [PATCH v2 0/2] Simple cover +Date: Tue, 30 Aug 2022 11:19:07 -0400 +Message-Id: +In-Reply-To: +References: + +> This is a simple trailer parsing test. + +Reviewed-by: Coverletter Reviewer1 + +-- +My sig + diff --git a/tests/samples/trailers-test-extinfo.txt b/tests/samples/trailers-test-extinfo.txt new file mode 100644 index 0000000..d36abbb --- /dev/null +++ b/tests/samples/trailers-test-extinfo.txt @@ -0,0 +1,29 @@ +From: Test Test +Subject: [PATCH] Simple test +Date: Tue, 30 Aug 2022 11:19:07 -0400 + +This is a simple trailer parsing test. + +Reviewed-by: Bogus Bupkes +[for the parts that are bogus] +Fixes: abcdef01234567890 +Tested-by: Some Person + [this person visually indented theirs] +Link: https://msgid.link/some@msgid.here # initial submission +Signed-off-by: Wrapped Persontrailer + +--- + +diff --git a/b4/junk.py b/b4/junk.py +index 12345678..23456789 100644 +--- a/b4/junk.py +--- b/b4/junk.py +@@@ -1,1 +1,1 @@ def junk(): + + +-junk1 ++junk2 + + +-- +2.wong.fu diff --git a/tests/samples/trailers-test-simple.txt b/tests/samples/trailers-test-simple.txt new file mode 100644 index 0000000..693d781 --- /dev/null +++ b/tests/samples/trailers-test-simple.txt @@ -0,0 +1,24 @@ +From: Test Test +Subject: [PATCH] Simple test +Date: Tue, 30 Aug 2022 11:19:07 -0400 + +This is a simple trailer parsing test. + +Reviewed-by: Bogus Bupkes +Fixes: abcdef01234567890 +Link: https://msgid.link/some@msgid.here +--- + +diff --git a/b4/junk.py b/b4/junk.py +index 12345678..23456789 100644 +--- a/b4/junk.py +--- b/b4/junk.py +@@@ -1,1 +1,1 @@ def junk(): + + +-junk1 ++junk2 + + +-- +2.wong.fu diff --git a/tests/test___init__.py b/tests/test___init__.py index d78667e..f97e166 100644 --- a/tests/test___init__.py +++ b/tests/test___init__.py @@ -1,7 +1,9 @@ import pytest # noqa import b4 -import re import os +import email +import mailbox +import io @pytest.mark.parametrize('source,expected', [ @@ -25,7 +27,6 @@ def test_save_git_am_mbox(tmpdir, source, regex, flags, ismbox): import re if source is not None: if ismbox: - import mailbox mbx = mailbox.mbox(f'tests/samples/{source}.txt') msgs = list(mbx) else: @@ -48,3 +49,64 @@ def test_save_git_am_mbox(tmpdir, source, regex, flags, ismbox): with open(dest, 'r') as fh: res = fh.read() assert re.search(regex, res, flags=flags) + + +@pytest.mark.parametrize('source,expected', [ + ('trailers-test-simple', + [('person', 'Reviewed-By', 'Bogus Bupkes ', None), + ('utility', 'Fixes', 'abcdef01234567890', None), + ('utility', 'Link', 'https://msgid.link/some@msgid.here', None), + ]), + ('trailers-test-extinfo', + [('person', 'Reviewed-by', 'Bogus Bupkes ', '[for the parts that are bogus]'), + ('utility', 'Fixes', 'abcdef01234567890', None), + ('person', 'Tested-by', 'Some Person ', ' [this person visually indented theirs]'), + ('utility', 'Link', 'https://msgid.link/some@msgid.here', ' # initial submission'), + ('person', 'Signed-off-by', 'Wrapped Persontrailer ', None), + ]), +]) +def test_parse_trailers(source, expected): + with open(f'tests/samples/{source}.txt', 'r') as fh: + msg = email.message_from_file(fh) + lmsg = b4.LoreMessage(msg) + gh, m, trs, bas, sig = b4.LoreMessage.get_body_parts(lmsg.body) + assert len(expected) == len(trs) + for tr in trs: + mytype, myname, myvalue, myextinfo = expected.pop(0) + mytr = b4.LoreTrailer(name=myname, value=myvalue, extinfo=myextinfo) + assert tr == mytr + assert tr.type == mytype + + +@pytest.mark.parametrize('source,serargs,amargs,reference,b4cfg', [ + ('single', {}, {}, 'defaults', {}), + ('single', {}, {'noaddtrailers': True}, 'noadd', {}), + ('single', {}, {'addmysob': True}, 'addmysob', {}), + ('single', {}, {'addmysob': True, 'copyccs': True}, 'copyccs', {}), + ('single', {}, {'addmysob': True, 'addlink': True}, 'addlink', {}), + ('single', {}, {'addmysob': True, 'copyccs': True}, 'ordered', + {'trailer-order': 'Cc,Tested*,Reviewed*,*'}), + ('single', {'sloppytrailers': True}, {'addmysob': True}, 'sloppy', {}), + ('with-cover', {}, {'addmysob': True}, 'defaults', {}), + ('with-cover', {}, {'covertrailers': True, 'addmysob': True}, 'covertrailers', {}), + ('custody', {}, {'addmysob': True, 'copyccs': True}, 'unordered', {}), + ('custody', {}, {'addmysob': True, 'copyccs': True}, 'ordered', + {'trailer-order': 'Cc,Fixes*,Link*,Suggested*,Reviewed*,Tested*,*'}), +]) +def test_followup_trailers(source, serargs, amargs, reference, b4cfg): + b4.USER_CONFIG = { + 'name': 'Test Override', + 'email': 'test-override@example.com', + } + b4.MAIN_CONFIG = dict(b4.DEFAULT_CONFIG) + b4.MAIN_CONFIG.update(b4cfg) + lmbx = b4.LoreMailbox() + for msg in mailbox.mbox(f'tests/samples/trailers-followup-{source}.mbox'): + lmbx.add_message(msg) + lser = lmbx.get_series(**serargs) + assert lser is not None + amsgs = lser.get_am_ready(**amargs) + ifh = io.StringIO() + b4.save_git_am_mbox(amsgs, ifh) + with open(f'tests/samples/trailers-followup-{source}-ref-{reference}.txt', 'r') as fh: + assert ifh.getvalue() == fh.read() -- cgit v1.2.3