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