From c22421ab5fb92b5d6ea8365ddfcb881ea9d07e60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Thu, 5 Jan 2023 20:13:49 +0000 Subject: [PATCH 1/5] Replace optparse with argparse to parse arguments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit optparse is obsolete and no longer maintained and we should be running on more modern python3's anyway. Signed-off-by: Alex Bennée --- v2 - fix %prog and VERSION handling v3 - fix merge conflicts --- git-publish | 87 +++++++++++++++++++++++++++-------------------------- 1 file changed, 44 insertions(+), 43 deletions(-) diff --git a/git-publish b/git-publish index 4c49c90..573ab95 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 @@ -557,99 +557,100 @@ 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") - return parser.parse_args() + return parser.parse_known_args() def main(): global VERBOSE From 93d6ba27f8dd6a61b6820ba8e87141e927a3be85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Thu, 5 Jan 2023 20:45:46 +0000 Subject: [PATCH 2/5] Only tag if we have written a new annotation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Otherwise we will blat over whatever we currently have stored. This behaviour will be more useful with the next patch. Signed-off-by: Alex Bennée --- git-publish | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-publish b/git-publish index 573ab95..95e438d 100755 --- a/git-publish +++ b/git-publish @@ -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) From a8a0a8136ef8dfc6b5da4e4388b06683295ef492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Thu, 5 Jan 2023 20:46:57 +0000 Subject: [PATCH 3/5] Add option --skip-final-edit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Of we are scripting our workflow we might be perfectly happy with what we last set. Allow the final edit to be skipped and keep the tag with the message as is. Signed-off-by: Alex Bennée --- git-publish | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/git-publish b/git-publish index 95e438d..71c7b11 100755 --- a/git-publish +++ b/git-publish @@ -649,6 +649,9 @@ def parse_args(): default=False, help='Send patches using separate git-send-email cmd') 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)') return parser.parse_known_args() @@ -830,7 +833,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: From 12753e3a90d936104b80621055595ef56024e50d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Thu, 5 Jan 2023 20:48:08 +0000 Subject: [PATCH 4/5] Add --dry-run option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Again this is useful for scripting. We can now test the whole chain with: git publish -v --base origin/master --skip-final-edit --no-inspect-emails --dry-run before finally hitting the big red button. Signed-off-by: Alex Bennée --- git-publish | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/git-publish b/git-publish index 71c7b11..2de4a89 100755 --- a/git-publish +++ b/git-publish @@ -652,6 +652,9 @@ def parse_args(): 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_known_args() @@ -822,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) @@ -953,10 +956,12 @@ 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) except (GitSendEmailError, GitHookError, InspectEmailsError): return 1 except GitError as e: @@ -968,7 +973,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)) From 8276d33205432bec015480401cbd61d4e1e4b71b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Thu, 5 Jan 2023 21:07:48 +0000 Subject: [PATCH 5/5] For --dry-run at least print a summary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using -v is overkill for a script. Signed-off-by: Alex Bennée --- v2 - move summary out of the finally step --- git-publish | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/git-publish b/git-publish index 2de4a89..23f4ff8 100755 --- a/git-publish +++ b/git-publish @@ -962,6 +962,12 @@ branch.%s.pushRemote is set appropriately? (Override with --no-check-url)''' % git_send_email(to, cc, selected_patches, suppress_cc, options.in_reply_to, options.thread, 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: