apply-pr 3.2.2__tar.gz → 3.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {apply_pr-3.2.2 → apply_pr-3.3.0}/PKG-INFO +1 -1
- apply_pr-3.3.0/apply_pr/changelog.py +352 -0
- {apply_pr-3.2.2 → apply_pr-3.3.0}/apply_pr/fabfile.py +9 -252
- apply_pr-3.3.0/apply_pr/github_utils.py +26 -0
- {apply_pr-3.2.2 → apply_pr-3.3.0}/apply_pr.egg-info/PKG-INFO +1 -1
- {apply_pr-3.2.2 → apply_pr-3.3.0}/apply_pr.egg-info/SOURCES.txt +2 -0
- {apply_pr-3.2.2 → apply_pr-3.3.0}/setup.py +1 -1
- {apply_pr-3.2.2 → apply_pr-3.3.0}/MANIFEST.in +0 -0
- {apply_pr-3.2.2 → apply_pr-3.3.0}/README.md +0 -0
- {apply_pr-3.2.2 → apply_pr-3.3.0}/apply_pr/__init__.py +0 -0
- {apply_pr-3.2.2 → apply_pr-3.3.0}/apply_pr/cli.py +0 -0
- {apply_pr-3.2.2 → apply_pr-3.3.0}/apply_pr/exceptions.py +0 -0
- {apply_pr-3.2.2 → apply_pr-3.3.0}/apply_pr/version.py +0 -0
- {apply_pr-3.2.2 → apply_pr-3.3.0}/apply_pr.egg-info/dependency_links.txt +0 -0
- {apply_pr-3.2.2 → apply_pr-3.3.0}/apply_pr.egg-info/entry_points.txt +0 -0
- {apply_pr-3.2.2 → apply_pr-3.3.0}/apply_pr.egg-info/requires.txt +0 -0
- {apply_pr-3.2.2 → apply_pr-3.3.0}/apply_pr.egg-info/top_level.txt +0 -0
- {apply_pr-3.2.2 → apply_pr-3.3.0}/requirements.txt +0 -0
- {apply_pr-3.2.2 → apply_pr-3.3.0}/setup.cfg +0 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from __future__ import (
|
|
3
|
+
with_statement, absolute_import, unicode_literals, print_function
|
|
4
|
+
)
|
|
5
|
+
import requests
|
|
6
|
+
import json
|
|
7
|
+
import re
|
|
8
|
+
import os
|
|
9
|
+
from slugify import slugify
|
|
10
|
+
|
|
11
|
+
from .github_utils import github_config
|
|
12
|
+
from requests.exceptions import ConnectionError
|
|
13
|
+
import logging
|
|
14
|
+
from collections import OrderedDict
|
|
15
|
+
from tqdm import tqdm
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
SKIP_LABELS = ['internal', 'custom', 'to be merged', 'deployed', 'traduccions']
|
|
20
|
+
GAS_LABEL = 'gas'
|
|
21
|
+
ELEC_LABEL = u'eléctrico'
|
|
22
|
+
OFICINA_VIRTUAL = 'oficinavirtual'
|
|
23
|
+
TYPE_LABELS = [ELEC_LABEL, GAS_LABEL, OFICINA_VIRTUAL]
|
|
24
|
+
TOP_FEATURE = u':fire: top feature'
|
|
25
|
+
COMMON_KEY = u'COMÚN'
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_label(label_keys, labels, skip_custom=False):
|
|
29
|
+
if not skip_custom:
|
|
30
|
+
for label in labels:
|
|
31
|
+
name = label['name'].lower()
|
|
32
|
+
if name == 'custom':
|
|
33
|
+
return 'custom'
|
|
34
|
+
if name == 'internal':
|
|
35
|
+
return 'internal'
|
|
36
|
+
for label in labels:
|
|
37
|
+
name = label['name'].lower()
|
|
38
|
+
for key in label_keys:
|
|
39
|
+
if key in name:
|
|
40
|
+
return key
|
|
41
|
+
return 'others'
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_url_image(image, owner, repository):
|
|
45
|
+
headers = {
|
|
46
|
+
'Accept': 'application/vnd.github+json',
|
|
47
|
+
'Authorization': 'Bearer %s' % github_config()['token']
|
|
48
|
+
}
|
|
49
|
+
url = "https://api.github.com/markdown"
|
|
50
|
+
data = {
|
|
51
|
+
'text': '''{image}'''.format(image=image),
|
|
52
|
+
'mode': 'gfm',
|
|
53
|
+
'context': '{owner}/{repository}'.format(owner=owner,
|
|
54
|
+
repository=repository)
|
|
55
|
+
}
|
|
56
|
+
r = requests.api.post(url, headers=headers, json=data)
|
|
57
|
+
if r.status_code >= 200 and r.status_code < 300:
|
|
58
|
+
import re
|
|
59
|
+
text = r.content
|
|
60
|
+
urls = re.findall(
|
|
61
|
+
'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+',
|
|
62
|
+
text)
|
|
63
|
+
if urls:
|
|
64
|
+
return urls[0]
|
|
65
|
+
else:
|
|
66
|
+
return image
|
|
67
|
+
else:
|
|
68
|
+
return image
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def print_item_detail(item, owner, repository, key=None):
|
|
72
|
+
def find_all(a_str, sub):
|
|
73
|
+
start = 0
|
|
74
|
+
while True:
|
|
75
|
+
start = a_str.find(sub, start)
|
|
76
|
+
if start == -1: return
|
|
77
|
+
yield start
|
|
78
|
+
start += len(sub) # use start += 1 to find overlapping matches
|
|
79
|
+
|
|
80
|
+
body = item['body'] or ''
|
|
81
|
+
body = re.sub('^# ', '#### ', body).strip()
|
|
82
|
+
body = re.sub('\n# ', '\n#### ', body).strip()
|
|
83
|
+
body = re.sub('^## ', '#### ', body).strip()
|
|
84
|
+
body = re.sub('\n## ', '\n#### ', body).strip()
|
|
85
|
+
body = re.sub(
|
|
86
|
+
'#(\d+)',
|
|
87
|
+
'[:fa-github: \g<1>](https://github.com/{}/{}/pull/\g<1>)'.format(
|
|
88
|
+
owner, repository
|
|
89
|
+
), body)
|
|
90
|
+
body = re.sub(
|
|
91
|
+
'- #(\d+)',
|
|
92
|
+
'- [:fa-github: \g<1>](https://github.com/{}/{}/pull/\g<1>)'
|
|
93
|
+
''.format(
|
|
94
|
+
owner, repository
|
|
95
|
+
), body)
|
|
96
|
+
## Search remove parts
|
|
97
|
+
idx = body.find('#### Afectaciones')
|
|
98
|
+
if idx > 0:
|
|
99
|
+
body = body[:idx - 1] + ''
|
|
100
|
+
idx_images = list(find_all(body, '![image]'))
|
|
101
|
+
if idx_images:
|
|
102
|
+
images_to_replace = {}
|
|
103
|
+
for idx_img in idx_images:
|
|
104
|
+
id_end_image = body.find(')', idx_img)
|
|
105
|
+
if id_end_image > 0:
|
|
106
|
+
image = body[idx_img:id_end_image + 1]
|
|
107
|
+
new_url = get_url_image(image, owner, repository)
|
|
108
|
+
new_image = ''.format(new_url)
|
|
109
|
+
images_to_replace[image] = new_image
|
|
110
|
+
for image, new_image in images_to_replace.items():
|
|
111
|
+
body = body.replace(image, new_image)
|
|
112
|
+
|
|
113
|
+
label = ''
|
|
114
|
+
if key:
|
|
115
|
+
for l in item['labels']:
|
|
116
|
+
if l['name'] not in SKIP_LABELS:
|
|
117
|
+
label += u' <span class="label" ' \
|
|
118
|
+
u'style="background-color: #{color};">{name}</span>'.format(
|
|
119
|
+
name=l['name'],
|
|
120
|
+
color=l['color'])
|
|
121
|
+
label = '\n' + label
|
|
122
|
+
message = (
|
|
123
|
+
u'\n\n### {title} [:fa-github: {number}]({url}) {label}\n\n{body}\n ---'.format(
|
|
124
|
+
title=item['title'], number=item['number'],
|
|
125
|
+
url=item['url'], body=body, label=label
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
return message
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def print_item(item, milestone):
|
|
132
|
+
title_anchor = slugify(item['title'])
|
|
133
|
+
message = u'\n* {title} [:fa-plus-circle: detalles](../detailed_{milestone}#{title_anchor}-{number}) - [:fa-github: {number}]({url})'.format(
|
|
134
|
+
title=item['title'], number=item['number'], url=item['url'],
|
|
135
|
+
milestone=milestone, title_anchor=title_anchor
|
|
136
|
+
)
|
|
137
|
+
return (message)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def get_pulls(url):
|
|
141
|
+
headers = {
|
|
142
|
+
'Accept': 'application/vnd.github.cannonball-preview+json',
|
|
143
|
+
'Authorization': 'token %s' % github_config()['token']
|
|
144
|
+
}
|
|
145
|
+
# NO GIS NO FACT
|
|
146
|
+
r = requests.get(url, headers=headers)
|
|
147
|
+
pull = json.loads(r.text)
|
|
148
|
+
total_prs = pull['total_count']
|
|
149
|
+
pulls_items = []
|
|
150
|
+
total_fetch = 0
|
|
151
|
+
page = 1
|
|
152
|
+
while total_fetch < total_prs:
|
|
153
|
+
items = pull.get('items', None)
|
|
154
|
+
if items is None:
|
|
155
|
+
break
|
|
156
|
+
# pprint.pprint(items)
|
|
157
|
+
total_fetch += len(items)
|
|
158
|
+
pulls_items += items
|
|
159
|
+
if total_fetch >= total_prs:
|
|
160
|
+
break
|
|
161
|
+
page += 1
|
|
162
|
+
new_url = url + "&page={}".format(page)
|
|
163
|
+
r = requests.get(new_url, headers=headers)
|
|
164
|
+
pull = json.loads(r.text)
|
|
165
|
+
return pulls_items
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def make_changelog(
|
|
170
|
+
milestone, show_issues=False, changelog_path='/tmp',
|
|
171
|
+
owner='gisce', repository='erp'):
|
|
172
|
+
import copy
|
|
173
|
+
if not os.path.exists(changelog_path):
|
|
174
|
+
os.makedirs(changelog_path)
|
|
175
|
+
headers = {
|
|
176
|
+
'Accept': 'application/vnd.github+json',
|
|
177
|
+
'Authorization': 'Bearer %s' % github_config()['token']
|
|
178
|
+
}
|
|
179
|
+
pulls_items = []
|
|
180
|
+
logger.info('Getting PRs from GitHub')
|
|
181
|
+
# NO GIS NO FACT
|
|
182
|
+
url = ("https://api.github.com/search/issues"
|
|
183
|
+
"?q=is:pr+is:merged+milestone:{milestone}+repo:{owner}/{repository}+-label:internal+-label:custom+-label:GIS+-label:facturacio"
|
|
184
|
+
"&type=pr"
|
|
185
|
+
"&sort=create"
|
|
186
|
+
"d&order=asc"
|
|
187
|
+
"&per_page=250").format(
|
|
188
|
+
milestone=milestone, owner=owner, repository=repository
|
|
189
|
+
)
|
|
190
|
+
pulls_no_gis_no_fact = get_pulls(url)
|
|
191
|
+
print('Total PRs no GIS no Fact: {}'.format(len(pulls_no_gis_no_fact)))
|
|
192
|
+
pulls_items.extend(pulls_no_gis_no_fact)
|
|
193
|
+
url = ("https://api.github.com/search/issues"
|
|
194
|
+
"?q=is:pr+is:merged+milestone:{milestone}+repo:{owner}/{repository}+-label:internal+-label:custom+label:GIS+-label:facturacio"
|
|
195
|
+
"&type=pr"
|
|
196
|
+
"&sort=create"
|
|
197
|
+
"d&order=asc"
|
|
198
|
+
"&per_page=100").format(
|
|
199
|
+
milestone=milestone, owner=owner, repository=repository
|
|
200
|
+
)
|
|
201
|
+
pulls_gis_no_fact = get_pulls(url)
|
|
202
|
+
print('Total PRs GIS no Fact: {}'.format(len(pulls_gis_no_fact)))
|
|
203
|
+
pulls_items.extend(pulls_gis_no_fact)
|
|
204
|
+
url = ("https://api.github.com/search/issues"
|
|
205
|
+
"?q=is:pr+is:merged+milestone:{milestone}+repo:{owner}/{repository}+-label:internal+-label:custom+-label:GIS+label:facturacio"
|
|
206
|
+
"&type=pr"
|
|
207
|
+
"&sort=create"
|
|
208
|
+
"d&order=asc"
|
|
209
|
+
"&per_page=100").format(
|
|
210
|
+
milestone=milestone, owner=owner, repository=repository
|
|
211
|
+
)
|
|
212
|
+
pulls_no_gis_fact = get_pulls(url)
|
|
213
|
+
print('Total PRs no GIS no Fact: {}'.format(len(pulls_no_gis_fact)))
|
|
214
|
+
pulls_items.extend(pulls_no_gis_fact)
|
|
215
|
+
isses_desc = []
|
|
216
|
+
top_pulls = []
|
|
217
|
+
pulls_desc = OrderedDict(
|
|
218
|
+
[
|
|
219
|
+
('custom', []),
|
|
220
|
+
('internal', []),
|
|
221
|
+
('bug', []),
|
|
222
|
+
('core', []),
|
|
223
|
+
('atr', []),
|
|
224
|
+
('telegestio', []),
|
|
225
|
+
('gis', []),
|
|
226
|
+
('facturacio', []),
|
|
227
|
+
('medidas', []),
|
|
228
|
+
('others', []),
|
|
229
|
+
('traduccions', []),
|
|
230
|
+
|
|
231
|
+
]
|
|
232
|
+
)
|
|
233
|
+
pulls_sep = {
|
|
234
|
+
GAS_LABEL: copy.deepcopy(pulls_desc),
|
|
235
|
+
ELEC_LABEL: copy.deepcopy(pulls_desc),
|
|
236
|
+
OFICINA_VIRTUAL: copy.deepcopy(pulls_desc),
|
|
237
|
+
'others': copy.deepcopy(pulls_desc)
|
|
238
|
+
}
|
|
239
|
+
label_keys = list(pulls_desc.keys())
|
|
240
|
+
other_desc = []
|
|
241
|
+
changelog_file = 'changelog_{}.md'.format(milestone)
|
|
242
|
+
top_file = 'top_{}.md'.format(milestone)
|
|
243
|
+
detailed_file = 'detailed_{}.md'.format(milestone)
|
|
244
|
+
print('Total PRs: {}'.format(len(pulls_items)))
|
|
245
|
+
number = 0
|
|
246
|
+
for item in tqdm(pulls_items):
|
|
247
|
+
url_item = item['html_url']
|
|
248
|
+
item_info = {
|
|
249
|
+
'title': item['title'],
|
|
250
|
+
'number': item['number'],
|
|
251
|
+
'url': url_item,
|
|
252
|
+
'body': item['body'],
|
|
253
|
+
'labels': item['labels'],
|
|
254
|
+
}
|
|
255
|
+
if 'issues' in url_item:
|
|
256
|
+
isses_desc.append(item_info)
|
|
257
|
+
elif 'pull' in url_item:
|
|
258
|
+
p_url = "https://api.github.com/repos/{owner}/{repository}/pulls/{number}".format(
|
|
259
|
+
owner=owner, repository=repository, number=item_info['number']
|
|
260
|
+
)
|
|
261
|
+
try:
|
|
262
|
+
r = requests.get(p_url, headers=headers)
|
|
263
|
+
pull_desc = json.loads(r.text)
|
|
264
|
+
item_info['pull_info'] = pull_desc
|
|
265
|
+
branch = pull_desc['base']['ref']
|
|
266
|
+
except ConnectionError as e:
|
|
267
|
+
tqdm.write(
|
|
268
|
+
'Failed to get infor for {}'.format(item_info['number']))
|
|
269
|
+
branch = 'developer'
|
|
270
|
+
|
|
271
|
+
if branch != 'developer':
|
|
272
|
+
continue
|
|
273
|
+
type_key = get_label(TYPE_LABELS, item['labels'], skip_custom=True)
|
|
274
|
+
top = get_label([TOP_FEATURE], item['labels'], skip_custom=True)
|
|
275
|
+
if TOP_FEATURE.lower() in top:
|
|
276
|
+
top_pulls.append(item_info)
|
|
277
|
+
key = get_label(label_keys, item['labels'])
|
|
278
|
+
pulls_sep[type_key][key].append(item_info)
|
|
279
|
+
else:
|
|
280
|
+
other_desc.append(item_info)
|
|
281
|
+
number += 1
|
|
282
|
+
logger.info('Total imported: {}'.format(number))
|
|
283
|
+
pulls_sep[GAS_LABEL].pop('custom')
|
|
284
|
+
pulls_sep[ELEC_LABEL].pop('custom')
|
|
285
|
+
pulls_sep[GAS_LABEL].pop('internal')
|
|
286
|
+
pulls_sep[ELEC_LABEL].pop('internal')
|
|
287
|
+
pulls_sep[ELEC_LABEL].pop('traduccions')
|
|
288
|
+
pulls_sep[GAS_LABEL].pop('traduccions')
|
|
289
|
+
for key in ['gis', 'telegestio', 'medidas', 'facturacio']:
|
|
290
|
+
pulls_sep[ELEC_LABEL][key] += pulls_sep['others'][key]
|
|
291
|
+
pulls_sep['others'][key] = []
|
|
292
|
+
pulls_sep['others'].pop('custom')
|
|
293
|
+
pulls_sep['others'].pop('traduccions')
|
|
294
|
+
pulls_sep['others'].pop('internal')
|
|
295
|
+
pulls_sep[COMMON_KEY] = pulls_sep.pop('others')
|
|
296
|
+
index_bug = label_keys.index('bug')
|
|
297
|
+
label_keys.pop(index_bug)
|
|
298
|
+
label_keys.append('bug')
|
|
299
|
+
|
|
300
|
+
# TOP FEATURES
|
|
301
|
+
logger.info('Writting top feature on {}/:'.format(changelog_path))
|
|
302
|
+
with open('{}/{}'.format(changelog_path, top_file), 'w') as f:
|
|
303
|
+
f.write(
|
|
304
|
+
"# TOP FEATURES version {milestone}\n".format(milestone=milestone))
|
|
305
|
+
for pull in top_pulls:
|
|
306
|
+
f.write(print_item(pull, milestone))
|
|
307
|
+
|
|
308
|
+
# CHANGELOGS
|
|
309
|
+
logger.info('Writting changelog on {}/:'.format(changelog_path))
|
|
310
|
+
with open('{}/{}'.format(changelog_path, changelog_file), 'w') as f:
|
|
311
|
+
f.write("# Changelog version {milestone}\n".format(milestone=milestone))
|
|
312
|
+
for type_l in TYPE_LABELS + [COMMON_KEY]:
|
|
313
|
+
f.write('\n## {key}\n'.format(key=type_l.upper()))
|
|
314
|
+
for key in label_keys:
|
|
315
|
+
pulls = pulls_sep[type_l].get(key, [])
|
|
316
|
+
if pulls:
|
|
317
|
+
f.write('\n### {key}\n'.format(key=key.upper()))
|
|
318
|
+
for pull in pulls:
|
|
319
|
+
f.write(print_item(pull, milestone))
|
|
320
|
+
if show_issues:
|
|
321
|
+
f.write('\n# Issues: \n')
|
|
322
|
+
for issue in isses_desc:
|
|
323
|
+
f.write(print_item(issue, milestone))
|
|
324
|
+
if other_desc:
|
|
325
|
+
f.write('\n# Others : \n')
|
|
326
|
+
for pull in other_desc:
|
|
327
|
+
f.write(print_item(pull, milestone))
|
|
328
|
+
logger.info(' {}/{}'.format(changelog_path, changelog_file))
|
|
329
|
+
with open('{}/{}'.format(changelog_path, detailed_file), 'w') as f:
|
|
330
|
+
f.write("# Detalles version {milestone}\n".format(milestone=milestone))
|
|
331
|
+
for type_l in TYPE_LABELS + [COMMON_KEY]:
|
|
332
|
+
f.write('\n## {key}\n'.format(key=type_l.upper()))
|
|
333
|
+
for key in label_keys:
|
|
334
|
+
pulls = pulls_sep[type_l].get(key, [])
|
|
335
|
+
if pulls:
|
|
336
|
+
f.write('\n### {key}\n'.format(key=key.upper()))
|
|
337
|
+
for pull in tqdm(pulls,
|
|
338
|
+
desc=' Generating info {} - {}'.format(
|
|
339
|
+
type_l.upper(), key)):
|
|
340
|
+
f.write(
|
|
341
|
+
print_item_detail(pull, owner, repository, key=key))
|
|
342
|
+
if show_issues:
|
|
343
|
+
logger.info('\n# Issues: \n')
|
|
344
|
+
for issue in isses_desc:
|
|
345
|
+
f.write(print_item_detail(issue, owner, repository, key=key))
|
|
346
|
+
if other_desc:
|
|
347
|
+
print('\n# Others : \n')
|
|
348
|
+
for pull in tqdm(other_desc,
|
|
349
|
+
desc=' Generating info for OTHER INFO'):
|
|
350
|
+
f.write(print_item_detail(pull, owner, repository, key=key))
|
|
351
|
+
logger.info(' {}/{}'.format(changelog_path, detailed_file))
|
|
352
|
+
return True
|
|
@@ -38,6 +38,9 @@ from packaging import version as vsn
|
|
|
38
38
|
from giscemultitools.githubutils.objects import GHAPIRequester
|
|
39
39
|
from giscemultitools.githubutils.utils import GithubUtils
|
|
40
40
|
|
|
41
|
+
from requests.exceptions import ConnectionError
|
|
42
|
+
from .github_utils import github_config, is_github_token_valid
|
|
43
|
+
from .changelog import make_changelog
|
|
41
44
|
|
|
42
45
|
logger = logging.getLogger(__name__)
|
|
43
46
|
|
|
@@ -46,27 +49,6 @@ for k in output.keys():
|
|
|
46
49
|
output[k] = False
|
|
47
50
|
|
|
48
51
|
|
|
49
|
-
def is_github_token_valid(token):
|
|
50
|
-
headers = {'Authorization': 'token {token}'.format(token=token)}
|
|
51
|
-
url = "https://api.github.com/user"
|
|
52
|
-
|
|
53
|
-
r = requests.get(url, headers=headers)
|
|
54
|
-
|
|
55
|
-
return r.status_code == 200
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def github_config(**config):
|
|
59
|
-
def validate(_config):
|
|
60
|
-
if 'token' not in _config or not _config['token']:
|
|
61
|
-
raise EnvironmentError("GITHUB_TOKEN variable not provided")
|
|
62
|
-
if not is_github_token_valid(_config['token']):
|
|
63
|
-
raise EnvironmentError("GITHUB_TOKEN not valid or expired")
|
|
64
|
-
|
|
65
|
-
res = config_from_environment('GITHUB', ['token'], **config)
|
|
66
|
-
validate(res)
|
|
67
|
-
return res
|
|
68
|
-
|
|
69
|
-
|
|
70
52
|
def apply_pr_config(**config):
|
|
71
53
|
return config_from_environment('APPLY_PR', **config)
|
|
72
54
|
|
|
@@ -1082,246 +1064,21 @@ def auto_changelog(milestone, show_issues=True):
|
|
|
1082
1064
|
for key in label_keys:
|
|
1083
1065
|
print ('\n## {key}\n'.format(key=key.upper()))
|
|
1084
1066
|
for pull in pulls_desc[key]:
|
|
1085
|
-
print(print_item(pull))
|
|
1067
|
+
print(print_item(pull, milestone))
|
|
1086
1068
|
if show_issues:
|
|
1087
1069
|
print('\n# Issues: \n')
|
|
1088
1070
|
for issue in isses_desc:
|
|
1089
|
-
print(print_item(issue))
|
|
1071
|
+
print(print_item(issue, milestone))
|
|
1090
1072
|
if other_desc:
|
|
1091
1073
|
print('\n# Others : \n')
|
|
1092
1074
|
for pull in other_desc:
|
|
1093
|
-
print(print_item(pull))
|
|
1075
|
+
print(print_item(pull, milestone))
|
|
1094
1076
|
return True
|
|
1095
1077
|
|
|
1096
|
-
|
|
1097
1078
|
@task
|
|
1098
1079
|
def create_changelog(
|
|
1099
1080
|
milestone, show_issues=False, changelog_path='/tmp',
|
|
1100
1081
|
owner='gisce', repository='erp'):
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
GAS_LABEL = 'gas'
|
|
1105
|
-
ELEC_LABEL = u'eléctrico'
|
|
1106
|
-
OFICINA_VIRTUAL = 'oficinavirtual'
|
|
1107
|
-
TYPE_LABELS = [ELEC_LABEL, GAS_LABEL, OFICINA_VIRTUAL]
|
|
1108
|
-
TOP_FEATURE = u':fire: top feature'
|
|
1109
|
-
COMMON_KEY = u'COMÚN'
|
|
1110
|
-
def get_label(label_keys, labels, skip_custom=False):
|
|
1111
|
-
if not skip_custom:
|
|
1112
|
-
for label in labels:
|
|
1113
|
-
name = label['name'].lower()
|
|
1114
|
-
if name == 'custom':
|
|
1115
|
-
return 'custom'
|
|
1116
|
-
for label in labels:
|
|
1117
|
-
name = label['name'].lower()
|
|
1118
|
-
for key in label_keys:
|
|
1119
|
-
if key in name:
|
|
1120
|
-
return key
|
|
1121
|
-
return 'others'
|
|
1122
|
-
|
|
1123
|
-
def print_item(item):
|
|
1124
|
-
title_anchor = slugify(item['title'])
|
|
1125
|
-
message = u'\n* {title} [:fa-plus-circle: detalles](../detailed_{milestone}#{title_anchor}-{number}) - [:fa-github: {number}]({url})'.format(
|
|
1126
|
-
title=item['title'], number=item['number'], url=item['url'],
|
|
1127
|
-
milestone=milestone, title_anchor=title_anchor
|
|
1128
|
-
)
|
|
1129
|
-
return (message)
|
|
1130
|
-
|
|
1131
|
-
def print_item_detail(item, key=None):
|
|
1132
|
-
body = item['body'] or ''
|
|
1133
|
-
body = re.sub('^# ', '#### ', body).strip()
|
|
1134
|
-
body = re.sub('\n# ', '\n#### ', body).strip()
|
|
1135
|
-
body = re.sub('^## ', '#### ', body).strip()
|
|
1136
|
-
body = re.sub('\n## ', '\n#### ', body).strip()
|
|
1137
|
-
body = re.sub(
|
|
1138
|
-
'#(\d+)',
|
|
1139
|
-
'[:fa-github: \g<1>](https://github.com/{}/{}/pull/\g<1>)'.format(
|
|
1140
|
-
owner, repository
|
|
1141
|
-
), body)
|
|
1142
|
-
body = re.sub(
|
|
1143
|
-
'- #(\d+)',
|
|
1144
|
-
'- [:fa-github: \g<1>](https://github.com/{}/{}/pull/\g<1>)'
|
|
1145
|
-
''.format(
|
|
1146
|
-
owner, repository
|
|
1147
|
-
), body)
|
|
1148
|
-
label = ''
|
|
1149
|
-
if key:
|
|
1150
|
-
for l in item['labels']:
|
|
1151
|
-
if l['name'] not in SKIP_LABELS:
|
|
1152
|
-
label += u' <span class="label" ' \
|
|
1153
|
-
u'style="background-color: #{color};">{name}</span>'.format(
|
|
1154
|
-
name=l['name'],
|
|
1155
|
-
color=l['color'])
|
|
1156
|
-
label = '\n'+label
|
|
1157
|
-
message = (
|
|
1158
|
-
u'\n\n### {title} [:fa-github: {number}]({url}) {label}\n\n{body}\n ---'.format(
|
|
1159
|
-
title=item['title'], number=item['number'],
|
|
1160
|
-
url=item['url'], body=body, label=label
|
|
1161
|
-
)
|
|
1162
|
-
)
|
|
1163
|
-
return message
|
|
1164
|
-
|
|
1165
|
-
logger.info('Getting PRs from GitHub')
|
|
1166
|
-
headers = {
|
|
1167
|
-
'Accept': 'application/vnd.github.cannonball-preview+json',
|
|
1168
|
-
'Authorization': 'token %s' % github_config()['token']
|
|
1169
|
-
}
|
|
1170
|
-
url = ("https://api.github.com/search/issues"
|
|
1171
|
-
"?q=is:pr+is:merged+milestone:{milestone}+repo:{owner}/{repository}"
|
|
1172
|
-
"&type=pr"
|
|
1173
|
-
"&sort=create"
|
|
1174
|
-
"d&order=asc"
|
|
1175
|
-
"&per_page=250").format(
|
|
1176
|
-
milestone=milestone, owner=owner, repository=repository
|
|
1177
|
-
)
|
|
1178
|
-
r = requests.get(url, headers=headers)
|
|
1179
|
-
|
|
1180
|
-
pull = json.loads(r.text)
|
|
1181
|
-
total_prs = pull['total_count']
|
|
1182
|
-
pulls_items= []
|
|
1183
|
-
total_fetch = 0
|
|
1184
|
-
page = 1
|
|
1185
|
-
while total_fetch < total_prs:
|
|
1186
|
-
items = pull['items']
|
|
1187
|
-
# pprint.pprint(items)
|
|
1188
|
-
total_fetch += len(items)
|
|
1189
|
-
pulls_items += items
|
|
1190
|
-
if total_fetch >= total_prs:
|
|
1191
|
-
break
|
|
1192
|
-
page += 1
|
|
1193
|
-
new_url = url + "&page={}".format(page)
|
|
1194
|
-
r = requests.get(new_url, headers=headers)
|
|
1195
|
-
pull = json.loads(r.text)
|
|
1196
|
-
if page == 10:
|
|
1197
|
-
break
|
|
1198
|
-
|
|
1199
|
-
isses_desc = []
|
|
1200
|
-
top_pulls = []
|
|
1201
|
-
pulls_desc = OrderedDict(
|
|
1202
|
-
[
|
|
1203
|
-
('custom', []),
|
|
1204
|
-
('bug', []),
|
|
1205
|
-
('core', []),
|
|
1206
|
-
('atr', []),
|
|
1207
|
-
('telegestio', []),
|
|
1208
|
-
('gis', []),
|
|
1209
|
-
('facturacio', []),
|
|
1210
|
-
('medidas', []),
|
|
1211
|
-
('others', []),
|
|
1212
|
-
('traduccions', []),
|
|
1213
|
-
|
|
1214
|
-
]
|
|
1215
|
-
)
|
|
1216
|
-
pulls_sep = {
|
|
1217
|
-
GAS_LABEL: copy.deepcopy(pulls_desc),
|
|
1218
|
-
ELEC_LABEL: copy.deepcopy(pulls_desc),
|
|
1219
|
-
OFICINA_VIRTUAL: copy.deepcopy(pulls_desc),
|
|
1220
|
-
'others': copy.deepcopy(pulls_desc)
|
|
1221
|
-
}
|
|
1222
|
-
label_keys = list(pulls_desc.keys())
|
|
1223
|
-
other_desc = []
|
|
1224
|
-
changelog_file = 'changelog_{}.md'.format(milestone)
|
|
1225
|
-
top_file = 'top_{}.md'.format(milestone)
|
|
1226
|
-
detailed_file = 'detailed_{}.md'.format(milestone)
|
|
1227
|
-
print('Total PRs: {}'.format(total_prs))
|
|
1228
|
-
number = 0
|
|
1229
|
-
for item in tqdm(pulls_items):
|
|
1230
|
-
url_item = item['html_url']
|
|
1231
|
-
item_info = {
|
|
1232
|
-
'title': item['title'],
|
|
1233
|
-
'number': item['number'],
|
|
1234
|
-
'url': url_item,
|
|
1235
|
-
'body': item['body'],
|
|
1236
|
-
'labels': item['labels'],
|
|
1237
|
-
}
|
|
1238
|
-
if 'issues' in url_item:
|
|
1239
|
-
isses_desc.append(item_info)
|
|
1240
|
-
elif 'pull' in url_item:
|
|
1241
|
-
p_url = "https://api.github.com/repos/{owner}/{repository}/pulls/{number}".format(
|
|
1242
|
-
owner=owner, repository=repository, number=item_info['number']
|
|
1243
|
-
)
|
|
1244
|
-
try:
|
|
1245
|
-
r = requests.get(p_url, headers=headers)
|
|
1246
|
-
pull_desc = json.loads(r.text)
|
|
1247
|
-
item_info['pull_info'] = pull_desc
|
|
1248
|
-
branch = pull_desc['base']['ref']
|
|
1249
|
-
except ConnectionError as e:
|
|
1250
|
-
tqdm.write('Failed to get infor for {}'.format(item_info['number']))
|
|
1251
|
-
branch = 'developer'
|
|
1252
|
-
|
|
1253
|
-
if branch != 'developer':
|
|
1254
|
-
continue
|
|
1255
|
-
type_key = get_label(TYPE_LABELS, item['labels'], skip_custom=True)
|
|
1256
|
-
top = get_label([TOP_FEATURE], item['labels'], skip_custom=True)
|
|
1257
|
-
if TOP_FEATURE.lower() in top:
|
|
1258
|
-
top_pulls.append(item_info)
|
|
1259
|
-
key = get_label(label_keys, item['labels'])
|
|
1260
|
-
pulls_sep[type_key][key].append(item_info)
|
|
1261
|
-
else:
|
|
1262
|
-
other_desc.append(item_info)
|
|
1263
|
-
number += 1
|
|
1264
|
-
logger.info('Total imported: {}'.format(number))
|
|
1265
|
-
pulls_sep[GAS_LABEL].pop('custom')
|
|
1266
|
-
pulls_sep[ELEC_LABEL].pop('custom')
|
|
1267
|
-
pulls_sep[ELEC_LABEL].pop('traduccions')
|
|
1268
|
-
pulls_sep[GAS_LABEL].pop('traduccions')
|
|
1269
|
-
for key in ['gis', 'telegestio', 'medidas', 'facturacio']:
|
|
1270
|
-
pulls_sep[ELEC_LABEL][key] += pulls_sep['others'][key]
|
|
1271
|
-
pulls_sep['others'][key] = []
|
|
1272
|
-
pulls_sep['others'].pop('custom')
|
|
1273
|
-
pulls_sep['others'].pop('traduccions')
|
|
1274
|
-
pulls_sep[COMMON_KEY] = pulls_sep.pop('others')
|
|
1275
|
-
index_bug = label_keys.index('bug')
|
|
1276
|
-
label_keys.pop(index_bug)
|
|
1277
|
-
label_keys.append('bug')
|
|
1278
|
-
|
|
1279
|
-
# TOP FEATURES
|
|
1280
|
-
logger.info('Writting top feature on {}/:'.format(changelog_path))
|
|
1281
|
-
with open('{}/{}'.format(changelog_path, top_file), 'w') as f:
|
|
1282
|
-
f.write("# TOP FEATURES version {milestone}\n".format(milestone=milestone))
|
|
1283
|
-
for pull in top_pulls:
|
|
1284
|
-
f.write(print_item(pull))
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
# CHANGELOGS
|
|
1288
|
-
logger.info('Writting changelog on {}/:'.format(changelog_path))
|
|
1289
|
-
with open('{}/{}'.format(changelog_path, changelog_file), 'w') as f:
|
|
1290
|
-
f.write("# Changelog version {milestone}\n".format(milestone=milestone))
|
|
1291
|
-
for type_l in TYPE_LABELS + [COMMON_KEY]:
|
|
1292
|
-
f.write('\n## {key}\n'.format(key=type_l.upper()))
|
|
1293
|
-
for key in label_keys:
|
|
1294
|
-
pulls = pulls_sep[type_l].get(key,[])
|
|
1295
|
-
if pulls:
|
|
1296
|
-
f.write('\n### {key}\n'.format(key=key.upper()))
|
|
1297
|
-
for pull in pulls:
|
|
1298
|
-
f.write(print_item(pull))
|
|
1299
|
-
if show_issues:
|
|
1300
|
-
f.write('\n# Issues: \n')
|
|
1301
|
-
for issue in isses_desc:
|
|
1302
|
-
f.write(print_item(issue))
|
|
1303
|
-
if other_desc:
|
|
1304
|
-
f.write('\n# Others : \n')
|
|
1305
|
-
for pull in other_desc:
|
|
1306
|
-
f.write(print_item(pull))
|
|
1307
|
-
logger.info(' {}/{}'.format(changelog_path, changelog_file))
|
|
1308
|
-
with open('{}/{}'.format(changelog_path, detailed_file) , 'w') as f:
|
|
1309
|
-
f.write("# Detalles version {milestone}\n".format(milestone=milestone))
|
|
1310
|
-
for type_l in TYPE_LABELS + [COMMON_KEY]:
|
|
1311
|
-
f.write('\n## {key}\n'.format(key=type_l.upper()))
|
|
1312
|
-
for key in label_keys:
|
|
1313
|
-
pulls = pulls_sep[type_l].get(key,[])
|
|
1314
|
-
if pulls:
|
|
1315
|
-
f.write('\n### {key}\n'.format(key=key.upper()))
|
|
1316
|
-
for pull in pulls:
|
|
1317
|
-
f.write(print_item_detail(pull, key))
|
|
1318
|
-
if show_issues:
|
|
1319
|
-
logger.info('\n# Issues: \n')
|
|
1320
|
-
for issue in isses_desc:
|
|
1321
|
-
f.write(print_item_detail(issue, key))
|
|
1322
|
-
if other_desc:
|
|
1323
|
-
print('\n# Others : \n')
|
|
1324
|
-
for pull in other_desc:
|
|
1325
|
-
f.write(print_item_detail(pull, key))
|
|
1326
|
-
logger.info(' {}/{}'.format(changelog_path, detailed_file))
|
|
1327
|
-
return True
|
|
1082
|
+
make_changelog(milestone, show_issues=show_issues,
|
|
1083
|
+
changelog_path=changelog_path,
|
|
1084
|
+
owner=owner, repository=repository)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from __future__ import (
|
|
3
|
+
with_statement, absolute_import, unicode_literals, print_function
|
|
4
|
+
)
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from osconf import config_from_environment
|
|
8
|
+
def is_github_token_valid(token):
|
|
9
|
+
headers = {'Authorization': 'token {token}'.format(token=token)}
|
|
10
|
+
url = "https://api.github.com/user"
|
|
11
|
+
|
|
12
|
+
r = requests.get(url, headers=headers)
|
|
13
|
+
|
|
14
|
+
return r.status_code == 200
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def github_config(**config):
|
|
18
|
+
def validate(_config):
|
|
19
|
+
if 'token' not in _config or not _config['token']:
|
|
20
|
+
raise EnvironmentError("GITHUB_TOKEN variable not provided")
|
|
21
|
+
if not is_github_token_valid(_config['token']):
|
|
22
|
+
raise EnvironmentError("GITHUB_TOKEN not valid or expired")
|
|
23
|
+
|
|
24
|
+
res = config_from_environment('GITHUB', ['token'], **config)
|
|
25
|
+
validate(res)
|
|
26
|
+
return res
|
|
@@ -3,9 +3,11 @@ README.md
|
|
|
3
3
|
requirements.txt
|
|
4
4
|
setup.py
|
|
5
5
|
apply_pr/__init__.py
|
|
6
|
+
apply_pr/changelog.py
|
|
6
7
|
apply_pr/cli.py
|
|
7
8
|
apply_pr/exceptions.py
|
|
8
9
|
apply_pr/fabfile.py
|
|
10
|
+
apply_pr/github_utils.py
|
|
9
11
|
apply_pr/version.py
|
|
10
12
|
apply_pr.egg-info/PKG-INFO
|
|
11
13
|
apply_pr.egg-info/SOURCES.txt
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|