diff options
author | Denis 'GNUtoo' Carikli <GNUtoo@cyberdimension.org> | 2021-02-23 14:30:50 +0100 |
---|---|---|
committer | Denis 'GNUtoo' Carikli <GNUtoo@cyberdimension.org> | 2021-03-04 13:03:42 +0100 |
commit | bf9513e6845feeff41754022bf3b918121583252 (patch) | |
tree | d65590a665f2575993ee938dead1b2e993857b00 | |
parent | f111e65b7dccb06e40468f516cc57464594dbb13 (diff) | |
download | vendor_replicant-scripts-bf9513e6845feeff41754022bf3b918121583252.tar.gz vendor_replicant-scripts-bf9513e6845feeff41754022bf3b918121583252.tar.bz2 vendor_replicant-scripts-bf9513e6845feeff41754022bf3b918121583252.zip |
[wip] Handle all git arguments
TODO:
- restore -C
- Make sure it works fine in all cases
Signed-off-by: Denis 'GNUtoo' Carikli <GNUtoo@cyberdimension.org>
-rwxr-xr-x | patches/replicant_prepare_patch.py | 199 |
1 files changed, 151 insertions, 48 deletions
diff --git a/patches/replicant_prepare_patch.py b/patches/replicant_prepare_patch.py index c1f52e7..cbb1261 100755 --- a/patches/replicant_prepare_patch.py +++ b/patches/replicant_prepare_patch.py @@ -14,10 +14,13 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>. -import os, sys +import argparse +import configparser +import enum +import os import re +import sys -import configparser import sh # Settings @@ -36,10 +39,14 @@ git show {commit} """ def usage(progname): - print('Usage:\n\t{} [-C <directory>] <git_revision> [nr_patches] <patches_serie_revision>'.format( - progname)) - print('Options:') - print('\t-C <directory>\t\tRun as if it was started in the given git directory') + output = "" + try: + output = self.git("format-patch", "-h") + except: + pass + + print(output.replace('git format-patch', progname)) + sys.exit(1) def get_config(): @@ -91,16 +98,18 @@ def get_config(): config.read_file(config_file) return config +def match_array(regex_str, array): + regex = re.compile(regex_str) + + for elm in array: + if re.match(regex, elm): + return True + return False + class GitRepo(object): - def __init__(self, config, directory): + def __init__(self, config): self.config = config - self.directory = None - - if directory: - self.directory = re.sub('/+$', '', directory) - self.git = sh.git.bake("-C", self.directory) - else: - self.git = sh.git.bake() + self.git = sh.git.bake() def get_repo_url(self): output = self.git('remote', 'get-url', self.config['local']['git_remote']) @@ -142,27 +151,21 @@ class GitRepo(object): return '{project}] [PATCH'.format(project=project_name) - def generate_patches(self, git_revision, nr_patches, patches_revision): - subject_prefix = self.get_subject_prefix() - - git_arguments = ['format-patch', git_revision, '-{}'.format(nr_patches)] + def format_patch(self, git_format_patch_arguments): + git_arguments = ['format-patch'] - if subject_prefix != None: + if match_array('^--subject-prefix', git_format_patch_arguments): + subject_prefix = self.get_subject_prefix() git_arguments.append('--subject-prefix={}'.format(subject_prefix)) - if patches_revision != None: - git_arguments.append('-v{}'.format(patches_revision)) + git_arguments += git_format_patch_arguments patches = self.git(*git_arguments).split(os.linesep) + patches.remove('') - if self.directory: - results = [] - for patch in patches: - results.append(self.directory + os.sep + patch) - return results - else: - return patches + return patches + def generate_cover_mail_text(self, commit, nr_patches, repo): cgit_url = 'https://git.replicant.us' @@ -199,30 +202,124 @@ class GitRepo(object): return command - def get_git_revision(self, git_revision): + def get_commit_hash(self, git_revision): output = self.git('--no-pager', 'log', '--oneline', git_revision, '-1', - '--format=%H') + '--format=%H') revision = re.sub(os.linesep, '', str(output)) return revision -if __name__ == '__main__': - nr_patches = 1 - patches_revision = None - directory = None + def get_top_commit_revision_from_range(self, rev_range, nr_patches): + output = self.git('--no-pager', 'rev-parse', rev_range).split(os.linesep) + output.remove('') + + # With git-format-patch, there are "two ways to specify which commits to + # operate on.": + # "1. A single commit, <since>, [...] leading to the tip of the current + # branch" + # "2. Generic <revision range> expression" + # See man git-format-patch for more details on how to parse. + + # Single commit + if len(output) == 1: + output = self.git('--no-pager', 'rev-parse', rev_range + '..HEAD') + output = output.split(os.linesep) + output.remove('') + return output[0] + # Generic revision range + else: + return output[0] + +def get_git_revision_from_cmdline(cmdline): + class Arg(enum.Enum): + NONE = 0, + REQUIRED = 1, + OPTIONAL = 2, + + git_format_patch_opts = [ + # shortopt, longopt, argument + # TODO: --no-[option] + # TODO: --base + [ None, 'add-header', Arg.REQUIRED ], + [ None, 'attach', Arg.OPTIONAL ], + [ None, 'cc', Arg.REQUIRED ], + [ None, 'cover-from-description', Arg.REQUIRED ], + [ None, 'cover-letter', Arg.NONE ], + [ None, 'creation-factor', Arg.REQUIRED ], + [ None, 'filename-max-length', Arg.REQUIRED ], + [ None, 'from', Arg.REQUIRED ], + [ None, 'inline', Arg.OPTIONAL ], + [ None, 'in-reply-to', Arg.REQUIRED ], + [ None, 'interdiff', Arg.REQUIRED ], + [ 'k', 'keep-subject', Arg.NONE ], + [ None, 'no-attach', Arg.NONE ], + [ None, 'no-binary', Arg.NONE ], + [ None, 'no-indent-heuristic', Arg.NONE ], + [ None, 'no-notes', Arg.NONE ], + [ 'N', 'no-numbered', Arg.NONE ], + [ None, 'no-renames', Arg.NONE ], + [ None, 'no-relative', Arg.NONE ], + [ None, 'no-signature', Arg.NONE ], + [ 'p', 'no-stat', Arg.NONE ], + [ None, 'no-thread', Arg.NONE ], + [ None, 'notes', Arg.OPTIONAL ], + [ 'n', 'numbered', Arg.NONE ], + [ None, 'numbered-files', Arg.NONE ], + [ 'o', 'output-directory', Arg.REQUIRED ], + [ None, 'progress', Arg.NONE ], + [ 'q', 'quiet', Arg.NONE ], + [ None, 'range-diff', Arg.REQUIRED ], + [ 'v', 'reroll-count', Arg.REQUIRED ], + [ None, 'rfc', Arg.NONE ], + [ None, 'signature', Arg.REQUIRED ], + [ None, 'signature-file', Arg.REQUIRED ], + [ 's', 'signoff', Arg.NONE ], + [ None, 'start-number', Arg.REQUIRED ], + [ None, 'stdout', Arg.NONE ], + [ None, 'subject-prefix', Arg.REQUIRED ], + [ None, 'suffix', Arg.REQUIRED ], + [ None, 'thread', Arg.OPTIONAL ], + [ None, 'to', Arg.REQUIRED ], + [ None, 'zero-commit', Arg.NONE ], + ] + + # We cannot use getopt because "Optional arguments [for long options] are + # not supported". Reference: https://docs.python.org/3.8/library/getopt.html + parser = argparse.ArgumentParser() + parser.add_argument('args', nargs=argparse.REMAINDER) + + for option in git_format_patch_opts: + if option[2] == Arg.REQUIRED: + if option[0] != None: + parser.add_argument('-' + option[0], nargs=1) + if option[1] != None: + parser.add_argument('--' + option[1], nargs=1) + elif option[2] != Arg.OPTIONAL: + if option[0] != None: + parser.add_argument('-' + option[0], nargs='?') + if option[1] != None: + parser.add_argument('--' + option[1], nargs='?') + else: + # For now, we can filter out those with - or -- + continue - if len(sys.argv) not in [2, 3, 4, 5, 6]: - usage(sys.argv[0]) + remaining_args = parser.parse_args(cmdline).__dict__.get('args', []) + # print(remaining_args) - if sys.argv[1] == '-C': - sys.argv.pop(1) - directory = sys.argv.pop(1) + # TODO: fails to show something with + # ./replicant_prepare_patch.py HEAD~4 --foo + # but it prints prints "HEAD~4" with: + # ./replicant_prepare_patch.py HEAD~4 + # for remaining_arg in remaining_args: + # print(remaining_arg) - if len (sys.argv) >= 3: - nr_patches = int(sys.argv[2]) + revision = remaining_args[0] + return revision +if __name__ == '__main__': + progname = sys.argv.pop(0) - if len (sys.argv) >= 4: - patches_revision = int(sys.argv[3]) + if len(sys.argv) == 0: + usage(progname) config = get_config() @@ -230,24 +327,30 @@ if __name__ == '__main__': print('Failed to find a configuration file') sys.exit(1) - repo = GitRepo(config, directory) - git_revision = repo.get_git_revision(sys.argv[1]) + repo = GitRepo(config) - patches = repo.generate_patches(git_revision, nr_patches, patches_revision) + try: + patches = repo.format_patch(sys.argv) + except: + usage(progname) print('patches:') print('--------') for patch in patches: print(patch) + git_revision_range = get_git_revision_from_cmdline(sys.argv) + top_revision = repo.get_top_commit_revision_from_range(git_revision_range, + len(patches)) + print() print('git command:') print('------------') print('git ' + ' '.join( - repo.generate_git_send_email_command(git_revision, patches))) + repo.generate_git_send_email_command(git_revision_range, patches))) print() print('Cover mail:') print('-----------') - print(repo.generate_cover_mail_text(git_revision, nr_patches, + print(repo.generate_cover_mail_text(top_revision, len(patches), repo.get_repo_name())) |