diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -1477,15 +1477,21 @@ def import_(ui, repo, patch1, *patches, text/plain body parts before first diff are added to commit message. - If imported patch was generated by hg export, user and description + If the 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. + If --exact is specified, import will set the working directory + to the parent of each patch before applying it, and will abort + if the resulting changeset has a different ID than the one + recorded in the patch. This may happen due to character set + problems or other deficiencies in the text patch format. + To read a patch from standard input, use patch name "-". """ patches = (patch1,) + patches - if not opts['force']: + if opts.get('exact') or not opts['force']: bail_if_changed(repo) d = opts["base"] @@ -1499,10 +1505,10 @@ def import_(ui, repo, patch1, *patches, if pf == '-': ui.status(_("applying patch from stdin\n")) - tmpname, message, user, date = patch.extract(ui, sys.stdin) + tmpname, message, user, date, nodeid, p1, p2 = patch.extract(ui, sys.stdin) else: ui.status(_("applying %s\n") % p) - tmpname, message, user, date = patch.extract(ui, file(pf)) + tmpname, message, user, date, nodeid, p1, p2 = patch.extract(ui, file(pf)) if tmpname is None: raise util.Abort(_('no diffs found')) @@ -1521,12 +1527,27 @@ def import_(ui, repo, patch1, *patches, ui.debug(_('message:\n%s\n') % message) files = {} + if opts.get('exact'): + if not nodeid or not p1: + raise util.Abort(_('not a mercurial patch')) + p1 = repo.lookup(p1) + p2 = repo.lookup(p2 or hex(nullid)) + + wctx = repo.workingctx() + wp = repo.workingctx().parents() + if p1 != wp[0].node(): + hg.clean(repo, p1, wlock=wlock) + repo.dirstate.setparents(p1, p2) try: fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root, files=files) finally: files = patch.updatedir(ui, repo, files, wlock=wlock) - repo.commit(files, message, user, date, wlock=wlock, lock=lock) + n = repo.commit(files, message, user, date, wlock=wlock, lock=lock) + if opts.get('exact'): + if hex(n) != nodeid: + repo.rollback() + raise util.Abort(_('patch is damaged or loses information')) finally: os.unlink(tmpname) @@ -2772,7 +2793,9 @@ table = { 'meaning as the corresponding patch option')), ('b', 'base', '', _('base path')), ('f', 'force', None, - _('skip check for outstanding uncommitted changes'))] + commitopts, + _('skip check for outstanding uncommitted changes')), + ('', 'exact', None, + _('apply patch to the nodes from which it was generated'))] + commitopts, _('hg import [-p NUM] [-m MESSAGE] [-f] PATCH...')), "incoming|in": (incoming, [('M', 'no-merges', None, _('do not show merges')), diff --git a/mercurial/patch.py b/mercurial/patch.py --- a/mercurial/patch.py +++ b/mercurial/patch.py @@ -33,11 +33,11 @@ def copyfile(src, dst, basedir=None): def extract(ui, fileobj): '''extract patch from data read from fileobj. - patch can be normal patch or contained in email message. + patch can be a normal patch or contained in an email message. - return tuple (filename, message, user, date). any item in returned - tuple can be None. if filename is None, fileobj did not contain - patch. caller must unlink filename when done.''' + return tuple (filename, message, user, date, node, p1, p2). + Any item in the returned tuple can be None. If filename is None, + fileobj did not contain a patch. Caller must unlink filename when done.''' # attempt to detect the start of a patch # (this heuristic is borrowed from quilt) @@ -54,6 +54,8 @@ def extract(ui, fileobj): user = msg['From'] # should try to parse msg['Date'] date = None + nodeid = None + parents = [] if message: if message.startswith('[PATCH'): @@ -97,6 +99,10 @@ def extract(ui, fileobj): ui.debug('From: %s\n' % user) elif line.startswith("# Date "): date = line[7:] + elif line.startswith("# Node ID "): + nodeid = line[10:] + elif line.startswith("# Parent "): + parents.append(line[10:]) elif line == '---' and 'git-send-email' in msg['X-Mailer']: ignoretext = True if not line.startswith('# ') and not ignoretext: @@ -117,8 +123,10 @@ def extract(ui, fileobj): tmpfp.close() if not diffs_seen: os.unlink(tmpname) - return None, message, user, date - return tmpname, message, user, date + return None, message, user, date, None, None, None + p1 = parents and parents.pop(0) or None + p2 = parents and parents.pop(0) or None + return tmpname, message, user, date, nodeid, p1, p2 GP_PATCH = 1 << 0 # we have to run patch GP_FILTER = 1 << 1 # there's some copy/rename operation