# HG changeset patch # User Vadim Gelfer # Date 1151392153 25200 # Node ID 158d3d2ae070c2197ec393c04a40156e8762669d # Parent 18cf95ad3666585dc7b9340a692c1b9ce21344ca import: parse email messages diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -12,7 +12,7 @@ demandload(globals(), "os re sys signal demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo") demandload(globals(), "fnmatch mdiff random signal tempfile time") demandload(globals(), "traceback errno socket version struct atexit sets bz2") -demandload(globals(), "archival changegroup") +demandload(globals(), "archival cStringIO changegroup email.Parser") demandload(globals(), "hgweb.server sshserver") class UnknownCommand(Exception): @@ -1719,11 +1719,15 @@ def import_(ui, repo, patch1, *patches, If there are outstanding changes in the working directory, import will abort unless given the -f flag. - If a patch looks like a mail message (its first line starts with - "From " or looks like an RFC822 header), it will not be applied - unless the -f option is used. The importer neither parses nor - discards mail headers, so use -f only to override the "mailness" - safety check, not to import a real mail message. + You can import a patch straight from a mail message. Even patches + as attachments work (body part must be type text/plain or + text/x-patch to be used). Sender and subject line of email + message are used as default committer and commit message. Any + text/plain body part before first diff is added to commit message. + + If imported patch was generated by hg export, user and description + from patch override values from message headers and body. Values + given on command line with -m and -u override these. To read a patch from standard input, use patch name "-". """ @@ -1739,79 +1743,93 @@ def import_(ui, repo, patch1, *patches, # attempt to detect the start of a patch # (this heuristic is borrowed from quilt) - diffre = re.compile(r'(?:Index:[ \t]|diff[ \t]|RCS file: |' + + diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' + 'retrieving revision [0-9]+(\.[0-9]+)*$|' + - '(---|\*\*\*)[ \t])') + '(---|\*\*\*)[ \t])', re.MULTILINE) for patch in patches: pf = os.path.join(d, patch) - message = [] + message = None user = None date = None hgpatch = False + + p = email.Parser.Parser() if pf == '-': - f = sys.stdin - fd, tmpname = tempfile.mkstemp(prefix='hg-patch-') - pf = tmpname - tmpfp = os.fdopen(fd, 'w') + msg = p.parse(sys.stdin) ui.status(_("applying patch from stdin\n")) else: - f = open(pf) - tmpfp, tmpname = None, None + msg = p.parse(file(pf)) ui.status(_("applying %s\n") % patch) + + fd, tmpname = tempfile.mkstemp(prefix='hg-patch-') + tmpfp = os.fdopen(fd, 'w') try: - while True: - line = f.readline() - if not line: break - if tmpfp: tmpfp.write(line) - line = line.rstrip() - if (not message and not hgpatch and - mailre.match(line) and not opts['force']): - if len(line) > 35: - line = line[:32] + '...' - raise util.Abort(_('first line looks like a ' - 'mail header: ') + line) - if diffre.match(line): + message = msg['Subject'] + if message: + message = message.replace('\n\t', ' ') + ui.debug('Subject: %s\n' % message) + user = msg['From'] + if user: + ui.debug('From: %s\n' % user) + diffs_seen = 0 + ok_types = ('text/plain', 'text/x-patch') + for part in msg.walk(): + content_type = part.get_content_type() + ui.debug('Content-Type: %s\n' % content_type) + if content_type not in ok_types: + continue + payload = part.get_payload(decode=True) + m = diffre.search(payload) + if m: + ui.debug(_('found patch at byte %d\n') % m.start(0)) + diffs_seen += 1 + hgpatch = False + fp = cStringIO.StringIO() + for line in payload[:m.start(0)].splitlines(): + if line.startswith('# HG changeset patch'): + ui.debug(_('patch generated by hg export\n')) + hgpatch = True + elif hgpatch: + if line.startswith('# User '): + user = line[7:] + ui.debug('From: %s\n' % user) + elif line.startswith("# Date "): + date = line[7:] + if not line.startswith('# '): + fp.write(line) + fp.write('\n') + hgpatch = False + message = fp.getvalue() or message if tmpfp: - for chunk in util.filechunkiter(f): - tmpfp.write(chunk) - break - elif hgpatch: - # parse values when importing the result of an hg export - if line.startswith("# User "): - user = line[7:] - ui.debug(_('User: %s\n') % user) - elif line.startswith("# Date "): - date = line[7:] - elif not line.startswith("# ") and line: - message.append(line) - hgpatch = False - elif line == '# HG changeset patch': - hgpatch = True - message = [] # We may have collected garbage - elif message or line: - message.append(line) + tmpfp.write(payload) + if not payload.endswith('\n'): + tmpfp.write('\n') + elif not diffs_seen and message and content_type == 'text/plain': + message += '\n' + payload if opts['message']: # pickup the cmdline msg message = opts['message'] elif message: # pickup the patch msg - message = '\n'.join(message).rstrip() + message = message.strip() else: # launch the editor message = None ui.debug(_('message:\n%s\n') % message) - if tmpfp: tmpfp.close() - files = util.patch(strip, pf, ui) - + tmpfp.close() + if not diffs_seen: + raise util.Abort(_('no diffs found')) + + files = util.patch(strip, tmpname, ui) if len(files) > 0: addremove_lock(ui, repo, files, {}) repo.commit(files, message, user, date) finally: - if tmpname: os.unlink(tmpname) + os.unlink(tmpname) def incoming(ui, repo, source="default", **opts): """show new changesets found in source