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 |
|
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 |
|
|
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 |
|
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 |
|
|
1413 | # first parent. | |
1410 |
|
|
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 |
|
|
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 |
|
|
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 |
|
1485 | patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix, | |
1439 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
236 | subject = msg[r'Subject'] and mail.headdecode(msg[r'Subject']) | |
227 |
|
|
237 | data['user'] = msg[r'From'] and mail.headdecode(msg[r'From']) | |
228 |
|
|
238 | if not subject and not data['user']: | |
229 |
|
|
239 | # Not an email, restore parsed headers if any | |
230 |
|
|
240 | subject = '\n'.join(': '.join(map(encoding.strtolocal, h)) | |
231 |
|
|
241 | for h in msg.items()) + '\n' | |
232 |
|
242 | |||
233 |
|
|
243 | # should try to parse msg['Date'] | |
234 |
|
|
244 | parents = [] | |
235 |
|
245 | |||
236 |
|
|
246 | if subject: | |
237 |
|
|
247 | if subject.startswith('[PATCH'): | |
238 |
|
|
248 | pend = subject.find(']') | |
239 |
|
|
249 | if pend >= 0: | |
240 |
|
|
250 | subject = subject[pend + 1:].lstrip() | |
241 |
|
|
251 | subject = re.sub(br'\n[ \t]+', ' ', subject) | |
242 |
|
|
252 | ui.debug('Subject: %s\n' % subject) | |
243 |
|
|
253 | if data['user']: | |
244 |
|
|
254 | ui.debug('From: %s\n' % data['user']) | |
245 |
|
|
255 | diffs_seen = 0 | |
246 |
|
|
256 | ok_types = ('text/plain', 'text/x-diff', 'text/x-patch') | |
247 |
|
|
257 | message = '' | |
248 |
|
|
258 | for part in msg.walk(): | |
249 |
|
|
259 | content_type = pycompat.bytestr(part.get_content_type()) | |
250 |
|
|
260 | ui.debug('Content-Type: %s\n' % content_type) | |
251 |
|
|
261 | if content_type not in ok_types: | |
252 |
|
|
262 | continue | |
253 |
|
|
263 | payload = part.get_payload(decode=True) | |
254 |
|
|
264 | m = diffre.search(payload) | |
255 |
|
|
265 | if m: | |
256 |
|
|
266 | hgpatch = False | |
257 |
|
|
267 | hgpatchheader = False | |
258 |
|
|
268 | ignoretext = False | |
259 |
|
269 | |||
260 |
|
|
270 | ui.debug('found patch at byte %d\n' % m.start(0)) | |
261 |
|
|
271 | diffs_seen += 1 | |
262 |
|
|
272 | cfp = stringio() | |
263 |
|
|
273 | for line in payload[:m.start(0)].splitlines(): | |
264 |
|
|
274 | if line.startswith('# HG changeset patch') and not hgpatch: | |
265 |
|
|
275 | ui.debug('patch generated by hg export\n') | |
266 |
|
|
276 | hgpatch = True | |
267 |
|
|
277 | hgpatchheader = True | |
268 |
|
|
278 | # drop earlier commit message content | |
269 |
|
|
279 | cfp.seek(0) | |
270 |
|
|
280 | cfp.truncate() | |
271 |
|
|
281 | subject = None | |
272 |
|
|
282 | elif hgpatchheader: | |
273 |
|
|
283 | if line.startswith('# User '): | |
274 |
|
|
284 | data['user'] = line[7:] | |
275 |
|
|
285 | ui.debug('From: %s\n' % data['user']) | |
276 |
|
|
286 | elif line.startswith("# Parent "): | |
277 |
|
|
287 | parents.append(line[9:].lstrip()) | |
278 |
|
|
288 | elif line.startswith("# "): | |
279 |
|
|
289 | for header, key in patchheadermap: | |
280 |
|
|
290 | prefix = '# %s ' % header | |
281 |
|
|
291 | if line.startswith(prefix): | |
282 |
|
|
292 | data[key] = line[len(prefix):] | |
283 |
|
|
293 | else: | |
284 |
|
|
294 | hgpatchheader = False | |
285 |
|
|
295 | elif line == '---': | |
286 |
|
|
296 | ignoretext = True | |
287 |
|
|
297 | if not hgpatchheader and not ignoretext: | |
288 |
|
|
298 | cfp.write(line) | |
289 |
|
|
299 | cfp.write('\n') | |
290 |
|
|
300 | message = cfp.getvalue() | |
291 |
|
|
301 | if tmpfp: | |
292 |
|
|
302 | tmpfp.write(payload) | |
293 |
|
|
303 | if not payload.endswith('\n'): | |
294 |
|
|
304 | tmpfp.write('\n') | |
295 |
|
|
305 | elif not diffs_seen and message and content_type == 'text/plain': | |
296 |
|
|
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