# HG changeset patch # User Brendan Cully # Date 1174585499 25200 # Node ID 47ba52121433c345b5d1bb978a9cf08ccc222634 # Parent f51317e241140c235e225f64b5f50460552efc4a Add import --exact. When this option is set, import will apply the patch (which must be generated by export) to the parents specified in the patch, and check that the node produced by the patch matches the node ID in the patch. 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