-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpatches.py
252 lines (212 loc) · 9.15 KB
/
patches.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# coding=utf8
# This script generates a ~/patches dir containing all patches from
# the mailing lists.
#
# It assumes that offlineimap has been used to download the mailing
# list messages to ~/gmail.
import collections
import cStringIO
import datetime
import email.utils
import mailbox
import os.path
import json
import re
SAFE_SUBJECT_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz'
PATCH_REGEXP = re.compile(r'\[[A-Z ]*PATCH')
CACHE_VERSION = 9
KNOWN_EMAILS = {
'[email protected]': u'Marek Olšák',
'[email protected]': 'Roland Scheidegger',
'[email protected]': u'Ville Syrjälä',
'[email protected]': '(bugzilla)',
'[email protected]': u'José Fonseca',
'[email protected]': 'Alex Deucher',
'[email protected]': 'Junyan He',
'[email protected]': 'Jerome Glisse',
'[email protected]': 'Chad Versace',
'[email protected]': 'Adrian Marius Negreanu',
'[email protected]': 'Ritvik Sharma',
'[email protected]': '(bounce)',
'[email protected]': 'Dmitry Cherkassov',
'[email protected]': 'Tom Stellard',
'[email protected]': 'Tom Stellard',
'[email protected]': 'Oliver McFadden',
'[email protected]': 'Anuj Phogat',
'[email protected]': 'James Benton',
'[email protected]': 'Alex Wu',
'[email protected]': 'Mandeep Singh Baines',
'[email protected]': u'José Fonseca',
'[email protected]': 'Zhigang Gong',
'[email protected]': 'Juan Zhao',
'[email protected]': 'Alexander von Gluck IV',
'[email protected]': 'Christoph Bumiller',
'[email protected]': 'Tom Fogal',
'[email protected]': u'Christian König',
'[email protected]': 'Younes Manton',
'[email protected]': u'Tapani Pälli',
}
PATCHES_DIR = os.path.expanduser('~/patches')
try:
os.makedirs(PATCHES_DIR)
except OSError:
pass
MessageInfo = collections.namedtuple('MessageInfo', (
'timestamp', 'key', 'subject', 'in_reply_to', 'message_id', 'analysis', 'sender'))
PatchAnalysis = collections.namedtuple('PatchAnalysis', (
'diffs_found',))
def decode_rfc2047_part(s, encoding):
if encoding is None or encoding in ['y', 'n', 'a', 'j']:
return s
return s.decode(encoding)
def decode_header(header):
parts = email.header.decode_header(header.replace('\n', ''))
return ' '.join(decode_rfc2047_part(s, e) for s, e in parts)
def safe_subject(subject):
while True:
for prefix in ['[Piglit] ', '[Mesa-dev] ']:
if subject.startswith(prefix):
subject = subject[len(prefix):]
break
else:
break
result = ''
for c in subject:
if c in SAFE_SUBJECT_CHARS:
result += c
elif len(result) > 0 and result[-1] != '-':
result += '-'
return result
def nice_time(timestamp):
dt = datetime.datetime.utcfromtimestamp(timestamp)
return dt.strftime('%Y%m%d-%H%M%S')
def analyze_patch(subject, data):
# TODO: debug [Piglit] [PATCH 1/3] piglit util: new functions piglit_program_pipeline_check_status/quiet
# TODO: handle multipart messages, e.g. [Piglit] [PATCH] Check for glsl before compiling a shader.
# TODO: handle renames, e.g. [Piglit] [PATCH 4/8] move variable-index-read.sh and variable-index-write.sh to generated_tests
# TODO: handle mode changes, e.g. [Mesa-dev] [PATCH 1/6] intel: remove executable bit from C file
# TODO: handle multipart base64 messages, e.g. [Mesa-dev] [PATCH] Fix glXChooseFBConfig with GLX_DRAWABLE_TYPE GLX_DONT_CARE
print('Interpreting message {0!r}'.format(subject))
m = email.message_from_string(data)
if m.is_multipart():
print('Multipart message: {0!r}'.format(subject))
return PatchAnalysis(False)
data = m.get_payload(None, True)
state = 0
diffs_found = False
for line in data.split('\n'):
if state == 0:
if line == '---':
state = 1
elif state == 1:
if line.startswith('diff '):
state = 2
elif line == '-- ':
state = 0
elif state == 2:
if line.startswith('index '):
state = 1
diffs_found = True
elif line.startswith('new file mode '):
pass
else:
state = 1
return PatchAnalysis(diffs_found)
def make_patches_from_mail_folder(folder_name, summary_data, old_cache, new_cache):
print 'Making patches from mail folder {0}'.format(folder_name)
mbox_dir = os.path.join(os.path.expanduser('~/gmail'), folder_name)
mbox = mailbox.Maildir(mbox_dir, create = False)
stuff = []
print ' Gathering data from mailbox'.format(folder_name)
for key in mbox.keys():
if key in old_cache['msgs']:
summary = old_cache['msgs'][key]
else:
msg = mbox[key]
subject = decode_header(msg['Subject'])
# TODO: email.utils.mktime_tz makes slight errors around daylight savings time.
timestamp = email.utils.mktime_tz(email.utils.parsedate_tz(msg['Date']))
message_id = decode_header(msg['Message-Id'])
in_reply_to = decode_header(msg['In-Reply-To']).split()[0] if 'In-Reply-To' in msg else None
analysis = analyze_patch(subject, mbox.get_string(key))
sender = decode_header(msg['From'])
summary = MessageInfo(timestamp, key, subject, in_reply_to, message_id, analysis, sender)
stuff.append(summary)
new_cache['msgs'][key] = summary
stuff.sort()
print ' Creating patch files'.format(folder_name)
for msg_info in stuff:
if msg_info.subject.lower().startswith('re'):
continue
if not (PATCH_REGEXP.search(msg_info.subject) or msg_info.analysis.diffs_found):
continue
filename = '{0}-{1}.patch'.format(nice_time(msg_info.timestamp), safe_subject(msg_info.subject)[:40])
path = os.path.join(PATCHES_DIR, filename)
summary_data.append((msg_info.timestamp, msg_info.subject, path, msg_info.key))
if not os.path.exists(path):
msg_str = mbox.get_string(msg_info.key)
with open(path, 'w') as f:
f.write(msg_str)
print path
def short_sender(sender):
NAME_WIDTH = 16
real, addr = email.utils.parseaddr(sender)
if addr in KNOWN_EMAILS:
name = KNOWN_EMAILS[addr]
elif real:
name = real
else:
name = addr
name += ' ' * NAME_WIDTH
return name[0:NAME_WIDTH]
def output_reply_tree(cache):
with open(os.path.join(PATCHES_DIR, 'replies.txt'), 'w') as f:
msg_to_reply_map = collections.defaultdict(list)
for key in cache['msgs']:
msg_info = cache['msgs'][key]
msg_to_reply_map[msg_info.in_reply_to].append((msg_info.timestamp, msg_info.message_id, msg_info.subject, msg_info.analysis, msg_info.sender))
already_printed_message_ids = set()
def dump_tree(prefix, message_id):
for timestamp, reply_id, subject, analysis, sender in sorted(msg_to_reply_map[message_id]):
if analysis.diffs_found:
tickmark = '*'
elif not PATCH_REGEXP.search(subject) or subject.lower().startswith('re'):
tickmark = 'x'
else:
tickmark = '-'
f.write(nice_time(timestamp) + ' ' + short_sender(sender).encode('utf8') + prefix + tickmark + ' ' +
subject.encode('unicode_escape') + '\n')
if reply_id in already_printed_message_ids:
f.write(prefix + ' ...\n')
else:
already_printed_message_ids.add(reply_id)
dump_tree(prefix + ' ', reply_id)
dump_tree(' ', None)
def reconstitute_cache(cache):
msgs = cache['msgs']
for key in msgs:
timestamp, key, subject, in_reply_to, message_id, analysis, sender = msgs[key]
analysis = PatchAnalysis(*analysis)
msgs[key] = MessageInfo(timestamp, key, subject, in_reply_to, message_id, analysis, sender)
old_cache = {'cache_version': CACHE_VERSION, 'msgs': {}}
try:
with open(os.path.join(PATCHES_DIR, 'cache.json'), 'r') as f:
cache = json.load(f)
if 'cache_version' in cache and cache['cache_version'] == CACHE_VERSION:
old_cache = cache
reconstitute_cache(old_cache)
else:
print 'cache.json is out of date. Rebuilding.'
except Exception, e:
print 'Non-fatal error loading old cache.json: {0}'.format(e)
new_cache = {'cache_version': CACHE_VERSION, 'msgs': {}}
summary_data = []
for folder_name in ['Mesa-dev', 'Piglit']:
make_patches_from_mail_folder(folder_name, summary_data, old_cache, new_cache)
summary_data.sort()
with open(os.path.join(PATCHES_DIR, 'summary.txt'), 'w') as f:
f.write(''.join('git am -3 {2!r} # {0} {1!r}\n'.format(nice_time(timestamp), subject, path)
for timestamp, subject, path, _ in summary_data))
output_reply_tree(new_cache)
with open(os.path.join(PATCHES_DIR, 'cache.json'), 'w') as f:
json.dump(new_cache, f, indent=2)