# HG changeset patch # User Matt Mackall # Date 1154636681 18000 # Node ID b550cd82f92aa46ead1f210ce336d01254905d45 # Parent 8cd3e19bf4a5024a7470c32329224d42c1368aae Move merge code to its own module Pull update and merge3 out of localrepo into merge.py s/self/repo/ Add temporary API function in hg.py Convert all users diff --git a/hgext/mq.py b/hgext/mq.py --- a/hgext/mq.py +++ b/hgext/mq.py @@ -179,11 +179,11 @@ class queue: self.ui.warn("patch didn't work out, merging %s\n" % patch) # apply failed, strip away that rev and merge. - repo.update(head, allow=False, force=True, wlock=wlock) + hg.update(repo, head, allow=False, force=True, wlock=wlock) self.strip(repo, n, update=False, backup='strip', wlock=wlock) c = repo.changelog.read(rev) - ret = repo.update(rev, allow=True, wlock=wlock) + ret = hg.update(repo, rev, allow=True, wlock=wlock) if ret: raise util.Abort(_("update returned %d") % ret) n = repo.commit(None, c[4], c[1], force=1, wlock=wlock) @@ -299,7 +299,7 @@ class queue: self.ui.warn(l + '\n') return (not f.close(), files, fuzz) - + def apply(self, repo, series, list=False, update_status=True, strict=False, patchdir=None, merge=None, wlock=None): # TODO unify with commands.py @@ -526,7 +526,7 @@ class queue: if c or a or d or r: raise util.Abort(_("local changes found")) urev = self.qparents(repo, rev) - repo.update(urev, allow=False, force=True, wlock=wlock) + hg.update(repo, urev, allow=False, force=True, wlock=wlock) repo.dirstate.write() # save is a list of all the branches we are truncating away @@ -1023,7 +1023,7 @@ class queue: if not r: self.ui.warn("Unable to load queue repository\n") return 1 - r.update(qpp[0], allow=False, force=True) + hg.update(r, qpp[0], allow=False, force=True) def save(self, repo, msg=None): if len(self.applied) == 0: @@ -1245,7 +1245,7 @@ def clone(ui, source, dest=None, **opts) dr.mq.strip(dr, qbase, update=False, backup=None) if not opts['noupdate']: ui.note(_('updating destination repo\n')) - dr.update(dr.changelog.tip()) + hg.update(dr, dr.changelog.tip()) def commit(ui, repo, *pats, **opts): """commit changes in the queue repository""" diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -921,7 +921,7 @@ def backout(ui, repo, rev, **opts): if opts['parent']: raise util.Abort(_('cannot use --parent on non-merge changeset')) parent = p1 - repo.update(node, force=True, show_stats=False) + hg.update(repo, node, force=True, show_stats=False) revert_opts = opts.copy() revert_opts['rev'] = hex(parent) revert(ui, repo, **revert_opts) @@ -2542,8 +2542,8 @@ def revert(ui, repo, *pats, **opts): if not opts.get('dry_run'): repo.dirstate.forget(forget[0]) - r = repo.update(node, False, True, update.has_key, False, wlock=wlock, - show_stats=False) + r = hg.update(repo, node, False, True, update.has_key, False, + wlock=wlock, show_stats=False) repo.dirstate.update(add[0], 'a') repo.dirstate.update(undelete[0], 'n') repo.dirstate.update(remove[0], 'r') @@ -2867,7 +2867,7 @@ def doupdate(ui, repo, node=None, merge= return 1 else: node = node and repo.lookup(node) or repo.changelog.tip() - return repo.update(node, allow=merge, force=clean, forcemerge=force) + return hg.update(repo, node, allow=merge, force=clean, forcemerge=force) def verify(ui, repo): """verify the integrity of the repository diff --git a/mercurial/hg.py b/mercurial/hg.py --- a/mercurial/hg.py +++ b/mercurial/hg.py @@ -10,7 +10,7 @@ from repo import * from demandload import * from i18n import gettext as _ demandload(globals(), "localrepo bundlerepo httprepo sshrepo statichttprepo") -demandload(globals(), "errno lock os shutil util") +demandload(globals(), "errno lock os shutil util merge") def _local(path): return (os.path.isfile(path and util.drop_scheme('file', path)) and @@ -38,7 +38,7 @@ def _lookup(path): return thing(path) except TypeError: return thing - + def islocal(repo): '''return true if repo or path is local''' if isinstance(repo, str): @@ -200,8 +200,17 @@ def clone(ui, source, dest=None, pull=Fa dest_lock.release() if update: - dest_repo.update(dest_repo.changelog.tip()) + merge.update(dest_repo, dest_repo.changelog.tip()) if dir_cleanup: dir_cleanup.close() return src_repo, dest_repo + + +# This should instead be several functions with short arglists, like +# update/merge/revert + +def update(repo, node, allow=False, force=False, choose=None, + moddirstate=True, forcemerge=False, wlock=None, show_stats=True): + return merge.update(repo, node, allow, force, choose, moddirstate, + forcemerge, wlock, show_stats) diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -1693,342 +1693,6 @@ class localrepository(repo.repository): return newheads - oldheads + 1 - def update(self, node, allow=False, force=False, choose=None, - moddirstate=True, forcemerge=False, wlock=None, show_stats=True): - pl = self.dirstate.parents() - if not force and pl[1] != nullid: - raise util.Abort(_("outstanding uncommitted merges")) - - err = False - - p1, p2 = pl[0], node - pa = self.changelog.ancestor(p1, p2) - m1n = self.changelog.read(p1)[0] - m2n = self.changelog.read(p2)[0] - man = self.manifest.ancestor(m1n, m2n) - m1 = self.manifest.read(m1n) - mf1 = self.manifest.readflags(m1n) - m2 = self.manifest.read(m2n).copy() - mf2 = self.manifest.readflags(m2n) - ma = self.manifest.read(man) - mfa = self.manifest.readflags(man) - - modified, added, removed, deleted, unknown = self.changes() - - # is this a jump, or a merge? i.e. is there a linear path - # from p1 to p2? - linear_path = (pa == p1 or pa == p2) - - if allow and linear_path: - raise util.Abort(_("there is nothing to merge, just use " - "'hg update' or look at 'hg heads'")) - if allow and not forcemerge: - if modified or added or removed: - raise util.Abort(_("outstanding uncommitted changes")) - - if not forcemerge and not force: - for f in unknown: - if f in m2: - t1 = self.wread(f) - t2 = self.file(f).read(m2[f]) - if cmp(t1, t2) != 0: - raise util.Abort(_("'%s' already exists in the working" - " dir and differs from remote") % f) - - # resolve the manifest to determine which files - # we care about merging - self.ui.note(_("resolving manifests\n")) - self.ui.debug(_(" force %s allow %s moddirstate %s linear %s\n") % - (force, allow, moddirstate, linear_path)) - self.ui.debug(_(" ancestor %s local %s remote %s\n") % - (short(man), short(m1n), short(m2n))) - - merge = {} - get = {} - remove = [] - - # construct a working dir manifest - mw = m1.copy() - mfw = mf1.copy() - umap = dict.fromkeys(unknown) - - for f in added + modified + unknown: - mw[f] = "" - mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False)) - - if moddirstate and not wlock: - wlock = self.wlock() - - for f in deleted + removed: - if f in mw: - del mw[f] - - # If we're jumping between revisions (as opposed to merging), - # and if neither the working directory nor the target rev has - # the file, then we need to remove it from the dirstate, to - # prevent the dirstate from listing the file when it is no - # longer in the manifest. - if moddirstate and linear_path and f not in m2: - self.dirstate.forget((f,)) - - # Compare manifests - for f, n in mw.iteritems(): - if choose and not choose(f): - continue - if f in m2: - s = 0 - - # is the wfile new since m1, and match m2? - if f not in m1: - t1 = self.wread(f) - t2 = self.file(f).read(m2[f]) - if cmp(t1, t2) == 0: - n = m2[f] - del t1, t2 - - # are files different? - if n != m2[f]: - a = ma.get(f, nullid) - # are both different from the ancestor? - if n != a and m2[f] != a: - self.ui.debug(_(" %s versions differ, resolve\n") % f) - # merge executable bits - # "if we changed or they changed, change in merge" - a, b, c = mfa.get(f, 0), mfw[f], mf2[f] - mode = ((a^b) | (a^c)) ^ a - merge[f] = (m1.get(f, nullid), m2[f], mode) - s = 1 - # are we clobbering? - # is remote's version newer? - # or are we going back in time? - elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]): - self.ui.debug(_(" remote %s is newer, get\n") % f) - get[f] = m2[f] - s = 1 - elif f in umap or f in added: - # this unknown file is the same as the checkout - # we need to reset the dirstate if the file was added - get[f] = m2[f] - - if not s and mfw[f] != mf2[f]: - if force: - self.ui.debug(_(" updating permissions for %s\n") % f) - util.set_exec(self.wjoin(f), mf2[f]) - else: - a, b, c = mfa.get(f, 0), mfw[f], mf2[f] - mode = ((a^b) | (a^c)) ^ a - if mode != b: - self.ui.debug(_(" updating permissions for %s\n") - % f) - util.set_exec(self.wjoin(f), mode) - del m2[f] - elif f in ma: - if n != ma[f]: - r = _("d") - if not force and (linear_path or allow): - r = self.ui.prompt( - (_(" local changed %s which remote deleted\n") % f) + - _("(k)eep or (d)elete?"), _("[kd]"), _("k")) - if r == _("d"): - remove.append(f) - else: - self.ui.debug(_("other deleted %s\n") % f) - remove.append(f) # other deleted it - else: - # file is created on branch or in working directory - if force and f not in umap: - self.ui.debug(_("remote deleted %s, clobbering\n") % f) - remove.append(f) - elif n == m1.get(f, nullid): # same as parent - if p2 == pa: # going backwards? - self.ui.debug(_("remote deleted %s\n") % f) - remove.append(f) - else: - self.ui.debug(_("local modified %s, keeping\n") % f) - else: - self.ui.debug(_("working dir created %s, keeping\n") % f) - - for f, n in m2.iteritems(): - if choose and not choose(f): - continue - if f[0] == "/": - continue - if f in ma and n != ma[f]: - r = _("k") - if not force and (linear_path or allow): - r = self.ui.prompt( - (_("remote changed %s which local deleted\n") % f) + - _("(k)eep or (d)elete?"), _("[kd]"), _("k")) - if r == _("k"): - get[f] = n - elif f not in ma: - self.ui.debug(_("remote created %s\n") % f) - get[f] = n - else: - if force or p2 == pa: # going backwards? - self.ui.debug(_("local deleted %s, recreating\n") % f) - get[f] = n - else: - self.ui.debug(_("local deleted %s\n") % f) - - del mw, m1, m2, ma - - if force: - for f in merge: - get[f] = merge[f][1] - merge = {} - - if linear_path or force: - # we don't need to do any magic, just jump to the new rev - branch_merge = False - p1, p2 = p2, nullid - else: - if not allow: - self.ui.status(_("this update spans a branch" - " affecting the following files:\n")) - fl = merge.keys() + get.keys() - fl.sort() - for f in fl: - cf = "" - if f in merge: - cf = _(" (resolve)") - self.ui.status(" %s%s\n" % (f, cf)) - self.ui.warn(_("aborting update spanning branches!\n")) - self.ui.status(_("(use 'hg merge' to merge across branches" - " or 'hg update -C' to lose changes)\n")) - return 1 - branch_merge = True - - xp1 = hex(p1) - xp2 = hex(p2) - if p2 == nullid: xxp2 = '' - else: xxp2 = xp2 - - self.hook('preupdate', throw=True, parent1=xp1, parent2=xxp2) - - # get the files we don't need to change - files = get.keys() - files.sort() - for f in files: - if f[0] == "/": - continue - self.ui.note(_("getting %s\n") % f) - t = self.file(f).read(get[f]) - self.wwrite(f, t) - util.set_exec(self.wjoin(f), mf2[f]) - if moddirstate: - if branch_merge: - self.dirstate.update([f], 'n', st_mtime=-1) - else: - self.dirstate.update([f], 'n') - - # merge the tricky bits - failedmerge = [] - files = merge.keys() - files.sort() - for f in files: - self.ui.status(_("merging %s\n") % f) - my, other, flag = merge[f] - ret = self.merge3(f, my, other, xp1, xp2) - if ret: - err = True - failedmerge.append(f) - util.set_exec(self.wjoin(f), flag) - if moddirstate: - if branch_merge: - # We've done a branch merge, mark this file as merged - # so that we properly record the merger later - self.dirstate.update([f], 'm') - else: - # We've update-merged a locally modified file, so - # we set the dirstate to emulate a normal checkout - # of that file some time in the past. Thus our - # merge will appear as a normal local file - # modification. - f_len = len(self.file(f).read(other)) - self.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1) - - remove.sort() - for f in remove: - self.ui.note(_("removing %s\n") % f) - util.audit_path(f) - try: - util.unlink(self.wjoin(f)) - except OSError, inst: - if inst.errno != errno.ENOENT: - self.ui.warn(_("update failed to remove %s: %s!\n") % - (f, inst.strerror)) - if moddirstate: - if branch_merge: - self.dirstate.update(remove, 'r') - else: - self.dirstate.forget(remove) - - if moddirstate: - self.dirstate.setparents(p1, p2) - - if show_stats: - stats = ((len(get), _("updated")), - (len(merge) - len(failedmerge), _("merged")), - (len(remove), _("removed")), - (len(failedmerge), _("unresolved"))) - note = ", ".join([_("%d files %s") % s for s in stats]) - self.ui.status("%s\n" % note) - if moddirstate: - if branch_merge: - if failedmerge: - self.ui.status(_("There are unresolved merges," - " you can redo the full merge using:\n" - " hg update -C %s\n" - " hg merge %s\n" - % (self.changelog.rev(p1), - self.changelog.rev(p2)))) - else: - self.ui.status(_("(branch merge, don't forget to commit)\n")) - elif failedmerge: - self.ui.status(_("There are unresolved merges with" - " locally modified files.\n")) - - self.hook('update', parent1=xp1, parent2=xxp2, error=int(err)) - return err - - def merge3(self, fn, my, other, p1, p2): - """perform a 3-way merge in the working directory""" - - def temp(prefix, node): - pre = "%s~%s." % (os.path.basename(fn), prefix) - (fd, name) = tempfile.mkstemp(prefix=pre) - f = os.fdopen(fd, "wb") - self.wwrite(fn, fl.read(node), f) - f.close() - return name - - fl = self.file(fn) - base = fl.ancestor(my, other) - a = self.wjoin(fn) - b = temp("base", base) - c = temp("other", other) - - self.ui.note(_("resolving %s\n") % fn) - self.ui.debug(_("file %s: my %s other %s ancestor %s\n") % - (fn, short(my), short(other), short(base))) - - cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge") - or "hgmerge") - r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=self.root, - environ={'HG_FILE': fn, - 'HG_MY_NODE': p1, - 'HG_OTHER_NODE': p2, - 'HG_FILE_MY_NODE': hex(my), - 'HG_FILE_OTHER_NODE': hex(other), - 'HG_FILE_BASE_NODE': hex(base)}) - if r: - self.ui.warn(_("merging %s failed!\n") % fn) - - os.unlink(b) - os.unlink(c) - return r - def verify(self): filelinkrevs = {} filenodes = {} diff --git a/mercurial/merge.py b/mercurial/merge.py new file mode 100644 --- /dev/null +++ b/mercurial/merge.py @@ -0,0 +1,348 @@ +# merge.py - directory-level update/merge handling for Mercurial +# +# Copyright 2006 Matt Mackall +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +from node import * +from i18n import gettext as _ +from demandload import * +demandload(globals(), "util os tempfile") + +def merge3(repo, fn, my, other, p1, p2): + """perform a 3-way merge in the working directory""" + + def temp(prefix, node): + pre = "%s~%s." % (os.path.basename(fn), prefix) + (fd, name) = tempfile.mkstemp(prefix=pre) + f = os.fdopen(fd, "wb") + repo.wwrite(fn, fl.read(node), f) + f.close() + return name + + fl = repo.file(fn) + base = fl.ancestor(my, other) + a = repo.wjoin(fn) + b = temp("base", base) + c = temp("other", other) + + repo.ui.note(_("resolving %s\n") % fn) + repo.ui.debug(_("file %s: my %s other %s ancestor %s\n") % + (fn, short(my), short(other), short(base))) + + cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge") + or "hgmerge") + r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=repo.root, + environ={'HG_FILE': fn, + 'HG_MY_NODE': p1, + 'HG_OTHER_NODE': p2, + 'HG_FILE_MY_NODE': hex(my), + 'HG_FILE_OTHER_NODE': hex(other), + 'HG_FILE_BASE_NODE': hex(base)}) + if r: + repo.ui.warn(_("merging %s failed!\n") % fn) + + os.unlink(b) + os.unlink(c) + return r + +def update(repo, node, allow=False, force=False, choose=None, + moddirstate=True, forcemerge=False, wlock=None, show_stats=True): + pl = repo.dirstate.parents() + if not force and pl[1] != nullid: + raise util.Abort(_("outstanding uncommitted merges")) + + err = False + + p1, p2 = pl[0], node + pa = repo.changelog.ancestor(p1, p2) + m1n = repo.changelog.read(p1)[0] + m2n = repo.changelog.read(p2)[0] + man = repo.manifest.ancestor(m1n, m2n) + m1 = repo.manifest.read(m1n) + mf1 = repo.manifest.readflags(m1n) + m2 = repo.manifest.read(m2n).copy() + mf2 = repo.manifest.readflags(m2n) + ma = repo.manifest.read(man) + mfa = repo.manifest.readflags(man) + + modified, added, removed, deleted, unknown = repo.changes() + + # is this a jump, or a merge? i.e. is there a linear path + # from p1 to p2? + linear_path = (pa == p1 or pa == p2) + + if allow and linear_path: + raise util.Abort(_("there is nothing to merge, just use " + "'hg update' or look at 'hg heads'")) + if allow and not forcemerge: + if modified or added or removed: + raise util.Abort(_("outstanding uncommitted changes")) + + if not forcemerge and not force: + for f in unknown: + if f in m2: + t1 = repo.wread(f) + t2 = repo.file(f).read(m2[f]) + if cmp(t1, t2) != 0: + raise util.Abort(_("'%s' already exists in the working" + " dir and differs from remote") % f) + + # resolve the manifest to determine which files + # we care about merging + repo.ui.note(_("resolving manifests\n")) + repo.ui.debug(_(" force %s allow %s moddirstate %s linear %s\n") % + (force, allow, moddirstate, linear_path)) + repo.ui.debug(_(" ancestor %s local %s remote %s\n") % + (short(man), short(m1n), short(m2n))) + + merge = {} + get = {} + remove = [] + + # construct a working dir manifest + mw = m1.copy() + mfw = mf1.copy() + umap = dict.fromkeys(unknown) + + for f in added + modified + unknown: + mw[f] = "" + mfw[f] = util.is_exec(repo.wjoin(f), mfw.get(f, False)) + + if moddirstate and not wlock: + wlock = repo.wlock() + + for f in deleted + removed: + if f in mw: + del mw[f] + + # If we're jumping between revisions (as opposed to merging), + # and if neither the working directory nor the target rev has + # the file, then we need to remove it from the dirstate, to + # prevent the dirstate from listing the file when it is no + # longer in the manifest. + if moddirstate and linear_path and f not in m2: + repo.dirstate.forget((f,)) + + # Compare manifests + for f, n in mw.iteritems(): + if choose and not choose(f): + continue + if f in m2: + s = 0 + + # is the wfile new since m1, and match m2? + if f not in m1: + t1 = repo.wread(f) + t2 = repo.file(f).read(m2[f]) + if cmp(t1, t2) == 0: + n = m2[f] + del t1, t2 + + # are files different? + if n != m2[f]: + a = ma.get(f, nullid) + # are both different from the ancestor? + if n != a and m2[f] != a: + repo.ui.debug(_(" %s versions differ, resolve\n") % f) + # merge executable bits + # "if we changed or they changed, change in merge" + a, b, c = mfa.get(f, 0), mfw[f], mf2[f] + mode = ((a^b) | (a^c)) ^ a + merge[f] = (m1.get(f, nullid), m2[f], mode) + s = 1 + # are we clobbering? + # is remote's version newer? + # or are we going back in time? + elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]): + repo.ui.debug(_(" remote %s is newer, get\n") % f) + get[f] = m2[f] + s = 1 + elif f in umap or f in added: + # this unknown file is the same as the checkout + # we need to reset the dirstate if the file was added + get[f] = m2[f] + + if not s and mfw[f] != mf2[f]: + if force: + repo.ui.debug(_(" updating permissions for %s\n") % f) + util.set_exec(repo.wjoin(f), mf2[f]) + else: + a, b, c = mfa.get(f, 0), mfw[f], mf2[f] + mode = ((a^b) | (a^c)) ^ a + if mode != b: + repo.ui.debug(_(" updating permissions for %s\n") + % f) + util.set_exec(repo.wjoin(f), mode) + del m2[f] + elif f in ma: + if n != ma[f]: + r = _("d") + if not force and (linear_path or allow): + r = repo.ui.prompt( + (_(" local changed %s which remote deleted\n") % f) + + _("(k)eep or (d)elete?"), _("[kd]"), _("k")) + if r == _("d"): + remove.append(f) + else: + repo.ui.debug(_("other deleted %s\n") % f) + remove.append(f) # other deleted it + else: + # file is created on branch or in working directory + if force and f not in umap: + repo.ui.debug(_("remote deleted %s, clobbering\n") % f) + remove.append(f) + elif n == m1.get(f, nullid): # same as parent + if p2 == pa: # going backwards? + repo.ui.debug(_("remote deleted %s\n") % f) + remove.append(f) + else: + repo.ui.debug(_("local modified %s, keeping\n") % f) + else: + repo.ui.debug(_("working dir created %s, keeping\n") % f) + + for f, n in m2.iteritems(): + if choose and not choose(f): + continue + if f[0] == "/": + continue + if f in ma and n != ma[f]: + r = _("k") + if not force and (linear_path or allow): + r = repo.ui.prompt( + (_("remote changed %s which local deleted\n") % f) + + _("(k)eep or (d)elete?"), _("[kd]"), _("k")) + if r == _("k"): + get[f] = n + elif f not in ma: + repo.ui.debug(_("remote created %s\n") % f) + get[f] = n + else: + if force or p2 == pa: # going backwards? + repo.ui.debug(_("local deleted %s, recreating\n") % f) + get[f] = n + else: + repo.ui.debug(_("local deleted %s\n") % f) + + del mw, m1, m2, ma + + if force: + for f in merge: + get[f] = merge[f][1] + merge = {} + + if linear_path or force: + # we don't need to do any magic, just jump to the new rev + branch_merge = False + p1, p2 = p2, nullid + else: + if not allow: + repo.ui.status(_("this update spans a branch" + " affecting the following files:\n")) + fl = merge.keys() + get.keys() + fl.sort() + for f in fl: + cf = "" + if f in merge: + cf = _(" (resolve)") + repo.ui.status(" %s%s\n" % (f, cf)) + repo.ui.warn(_("aborting update spanning branches!\n")) + repo.ui.status(_("(use 'hg merge' to merge across branches" + " or 'hg update -C' to lose changes)\n")) + return 1 + branch_merge = True + + xp1 = hex(p1) + xp2 = hex(p2) + if p2 == nullid: xxp2 = '' + else: xxp2 = xp2 + + repo.hook('preupdate', throw=True, parent1=xp1, parent2=xxp2) + + # get the files we don't need to change + files = get.keys() + files.sort() + for f in files: + if f[0] == "/": + continue + repo.ui.note(_("getting %s\n") % f) + t = repo.file(f).read(get[f]) + repo.wwrite(f, t) + util.set_exec(repo.wjoin(f), mf2[f]) + if moddirstate: + if branch_merge: + repo.dirstate.update([f], 'n', st_mtime=-1) + else: + repo.dirstate.update([f], 'n') + + # merge the tricky bits + failedmerge = [] + files = merge.keys() + files.sort() + for f in files: + repo.ui.status(_("merging %s\n") % f) + my, other, flag = merge[f] + ret = merge3(repo, f, my, other, xp1, xp2) + if ret: + err = True + failedmerge.append(f) + util.set_exec(repo.wjoin(f), flag) + if moddirstate: + if branch_merge: + # We've done a branch merge, mark this file as merged + # so that we properly record the merger later + repo.dirstate.update([f], 'm') + else: + # We've update-merged a locally modified file, so + # we set the dirstate to emulate a normal checkout + # of that file some time in the past. Thus our + # merge will appear as a normal local file + # modification. + f_len = len(repo.file(f).read(other)) + repo.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1) + + remove.sort() + for f in remove: + repo.ui.note(_("removing %s\n") % f) + util.audit_path(f) + try: + util.unlink(repo.wjoin(f)) + except OSError, inst: + if inst.errno != errno.ENOENT: + repo.ui.warn(_("update failed to remove %s: %s!\n") % + (f, inst.strerror)) + if moddirstate: + if branch_merge: + repo.dirstate.update(remove, 'r') + else: + repo.dirstate.forget(remove) + + if moddirstate: + repo.dirstate.setparents(p1, p2) + + if show_stats: + stats = ((len(get), _("updated")), + (len(merge) - len(failedmerge), _("merged")), + (len(remove), _("removed")), + (len(failedmerge), _("unresolved"))) + note = ", ".join([_("%d files %s") % s for s in stats]) + repo.ui.status("%s\n" % note) + if moddirstate: + if branch_merge: + if failedmerge: + repo.ui.status(_("There are unresolved merges," + " you can redo the full merge using:\n" + " hg update -C %s\n" + " hg merge %s\n" + % (repo.changelog.rev(p1), + repo.changelog.rev(p2)))) + else: + repo.ui.status(_("(branch merge, don't forget to commit)\n")) + elif failedmerge: + repo.ui.status(_("There are unresolved merges with" + " locally modified files.\n")) + + repo.hook('update', parent1=xp1, parent2=xxp2, error=int(err)) + return err +