##// END OF EJS Templates
patch: make extract() a context manager (API)...
Gregory Szorc -
r37639:5537d8f5 default
parent child Browse files
Show More
@@ -1379,141 +1379,139 b' def tryimportone(ui, repo, patchdata, pa'
1379 1379 strip = opts["strip"]
1380 1380 prefix = opts["prefix"]
1381 1381 sim = float(opts.get('similarity') or 0)
1382
1382 1383 if not tmpname:
1383 return (None, None, False)
1384 return None, None, False
1384 1385
1385 1386 rejects = False
1386 1387
1387 try:
1388 cmdline_message = logmessage(ui, opts)
1389 if cmdline_message:
1390 # pickup the cmdline msg
1391 message = cmdline_message
1392 elif message:
1393 # pickup the patch msg
1394 message = message.strip()
1395 else:
1396 # launch the editor
1397 message = None
1398 ui.debug('message:\n%s\n' % (message or ''))
1399
1400 if len(parents) == 1:
1401 parents.append(repo[nullid])
1402 if opts.get('exact'):
1403 if not nodeid or not p1:
1404 raise error.Abort(_('not a Mercurial patch'))
1388 cmdline_message = logmessage(ui, opts)
1389 if cmdline_message:
1390 # pickup the cmdline msg
1391 message = cmdline_message
1392 elif message:
1393 # pickup the patch msg
1394 message = message.strip()
1395 else:
1396 # launch the editor
1397 message = None
1398 ui.debug('message:\n%s\n' % (message or ''))
1399
1400 if len(parents) == 1:
1401 parents.append(repo[nullid])
1402 if opts.get('exact'):
1403 if not nodeid or not p1:
1404 raise error.Abort(_('not a Mercurial patch'))
1405 p1 = repo[p1]
1406 p2 = repo[p2 or nullid]
1407 elif p2:
1408 try:
1405 1409 p1 = repo[p1]
1406 p2 = repo[p2 or nullid]
1407 elif p2:
1408 try:
1409 p1 = repo[p1]
1410 p2 = repo[p2]
1411 # Without any options, consider p2 only if the
1412 # patch is being applied on top of the recorded
1413 # first parent.
1414 if p1 != parents[0]:
1415 p1 = parents[0]
1416 p2 = repo[nullid]
1417 except error.RepoError:
1418 p1, p2 = parents
1419 if p2.node() == nullid:
1420 ui.warn(_("warning: import the patch as a normal revision\n"
1421 "(use --exact to import the patch as a merge)\n"))
1410 p2 = repo[p2]
1411 # Without any options, consider p2 only if the
1412 # patch is being applied on top of the recorded
1413 # first parent.
1414 if p1 != parents[0]:
1415 p1 = parents[0]
1416 p2 = repo[nullid]
1417 except error.RepoError:
1418 p1, p2 = parents
1419 if p2.node() == nullid:
1420 ui.warn(_("warning: import the patch as a normal revision\n"
1421 "(use --exact to import the patch as a merge)\n"))
1422 else:
1423 p1, p2 = parents
1424
1425 n = None
1426 if update:
1427 if p1 != parents[0]:
1428 updatefunc(repo, p1.node())
1429 if p2 != parents[1]:
1430 repo.setparents(p1.node(), p2.node())
1431
1432 if opts.get('exact') or importbranch:
1433 repo.dirstate.setbranch(branch or 'default')
1434
1435 partial = opts.get('partial', False)
1436 files = set()
1437 try:
1438 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1439 files=files, eolmode=None, similarity=sim / 100.0)
1440 except error.PatchError as e:
1441 if not partial:
1442 raise error.Abort(pycompat.bytestr(e))
1443 if partial:
1444 rejects = True
1445
1446 files = list(files)
1447 if nocommit:
1448 if message:
1449 msgs.append(message)
1422 1450 else:
1423 p1, p2 = parents
1424
1425 n = None
1426 if update:
1427 if p1 != parents[0]:
1428 updatefunc(repo, p1.node())
1429 if p2 != parents[1]:
1430 repo.setparents(p1.node(), p2.node())
1431
1432 if opts.get('exact') or importbranch:
1433 repo.dirstate.setbranch(branch or 'default')
1434
1435 partial = opts.get('partial', False)
1451 if opts.get('exact') or p2:
1452 # If you got here, you either use --force and know what
1453 # you are doing or used --exact or a merge patch while
1454 # being updated to its first parent.
1455 m = None
1456 else:
1457 m = scmutil.matchfiles(repo, files or [])
1458 editform = mergeeditform(repo[None], 'import.normal')
1459 if opts.get('exact'):
1460 editor = None
1461 else:
1462 editor = getcommiteditor(editform=editform,
1463 **pycompat.strkwargs(opts))
1464 extra = {}
1465 for idfunc in extrapreimport:
1466 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1467 overrides = {}
1468 if partial:
1469 overrides[('ui', 'allowemptycommit')] = True
1470 with repo.ui.configoverride(overrides, 'import'):
1471 n = repo.commit(message, user,
1472 date, match=m,
1473 editor=editor, extra=extra)
1474 for idfunc in extrapostimport:
1475 extrapostimportmap[idfunc](repo[n])
1476 else:
1477 if opts.get('exact') or importbranch:
1478 branch = branch or 'default'
1479 else:
1480 branch = p1.branch()
1481 store = patch.filestore()
1482 try:
1436 1483 files = set()
1437 1484 try:
1438 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1439 files=files, eolmode=None, similarity=sim / 100.0)
1485 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1486 files, eolmode=None)
1440 1487 except error.PatchError as e:
1441 if not partial:
1442 raise error.Abort(pycompat.bytestr(e))
1443 if partial:
1444 rejects = True
1445
1446 files = list(files)
1447 if nocommit:
1448 if message:
1449 msgs.append(message)
1488 raise error.Abort(stringutil.forcebytestr(e))
1489 if opts.get('exact'):
1490 editor = None
1450 1491 else:
1451 if opts.get('exact') or p2:
1452 # If you got here, you either use --force and know what
1453 # you are doing or used --exact or a merge patch while
1454 # being updated to its first parent.
1455 m = None
1456 else:
1457 m = scmutil.matchfiles(repo, files or [])
1458 editform = mergeeditform(repo[None], 'import.normal')
1459 if opts.get('exact'):
1460 editor = None
1461 else:
1462 editor = getcommiteditor(editform=editform,
1463 **pycompat.strkwargs(opts))
1464 extra = {}
1465 for idfunc in extrapreimport:
1466 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1467 overrides = {}
1468 if partial:
1469 overrides[('ui', 'allowemptycommit')] = True
1470 with repo.ui.configoverride(overrides, 'import'):
1471 n = repo.commit(message, user,
1472 date, match=m,
1473 editor=editor, extra=extra)
1474 for idfunc in extrapostimport:
1475 extrapostimportmap[idfunc](repo[n])
1476 else:
1477 if opts.get('exact') or importbranch:
1478 branch = branch or 'default'
1479 else:
1480 branch = p1.branch()
1481 store = patch.filestore()
1482 try:
1483 files = set()
1484 try:
1485 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1486 files, eolmode=None)
1487 except error.PatchError as e:
1488 raise error.Abort(stringutil.forcebytestr(e))
1489 if opts.get('exact'):
1490 editor = None
1491 else:
1492 editor = getcommiteditor(editform='import.bypass')
1493 memctx = context.memctx(repo, (p1.node(), p2.node()),
1494 message,
1495 files=files,
1496 filectxfn=store,
1497 user=user,
1498 date=date,
1499 branch=branch,
1500 editor=editor)
1501 n = memctx.commit()
1502 finally:
1503 store.close()
1504 if opts.get('exact') and nocommit:
1505 # --exact with --no-commit is still useful in that it does merge
1506 # and branch bits
1507 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1508 elif opts.get('exact') and hex(n) != nodeid:
1509 raise error.Abort(_('patch is damaged or loses information'))
1510 msg = _('applied to working directory')
1511 if n:
1512 # i18n: refers to a short changeset id
1513 msg = _('created %s') % short(n)
1514 return (msg, n, rejects)
1515 finally:
1516 os.unlink(tmpname)
1492 editor = getcommiteditor(editform='import.bypass')
1493 memctx = context.memctx(repo, (p1.node(), p2.node()),
1494 message,
1495 files=files,
1496 filectxfn=store,
1497 user=user,
1498 date=date,
1499 branch=branch,
1500 editor=editor)
1501 n = memctx.commit()
1502 finally:
1503 store.close()
1504 if opts.get('exact') and nocommit:
1505 # --exact with --no-commit is still useful in that it does merge
1506 # and branch bits
1507 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1508 elif opts.get('exact') and hex(n) != nodeid:
1509 raise error.Abort(_('patch is damaged or loses information'))
1510 msg = _('applied to working directory')
1511 if n:
1512 # i18n: refers to a short changeset id
1513 msg = _('created %s') % short(n)
1514 return msg, n, rejects
1517 1515
1518 1516 # facility to let extensions include additional data in an exported patch
1519 1517 # list of identifiers to be executed in order
@@ -3089,11 +3089,10 b' def import_(ui, repo, patch1=None, *patc'
3089 3089
3090 3090 haspatch = False
3091 3091 for hunk in patch.split(patchfile):
3092 patchdata = patch.extract(ui, hunk)
3093
3094 msg, node, rej = cmdutil.tryimportone(ui, repo, patchdata,
3095 parents, opts,
3096 msgs, hg.clean)
3092 with patch.extract(ui, hunk) as patchdata:
3093 msg, node, rej = cmdutil.tryimportone(ui, repo, patchdata,
3094 parents, opts,
3095 msgs, hg.clean)
3097 3096 if msg:
3098 3097 haspatch = True
3099 3098 ui.note(msg + '\n')
@@ -9,6 +9,7 b''
9 9 from __future__ import absolute_import, print_function
10 10
11 11 import collections
12 import contextlib
12 13 import copy
13 14 import difflib
14 15 import email
@@ -192,6 +193,7 b" patchheadermap = [('Date', 'date'),"
192 193 ('Node ID', 'nodeid'),
193 194 ]
194 195
196 @contextlib.contextmanager
195 197 def extract(ui, fileobj):
196 198 '''extract patch from data read from fileobj.
197 199
@@ -209,6 +211,16 b' def extract(ui, fileobj):'
209 211 Any item can be missing from the dictionary. If filename is missing,
210 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 224 # attempt to detect the start of a patch
213 225 # (this heuristic is borrowed from quilt)
214 226 diffre = re.compile(br'^(?:Index:[ \t]|diff[ \t]-|RCS file: |'
@@ -218,86 +230,80 b' def extract(ui, fileobj):'
218 230 re.MULTILINE | re.DOTALL)
219 231
220 232 data = {}
221 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
222 tmpfp = os.fdopen(fd, r'wb')
223 try:
224 msg = pycompat.emailparser().parse(fileobj)
233
234 msg = pycompat.emailparser().parse(fileobj)
225 235
226 subject = msg[r'Subject'] and mail.headdecode(msg[r'Subject'])
227 data['user'] = msg[r'From'] and mail.headdecode(msg[r'From'])
228 if not subject and not data['user']:
229 # Not an email, restore parsed headers if any
230 subject = '\n'.join(': '.join(map(encoding.strtolocal, h))
231 for h in msg.items()) + '\n'
236 subject = msg[r'Subject'] and mail.headdecode(msg[r'Subject'])
237 data['user'] = msg[r'From'] and mail.headdecode(msg[r'From'])
238 if not subject and not data['user']:
239 # Not an email, restore parsed headers if any
240 subject = '\n'.join(': '.join(map(encoding.strtolocal, h))
241 for h in msg.items()) + '\n'
232 242
233 # should try to parse msg['Date']
234 parents = []
243 # should try to parse msg['Date']
244 parents = []
235 245
236 if subject:
237 if subject.startswith('[PATCH'):
238 pend = subject.find(']')
239 if pend >= 0:
240 subject = subject[pend + 1:].lstrip()
241 subject = re.sub(br'\n[ \t]+', ' ', subject)
242 ui.debug('Subject: %s\n' % subject)
243 if data['user']:
244 ui.debug('From: %s\n' % data['user'])
245 diffs_seen = 0
246 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
247 message = ''
248 for part in msg.walk():
249 content_type = pycompat.bytestr(part.get_content_type())
250 ui.debug('Content-Type: %s\n' % content_type)
251 if content_type not in ok_types:
252 continue
253 payload = part.get_payload(decode=True)
254 m = diffre.search(payload)
255 if m:
256 hgpatch = False
257 hgpatchheader = False
258 ignoretext = False
246 if subject:
247 if subject.startswith('[PATCH'):
248 pend = subject.find(']')
249 if pend >= 0:
250 subject = subject[pend + 1:].lstrip()
251 subject = re.sub(br'\n[ \t]+', ' ', subject)
252 ui.debug('Subject: %s\n' % subject)
253 if data['user']:
254 ui.debug('From: %s\n' % data['user'])
255 diffs_seen = 0
256 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
257 message = ''
258 for part in msg.walk():
259 content_type = pycompat.bytestr(part.get_content_type())
260 ui.debug('Content-Type: %s\n' % content_type)
261 if content_type not in ok_types:
262 continue
263 payload = part.get_payload(decode=True)
264 m = diffre.search(payload)
265 if m:
266 hgpatch = False
267 hgpatchheader = False
268 ignoretext = False
259 269
260 ui.debug('found patch at byte %d\n' % m.start(0))
261 diffs_seen += 1
262 cfp = stringio()
263 for line in payload[:m.start(0)].splitlines():
264 if line.startswith('# HG changeset patch') and not hgpatch:
265 ui.debug('patch generated by hg export\n')
266 hgpatch = True
267 hgpatchheader = True
268 # drop earlier commit message content
269 cfp.seek(0)
270 cfp.truncate()
271 subject = None
272 elif hgpatchheader:
273 if line.startswith('# User '):
274 data['user'] = line[7:]
275 ui.debug('From: %s\n' % data['user'])
276 elif line.startswith("# Parent "):
277 parents.append(line[9:].lstrip())
278 elif line.startswith("# "):
279 for header, key in patchheadermap:
280 prefix = '# %s ' % header
281 if line.startswith(prefix):
282 data[key] = line[len(prefix):]
283 else:
284 hgpatchheader = False
285 elif line == '---':
286 ignoretext = True
287 if not hgpatchheader and not ignoretext:
288 cfp.write(line)
289 cfp.write('\n')
290 message = cfp.getvalue()
291 if tmpfp:
292 tmpfp.write(payload)
293 if not payload.endswith('\n'):
294 tmpfp.write('\n')
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
270 ui.debug('found patch at byte %d\n' % m.start(0))
271 diffs_seen += 1
272 cfp = stringio()
273 for line in payload[:m.start(0)].splitlines():
274 if line.startswith('# HG changeset patch') and not hgpatch:
275 ui.debug('patch generated by hg export\n')
276 hgpatch = True
277 hgpatchheader = True
278 # drop earlier commit message content
279 cfp.seek(0)
280 cfp.truncate()
281 subject = None
282 elif hgpatchheader:
283 if line.startswith('# User '):
284 data['user'] = line[7:]
285 ui.debug('From: %s\n' % data['user'])
286 elif line.startswith("# Parent "):
287 parents.append(line[9:].lstrip())
288 elif line.startswith("# "):
289 for header, key in patchheadermap:
290 prefix = '# %s ' % header
291 if line.startswith(prefix):
292 data[key] = line[len(prefix):]
293 else:
294 hgpatchheader = False
295 elif line == '---':
296 ignoretext = True
297 if not hgpatchheader and not ignoretext:
298 cfp.write(line)
299 cfp.write('\n')
300 message = cfp.getvalue()
301 if tmpfp:
302 tmpfp.write(payload)
303 if not payload.endswith('\n'):
304 tmpfp.write('\n')
305 elif not diffs_seen and message and content_type == 'text/plain':
306 message += '\n' + payload
301 307
302 308 if subject and not message.startswith(subject):
303 309 message = '%s\n%s' % (subject, message)
@@ -310,8 +316,7 b' def extract(ui, fileobj):'
310 316
311 317 if diffs_seen:
312 318 data['filename'] = tmpname
313 else:
314 os.unlink(tmpname)
319
315 320 return data
316 321
317 322 class patchmeta(object):
General Comments 0
You need to be logged in to leave comments. Login now