While the Link: tag is disruptive to some top-level maintainer workflows
[1], it is also useful to a significant number of developers and subsystem
maintainers.

It is also the case that dynamic patch-id lookup [2] is an incomplete
replacement for having the submission Link: trailer readily available.
Specifically, navigating to a patch on gitweb or displaying the patch in
the local developer tree it is convenient to have the metadata inline.

A method to have that metadata available without polluting upstream is to
keep git notes locally.

Add a new option to shazam that annotates newly applied commits with the
Link: trailer of the submission. Honor the b4.linkmask option to use the
preferred namespace (patch.msgid.link) for these links.

Note: Claude Sonnet 4 was used to help early drafts of this patch, but all
submitted lines are authored by me or copied from other parts of b4.

Link: 
http://lore.kernel.org/CAHk-=whP2zoFm+-EmgQ69-00cxM5jgoEGWyAYVQ8bQYFbb2j=q...@mail.gmail.com
 [1]
Signed-off-by: Dan Williams <[email protected]>
---
 src/b4/command.py |  2 ++
 src/b4/mbox.py    | 58 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 60 insertions(+)

diff --git a/src/b4/command.py b/src/b4/command.py
index 455124d9726a..678b0b53d6b9 100644
--- a/src/b4/command.py
+++ b/src/b4/command.py
@@ -211,6 +211,8 @@ def setup_parser() -> argparse.ArgumentParser:
     sp_sh = subparsers.add_parser('shazam', help='Like b4 am, but applies the 
series to your tree')
     cmd_retrieval_common_opts(sp_sh)
     cmd_am_common_opts(sp_sh)
+    sp_sh.add_argument('-L', '--add-link-note', dest='addlinknote', 
action='store_true', default=False,
+                       help='Add a git note with Link: trailer for every 
created commit')
     sh_g = sp_sh.add_mutually_exclusive_group()
     sh_g.add_argument('-H', '--make-fetch-head', dest='makefetchhead', 
action='store_true', default=False,
                       help='Attempt to treat series as a pull request and 
fetch it into FETCH_HEAD')
diff --git a/src/b4/mbox.py b/src/b4/mbox.py
index 8810ddd71b21..9479b8995019 100644
--- a/src/b4/mbox.py
+++ b/src/b4/mbox.py
@@ -354,6 +354,8 @@ def make_am(msgs: List[EmailMessage], cmdargs: 
argparse.Namespace, msgid: str) -
             logger.info(out.strip())
             if ecode == 0:
                 thanks_record_am(lser, cherrypick=cherrypick)
+                if cmdargs.addlinknote:
+                    shazam_notes(topdir, lser, 'HEAD')
             sys.exit(ecode)
 
         base_commit = get_base_commit(topdir, first_body, lser, cmdargs)
@@ -448,6 +450,9 @@ def make_am(msgs: List[EmailMessage], cmdargs: 
argparse.Namespace, msgid: str) -
             # We exec git-merge and let it take over
             os.execvp(mergecmd[0], mergecmd)
 
+        if cmdargs.addlinknote:
+            shazam_notes(topdir, lser, 'FETCH_HEAD')
+
         logger.info('You can now merge or checkout FETCH_HEAD')
         logger.info('  e.g.: %s', ' '.join(mergecmd))
         sys.exit(0)
@@ -547,6 +552,59 @@ def thanks_record_am(lser: b4.LoreSeries, cherrypick: 
Optional[List[int]]) -> No
         b4.patchwork_set_state(msgids, pwstate)
 
 
+def commits_by_patchid(gitdir: Optional[str], branch: str, num_patches: int) 
-> b4.Dict[str, str]:
+    """Create a patch-id to commit lookup for the top N commits"""
+
+    commits = dict()
+
+    args = ['log', '--no-abbrev', '--no-decorate', '--oneline', 
f'-{num_patches}', branch]
+    lines = b4.git_get_command_lines(gitdir, args)
+    if not lines:
+        return commits
+
+    for line in lines:
+        commit_id, subject = line.split(maxsplit=1)
+
+        ecode, diff_out = b4.git_get_rev_diff(gitdir, commit_id)
+        if ecode != 0 or not diff_out.strip():
+            continue
+
+        patch_id = b4.LoreMessage.get_patch_id(diff_out)
+        if patch_id:
+            commits[patch_id] = commit_id
+
+    return commits
+
+
+def shazam_notes(gitdir: Optional[str], lser: 'b4.LoreSeries', branch: str) -> 
None:
+    """Match commits to LoreMessages using git patch-id and emit debug info 
for later git notes processing."""
+    if not lser or not lser.patches:
+        return
+
+    lmsgs = [lmsg for lmsg in lser.patches if lmsg is not None and 
lmsg.has_diff]
+    if not lmsgs:
+        return
+
+    # Cache recently applied commits by patch-id (account for a merge commit)
+    commits = commits_by_patchid(gitdir, branch, len(lmsgs) + 1)
+    if not commits:
+        return
+
+    # Add link trailer notes
+    for lmsg in lmsgs:
+        patch_id = lmsg.git_patch_id
+        if not patch_id:
+            continue
+
+        if patch_id not in commits:
+            continue
+
+        commit_id = commits[patch_id]
+        linktrailer = lmsg.linktrailer
+        note_message = f"{linktrailer.name}: {linktrailer.value}"
+
+        b4.git_run_command(gitdir, ['notes', 'append', '-m', note_message, 
commit_id])
+
 def save_as_quilt(am_msgs: List[EmailMessage], q_dirname: str) -> None:
     if os.path.exists(q_dirname):
         logger.critical('ERROR: Directory %s exists, not saving quilt 
patches', q_dirname)
-- 
2.51.0


Reply via email to