c813475b44e676dd1ae1e882a1d1789fdf86ae0d
[freebsd-maintainer-scripts/.git] / getpatch
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
31 import argparse
32 import codecs
33 import re
34 import sys
35 if sys.version_info.major == 3:
36     import urllib.request as urllib2
37 else:
38     import urllib2
39
40 """
41 FreeBSD getpatch handles Gnats and Bugzilla patch attachments
42 """
43
44 class GetPatch(object):
45
46     def __init__(self, pr, category='ports'):
47         self.pr = pr
48         self.category = category
49         self.patchs = []
50         self.url = ""
51         self.patch = ""
52         self.output_stdout = False
53         self.default_locale = sys.getdefaultencoding()
54
55     def fetch(self, *largs, **kwargs):
56         raise NotImplementedError()
57
58     def write(self, filename, data):
59         if filename.endswith(('.patch', '.txt')):
60             filename = filename[:filename.rindex('.')]+'.diff'
61         f=codecs.open(filename, encoding=self.default_locale, mode='w')
62         f.write(data.decode(self.default_locale))
63         f.close()
64         self.out("[+] %s created" % filename)
65
66     def get(self,only_last=False, output_stdout=False):
67         self.output_stdout = output_stdout
68         self.fetch(self.pr, category=self.category)
69
70         if len(self.patchs) == 0:
71             self.out("[-] No patch found")
72             sys.exit(1)
73
74         if only_last:
75             self.patchs = [self.patchs.pop()]
76
77         for patch in self.patchs:
78             url = patch['url']
79             p = patch['name']
80
81             data = urllib2.urlopen(url).read()
82
83             if self.output_stdout:
84                 sys.stdout.write(data.decode(self.default_locale))
85             else:
86                 self.write(p, data)
87
88     def add_patch(self, url, name):
89         self.patchs.append({'url': url, 'name': name})
90
91     def out(self, s):
92         if not self.output_stdout:
93             print(s)
94
95 class GnatsGetPatch(GetPatch):
96
97     URL_BASE = 'https://www.freebsd.org/cgi'
98     URL = '%s/query-pr.cgi?pr=' % URL_BASE
99     REGEX = r'<b>Download <a href="([^"]*)">([^<]*)</a>'
100
101     def __init__(self, pr, category):
102         GetPatch.__init__(self, pr, category)
103
104     def fetch(self, *largs, **kwargs):
105         category = kwargs['category']
106         target = ("%s/%s" % (category, self.pr), "%s" % self.pr)[category=='']
107         self.out("[+] Fetching patch for pr %s" % target)
108         pattern = re.compile(self.REGEX)
109         u = urllib2.urlopen(self.URL+'%s' % target)
110         data = u.read()
111         if data == None:
112             self.out("[-] No patch found")
113             sys.exit(1)
114
115         for patchs in re.findall(pattern, str(data)):
116             self.add_patch(patchs[0], patchs[1])
117
118 class BzGetPatch(GetPatch):
119
120     URL_BASE= 'https://bugs.freebsd.org/bugzilla/'
121     URL_SHOW = '%s/show_bug.cgi?id=' % URL_BASE
122     REGEX_URL = r'<a href="([^<]+)">Details</a>'
123     REGEX = r'<div class="details">([^ ]+) \(text/plain(?:; charset=[-\w]+)?\)'
124
125     def __init__(self, pr, category):
126         GetPatch.__init__(self, pr, category)
127
128     def _get_patch_name(self, url):
129         match = re.search(self.REGEX, urllib2.urlopen(url).read())
130         if match is None:
131             return None
132         return match.group(1)
133
134     def _get_patch_urls(self, data):
135         patch_urls = {}
136         for url in re.findall(self.REGEX_URL, data):
137             url = '%s/%s' % (self.URL_BASE, url)
138             file_name = self._get_patch_name(url)
139             if file_name is None:
140                 self.out("[-] Could not determine the patch file name in %s. "
141                          "Skipping." % url)
142                 continue
143             download_url = url[:url.find('&')]
144             patch_urls[download_url] = file_name
145         return patch_urls
146
147     def fetch(self, *largs, **kwargs):
148         category = kwargs['category']
149         self.out("[+] Fetching patch for pr %s/%s" % (category, self.pr))
150         u = urllib2.urlopen(self.URL_SHOW+'%s' % self.pr)
151         data = u.read()
152
153         if data == None:
154             self.out("[-] No patch found")
155             sys.exit(1)
156
157         patch_urls = self._get_patch_urls(data)
158         if not patch_urls:
159             self.out("[-] No patch found")
160             sys.exit(1)
161
162         for url, file_name in patch_urls.iteritems():
163             self.add_patch(url, file_name)
164
165 def main():
166
167     parser = argparse.ArgumentParser(
168             description='Gets patch attachments from a Bug Tracking System'
169     )
170     parser.add_argument('pr', metavar='pr', type=str, nargs=1,
171             help='Pr id number')
172     parser.add_argument('--mode', type=str, choices=['gnats','bz'],
173             default='bz', help='available modes to retrieve patch')
174     parser.add_argument('--last', action='store_true',
175             help='only retrieve the latest iteration of a patch')
176     parser.add_argument('--stdout', action='store_true',
177             help='dump patch on stdout')
178
179     if len(sys.argv) == 1:
180         parser.print_help()
181         sys.exit(1)
182
183     args = parser.parse_args()
184
185     category = ""
186     pr = str(args.pr[0])
187
188     if '/' in pr and pr is not None:
189         category, pr = pr.split('/')
190
191     Clazz = globals()['%sGetPatch' % args.mode.capitalize()]
192     gp = Clazz(pr, category)
193     gp.get(only_last=args.last, output_stdout=args.stdout)
194
195 if __name__ == '__main__':
196     main()