getpatch: update code with bsd-like errors code, % -> format(), pep8 fixes
[freebsd-maintainer-scripts/.git] / getpatch
CommitLineData
efd73192
SB
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# Copyright (c) 2012 Sofian Brabez <sbz@FreeBSD.org>
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10# 1. Redistributions of source code must retain the above copyright
11# notice, this list of conditions and the following disclaimer
12# 2. Redistributions in binary form must reproduce the above copyright
13# notice, this list of conditions and the following disclaimer in the
14# documentation and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26#
27# $FreeBSD: head/Tools/scripts/getpatch 362667 2014-07-23 12:14:32Z sbz $
28#
29# MAINTAINER= sbz@FreeBSD.org
30
31import argparse
32import codecs
2b746d10 33import os
efd73192 34import re
2b746d10 35import ssl
efd73192
SB
36import sys
37if sys.version_info.major == 3:
38 import urllib.request as urllib2
39else:
40 import urllib2
41
42"""
43FreeBSD getpatch handles Gnats and Bugzilla patch attachments
44"""
45
2b746d10
SB
46
47def create_ssl_context(cafile):
48 if os.path.exists(cafile):
49 return ssl.create_default_context(cafile=cafile)
50 else:
51 return ssl._create_unverified_context()
52
53
efd73192
SB
54class GetPatch(object):
55
56 def __init__(self, pr, category='ports'):
57 self.pr = pr
58 self.category = category
59 self.patchs = []
60 self.url = ""
61 self.patch = ""
62 self.output_stdout = False
63 self.default_locale = sys.getdefaultencoding()
2b746d10 64 self.ssl_context = create_ssl_context('/usr/local/etc/ssl/cert.pem')
efd73192
SB
65
66 def fetch(self, *largs, **kwargs):
67 raise NotImplementedError()
68
69 def write(self, filename, data):
70 if filename.endswith(('.patch', '.txt')):
f772ae3b 71 filename = "{}.diff".format(filename[:filename.rindex('.')])
e265cb60 72 f = codecs.open(filename, encoding=self.default_locale, mode='w')
efd73192
SB
73 f.write(data.decode(self.default_locale))
74 f.close()
f772ae3b 75 self.out("[+] {} created".format(filename))
efd73192 76
e265cb60 77 def get(self, only_last=False, output_stdout=False):
efd73192
SB
78 self.output_stdout = output_stdout
79 self.fetch(self.pr, category=self.category)
80
81 if len(self.patchs) == 0:
82 self.out("[-] No patch found")
f772ae3b 83 sys.exit(os.EX_UNAVAILABLE)
efd73192
SB
84
85 if only_last:
86 self.patchs = [self.patchs.pop()]
87
88 for patch in self.patchs:
89 url = patch['url']
90 p = patch['name']
91
2b746d10 92 data = urllib2.urlopen(url, context=self.ssl_context).read()
efd73192
SB
93
94 if self.output_stdout:
95 sys.stdout.write(data.decode(self.default_locale))
96 else:
97 self.write(p, data)
98
99 def add_patch(self, url, name):
100 self.patchs.append({'url': url, 'name': name})
101
102 def out(self, s):
103 if not self.output_stdout:
104 print(s)
105
e265cb60 106
efd73192
SB
107class GnatsGetPatch(GetPatch):
108
109 URL_BASE = 'https://www.freebsd.org/cgi'
f772ae3b 110 URL = '{}/query-pr.cgi?pr='.format(URL_BASE)
efd73192
SB
111 REGEX = r'<b>Download <a href="([^"]*)">([^<]*)</a>'
112
113 def __init__(self, pr, category):
114 GetPatch.__init__(self, pr, category)
115
116 def fetch(self, *largs, **kwargs):
117 category = kwargs['category']
f772ae3b
SB
118 target = ("{}/{}".format(category, self.pr),
119 "{}".format(self.pr))[category == '']
120 self.out("[+] Fetching patch for pr {}".format(target))
efd73192 121 pattern = re.compile(self.REGEX)
f772ae3b
SB
122 u = urllib2.urlopen("{}{}".format(self.URL, target),
123 context=self.ssl_context)
efd73192 124 data = u.read()
e265cb60 125 if data is None:
efd73192 126 self.out("[-] No patch found")
f772ae3b 127 sys.exit(os.EX_UNAVAILABLE)
efd73192
SB
128
129 for patchs in re.findall(pattern, str(data)):
130 self.add_patch(patchs[0], patchs[1])
131
e265cb60 132
efd73192
SB
133class BzGetPatch(GetPatch):
134
e265cb60 135 URL_BASE = 'https://bugs.freebsd.org/bugzilla/'
f772ae3b 136 URL_SHOW = '{}/show_bug.cgi?id='.format(URL_BASE)
efd73192
SB
137 REGEX_URL = r'<a href="([^<]+)">Details</a>'
138 REGEX = r'<div class="details">([^ ]+) \(text/plain(?:; charset=[-\w]+)?\)'
139
140 def __init__(self, pr, category):
141 GetPatch.__init__(self, pr, category)
142
143 def _get_patch_name(self, url):
f772ae3b
SB
144 data = urllib2.urlopen(url, context=self.ssl_context).read()
145 match = re.search(self.REGEX, str(data))
efd73192
SB
146 if match is None:
147 return None
148 return match.group(1)
149
150 def _get_patch_urls(self, data):
151 patch_urls = {}
f772ae3b
SB
152 for url in re.findall(self.REGEX_URL, str(data)):
153 url = '{}{}'.format(self.URL_BASE, url)
efd73192
SB
154 file_name = self._get_patch_name(url)
155 if file_name is None:
f772ae3b
SB
156 msg = "[-] Could not determine the patch file name in {}." \
157 "Skipping."
158 self.out(msg.format(url))
efd73192
SB
159 continue
160 download_url = url[:url.find('&')]
161 patch_urls[download_url] = file_name
162 return patch_urls
163
164 def fetch(self, *largs, **kwargs):
165 category = kwargs['category']
f772ae3b
SB
166 target = ("{}/{}".format(category, self.pr),
167 "{}".format(self.pr))[category == '']
168 self.out("[+] Fetching patch for pr {}".format(target))
169 u = urllib2.urlopen("{}{}".format(self.URL_SHOW, self.pr),
170 context=self.ssl_context)
efd73192
SB
171 data = u.read()
172
e265cb60 173 if data is None:
efd73192 174 self.out("[-] No patch found")
f772ae3b 175 sys.exit(os.EX_UNAVAILABLE)
efd73192
SB
176
177 patch_urls = self._get_patch_urls(data)
178 if not patch_urls:
179 self.out("[-] No patch found")
f772ae3b 180 sys.exit(os.EX_UNAVAILABLE)
efd73192 181
f772ae3b 182 for url, file_name in patch_urls.items():
efd73192
SB
183 self.add_patch(url, file_name)
184
e265cb60 185
efd73192
SB
186def main():
187
188 parser = argparse.ArgumentParser(
189 description='Gets patch attachments from a Bug Tracking System'
190 )
191 parser.add_argument('pr', metavar='pr', type=str, nargs=1,
f772ae3b 192 help='Pr id number')
e265cb60 193 parser.add_argument('--mode', type=str, choices=['gnats', 'bz'],
f772ae3b 194 default='bz', help='available modes to retrieve patch')
efd73192 195 parser.add_argument('--last', action='store_true',
f772ae3b 196 help='only retrieve the latest iteration of a patch')
efd73192 197 parser.add_argument('--stdout', action='store_true',
f772ae3b 198 help='dump patch on stdout')
efd73192
SB
199
200 if len(sys.argv) == 1:
201 parser.print_help()
f772ae3b 202 sys.exit(os.EX_USAGE)
efd73192
SB
203
204 args = parser.parse_args()
205
206 category = ""
207 pr = str(args.pr[0])
208
e265cb60 209 if pr and '/' in pr:
efd73192
SB
210 category, pr = pr.split('/')
211
212 Clazz = globals()['%sGetPatch' % args.mode.capitalize()]
213 gp = Clazz(pr, category)
214 gp.get(only_last=args.last, output_stdout=args.stdout)
215
f772ae3b
SB
216 return os.EX_OK
217
efd73192 218if __name__ == '__main__':
f772ae3b 219 sys.exit(main())