Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package b4 for openSUSE:Factory checked in at 2024-09-25 21:54:03 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/b4 (Old) and /work/SRC/openSUSE:Factory/.b4.new.29891 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "b4" Wed Sep 25 21:54:03 2024 rev:43 rq:1203258 version:0.14.2 Changes: -------- --- /work/SRC/openSUSE:Factory/b4/b4.changes 2024-07-25 16:21:26.805255600 +0200 +++ /work/SRC/openSUSE:Factory/.b4.new.29891/b4.changes 2024-09-25 21:54:33.323436538 +0200 @@ -1,0 +2,9 @@ +Wed Sep 18 05:13:21 UTC 2024 - Jiri Slaby <jsl...@suse.cz> + +- update to 0.14.2: + * fixes "b4 am/shazam" sometimes pulling in bogus trailers + * fixes "b4 send" treating message-ids as email addresses to cc + * fixes "prep-perpatch-check-cmd" for custom checkpatch configurations + * fixes "b4 send" expanding prerequisites in incorrect order + +------------------------------------------------------------------- Old: ---- b4-0.14.1.tar.gz New: ---- b4-0.14.2.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ b4.spec ++++++ --- /var/tmp/diff_new_pack.DjsVoZ/_old 2024-09-25 21:54:33.767455006 +0200 +++ /var/tmp/diff_new_pack.DjsVoZ/_new 2024-09-25 21:54:33.771455172 +0200 @@ -24,7 +24,7 @@ %global pprefix python311 %endif Name: b4 -Version: 0.14.1 +Version: 0.14.2 Release: 0 Summary: Helper scripts for kernel.org patches License: GPL-2.0-or-later ++++++ b4-0.14.1.tar.gz -> b4-0.14.2.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/b4-0.14.1/pyproject.toml new/b4-0.14.2/pyproject.toml --- old/b4-0.14.1/pyproject.toml 2024-07-23 16:42:58.000000000 +0200 +++ new/b4-0.14.2/pyproject.toml 2024-09-17 15:24:20.000000000 +0200 @@ -4,7 +4,7 @@ [project] name = "b4" -version = "0.14.1" +version = "0.14.2" description = "A tool to work with public-inbox and patch archives" readme = "README.rst" keywords = ["git", "public-inbox", "lore.kernel.org", "patch", "email", "workflow"] @@ -47,7 +47,7 @@ norecursedirs = "tests/helpers" [tool.bumpversion] -current_version = "0.14.1" +current_version = "0.14.2" files = [ {filename = "src/b4/__init__.py"}, {filename = "src/b4/man/b4.5.rst"}, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/b4-0.14.1/src/b4/__init__.py new/b4-0.14.2/src/b4/__init__.py --- old/b4-0.14.1/src/b4/__init__.py 2024-07-23 16:42:58.000000000 +0200 +++ new/b4-0.14.2/src/b4/__init__.py 2024-09-17 15:24:20.000000000 +0200 @@ -64,7 +64,7 @@ # global setting allowing us to turn off networking can_network = True -__VERSION__ = '0.14.1' +__VERSION__ = '0.14.2' PW_REST_API_VERSION = '1.2' @@ -394,17 +394,27 @@ break # Carry over trailers from previous series if patch/metadata did not change + logger.debug('Analyzing trailer_map (%s entries)', len(self.trailer_map)) for lmsg in lser.patches: if lmsg is None or lmsg.git_patch_id is None: continue + logger.debug(' matching patch_id %s from: %s', lmsg.git_patch_id, lmsg.full_subject) if lmsg.git_patch_id in self.trailer_map: for fmsg in self.trailer_map[lmsg.git_patch_id]: + logger.debug(' matched: %s', fmsg.msgid) fltrs, fmis = fmsg.get_trailers(sloppy=sloppytrailers) for fltr in fltrs: - if fltr not in lmsg.trailers and fltr not in lmsg.followup_trailers: - logger.debug(' adding "%s" from trailer_map to: %s', - fltr.as_string(), lmsg.full_subject) - lmsg.followup_trailers.append(fltr) + if fltr in lmsg.trailers: + logger.debug(' trailer already exists') + continue + if fltr in lmsg.followup_trailers: + logger.debug(' identical trailer received for this series') + continue + logger.debug(' carrying over the trailer to this series (may be duplicate)') + logger.debug(' %s', lmsg.full_subject) + logger.debug(' + %s', fltr.as_string()) + logger.debug(' via: %s', fltr.lmsg.msgid) + lmsg.followup_trailers.append(fltr) for fltr in fmis: lser.trailer_mismatches.add((fltr.name, fltr.value, fmsg.fromname, fmsg.fromemail)) @@ -1142,7 +1152,7 @@ # header-based info in_reply_to: Optional[str] - references: Set[str] + references: List[str] fromname: str fromemail: str date: datetime.datetime @@ -1213,14 +1223,15 @@ self.expected = self.counter self.in_reply_to = LoreMessage.get_clean_msgid(self.msg, header='In-Reply-To') - self.references = set() if self.in_reply_to: - self.references.add(self.in_reply_to) + self.references = [self.in_reply_to] + else: + self.references = list() if self.msg.get('References'): for pair in email.utils.getaddresses([str(x) for x in self.msg.get_all('references', [])]): - if pair: - self.references.add(pair[1]) + if pair and pair[1].strip() and pair[1] not in self.references: + self.references.append(pair[1]) try: fromdata = email.utils.getaddresses([LoreMessage.clean_header(str(x)) @@ -1275,13 +1286,14 @@ mismatches = set() for ltr in self.trailers: + logger.debug('Examining: %s', ltr.as_string()) ltr.lmsg = self if sloppy or ltr.type != 'person': trailers.append(ltr) continue if ltr.email_eq(self.fromemail): - logger.debug(' trailer email match') + logger.debug(' trailer email match with %s', self.fromemail) trailers.append(ltr) continue @@ -1291,7 +1303,7 @@ hlname = self.fromname.lower() if tlname == hlname: - logger.debug(' trailer exact name match') + logger.debug(' trailer exact name match with %s', self.fromname) nmatch = True # Finally, see if the header From has a comma in it and try to find all # parts in the trailer name @@ -1301,12 +1313,14 @@ if hlname.find(nchunk.strip()) < 0: nmatch = False break + if nmatch: + logger.debug(' trailer fuzzy name match with %s', self.fromname) + if nmatch: - logger.debug(' trailer fuzzy name match') trailers.append(ltr) continue - logger.debug('trailer did not match: %s: %s', ltr.name, ltr.value) + logger.debug(' trailer did not match: %s: %s', ltr.name, ltr.value) mismatches.add(ltr) return trailers, mismatches @@ -1520,18 +1534,29 @@ topdir = git_get_toplevel() mycmd = os.path.basename(cmdargs[0]) ecode, out, err = _run_command(cmdargs, stdin=bdata, rundir=topdir) + report = list() + out = out.strip() out_lines = out.decode(errors='replace').split('\n') if out else list() - report = list() - if out_lines: - for line in out_lines: - flag = 'fail' if 'ERROR:' in line else 'warning' - # Remove '-:' from the start of the line, because it's never useful - if line.startswith('-:'): - line = line[2:] - report.append((flag, f'{mycmd}: {line}')) - else: - report.append(('success', f'{mycmd}: passed all checks')) + for line in out_lines: + flag = 'fail' if 'ERROR:' in line else 'warning' + # Remove '-:' from the start of the line, because it's never useful + if line.startswith('-:'): + line = line[2:] + report.append((flag, f'{mycmd}: {line}')) + + err = err.strip() + err_lines = err.decode(errors='replace').split('\n') if err else list() + for line in err_lines: + if line.startswith('-:'): + line = line[2:] + report.append(('fail', f'{mycmd}: {line}')) + + if (not out_lines) and (not err_lines): + if ecode: + report.append(('fail', f'{mycmd}: Exited with error code {ecode}')) + else: + report.append(('success', f'{mycmd}: passed all checks')) save_cache(report, cacheid, suffix='checks', is_json=True) return report @@ -1976,7 +2001,7 @@ @staticmethod def find_trailers(body: str, followup: bool = False) -> Tuple[List[LoreTrailer], List[str]]: - ignores = {'phone', 'email'} + ignores = {'phone', 'email', 'prerequisite-message-id'} headers = {'subject', 'date', 'from'} links = {'link', 'buglink', 'closes'} nonperson = links | {'fixes', 'subject', 'date', 'obsoleted-by', 'change-id', 'base-commit'} @@ -2843,6 +2868,14 @@ return gitconfig +def _val_to_path(topdir, val): + if val.startswith('./'): + # replace it with full topdir path + return os.path.abspath(os.path.join(topdir, val)) + else: + return val + + def _setup_main_config(cmdargs: Optional[argparse.Namespace] = None) -> None: global MAIN_CONFIG @@ -2851,23 +2884,24 @@ # so load them up and use as defaults topdir = git_get_toplevel() wtglobs = ['prep-*-check-cmd', 'send-*', '*mask', '*template*', 'trailer*', 'pw-*'] + multivals = ['keyringsrc', 'am-perpatch-check-cmd', 'prep-perpatch-check-cmd'] if topdir: wtcfg = os.path.join(topdir, '.b4-config') if os.access(wtcfg, os.R_OK): logger.debug('Loading worktree configs from %s', wtcfg) - wtconfig = get_config_from_git(r'b4\..*', source=wtcfg) + wtconfig = get_config_from_git(r'b4\..*', multivals=multivals, source=wtcfg) logger.debug('wtcfg=%s', wtconfig) for key, val in wtconfig.items(): - if val.startswith('./'): - # replace it with full topdir path - val = os.path.abspath(os.path.join(topdir, val)) + if key in multivals: + val = [_val_to_path(topdir, x) for x in val] + else: + val = _val_to_path(topdir, val) for wtglob in wtglobs: if fnmatch.fnmatch(key, wtglob): logger.debug('wtcfg: %s=%s', key, val) defcfg[key] = val break - config = get_config_from_git(r'b4\..*', defaults=defcfg, - multivals=['keyringsrc', 'am-perpatch-check-cmd', 'prep-perpatch-check-cmd']) + config = get_config_from_git(r'b4\..*', defaults=defcfg, multivals=multivals) config['listid-preference'] = config['listid-preference'].split(',') config['listid-preference'].remove('*') config['listid-preference'].append('*') @@ -3277,6 +3311,16 @@ return msgs +def get_series_by_msgid(msgid: str, nocache: bool = False) -> Optional['LoreMailbox']: + lmbx = LoreMailbox() + t_msgs = get_pi_thread_by_msgid(msgid, nocache=nocache) + if t_msgs: + for t_msg in t_msgs: + lmbx.add_message(t_msg) + + return lmbx + + def get_series_by_change_id(change_id: str, nocache: bool = False) -> Optional['LoreMailbox']: q = f'nq:"change-id:{change_id}"' q_msgs = get_pi_search_results(q, nocache=nocache, full_threads=False) @@ -3410,8 +3454,19 @@ if commit in ignore_commits: logger.debug('Ignoring commit %s', commit) continue - ecode, out = git_run_command(gitdir, ['show', '--format=email', '--patch-with-stat', '--encoding=utf-8', - commit], decode=False) + ecode, out = git_run_command( + gitdir, + [ + 'show', + '--format=email', + '--full-index', + '--binary', + '--patch-with-stat', + '--encoding=utf-8', + commit, + ], + decode=False, + ) if ecode > 0: raise RuntimeError(f'Could not get a patch out of {commit}') msg = email.message_from_bytes(out, policy=emlpolicy) @@ -4294,36 +4349,29 @@ # Find the patch-id to which this belongs pfound = False for refmid in qlmsg.references: + logger.debug(' looking at parent ref: %s', refmid) if refmid in qmid_map: + logger.debug(' found in qmid_map: %s', refmid) # Is it a patch? _qmsg = qmid_map[refmid] - if _qmsg.has_diff: + logger.debug(' subj: %s', _qmsg.full_subject) + # Is it the cover letter? + if (_qmsg.counter == 0 and (not _qmsg.counters_inferred or _qmsg.has_diffstat) + and _qmsg.msgid in ref_map): + logger.debug(' stopping: found the cover letter for %s', qlmsg.full_subject) + break + elif _qmsg.has_diff: pqpid = _qmsg.git_patch_id if pqpid: + logger.debug(' pqpid: %s', pqpid) # Found our parent patch if pqpid not in patchid_map: patchid_map[pqpid] = list() if qlmsg not in patchid_map[pqpid]: patchid_map[pqpid].append(qlmsg) + logger.debug(' matched patch-id %s to %s', pqpid, qlmsg.full_subject) pfound = True - logger.debug(' found matching patch-id for %s', qlmsg.subject) break - # Is it a cover letter? - elif (_qmsg.counter == 0 and (not _qmsg.counters_inferred or _qmsg.has_diffstat) - and _qmsg.msgid in ref_map): - logger.debug(' found a cover letter for %s', qlmsg.subject) - # Now we find all descendant patches of the same series - for _child_msgid in ref_map[_qmsg.msgid]: - if _child_msgid == refmid: - continue - if qmid_map[_child_msgid].has_diff: - cqpid = qmid_map[_child_msgid].git_patch_id - if cqpid: - # Found our parent patch - if cqpid not in patchid_map: - patchid_map[cqpid] = list() - if qlmsg not in patchid_map[cqpid]: - patchid_map[cqpid].append(qlmsg) else: logger.debug(' skipping parent without a diff or diffstat') if not pfound: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/b4-0.14.1/src/b4/ez.py new/b4-0.14.2/src/b4/ez.py --- old/b4-0.14.1/src/b4/ez.py 2024-07-23 16:42:58.000000000 +0200 +++ new/b4-0.14.2/src/b4/ez.py 2024-09-17 15:24:20.000000000 +0200 @@ -1509,11 +1509,16 @@ prerequisites += f'prerequisite-{prereq}\n' if expandprereqs: msgid = chunks[1].strip('<>') - spatches = b4.get_pi_thread_by_msgid(msgid) - if not spatches: + lmbx = b4.get_series_by_msgid(msgid) + if not lmbx: logger.info('Nothing known about message-id: %s', msgid) logger.info('Consider running --check-deps') continue + wantver = max(lmbx.series.keys()) + for lmsg in lmbx.series[wantver].patches: + if not lmsg: + continue + spatches.append(lmsg.get_am_message(add_trailers=False)) if prereq.startswith('change-id:'): prerequisites += f'prerequisite-{prereq}\n' @@ -1595,6 +1600,9 @@ if addtracking: patches[0][1].add_header('X-B4-Tracking', thdata) + # Add X-Change-ID header + patches[0][1].add_header('X-Change-ID', change_id) + samethread = config.get('send-same-thread', '').lower() in {'yes', 'true', 'y'} if samethread and revision > 1: oldrev = revision - 1 @@ -2163,45 +2171,55 @@ tagname, revision = get_sent_tagname(change_id, tagprefix, revision) logger.debug('checking if we already have %s', tagname) + topdir = b4.git_get_toplevel() if not b4.git_revparse_tag(None, tagname): + strategy = get_cover_strategy() + tagcommit = 'HEAD' try: - strategy = get_cover_strategy() if strategy == 'commit': - # Find out the head commit, which is the end of our range - gitargs = ['rev-parse', 'HEAD'] - ecode, out = b4.git_run_command(None, gitargs) - end_commit = out.strip() - # Detach the head at our parent commit and apply the cover-less series - cover_commit = find_cover_commit() - gitargs = ['checkout', f'{cover_commit}~1'] - ecode, out = b4.git_run_command(None, gitargs) - if ecode > 0: - raise RuntimeError('Could not switch to a detached head') - # cherry-pick from cover letter to the last commit - gitargs = ['cherry-pick', f'{cover_commit}..{end_commit}'] - ecode, out = b4.git_run_command(None, gitargs) - if ecode > 0: - raise RuntimeError('Could not cherry-pick the cover-less range') - # Find out the head commit - gitargs = ['rev-parse', 'HEAD'] - ecode, out = b4.git_run_command(None, gitargs) - if ecode > 0: - raise RuntimeError('Could not find the HEAD commit of the detached head') - tagcommit = out.strip() - # Switch back to our branch - gitargs = ['checkout', mybranch] - ecode, out = b4.git_run_command(None, gitargs) - if ecode > 0: - raise RuntimeError('Could not switch back to %s' % mybranch) + base_commit, start_commit, end_commit = get_series_range(usebranch=mybranch) + with b4.git_temp_worktree(topdir, base_commit) as gwt: + logger.debug('Preparing a sparse worktree') + ecode, out = b4.git_run_command(gwt, ['sparse-checkout', 'init'], logstderr=True) + if ecode > 0: + logger.critical('Error running sparse-checkout init') + logger.critical(out) + raise RuntimeError + ecode, out = b4.git_run_command(gwt, ['checkout'], logstderr=True) + if ecode > 0: + logger.critical('Error running checkout into sparse workdir') + logger.critical(out) + raise RuntimeError + gitargs = ['cherry-pick', f'{start_commit}..{end_commit}'] + ecode, out = b4.git_run_command(gwt, gitargs, logstderr=True) + if ecode > 0: + # In theory, this shouldn't happen + logger.critical('Unable to cleanly apply series, see failure log below') + logger.critical('---') + logger.critical(out.strip()) + logger.critical('---') + logger.critical('Not fetching into FETCH_HEAD') + raise RuntimeError + gitargs = ['rev-parse', 'HEAD'] + ecode, out = b4.git_run_command(gwt, gitargs, logstderr=True) + if ecode > 0: + logger.critical('Unable to resolve FETCH_HEAD') + logger.critical(out.strip()) + raise RuntimeError + tagcommit = out.strip() + gitargs = ['fetch', gwt] + ecode, out = b4.git_run_command(topdir, gitargs, logstderr=True) + if ecode > 0: + logger.critical('Unable to fetch from the worktree') + logger.critical(out.strip()) + raise RuntimeError elif strategy == 'tip-commit': cover_commit = find_cover_commit() tagcommit = f'{cover_commit}~1' - else: - tagcommit = 'HEAD' logger.info('Tagging %s', tagname) gitargs = ['tag', '-a', '-F', '-', tagname, tagcommit] - ecode, out = b4.git_run_command(None, gitargs, stdin=tag_msg.encode()) + ecode, out = b4.git_run_command(topdir, gitargs, stdin=tag_msg.encode()) if ecode > 0: # Not a fatal error, complain about it and move on logger.info('Could not tag %s as %s:', tagcommit, tagname) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/b4-0.14.1/src/b4/man/b4.5 new/b4-0.14.2/src/b4/man/b4.5 --- old/b4-0.14.1/src/b4/man/b4.5 2024-07-23 16:42:58.000000000 +0200 +++ new/b4-0.14.2/src/b4/man/b4.5 2024-09-17 15:24:20.000000000 +0200 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "B4" 5 "2024-06-14" "0.14.1" +.TH "B4" 5 "2024-06-14" "0.14.2" .SH NAME B4 \- Work with code submissions in a public-inbox archive .SH SYNOPSIS diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/b4-0.14.1/src/b4/man/b4.5.rst new/b4-0.14.2/src/b4/man/b4.5.rst --- old/b4-0.14.1/src/b4/man/b4.5.rst 2024-07-23 16:42:58.000000000 +0200 +++ new/b4-0.14.2/src/b4/man/b4.5.rst 2024-09-17 15:24:20.000000000 +0200 @@ -8,7 +8,7 @@ :Date: 2024-06-14 :Copyright: The Linux Foundation and contributors :License: GPLv2+ -:Version: 0.14.1 +:Version: 0.14.2 :Manual section: 5 SYNOPSIS