207 - p1, |
209 - p1, |
208 - p2. |
210 - p2. |
209 Any item can be missing from the dictionary. If filename is missing, |
211 Any item can be missing from the dictionary. If filename is missing, |
210 fileobj did not contain a patch. Caller must unlink filename when done.''' |
212 fileobj did not contain a patch. Caller must unlink filename when done.''' |
211 |
213 |
|
214 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-') |
|
215 tmpfp = os.fdopen(fd, r'wb') |
|
216 try: |
|
217 yield _extract(ui, fileobj, tmpname, tmpfp) |
|
218 finally: |
|
219 tmpfp.close() |
|
220 os.unlink(tmpname) |
|
221 |
|
222 def _extract(ui, fileobj, tmpname, tmpfp): |
|
223 |
212 # attempt to detect the start of a patch |
224 # attempt to detect the start of a patch |
213 # (this heuristic is borrowed from quilt) |
225 # (this heuristic is borrowed from quilt) |
214 diffre = re.compile(br'^(?:Index:[ \t]|diff[ \t]-|RCS file: |' |
226 diffre = re.compile(br'^(?:Index:[ \t]|diff[ \t]-|RCS file: |' |
215 br'retrieving revision [0-9]+(\.[0-9]+)*$|' |
227 br'retrieving revision [0-9]+(\.[0-9]+)*$|' |
216 br'---[ \t].*?^\+\+\+[ \t]|' |
228 br'---[ \t].*?^\+\+\+[ \t]|' |
217 br'\*\*\*[ \t].*?^---[ \t])', |
229 br'\*\*\*[ \t].*?^---[ \t])', |
218 re.MULTILINE | re.DOTALL) |
230 re.MULTILINE | re.DOTALL) |
219 |
231 |
220 data = {} |
232 data = {} |
221 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-') |
233 |
222 tmpfp = os.fdopen(fd, r'wb') |
234 msg = pycompat.emailparser().parse(fileobj) |
223 try: |
235 |
224 msg = pycompat.emailparser().parse(fileobj) |
236 subject = msg[r'Subject'] and mail.headdecode(msg[r'Subject']) |
225 |
237 data['user'] = msg[r'From'] and mail.headdecode(msg[r'From']) |
226 subject = msg[r'Subject'] and mail.headdecode(msg[r'Subject']) |
238 if not subject and not data['user']: |
227 data['user'] = msg[r'From'] and mail.headdecode(msg[r'From']) |
239 # Not an email, restore parsed headers if any |
228 if not subject and not data['user']: |
240 subject = '\n'.join(': '.join(map(encoding.strtolocal, h)) |
229 # Not an email, restore parsed headers if any |
241 for h in msg.items()) + '\n' |
230 subject = '\n'.join(': '.join(map(encoding.strtolocal, h)) |
242 |
231 for h in msg.items()) + '\n' |
243 # should try to parse msg['Date'] |
232 |
244 parents = [] |
233 # should try to parse msg['Date'] |
245 |
234 parents = [] |
246 if subject: |
235 |
247 if subject.startswith('[PATCH'): |
236 if subject: |
248 pend = subject.find(']') |
237 if subject.startswith('[PATCH'): |
249 if pend >= 0: |
238 pend = subject.find(']') |
250 subject = subject[pend + 1:].lstrip() |
239 if pend >= 0: |
251 subject = re.sub(br'\n[ \t]+', ' ', subject) |
240 subject = subject[pend + 1:].lstrip() |
252 ui.debug('Subject: %s\n' % subject) |
241 subject = re.sub(br'\n[ \t]+', ' ', subject) |
253 if data['user']: |
242 ui.debug('Subject: %s\n' % subject) |
254 ui.debug('From: %s\n' % data['user']) |
243 if data['user']: |
255 diffs_seen = 0 |
244 ui.debug('From: %s\n' % data['user']) |
256 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch') |
245 diffs_seen = 0 |
257 message = '' |
246 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch') |
258 for part in msg.walk(): |
247 message = '' |
259 content_type = pycompat.bytestr(part.get_content_type()) |
248 for part in msg.walk(): |
260 ui.debug('Content-Type: %s\n' % content_type) |
249 content_type = pycompat.bytestr(part.get_content_type()) |
261 if content_type not in ok_types: |
250 ui.debug('Content-Type: %s\n' % content_type) |
262 continue |
251 if content_type not in ok_types: |
263 payload = part.get_payload(decode=True) |
252 continue |
264 m = diffre.search(payload) |
253 payload = part.get_payload(decode=True) |
265 if m: |
254 m = diffre.search(payload) |
266 hgpatch = False |
255 if m: |
267 hgpatchheader = False |
256 hgpatch = False |
268 ignoretext = False |
257 hgpatchheader = False |
269 |
258 ignoretext = False |
270 ui.debug('found patch at byte %d\n' % m.start(0)) |
259 |
271 diffs_seen += 1 |
260 ui.debug('found patch at byte %d\n' % m.start(0)) |
272 cfp = stringio() |
261 diffs_seen += 1 |
273 for line in payload[:m.start(0)].splitlines(): |
262 cfp = stringio() |
274 if line.startswith('# HG changeset patch') and not hgpatch: |
263 for line in payload[:m.start(0)].splitlines(): |
275 ui.debug('patch generated by hg export\n') |
264 if line.startswith('# HG changeset patch') and not hgpatch: |
276 hgpatch = True |
265 ui.debug('patch generated by hg export\n') |
277 hgpatchheader = True |
266 hgpatch = True |
278 # drop earlier commit message content |
267 hgpatchheader = True |
279 cfp.seek(0) |
268 # drop earlier commit message content |
280 cfp.truncate() |
269 cfp.seek(0) |
281 subject = None |
270 cfp.truncate() |
282 elif hgpatchheader: |
271 subject = None |
283 if line.startswith('# User '): |
272 elif hgpatchheader: |
284 data['user'] = line[7:] |
273 if line.startswith('# User '): |
285 ui.debug('From: %s\n' % data['user']) |
274 data['user'] = line[7:] |
286 elif line.startswith("# Parent "): |
275 ui.debug('From: %s\n' % data['user']) |
287 parents.append(line[9:].lstrip()) |
276 elif line.startswith("# Parent "): |
288 elif line.startswith("# "): |
277 parents.append(line[9:].lstrip()) |
289 for header, key in patchheadermap: |
278 elif line.startswith("# "): |
290 prefix = '# %s ' % header |
279 for header, key in patchheadermap: |
291 if line.startswith(prefix): |
280 prefix = '# %s ' % header |
292 data[key] = line[len(prefix):] |
281 if line.startswith(prefix): |
293 else: |
282 data[key] = line[len(prefix):] |
294 hgpatchheader = False |
283 else: |
295 elif line == '---': |
284 hgpatchheader = False |
296 ignoretext = True |
285 elif line == '---': |
297 if not hgpatchheader and not ignoretext: |
286 ignoretext = True |
298 cfp.write(line) |
287 if not hgpatchheader and not ignoretext: |
299 cfp.write('\n') |
288 cfp.write(line) |
300 message = cfp.getvalue() |
289 cfp.write('\n') |
301 if tmpfp: |
290 message = cfp.getvalue() |
302 tmpfp.write(payload) |
291 if tmpfp: |
303 if not payload.endswith('\n'): |
292 tmpfp.write(payload) |
304 tmpfp.write('\n') |
293 if not payload.endswith('\n'): |
305 elif not diffs_seen and message and content_type == 'text/plain': |
294 tmpfp.write('\n') |
306 message += '\n' + payload |
295 elif not diffs_seen and message and content_type == 'text/plain': |
|
296 message += '\n' + payload |
|
297 except: # re-raises |
|
298 tmpfp.close() |
|
299 os.unlink(tmpname) |
|
300 raise |
|
301 |
307 |
302 if subject and not message.startswith(subject): |
308 if subject and not message.startswith(subject): |
303 message = '%s\n%s' % (subject, message) |
309 message = '%s\n%s' % (subject, message) |
304 data['message'] = message |
310 data['message'] = message |
305 tmpfp.close() |
311 tmpfp.close() |