[RFC,15/16] patman: Support updating a branch with review tags

Message ID 20200706034203.2171077-16-sjg@chromium.org
State New
Headers show
Series
  • RFC: patman: Collect review tags and comments from Patchwork
Related show

Commit Message

Simon Glass July 6, 2020, 3:42 a.m.
It is tedious to add review tags into the local branch and errors can
sometimes be made. Add an option to create a new branch with the review
tags obtained from patchwork.

Signed-off-by: Simon Glass <sjg at chromium.org>
---

 tools/patman/README     | 17 +++++++++++--
 tools/patman/control.py |  9 ++++---
 tools/patman/main.py    |  7 +++++-
 tools/patman/status.py  | 54 ++++++++++++++++++++++++++++++++++++++---
 4 files changed, 78 insertions(+), 9 deletions(-)

Patch

diff --git a/tools/patman/README b/tools/patman/README
index a85974740f..595a0d616b 100644
--- a/tools/patman/README
+++ b/tools/patman/README
@@ -11,11 +11,13 @@  This tool is a Python script which:
 - Runs the patches through checkpatch.pl and its own checks
 - Optionally emails them out to selected people
 
-It also shows review tags from Patchwork so you can update your local patches.
+It also has some Patchwork features:
+- shows review tags from Patchwork so you can update your local patches
+- pulls these down into a new branch on request
 
 It is intended to automate patch creation and make it a less
 error-prone process. It is useful for U-Boot and Linux work so far,
-since it uses the checkpatch.pl script.
+since they use the checkpatch.pl script.
 
 It is configured almost entirely by tags it finds in your commits.
 This means that you can work on a number of different branches at
@@ -378,6 +380,17 @@  attracted another review each. If the series needs changes, you can update
 these commits with the new review tag before sending the next version of the
 series.
 
+To automatically pull into these tags into a new branch, use the -d option:
+
+    patman status -d mtrr4
+
+This will create a new 'mtrr4' branch which is the same as your current branch
+but has the new review tags in it. You can check that this worked with:
+
+    patman -b mtrr4 status
+
+which should show that there are no new responses compared to this new branch.
+
 
 Example Work Flow
 =================
diff --git a/tools/patman/control.py b/tools/patman/control.py
index 3bb3c236e4..b6ba0a56c0 100644
--- a/tools/patman/control.py
+++ b/tools/patman/control.py
@@ -174,11 +174,11 @@  def send(args):
             args.limit, args.dry_run, args.in_reply_to, args.thread,
             args.smtp_server)
 
-def patchwork_status(branch, count, start, end):
+def patchwork_status(branch, count, start, end, dest_branch, force):
     """Check the status of patches in patchwork
 
     This finds the series in patchwork using the Series-link tag, checks for new
-    comments / review tags and displays them
+    review tags, displays then and creates a new branch with the review tags.
 
     Args:
         branch (str): Branch to create patches from (None = current)
@@ -187,6 +187,9 @@  def patchwork_status(branch, count, start, end):
         start (int): Start partch to use (0=first / top of branch)
         end (int): End patch to use (0=last one in series, 1=one before that,
             etc.)
+        dest_branch (str): Name of new branch to create with the updated tags
+            (None to not create a branch)
+        force (bool): With dest_branch, force overwriting an existing branch
 
     Raises:
         ValueError: if the branch has no Series-link value
@@ -203,4 +206,4 @@  def patchwork_status(branch, count, start, end):
     # Import this here to avoid failing on other commands if the dependencies
     # are not present
     from patman import status
-    status.check_patchwork_status(series, link, branch)
+    status.check_patchwork_status(series, link, branch, dest_branch, force)
diff --git a/tools/patman/main.py b/tools/patman/main.py
index 227eb3b228..c8fc035c10 100755
--- a/tools/patman/main.py
+++ b/tools/patman/main.py
@@ -90,6 +90,10 @@  AddCommonArgs(test_parser)
 
 status = subparsers.add_parser('status',
                                help='Check status of patches in patchwork')
+status.add_argument('-d', '--dest-branch', type=str,
+                    help='Name of branch to create with collected responses')
+status.add_argument('-f', '--force', action='store_true',
+                    help='Force overwriting an existing branch')
 AddCommonArgs(status)
 
 # Parse options twice: first to get the project and second to handle
@@ -156,7 +160,8 @@  elif args.cmd == 'send':
 elif args.cmd == 'status':
     ret_code = 0
     try:
-        control.patchwork_status(args.branch, args.count, args.start, args.end)
+        control.patchwork_status(args.branch, args.count, args.start, args.end,
+                                 args.dest_branch, args.force)
     except Exception as e:
         print('patman: %s: %s' % (type(e).__name__, e))
         if args.debug:
diff --git a/tools/patman/status.py b/tools/patman/status.py
index c2e98a5d11..71124d6e5e 100644
--- a/tools/patman/status.py
+++ b/tools/patman/status.py
@@ -304,7 +304,48 @@  def ShowResponses(rtags, indent, is_new):
             count += 1
     return count
 
-def check_patchwork_status(series, url, branch):
+def CreateBranch(series, new_rtag_list, branch, dest_branch, overwrite):
+    if branch == dest_branch:
+        raise ValueError(
+            'Destination branch must not be the same as the original branch')
+    repo = pygit2.Repository('.')
+    count = len(series.commits)
+    old_br = repo.branches[branch]
+    new_br = repo.branches.get(dest_branch)
+    if new_br:
+        if not overwrite:
+            raise ValueError("Branch '%s' already exists (-f to overwrite)" %
+                             dest_branch)
+        new_br.delete()
+    target = repo.revparse_single('%s~%d' % (branch, count))
+    repo.branches.local.create(dest_branch, target)
+
+    num_added = 0
+    for seq in range(count):
+        basket = repo.branches.get(dest_branch)
+        cherry = repo.revparse_single('%s~%d' % (branch, count - seq - 1))
+
+        base = repo.merge_base(cherry.oid, basket.target)
+        base_tree = cherry.parents[0].tree
+
+        index = repo.merge_trees(base_tree, basket, cherry)
+        tree_id = index.write_tree(repo)
+
+        author    = cherry.author
+        committer = cherry.committer
+        lines = []
+        for tag, people in new_rtag_list[seq].items():
+            for who in people:
+                lines.append('%s: %s' % (tag, who))
+                num_added += 1
+        message = cherry.message + '\n'.join(lines)
+
+        basket = repo.create_commit(
+            basket.name, cherry.author, cherry.committer, message, tree_id,
+            [basket.target])
+    return num_added
+
+def check_patchwork_status(series, url, branch, dest_branch, force):
     patches = CollectPatches(series, url)
     col = terminal.Color()
     count = len(patches)
@@ -330,5 +371,12 @@  def check_patchwork_status(series, url, branch):
         ShowResponses(base_rtags, indent, False)
         num_to_add += ShowResponses(new_rtags, indent, True)
 
-    print("%d new response%s available in patchwork" %
-          (num_to_add, 's' if num_to_add != 1 else ''))
+    print("%d new response%s available in patchwork%s" %
+          (num_to_add, 's' if num_to_add != 1 else '',
+           '' if dest_branch else ' (use -d to write them to a new branch)'))
+
+    if dest_branch:
+        num_added = CreateBranch(series, new_rtag_list, branch,
+                                 dest_branch, force)
+        print("%d response%s added from patchwork into new branch '%s'" %
+              (num_added, 's' if num_added != 1 else '', dest_branch))