Index: head/Tools/scripts/README.getpatch =================================================================== --- head/Tools/scripts/README.getpatch (revision 416845) +++ head/Tools/scripts/README.getpatch (revision 416846) @@ -1,85 +1,89 @@ GETPATCH(1) FreeBSD General Commands Manual GETPATCH(1) NAME getpatch - Utility to download patch attachments on Bug Tracking Systems SYNOPSIS getpatch [-h] [--mode gnats|bz] [--last] [--stdout] DESCRIPTION getpatch is a utility to download patch attachments from Bug Tracking Systems such Gnats and Bugzilla. It supports retrieving multiple attachments from the command line. It's written in python without any extra dependencies. In addition to the functionalities offered by other similar tools suchs as getpr, it does web scrapping on the BTS web interface in order to retrieve the patches attached to PR. The following command line options are supported: -h Prints a multi-line help message and exits. --mode Specifies the BTS. Currently "gnats" and "bz" are supported. --last Only retrieves the latest iteration of a patch. --stdout Dumps the patch to stdout file descriptor. Options can be used after or before the argument on the command line. FILES ${PORTSDIR}/Tools/scripts/getpatch EXAMPLES Retrieve all patches attached to PR ports/166692 from Gnats BTS: getpatch --mode gnats ports/166692 Retrieve all patches attached to PR ports/166692 from Bugzilla BTS: getpatch --mode bz ports/166692 or getpatch 166692 Bugzilla is the default BTS and category isn't mandatory. Retrieve only the latest iteration of the patch: getpatch --last ports/166692 Retrieve a patch and dump it to standard output getpatch --stdout ports/166692 From inside a port's directory, patching on the fly can be done as follows: For a diff cd ${PORTSDIR}/category/port patch -p0 < <(getpatch 166692 --stdout) For a shar cd ${PORTSDIR}/category/port sh <(getpatch 166692 --stdout) Redirection <() depends on the shell you're using; validated with zsh and bash. -DIAGNOSTICS - getpatch exits 0 on success or 1 if a help message was displayed. +EXIT STATUS + getpatch exits with one of the following values according to sysexits code: + 0 on success. + 64 if a help message was displayed. + 69 if patches are not found. + SEE ALSO getpr AUTHORS Sofian Brabez BUGS If you're using getpatch and you encounter a bug or want an improvement don't hesitate to mail me. -FreeBSD 18 June 2014 FreeBSD +FreeBSD 12 June 2016 FreeBSD Index: head/Tools/scripts/getpatch =================================================================== --- head/Tools/scripts/getpatch (revision 416845) +++ head/Tools/scripts/getpatch (revision 416846) @@ -1,196 +1,219 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright (c) 2012 Sofian Brabez # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # # $FreeBSD$ # # MAINTAINER= sbz@FreeBSD.org import argparse import codecs +import os import re +import ssl import sys if sys.version_info.major == 3: import urllib.request as urllib2 else: import urllib2 """ FreeBSD getpatch handles Gnats and Bugzilla patch attachments """ + +def create_ssl_context(cafile): + if os.path.exists(cafile): + return ssl.create_default_context(cafile=cafile) + else: + return ssl._create_unverified_context() + + class GetPatch(object): def __init__(self, pr, category='ports'): self.pr = pr self.category = category self.patchs = [] self.url = "" self.patch = "" self.output_stdout = False self.default_locale = sys.getdefaultencoding() + self.ssl_context = create_ssl_context('/usr/local/etc/ssl/cert.pem') def fetch(self, *largs, **kwargs): raise NotImplementedError() def write(self, filename, data): if filename.endswith(('.patch', '.txt')): - filename = filename[:filename.rindex('.')]+'.diff' - f=codecs.open(filename, encoding=self.default_locale, mode='w') + filename = "{}.diff".format(filename[:filename.rindex('.')]) + f = codecs.open(filename, encoding=self.default_locale, mode='w') f.write(data.decode(self.default_locale)) f.close() - self.out("[+] %s created" % filename) + self.out("[+] {} created".format(filename)) - def get(self,only_last=False, output_stdout=False): + def get(self, only_last=False, output_stdout=False): self.output_stdout = output_stdout self.fetch(self.pr, category=self.category) if len(self.patchs) == 0: self.out("[-] No patch found") - sys.exit(1) + sys.exit(os.EX_UNAVAILABLE) if only_last: self.patchs = [self.patchs.pop()] for patch in self.patchs: url = patch['url'] p = patch['name'] - data = urllib2.urlopen(url).read() + data = urllib2.urlopen(url, context=self.ssl_context).read() if self.output_stdout: sys.stdout.write(data.decode(self.default_locale)) else: self.write(p, data) def add_patch(self, url, name): self.patchs.append({'url': url, 'name': name}) def out(self, s): if not self.output_stdout: print(s) + class GnatsGetPatch(GetPatch): URL_BASE = 'https://www.freebsd.org/cgi' - URL = '%s/query-pr.cgi?pr=' % URL_BASE + URL = '{}/query-pr.cgi?pr='.format(URL_BASE) REGEX = r'Download ([^<]*)' def __init__(self, pr, category): GetPatch.__init__(self, pr, category) def fetch(self, *largs, **kwargs): category = kwargs['category'] - target = ("%s/%s" % (category, self.pr), "%s" % self.pr)[category==''] - self.out("[+] Fetching patch for pr %s" % target) + target = ("{}/{}".format(category, self.pr), + "{}".format(self.pr))[category == ''] + self.out("[+] Fetching patch for pr {}".format(target)) pattern = re.compile(self.REGEX) - u = urllib2.urlopen(self.URL+'%s' % target) + u = urllib2.urlopen("{}{}".format(self.URL, target), + context=self.ssl_context) data = u.read() - if data == None: + if data is None: self.out("[-] No patch found") - sys.exit(1) + sys.exit(os.EX_UNAVAILABLE) for patchs in re.findall(pattern, str(data)): self.add_patch(patchs[0], patchs[1]) + class BzGetPatch(GetPatch): - URL_BASE= 'https://bugs.freebsd.org/bugzilla/' - URL_SHOW = '%s/show_bug.cgi?id=' % URL_BASE + URL_BASE = 'https://bugs.freebsd.org/bugzilla/' + URL_SHOW = '{}/show_bug.cgi?id='.format(URL_BASE) REGEX_URL = r'Details' REGEX = r'
([^ ]+) \(text/plain(?:; charset=[-\w]+)?\)' def __init__(self, pr, category): GetPatch.__init__(self, pr, category) def _get_patch_name(self, url): - match = re.search(self.REGEX, urllib2.urlopen(url).read()) + data = urllib2.urlopen(url, context=self.ssl_context).read() + match = re.search(self.REGEX, str(data)) if match is None: return None return match.group(1) def _get_patch_urls(self, data): patch_urls = {} - for url in re.findall(self.REGEX_URL, data): - url = '%s/%s' % (self.URL_BASE, url) + for url in re.findall(self.REGEX_URL, str(data)): + url = '{}{}'.format(self.URL_BASE, url) file_name = self._get_patch_name(url) if file_name is None: - self.out("[-] Could not determine the patch file name in %s. " - "Skipping." % url) + msg = "[-] Could not determine the patch file name in {}." \ + "Skipping." + self.out(msg.format(url)) continue download_url = url[:url.find('&')] patch_urls[download_url] = file_name return patch_urls def fetch(self, *largs, **kwargs): category = kwargs['category'] - self.out("[+] Fetching patch for pr %s/%s" % (category, self.pr)) - u = urllib2.urlopen(self.URL_SHOW+'%s' % self.pr) + target = ("{}/{}".format(category, self.pr), + "{}".format(self.pr))[category == ''] + self.out("[+] Fetching patch for pr {}".format(target)) + u = urllib2.urlopen("{}{}".format(self.URL_SHOW, self.pr), + context=self.ssl_context) data = u.read() - if data == None: + if data is None: self.out("[-] No patch found") - sys.exit(1) + sys.exit(os.EX_UNAVAILABLE) patch_urls = self._get_patch_urls(data) if not patch_urls: self.out("[-] No patch found") - sys.exit(1) + sys.exit(os.EX_UNAVAILABLE) - for url, file_name in patch_urls.iteritems(): + for url, file_name in patch_urls.items(): self.add_patch(url, file_name) + def main(): parser = argparse.ArgumentParser( description='Gets patch attachments from a Bug Tracking System' ) parser.add_argument('pr', metavar='pr', type=str, nargs=1, - help='Pr id number') - parser.add_argument('--mode', type=str, choices=['gnats','bz'], - default='bz', help='available modes to retrieve patch') + help='Pr id number') + parser.add_argument('--mode', type=str, choices=['gnats', 'bz'], + default='bz', help='available modes to retrieve patch') parser.add_argument('--last', action='store_true', - help='only retrieve the latest iteration of a patch') + help='only retrieve the latest iteration of a patch') parser.add_argument('--stdout', action='store_true', - help='dump patch on stdout') + help='dump patch on stdout') if len(sys.argv) == 1: parser.print_help() - sys.exit(1) + sys.exit(os.EX_USAGE) args = parser.parse_args() category = "" pr = str(args.pr[0]) - if '/' in pr and pr is not None: + if pr and '/' in pr: category, pr = pr.split('/') Clazz = globals()['%sGetPatch' % args.mode.capitalize()] gp = Clazz(pr, category) gp.get(only_last=args.last, output_stdout=args.stdout) + return os.EX_OK + if __name__ == '__main__': - main() + sys.exit(main())