summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDenis 'GNUtoo' Carikli <GNUtoo@cyberdimension.org>2021-02-23 14:30:50 +0100
committerDenis 'GNUtoo' Carikli <GNUtoo@cyberdimension.org>2021-03-04 13:03:42 +0100
commitbf9513e6845feeff41754022bf3b918121583252 (patch)
treed65590a665f2575993ee938dead1b2e993857b00
parentf111e65b7dccb06e40468f516cc57464594dbb13 (diff)
downloadvendor_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-xpatches/replicant_prepare_patch.py199
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()))