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 = {}