control.py 8.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# SPDX-License-Identifier: GPL-2.0+
#
# Copyright 2020 Google LLC
#
"""Handles the main control logic of patman

This module provides various functions called by the main program to implement
the features of patman.
"""

import os
import sys

from patman import checkpatch
from patman import gitutil
from patman import patchstream
from patman import terminal

def setup():
    """Do required setup before doing anything"""
    gitutil.Setup()

23
def prepare_patches(col, branch, count, start, end, ignore_binary):
24 25 26 27 28 29 30
    """Figure out what patches to generate, then generate them

    The patch files are written to the current directory, e.g. 0001_xxx.patch
    0002_yyy.patch

    Args:
        col (terminal.Color): Colour output object
31
        branch (str): Branch to create patches from (None = current)
32 33 34
        count (int): Number of patches to produce, or -1 to produce patches for
            the current branch back to the upstream commit
        start (int): Start partch to use (0=first / top of branch)
35 36
        end (int): End patch to use (0=last one in series, 1=one before that,
            etc.)
37 38 39 40 41 42 43 44 45 46 47
        ignore_binary (bool): Don't generate patches for binary files

    Returns:
        Tuple:
            Series object for this series (set of patches)
            Filename of the cover letter as a string (None if none)
            patch_files: List of patch filenames, each a string, e.g.
                ['0001_xxx.patch', '0002_yyy.patch']
    """
    if count == -1:
        # Work out how many patches to send if we can
48
        count = (gitutil.CountCommitsToBranch(branch) - start)
49 50

    if not count:
51 52 53
        str = 'No commits found to process - please use -c flag, or run:\n' \
              '  git branch --set-upstream-to remote/branch'
        sys.exit(col.Color(col.RED, str))
54 55

    # Read the metadata from the commits
56
    to_do = count - end
57
    series = patchstream.get_metadata(branch, start, to_do)
58
    cover_fname, patch_files = gitutil.CreatePatches(
59
        branch, start, to_do, ignore_binary, series)
60 61

    # Fix up the patch files to our liking, and insert the cover letter
62
    patchstream.fix_patches(series, patch_files)
63
    if cover_fname and series.get('cover'):
64
        patchstream.insert_cover_letter(cover_fname, series, to_do)
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
    return series, cover_fname, patch_files

def check_patches(series, patch_files, run_checkpatch, verbose):
    """Run some checks on a set of patches

    This santiy-checks the patman tags like Series-version and runs the patches
    through checkpatch

    Args:
        series (Series): Series object for this series (set of patches)
        patch_files (list): List of patch filenames, each a string, e.g.
            ['0001_xxx.patch', '0002_yyy.patch']
        run_checkpatch (bool): True to run checkpatch.pl
        verbose (bool): True to print out every line of the checkpatch output as
            it is parsed

    Returns:
        bool: True if the patches had no errors, False if they did
    """
    # Do a few checks on the series
    series.DoChecks()

    # Check the patches, and run them through 'git am' just to be sure
    if run_checkpatch:
        ok = checkpatch.CheckPatches(verbose, patch_files)
    else:
        ok = True
    return ok


def email_patches(col, series, cover_fname, patch_files, process_tags, its_a_go,
                  ignore_bad_tags, add_maintainers, limit, dry_run, in_reply_to,
                  thread, smtp_server):
    """Email patches to the recipients

    This emails out the patches and cover letter using 'git send-email'. Each
    patch is copied to recipients identified by the patch tag and output from
    the get_maintainer.pl script. The cover letter is copied to all recipients
    of any patch.

    To make this work a CC file is created holding the recipients for each patch
    and the cover letter. See the main program 'cc_cmd' for this logic.

    Args:
        col (terminal.Color): Colour output object
        series (Series): Series object for this series (set of patches)
        cover_fname (str): Filename of the cover letter as a string (None if
            none)
        patch_files (list): List of patch filenames, each a string, e.g.
            ['0001_xxx.patch', '0002_yyy.patch']
        process_tags (bool): True to process subject tags in each patch, e.g.
            for 'dm: spi: Add SPI support' this would be 'dm' and 'spi'. The
            tags are looked up in the configured sendemail.aliasesfile and also
            in ~/.patman (see README)
        its_a_go (bool): True if we are going to actually send the patches,
            False if the patches have errors and will not be sent unless
            @ignore_errors
        ignore_bad_tags (bool): True to just print a warning for unknown tags,
            False to halt with an error
        add_maintainers (bool): Run the get_maintainer.pl script for each patch
        limit (int): Limit on the number of people that can be cc'd on a single
            patch or the cover letter (None if no limit)
        dry_run (bool): Don't actually email the patches, just print out what
            would be sent
        in_reply_to (str): If not None we'll pass this to git as --in-reply-to.
            Should be a message ID that this is in reply to.
        thread (bool): True to add --thread to git send-email (make all patches
            reply to cover-letter or first patch in series)
        smtp_server (str): SMTP server to use to send patches (None for default)
    """
    cc_file = series.MakeCcFile(process_tags, cover_fname, not ignore_bad_tags,
                                add_maintainers, limit)

    # Email the patches out (giving the user time to check / cancel)
    cmd = ''
    if its_a_go:
        cmd = gitutil.EmailPatches(
            series, cover_fname, patch_files, dry_run, not ignore_bad_tags,
            cc_file, in_reply_to=in_reply_to, thread=thread,
            smtp_server=smtp_server)
    else:
        print(col.Color(col.RED, "Not sending emails due to errors/warnings"))

    # For a dry run, just show our actions as a sanity check
    if dry_run:
        series.ShowActions(patch_files, cmd, process_tags)
        if not its_a_go:
            print(col.Color(col.RED, "Email would not be sent"))

    os.remove(cc_file)

S
Simon Glass 已提交
156
def send(args):
157 158 159
    """Create, check and send patches by email

    Args:
S
Simon Glass 已提交
160
        args (argparse.Namespace): Arguments to patman
161 162 163 164
    """
    setup()
    col = terminal.Color()
    series, cover_fname, patch_files = prepare_patches(
S
Simon Glass 已提交
165 166 167 168
        col, args.branch, args.count, args.start, args.end,
        args.ignore_binary)
    ok = check_patches(series, patch_files, args.check_patch,
                       args.verbose)
169

170 171
    ok = ok and gitutil.CheckSuppressCCConfig()

S
Simon Glass 已提交
172
    its_a_go = ok or args.ignore_errors
S
Simon Glass 已提交
173 174 175 176 177
    email_patches(
        col, series, cover_fname, patch_files, args.process_tags,
        its_a_go, args.ignore_bad_tags, args.add_maintainers,
        args.limit, args.dry_run, args.in_reply_to, args.thread,
        args.smtp_server)
178

179
def patchwork_status(branch, count, start, end, dest_branch, force):
180 181 182
    """Check the status of patches in patchwork

    This finds the series in patchwork using the Series-link tag, checks for new
183
    review tags, displays then and creates a new branch with the review tags.
184 185 186 187 188 189 190 191

    Args:
        branch (str): Branch to create patches from (None = current)
        count (int): Number of patches to produce, or -1 to produce patches for
            the current branch back to the upstream commit
        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.)
192 193 194
        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
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225

    Raises:
        ValueError: if the branch has no Series-link value
    """
    if count == -1:
        # Work out how many patches to send if we can
        count = (gitutil.CountCommitsToBranch(branch) - start)

    series = patchstream.get_metadata(branch, start, count - end)
    warnings = 0
    for cmt in series.commits:
        if cmt.warn:
            print('%d warnings for %s:' % (len(cmt.warn), cmt.hash))
            for warn in cmt.warn:
                print('\t', warn)
                warnings += 1
            print
    if warnings:
        raise ValueError('Please fix warnings before running status')
    links = series.get('links')
    if not links:
        raise ValueError("Branch has no Series-links value")

    # Find the link without a version number (we don't support versions yet)
    found = [link for link in links.split() if not ':' in link]
    if not found:
        raise ValueError('Series-links has no current version (without :)')

    # Import this here to avoid failing on other commands if the dependencies
    # are not present
    from patman import status
226
    status.check_patchwork_status(series, found[0], branch, dest_branch, force)