diff --git a/git-publish b/git-publish index 4c49c90..23f4ff8 100755 --- a/git-publish +++ b/git-publish @@ -16,7 +16,7 @@ import email.policy import os import glob import sys -import optparse +import argparse import re import tempfile import shutil @@ -467,7 +467,7 @@ def tag(name, template, annotate=False, force=False, sign=False, keyid=None): with os.fdopen(fd, 'wb') as f: f.write(new_content.encode(GIT_ENCODING)) - git_tag(name, annotate=tmpfile, force=force, sign=sign, keyid=keyid) + git_tag(name, annotate=tmpfile, force=force, sign=sign, keyid=keyid) finally: if tmpfile: os.unlink(tmpfile) @@ -557,99 +557,106 @@ def inspect_menu(tmpdir, to_list, cc_list, patches, suppress_cc, in_reply_to, return patches def parse_args(): + prog = os.path.basename(sys.argv[0]) - parser = optparse.OptionParser(version='%%prog %s' % VERSION, - usage='%prog [options] -- [common format-patch options]', + parser = argparse.ArgumentParser(prog=f"{prog} {VERSION}", + usage=f"{prog} [options] -- [common format-patch options]", description='Prepare and store patch revisions as git tags.', epilog='Please report bugs to Stefan Hajnoczi .') - parser.add_option('--annotate', dest='annotate', action='store_true', + parser.add_argument('--annotate', dest='annotate', action='store_true', default=False, help='review and edit each patch email') - parser.add_option('-b', '--base', dest='base', default=None, + parser.add_argument('-b', '--base', dest='base', default=None, help='branch which this is based off [defaults to master]') - parser.add_option('--blurb-template', dest='blurb_template', default=None, + parser.add_argument('--blurb-template', dest='blurb_template', default=None, help='Template for blurb [defaults to *** BLURB HERE ***]') - parser.add_option('--cc', dest='cc', action='append', default=[], + parser.add_argument('--cc', dest='cc', action='append', default=[], help='specify a Cc: email recipient') - parser.add_option('--cc-cmd', + parser.add_argument('--cc-cmd', help='specify a command whose output to add to the cc list') - parser.add_option('--no-check-url', dest='check_url', action='store_false', + parser.add_argument('--no-check-url', dest='check_url', action='store_false', help='skip publicly accessible pull request URL check') - parser.add_option('--check-url', dest='check_url', action='store_true', + parser.add_argument('--check-url', dest='check_url', action='store_true', help='check pull request URLs are publicly accessible') - parser.add_option('--skip', type='int', dest='skip', metavar='N', default=0, + parser.add_argument('--skip', type=int, dest='skip', metavar='N', default=0, help='unselect the first N patch emails (including the cover letter if any)') - parser.add_option('--edit', dest='edit', action='store_true', + parser.add_argument('--edit', dest='edit', action='store_true', default=False, help='edit message but do not tag a new version') - parser.add_option('--no-inspect-emails', dest='inspect_emails', + parser.add_argument('--no-inspect-emails', dest='inspect_emails', action='store_false', help='no confirmation before sending emails') - parser.add_option('--inspect-emails', dest='inspect_emails', + parser.add_argument('--inspect-emails', dest='inspect_emails', action='store_true', default=True, help='show confirmation before sending emails') - parser.add_option('-n', '--number', type='int', dest='number', default=-1, + parser.add_argument('-n', '--number', type=int, dest='number', default=-1, help='version number [auto-generated by default]') - parser.add_option('--no-message', '--no-cover-letter', dest='message', + parser.add_argument('--no-message', '--no-cover-letter', dest='message', action='store_false', help='do not add a message') - parser.add_option('-m', '--message', '--cover-letter', dest='message', + parser.add_argument('-m', '--message', '--cover-letter', dest='message', action='store_true', help='add a message') - parser.add_option('--no-cover-info', dest='cover_info', + parser.add_argument('--no-cover-info', dest='cover_info', action='store_false', default=True, help='do not append comments information when editing the cover letter') - parser.add_option('--no-binary', dest='binary', + parser.add_argument('--no-binary', dest='binary', action='store_false', default=True, help='Do not output contents of changes in binary files, instead display a notice that those files changed') - parser.add_option('--profile', '-p', dest='profile_name', default='default', + parser.add_argument('--profile', '-p', dest='profile_name', default='default', help='select default settings profile') - parser.add_option('--pull-request', dest='pull_request', action='store_true', + parser.add_argument('--pull-request', dest='pull_request', action='store_true', default=False, help='tag and send as a pull request') - parser.add_option('--sign-pull', dest='sign_pull', action='store_true', + parser.add_argument('--sign-pull', dest='sign_pull', action='store_true', help='sign tag when sending pull request') - parser.add_option('-k', '--keyid', dest='keyid', + parser.add_argument('-k', '--keyid', dest='keyid', help='use the given GPG key when signing pull request tag') - parser.add_option('--no-sign-pull', dest='sign_pull', action='store_false', + parser.add_argument('--no-sign-pull', dest='sign_pull', action='store_false', help='do not sign tag when sending pull request') - parser.add_option('--subject-prefix', dest='prefix', default=None, + parser.add_argument('--subject-prefix', dest='prefix', default=None, help='set the email Subject: header prefix') - parser.add_option('--clear-subject-prefix', dest='clear_prefix', + parser.add_argument('--clear-subject-prefix', dest='clear_prefix', action='store_true', default=False, help='clear the per-branch subject prefix') - parser.add_option('--setup', dest='setup', action='store_true', default=False, + parser.add_argument('--setup', dest='setup', action='store_true', default=False, help='add git alias in ~/.gitconfig') - parser.add_option('-t', '--topic', dest='topic', + parser.add_argument('-t', '--topic', dest='topic', help='topic name [defaults to current branch name]') - parser.add_option('--to', dest='to', action='append', default=[], + parser.add_argument('--to', dest='to', action='append', default=[], help='specify a primary email recipient') - parser.add_option('-s', '--signoff', dest='signoff', action='store_true', + parser.add_argument('-s', '--signoff', dest='signoff', action='store_true', default=False, help='add Signed-off-by: to commits when emailing') - parser.add_option('--notes', dest='notes', action='store_true', + parser.add_argument('--notes', dest='notes', action='store_true', default=False, help='Append the notes (see git-notes(1)) for the commit after the three-dash line.') - parser.add_option('--suppress-cc', dest='suppress_cc', + parser.add_argument('--suppress-cc', dest='suppress_cc', help='override auto-cc when sending email (man git-send-email for details)') - parser.add_option('-v', '--verbose', dest='verbose', + parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', default=False, help='show executed git commands (useful for troubleshooting)') - parser.add_option('--forget-cc', dest='forget_cc', action='store_true', + parser.add_argument('--forget-cc', dest='forget_cc', action='store_true', default=False, help='Forget all previous CC emails') - parser.add_option('--override-to', dest='override_to', action='store_true', + parser.add_argument('--override-to', dest='override_to', action='store_true', default=False, help='Ignore any profile or saved TO emails') - parser.add_option('--override-cc', dest='override_cc', action='store_true', + parser.add_argument('--override-cc', dest='override_cc', action='store_true', default=False, help='Ignore any profile or saved CC emails') - parser.add_option('--in-reply-to', "-R", + parser.add_argument('--in-reply-to', "-R", help='specify the In-Reply-To: of the cover letter (or the single patch)') - parser.add_option('--no-thread', dest='thread', action='store_false', + parser.add_argument('--no-thread', dest='thread', action='store_false', help='do not add In-Reply-To: headers to any email') - parser.add_option('--thread', dest='thread', action='store_true', + parser.add_argument('--thread', dest='thread', action='store_true', help='add In-Reply-To: headers to sent emails') - parser.add_option('--add-header', '-H', action='append', dest='headers', + parser.add_argument('--add-header', '-H', action='append', dest='headers', help='specify custom headers to git-send-email') - parser.add_option('--separate-send', '-S', dest='separate_send', action='store_true', + parser.add_argument('--separate-send', '-S', dest='separate_send', action='store_true', default=False, help='Send patches using separate git-send-email cmd') - parser.add_option('--send-email-args', action='append', default=[], + parser.add_argument('--send-email-args', action='append', default=[], help="Arguments forwarded to git-send-email") + parser.add_argument('--skip-final-edit', dest='skip_final_edit', + action='store_true', default=False, + help='Skip final edit step (useful for scripting)') + parser.add_argument('--dry-run', dest='dry_run', + action='store_true', default=False, + help='Do everything except tagging and sending the emails') - return parser.parse_args() + return parser.parse_known_args() def main(): global VERBOSE @@ -818,7 +825,7 @@ branch.%s.pushRemote is set appropriately? (Override with --no-check-url)''' % cl_info += git_cover_letter_info(base, topic, to, cc, options.in_reply_to, number) # Tag the tree - if options.pull_request: + if options.pull_request and not options.dry_run: tag_message = get_latest_tag_message(topic, ['Pull request']) tag_message += cl_info tag(tag_name_pull_request(topic), tag_message, annotate=message, force=True, sign=sign_pull, keyid=keyid) @@ -829,7 +836,7 @@ branch.%s.pushRemote is set appropriately? (Override with --no-check-url)''' % '', blurb_template]) tag_message += cl_info - anno = options.edit or message + anno = (options.edit or message) and not options.skip_final_edit tag(tag_name_staging(topic), tag_message, annotate=anno, force=True) if options.clear_prefix: @@ -949,10 +956,18 @@ branch.%s.pushRemote is set appropriately? (Override with --no-check-url)''' % if (options.separate_send): for patch in selected_patches: git_send_email(to, cc, [patch], suppress_cc, options.in_reply_to, options.thread, - send_email_args=options.send_email_args) + send_email_args=options.send_email_args, + dry_run=options.dry_run) else: git_send_email(to, cc, selected_patches, suppress_cc, options.in_reply_to, options.thread, - send_email_args=options.send_email_args) + send_email_args=options.send_email_args, + dry_run=options.dry_run) + + if options.dry_run: + npatches = len(selected_patches) + npeople = len(to) + len(cc) + print(f"Would have sent of {npatches} patches to {npeople} people") + except (GitSendEmailError, GitHookError, InspectEmailsError): return 1 except GitError as e: @@ -964,7 +979,7 @@ branch.%s.pushRemote is set appropriately? (Override with --no-check-url)''' % git_save_email_lists(topic, to, cc, options.override_cc) - if not options.pull_request: + if not options.pull_request and not options.dry_run: # Publishing is done, stablize the tag now _git_check('tag', '-f', tag_name(topic, number), tag_name_staging(topic)) git_delete_tag(tag_name_staging(topic))