diff options
author | Konstantin Ryabitsev <konstantin@linuxfoundation.org> | 2022-12-09 13:57:25 -0500 |
---|---|---|
committer | Konstantin Ryabitsev <konstantin@linuxfoundation.org> | 2022-12-09 13:57:25 -0500 |
commit | 940469727b1124dbb10d1e6f7570d239aeda8ad2 (patch) | |
tree | 663fcfe0acda3ed5aa4391ec6cefb402317064ed | |
parent | 64ef0eed4eda48226e4fd3495963a36b964f1cd4 (diff) | |
download | b4-940469727b1124dbb10d1e6f7570d239aeda8ad2.tar.gz |
ez: refactor single-patch-series code
The way I initially implemented single-patch series handling was too
hurky and caused repeated problems with things like resend.
This is a cleaner reimplementation of the same functionality.
Link: https://social.kernel.org/notice/AQQySDDBI14Eqo3HZw # what is "hurky"
Reported-by: Mark Brown <broonie@kernel.org>
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
-rw-r--r-- | b4/__init__.py | 171 | ||||
-rw-r--r-- | b4/ez.py | 329 |
2 files changed, 246 insertions, 254 deletions
diff --git a/b4/__init__.py b/b4/__init__.py index f562c36..54dccc6 100644 --- a/b4/__init__.py +++ b/b4/__init__.py @@ -73,6 +73,8 @@ dkimlogger.addFilter(_dkim_log_filter) HUNK_RE = re.compile(r'^@@ -\d+(?:,(\d+))? \+\d+(?:,(\d+))? @@') FILENAME_RE = re.compile(r'^(---|\+\+\+) (\S+)') +DIFF_RE = re.compile(r'^(---.*\n\+\+\+|GIT binary patch|diff --git \w/\S+ \w/\S+)', flags=re.M | re.I) +DIFFSTAT_RE = re.compile(r'^\s*\d+ file.*\d+ (insertion|deletion)', flags=re.M | re.I) ATT_PASS_SIMPLE = 'v' ATT_FAIL_SIMPLE = 'x' @@ -1031,9 +1033,6 @@ class LoreMessage: if self.date.tzinfo is None: self.date = self.date.replace(tzinfo=datetime.timezone.utc) - self.diffre = re.compile(r'^(---.*\n\+\+\+|GIT binary patch|diff --git \w/\S+ \w/\S+)', flags=re.M | re.I) - self.diffstatre = re.compile(r'^\s*\d+ file.*\d+ (insertion|deletion)', flags=re.M | re.I) - # walk until we find the first text/plain part mcharset = self.msg.get_content_charset() if not mcharset: @@ -1064,7 +1063,7 @@ class LoreMessage: continue # If we already found a body, but we now find something that contains a diff, # then we prefer this part - if self.diffre.search(payload): + if DIFF_RE.search(payload): self.body = payload if self.body is None: @@ -1073,9 +1072,9 @@ class LoreMessage: logger.info(' Not plaintext: %s', self.full_subject) return - if self.diffstatre.search(self.body): + if DIFFSTAT_RE.search(self.body): self.has_diffstat = True - if self.diffre.search(self.body): + if DIFF_RE.search(self.body): self.has_diff = True self.pwhash = LoreMessage.get_patchwork_hash(self.body) self.blob_indexes = LoreMessage.get_indexes(self.body) @@ -1828,7 +1827,7 @@ class LoreMessage: bparts = basement.split('---\n') for bpart in list(bparts): # If it's a diff or diffstat, we don't care to keep it - if self.diffre.search(bpart) or self.diffstatre.search(bpart): + if DIFF_RE.search(bpart) or DIFFSTAT_RE.search(bpart): bparts.remove(bpart) if bparts: self.message += '---\n' + '---\n'.join(bparts) @@ -1985,6 +1984,22 @@ class LoreSubject: return ret + def get_rebuilt_subject(self, eprefixes: Optional[List[str]]): + _pfx = self.get_extra_prefixes() + if eprefixes: + for _epfx in eprefixes: + if _epfx not in _pfx: + _pfx.append(_epfx) + if self.revision > 1: + _pfx.append(f'v{self.revision}') + if self.expected > 1: + _pfx.append('%s/%s' % (str(self.counter).zfill(len(str(self.expected))), self.expected)) + + if len(_pfx): + return '[PATCH ' + ' '.join(_pfx) + '] ' + self.subject + else: + return f'[PATCH] {self.subject}' + def get_slug(self, sep='_', with_counter: bool = True): unsafe = self.subject if with_counter: @@ -2715,21 +2730,21 @@ def get_pi_thread_by_msgid(msgid: str, nocache: bool = False, def git_range_to_patches(gitdir: Optional[str], start: str, end: str, - covermsg: Optional[email.message.EmailMessage] = None, prefixes: Optional[List[str]] = None, + revision: Optional[int] = 1, msgid_tpt: Optional[str] = None, seriests: Optional[int] = None, mailfrom: Optional[Tuple[str, str]] = None, extrahdrs: Optional[List[Tuple[str, str]]] = None, - ignore_commits: Optional[Set[str]] = None, - thread: bool = False, - keepdate: bool = False) -> List[Tuple[str, email.message.Message]]: - patches = list() + ignore_commits: Optional[Set[str]] = None) -> List[Tuple[str, email.message.Message]]: commits = git_get_command_lines(gitdir, ['rev-list', '--reverse', f'{start}..{end}']) if not commits: raise RuntimeError(f'Could not run rev-list {start}..{end}') if ignore_commits is None: ignore_commits = set() + + # Go through them once to drop ignored commits and get bodies + patches = list() for commit in commits: if commit in ignore_commits: logger.debug('Ignoring commit %s', commit) @@ -2739,126 +2754,46 @@ def git_range_to_patches(gitdir: Optional[str], start: str, end: str, if ecode > 0: raise RuntimeError(f'Could not get a patch out of {commit}') msg = email.message_from_bytes(out, policy=emlpolicy) - msg.set_charset('utf-8') - # Clean subject and From to remove any 7bit-safe encoding - msg.replace_header('From', LoreMessage.clean_header(msg.get('From'))) - msg.replace_header('Subject', LoreMessage.clean_header(msg.get('Subject'))) - logger.debug(' %s', msg.get('Subject')) - patches.append((commit, msg)) - startfrom = 1 fullcount = len(patches) if fullcount == 0: raise RuntimeError(f'Could not run rev-list {start}..{end}') - if covermsg: - if fullcount == 1: - # Single-patch series, mix in the cover letter into the patch basement - pmsg = patches[0][1] - pbody = pmsg.get_payload(decode=True).decode() - pheaders, pmessage, ptrailers, pbasement, psignature = LoreMessage.get_body_parts(pbody) - cbody = covermsg.get_payload(decode=True).decode() - cheaders, cmessage, ctrailers, cbasement, csignature = LoreMessage.get_body_parts(cbody) - nbparts = list() - todests = list() - ccdests = list() - nmessage = '' - if len(cmessage.strip()): - cls = LoreSubject(covermsg.get('Subject')) - nmessage += cls.subject + '\n\n' + cmessage.rstrip('\r\n') + '\n' - for ctr in list(ctrailers): - # We always hide To: and Cc: cover trailers from single-patch series - if ctr.lname == 'to': - ctrailers.remove(ctr) - todests.append(ctr.addr) - elif ctr.lname == 'cc': - ctrailers.remove(ctr) - ccdests.append(ctr.addr) - elif ctr in ptrailers: - ctrailers.remove(ctr) - if ctrailers: - if nmessage: - nmessage += '\n' - for ctr in ctrailers: - nmessage += ctr.as_string() + '\n' - if len(nmessage.strip()): - nbparts.append(nmessage) - - # Find the section with changelogs - utility = None - for section in cbasement.split('---\n'): - if re.search(r'^change-id: ', section, flags=re.I | re.M): - utility = section - continue - if re.search(r'^changes in v\d', section, flags=re.I | re.M): - nbparts.append(section.strip('\r\n') + '\n') - - nbparts.append(pbasement.rstrip('\r\n') + '\n\n') - if utility: - nbparts.append(utility) - - newbasement = '---\n'.join(nbparts) - - pbody = LoreMessage.rebuild_message(pheaders, pmessage, ptrailers, newbasement, csignature) - pmsg.set_payload(pbody, charset='utf-8') - # Add any To: and Cc: headers from the cover_message - toh = covermsg.get('To') - if toh: - todests.append(email.utils.getaddresses([toh])) - cch = covermsg.get('Cc') - if cch: - ccdests.append(email.utils.getaddresses([cch])) - - if todests: - pmsg.add_header('To', format_addrs(todests)) - if ccdests: - pmsg.add_header('Cc', format_addrs(ccdests)) - startfrom = 0 - else: - patches.insert(0, (None, covermsg)) - startfrom = 0 - - # Go through and apply any outstanding fixes - if prefixes: - prefixes = ' ' + ' '.join(prefixes) - else: - prefixes = '' - vlines = git_get_command_lines(None, ['--version']) if len(vlines) == 1: gitver = vlines[0].split()[-1] else: gitver = None - for counter in range(startfrom, len(patches)): - msg = patches[counter][1] - subject = msg.get('Subject') - csubject = re.sub(r'^\[PATCH]\s*', '', subject) - if fullcount > 1: - pline = '[PATCH%s %s/%s]' % (prefixes, str(counter).zfill(len(str(fullcount))), fullcount) - else: - pline = '[PATCH%s]' % prefixes - msg.replace_header('Subject', f'{pline} {csubject}') + expected = len(patches) + for counter, (commit, msg) in enumerate(patches): + msg.set_charset('utf-8') + # Clean From to remove any 7bit-safe encoding + origfrom = LoreMessage.clean_header(msg.get('From')) + lsubject = LoreSubject(msg.get('Subject')) + lsubject.counter = counter + 1 + lsubject.expected = expected + lsubject.revision = revision + subject = lsubject.get_rebuilt_subject(eprefixes=prefixes) + + logger.debug(' %s', subject) + msg.replace_header('Subject', subject) + inbodyhdrs = list() + setfrom = origfrom if mailfrom: # Move the original From and Date into the body - origfrom = msg.get('From') - if origfrom: - origfrom = LoreMessage.clean_header(origfrom) - origpair = email.utils.parseaddr(origfrom) - if origpair[1] != mailfrom[1]: - msg.replace_header('From', format_addrs([mailfrom])) - inbodyhdrs.append(f'From: {origfrom}') - else: - msg.add_header('From', format_addrs([mailfrom])) + origpair = email.utils.parseaddr(origfrom) + if origpair[1] != mailfrom[1]: + setfrom = format_addrs([mailfrom]) + inbodyhdrs.append(f'From: {origfrom}') + msg.replace_header('From', setfrom) if seriests: patchts = seriests + counter origdate = msg.get('Date') if origdate: - if keepdate: - inbodyhdrs.append(f'Date: {origdate}') msg.replace_header('Date', email.utils.formatdate(patchts, localtime=True)) else: msg.add_header('Date', email.utils.formatdate(patchts, localtime=True)) @@ -2881,17 +2816,7 @@ def git_range_to_patches(gitdir: Optional[str], start: str, end: str, msg.add_header(hdrname, hdrval) if msgid_tpt: - msg.add_header('Message-Id', msgid_tpt % str(counter)) - refto = None - if counter > 0 and covermsg: - # Thread to the cover letter - refto = msgid_tpt % str(0) - if counter > 1 and not covermsg: - # Thread to the first patch - refto = msgid_tpt % str(1) - if refto and thread: - msg.add_header('References', refto) - msg.add_header('In-Reply-To', refto) + msg.add_header('Message-Id', msgid_tpt % str(lsubject.counter)) return patches @@ -24,7 +24,6 @@ import base64 import textwrap import gzip import io -import copy from typing import Optional, Tuple, List, Union from email import utils @@ -264,10 +263,9 @@ def start_new_series(cmdargs: argparse.Namespace) -> None: tracking = json.loads(btracking.decode()) logger.debug('tracking: %s', tracking) cover_sections = list() - diffstatre = re.compile(r'^\s*\d+ file.*\d+ (insertion|deletion)', flags=re.M | re.I) for section in cmsg.body.split('\n---\n'): # we stop caring once we see a diffstat - if diffstatre.search(section): + if b4.DIFFSTAT_RE.search(section): break cover_sections.append(section) cover = '\n---\n'.join(cover_sections).strip() @@ -966,45 +964,6 @@ def get_base_changeid_from_tag(tagname: str) -> Tuple[str, str, str]: return cover, base_commit, change_id -def get_sent_tag_as_patches(tagname: str, revision: int, hide_cover_to_cc: bool = False - ) -> Tuple[List, List, List[Tuple[str, email.message.Message]]]: - cover, base_commit, change_id = get_base_changeid_from_tag(tagname) - # First line is the subject - csubject, cbody = cover.split('\n', maxsplit=1) - cbody = cbody.strip() + '\n-- \n' + b4.get_email_signature() - if hide_cover_to_cc: - tos, ccs, body = remove_to_cc_trailers(cbody) - else: - tos = list() - ccs = list() - - lsubject = b4.LoreSubject(csubject) - cmsg = email.message.EmailMessage() - cmsg.add_header('Subject', lsubject.subject) - cmsg.set_payload(cbody.lstrip(), charset='utf-8') - prefixes = ['RESEND'] - prefixes += lsubject.get_extra_prefixes(exclude=['resend']) - if revision != 1: - prefixes.append(f'v{revision}') - seriests = int(time.time()) - msgid_tpt = make_msgid_tpt(change_id, str(revision)) - sconfig = b4.get_sendemail_config() - fromaddr = sconfig.get('from') - if fromaddr: - mailfrom = email.utils.parseaddr(fromaddr) - else: - usercfg = b4.get_user_config() - mailfrom = (usercfg.get('name'), usercfg.get('email')) - - patches = b4.git_range_to_patches(None, base_commit, tagname, - covermsg=cmsg, prefixes=prefixes, - msgid_tpt=msgid_tpt, - seriests=seriests, - thread=True, - mailfrom=mailfrom) - return tos, ccs, patches - - def make_msgid_tpt(change_id: str, revision: str, domain: Optional[str] = None) -> str: if not domain: usercfg = b4.get_user_config() @@ -1024,8 +983,8 @@ def make_msgid_tpt(change_id: str, revision: str, domain: Optional[str] = None) return msgid_tpt -def remove_to_cc_trailers(body: str) -> Tuple[List, List, str]: - htrs, cmsg, mtrs, basement, sig = b4.LoreMessage.get_body_parts(body) +def get_cover_dests(cbody: str, hide: bool = True) -> Tuple[List, List, str]: + htrs, cmsg, mtrs, basement, sig = b4.LoreMessage.get_body_parts(cbody) tos = list() ccs = list() for mtr in list(mtrs): @@ -1035,14 +994,143 @@ def remove_to_cc_trailers(body: str) -> Tuple[List, List, str]: elif mtr.lname == 'cc': ccs.append(mtr.addr) mtrs.remove(mtr) - body = b4.LoreMessage.rebuild_message(htrs, cmsg, mtrs, basement, sig) - return tos, ccs, body + if hide: + cbody = b4.LoreMessage.rebuild_message(htrs, cmsg, mtrs, basement, sig) + return tos, ccs, cbody + + +def add_cover(csubject: b4.LoreSubject, msgid_tpt: str, patches: List[Tuple[str, email.message.Message]], + cbody: str, thread: bool = True): + fp = patches[0][1] + cmsg = email.message.EmailMessage() + cmsg.add_header('From', fp['From']) + fpls = b4.LoreSubject(fp['Subject']) + + csubject.expected = fpls.expected + csubject.counter = 0 + csubject.revision = fpls.revision + cmsg.add_header('Subject', csubject.get_rebuilt_subject(eprefixes=fpls.get_extra_prefixes())) + cmsg.add_header('Message-Id', msgid_tpt % str(0)) + + cmsg.set_payload(cbody, charset='utf-8') + cmsg.set_charset('utf-8') + + patches.insert(0, ('', cmsg)) + if thread: + rethread(patches) + + +def mixin_cover(cbody: str, patches: List[Tuple[str, email.message.Message]]) -> None: + msg = patches[0][1] + pbody = msg.get_payload(decode=True).decode() + pheaders, pmessage, ptrailers, pbasement, psignature = b4.LoreMessage.get_body_parts(pbody) + cheaders, cmessage, ctrailers, cbasement, csignature = b4.LoreMessage.get_body_parts(cbody) + nbparts = list() + nmessage = cmessage.rstrip('\r\n') + '\n' + + for ctr in list(ctrailers): + # We hide any trailers already present in the patch itself, + # or To:/Cc: trailers, which we parse elsewhere + if ctr in ptrailers or ctr.lname in ('to', 'cc'): + ctrailers.remove(ctr) + if ctrailers: + if nmessage: + nmessage += '\n' + for ctr in ctrailers: + nmessage += ctr.as_string() + '\n' + + if len(nmessage.strip()): + nbparts.append(nmessage) + + # Find the section with changelogs + utility = None + for section in cbasement.split('---\n'): + if re.search(b4.DIFFSTAT_RE, section): + # Skip this section + continue + if re.search(r'^change-id: ', section, flags=re.I | re.M): + # We move this to the bottom + utility = section + continue + nbparts.append(section.strip('\r\n') + '\n') + + nbparts.append(pbasement.rstrip('\r\n') + '\n\n') + if utility: + nbparts.append(utility) + + newbasement = '---\n'.join(nbparts) + + pbody = b4.LoreMessage.rebuild_message(pheaders, pmessage, ptrailers, newbasement, csignature) + msg.set_payload(pbody, charset='utf-8') + + +def get_cover_subject_body(cover: str) -> Tuple[b4.LoreSubject, str]: + clines = cover.splitlines() + if len(clines) < 2 or len(clines[1].strip()) or not len(clines[0].strip()): + csubject = '(no cover subject)' + cbody = cover.strip() + else: + csubject = clines[0] + cbody = '\n'.join(clines[2:]).strip() + + lsubject = b4.LoreSubject(csubject) + return lsubject, cbody + + +def rethread(patches: List[Tuple[str, email.message.Message]]): + refto = patches[0][1].get('message-id') + for commit, msg in patches[1:]: + msg.add_header('References', refto) + msg.add_header('In-Reply-To', refto) + + +def get_mailfrom() -> Tuple[str, str]: + sconfig = b4.get_sendemail_config() + fromaddr = sconfig.get('from') + if fromaddr: + return email.utils.parseaddr(fromaddr) + + usercfg = b4.get_user_config() + return usercfg.get('name'), usercfg.get('email') def get_prep_branch_as_patches(movefrom: bool = True, thread: bool = True, - hide_cover_to_cc: bool = False - ) -> Tuple[List, List, List[Tuple[str, email.message.Message]]]: + addtracking: bool = True, hide_cover_to_cc: bool = False + ) -> Tuple[List, List, str, List[Tuple[str, email.message.Message]]]: cover, tracking = load_cover(strip_comments=True) + + prefixes = tracking['series'].get('prefixes', list()) + start_commit = get_series_start() + change_id = tracking['series'].get('change-id') + revision = tracking['series'].get('revision') + msgid_tpt = make_msgid_tpt(change_id, revision) + seriests = int(time.time()) + + mailfrom = None + if movefrom: + mailfrom = get_mailfrom() + + strategy = get_cover_strategy() + ignore_commits = None + if strategy in {'commit', 'tip-commit'}: + cover_commit = find_cover_commit() + if cover_commit: + ignore_commits = {cover_commit} + + csubject, cbody = get_cover_subject_body(cover) + for cprefix in csubject.get_extra_prefixes(exclude=prefixes): + prefixes.append(cprefix) + + patches = b4.git_range_to_patches(None, start_commit, 'HEAD', + revision=revision, + prefixes=prefixes, + msgid_tpt=msgid_tpt, + seriests=seriests, + mailfrom=mailfrom, + ignore_commits=ignore_commits) + + base_commit, shortlog, diffstat = get_series_details(start_commit=start_commit) + config = b4.get_main_config() cover_template = DEFAULT_COVER_TEMPLATE if config.get('prep-cover-template'): @@ -1055,88 +1143,66 @@ def get_prep_branch_as_patches(movefrom: bool = True, thread: bool = True, sys.exit(2) # Put together the cover letter - clines = cover.splitlines() - if len(clines) < 2 or len(clines[1].strip()) or not len(clines[0].strip()): - csubject = '(no cover subject)' - cbody = cover - else: - csubject = clines[0] - cbody = '\n'.join(clines[2:]) - - start_commit = get_series_start() - base_commit, shortlog, diffstat = get_series_details(start_commit=start_commit) - change_id = tracking['series'].get('change-id') - revision = tracking['series'].get('revision') tptvals = { - 'subject': csubject, - 'cover': cbody.strip(), + 'cover': cbody, 'shortlog': shortlog, 'diffstat': diffstat, 'change_id': change_id, 'base_commit': base_commit, 'signature': b4.get_email_signature(), } - body = Template(cover_template.lstrip()).safe_substitute(tptvals) - if hide_cover_to_cc: - tos, ccs, body = remove_to_cc_trailers(body) - else: - tos = list() - ccs = list() - - cmsg = email.message.EmailMessage() - prefixes = tracking['series'].get('prefixes', list()) - if '[' in csubject: - lsubject = b4.LoreSubject(csubject) - prefixes += lsubject.get_extra_prefixes(exclude=prefixes) - subject = lsubject.subject - else: - subject = csubject - if revision != 1: - prefixes.append(f'v{revision}') - - cmsg.add_header('Subject', subject) - cmsg.set_payload(body, charset='utf-8') + cover_letter = Template(cover_template.lstrip()).safe_substitute(tptvals) # Store tracking info in the header in a safe format, which should allow us to # fully restore our work from the already sent series. ztracking = gzip.compress(bytes(json.dumps(tracking), 'utf-8')) b64tracking = base64.b64encode(ztracking).decode() # A little trick for pretty wrapping - wrapped = textwrap.wrap('X-B4-Tracking: v=1; b=' + b64tracking, width=76) - cmsg.add_header('X-B4-Tracking', ' '.join(wrapped).replace('X-B4-Tracking: ', '')) + wrapped = textwrap.wrap('X-B4-Tracking: v=1; b=' + b64tracking, width=75) + thdata = ' '.join(wrapped).replace('X-B4-Tracking: ', '') - seriests = int(time.time()) - msgid_tpt = make_msgid_tpt(change_id, revision) - if movefrom: - sconfig = b4.get_sendemail_config() - fromaddr = sconfig.get('from') - if fromaddr: - mailfrom = email.utils.parseaddr(fromaddr) - else: - usercfg = b4.get_user_config() - mailfrom = (usercfg.get('name'), usercfg.get('email')) + alltos, allccs, cbody = get_cover_dests(cover_letter, hide=hide_cover_to_cc) + if len(patches) == 1: + mixin_cover(cbody, patches) else: - mailfrom = None + add_cover(csubject, msgid_tpt, patches, cbody, thread=thread) - strategy = get_cover_strategy() - ignore_commits = None - if strategy in {'commit', 'tip-commit'}: - cover_commit = find_cover_commit() - if cover_commit: - ignore_commits = {cover_commit} + if addtracking: + patches[0][1].add_header('X-B4-Tracking', thdata) - patches = b4.git_range_to_patches(None, start_commit, 'HEAD', - covermsg=cmsg, prefixes=prefixes, + tag_msg = f'{csubject.full_subject}\n\n{cover_letter}' + return alltos, allccs, tag_msg, patches + + +def get_sent_tag_as_patches(tagname: str, revision: int, hide_cover_to_cc: bool = False + ) -> Tuple[List, List, List[Tuple[str, email.message.Message]]]: + cover, base_commit, change_id = get_base_changeid_from_tag(tagname) + + csubject, cbody = get_cover_subject_body(cover) + cbody = cbody.strip() + '\n-- \n' + b4.get_email_signature() + prefixes = ['RESEND'] + csubject.get_extra_prefixes(exclude=['RESEND']) + msgid_tpt = make_msgid_tpt(change_id, str(revision)) + seriests = int(time.time()) + mailfrom = get_mailfrom() + + patches = b4.git_range_to_patches(None, base_commit, tagname, + revision=revision, + prefixes=prefixes, msgid_tpt=msgid_tpt, seriests=seriests, - thread=thread, - mailfrom=mailfrom, - ignore_commits=ignore_commits) - return tos, ccs, patches + mailfrom=mailfrom) + + alltos, allccs, cbody = get_cover_dests(cbody, hide=hide_cover_to_cc) + if len(patches) == 1: + mixin_cover(cbody, patches) + else: + add_cover(csubject, msgid_tpt, patches, cbody) + + return alltos, allccs, patches def format_patch(output_dir: str) -> None: try: - tos, ccs, patches = get_prep_branch_as_patches(thread=False, movefrom=False) + tos, ccs, tstr, patches = get_prep_branch_as_patches(thread=False, movefrom=False, addtracking=False) except RuntimeError as ex: logger.critical('CRITICAL: Failed to convert range to patches: %s', ex) sys.exit(1) @@ -1169,6 +1235,8 @@ def cmd_send(cmdargs: argparse.Namespace) -> None: if not cmdargs.hide_cover_to_cc and config.get('send-hide-cover-to-cc', '').lower() in {'yes', 'true', '1'}: cmdargs.hide_cover_to_cc = True + tag_msg = None + cl_msgid = None if cmdargs.resend: tagname, revision = get_sent_tagname(mybranch, SENT_TAG_PREFIX, cmdargs.resend) @@ -1196,7 +1264,8 @@ def cmd_send(cmdargs: argparse.Namespace) -> None: sys.exit(1) try: - todests, ccdests, patches = get_prep_branch_as_patches(hide_cover_to_cc=cmdargs.hide_cover_to_cc) + todests, ccdests, tag_msg, patches = get_prep_branch_as_patches( + hide_cover_to_cc=cmdargs.hide_cover_to_cc) except RuntimeError as ex: logger.critical('CRITICAL: Failed to convert range to patches: %s', ex) sys.exit(1) @@ -1336,7 +1405,7 @@ def cmd_send(cmdargs: argparse.Namespace) -> None: smtpserver = sconfig.get('smtpserver', 'localhost') logger.info(' - via SMTP server %s', smtpserver) - if not cmdargs.reflect: + if not (cmdargs.reflect or cmdargs.resend): logger.info(' - tag and reroll the series to the next revision') logger.info('') try: @@ -1350,13 +1419,12 @@ def cmd_send(cmdargs: argparse.Namespace) -> None: if cmdargs.no_sign or config.get('send-no-patatt-sign', '').lower() in {'yes', 'true', 'y'}: sign = False - cover_msg = None send_msgs = list() for commit, msg in patches: if not msg: continue - if cover_msg is None: - cover_msg = copy.deepcopy(msg) + if not cl_msgid: + cl_msgid = b4.LoreMessage.get_clean_msgid(msg) myto = list(allto) mycc = list(allcc) @@ -1437,7 +1505,7 @@ def cmd_send(cmdargs: argparse.Namespace) -> None: logger.debug('Not updating cover/tracking on resend') return - reroll(mybranch, cover_msg) + reroll(mybranch, tag_msg, cl_msgid) def get_sent_tagname(branch: str, tagprefix: str, revstr: Union[str, int]) -> Tuple[str, Optional[int]]: @@ -1459,20 +1527,11 @@ def get_sent_tagname(branch: str, tagprefix: str, revstr: Union[str, int]) -> Tu return f'{tagprefix}{branch}-v{revision}', revision -def reroll(mybranch: str, cover_msg: email.message.Message, tagprefix: str = SENT_TAG_PREFIX): - # Prepare annotated tag body from the cover letter - lsubject = b4.LoreSubject(cover_msg.get('subject')) - cbody = cover_msg.get_payload(decode=True).decode() +def reroll(mybranch: str, tag_msg: str, msgid: str, tagprefix: str = SENT_TAG_PREFIX): # Remove signature - chunks = cbody.rsplit('\n-- \n') + chunks = tag_msg.rsplit('\n-- \n') if len(chunks) > 1: - cbody = chunks[0] + '\n' - prefixes = lsubject.get_extra_prefixes() - if prefixes: - subject = '[%s] %s' % (' '.join(prefixes), lsubject.subject) - else: - subject = lsubject.subject - cover_body = subject + '\n\n' + cbody + tag_msg = chunks[0] + '\n' cover, tracking = load_cover(strip_comments=True) revision = tracking['series']['revision'] @@ -1518,7 +1577,7 @@ def reroll(mybranch: str, cover_msg: email.message.Message, tagprefix: str = SEN logger.info('Tagging %s', tagname) gitargs = ['tag', '-a', '-F', '-', tagname, tagcommit] - ecode, out = b4.git_run_command(None, gitargs, stdin=cover_body.encode()) + ecode, out = b4.git_run_command(None, gitargs, stdin=tag_msg.encode()) if ecode > 0: # Not a fatal error, just complain about it logger.info('Could not tag %s as %s:', tagcommit, tagname) @@ -1530,7 +1589,6 @@ def reroll(mybranch: str, cover_msg: email.message.Message, tagprefix: str = SEN else: logger.info('NOTE: Tagname %s already exists', tagname) - cover_msgid = b4.LoreMessage.get_clean_msgid(cover_msg) logger.info('Recording series message-id in cover letter tracking') cover, tracking = load_cover(strip_comments=False) vrev = f'v{revision}' @@ -1538,7 +1596,7 @@ def reroll(mybranch: str, cover_msg: email.message.Message, tagprefix: str = SEN tracking['series']['history'] = dict() if vrev not in tracking['series']['history']: tracking['series']['history'][vrev] = list() - tracking['series']['history'][vrev].append(cover_msgid) + tracking['series']['history'][vrev].append(msgid) oldrev = tracking['series']['revision'] newrev = oldrev + 1 @@ -1685,7 +1743,7 @@ def auto_to_cc() -> None: logger.debug('added %s to seen', ltr.addr[1]) extras.append(ltr) - tos, ccs, patches = get_prep_branch_as_patches() + tos, ccs, tag_msg, patches = get_prep_branch_as_patches() logger.info('Collecting To/Cc addresses') # Go through the messages to make to/cc headers for commit, msg in patches: @@ -1772,7 +1830,16 @@ def cmd_prep(cmdargs: argparse.Namespace) -> None: if msgs: for msg in msgs: if b4.LoreMessage.get_clean_msgid(msg) == msgid: - return reroll(mybranch, msg) + # Prepare annotated tag body from the cover letter + lsubject = b4.LoreSubject(msg.get('subject')) + cbody = msg.get_payload(decode=True).decode() + prefixes = lsubject.get_extra_prefixes() + if prefixes: + subject = '[%s] %s' % (' '.join(prefixes), lsubject.subject) + else: + subject = lsubject.subject + tag_msg = subject + '\n\n' + cbody + return reroll(mybranch, tag_msg, msgid) logger.critical('CRITICAL: could not retrieve %s', msgid) sys.exit(1) |