# HG changeset patch # User Brendan Cully # Date 1159902873 25200 # Node ID a184cd0c2db9fc470d0ed5c505fbefc64ee092c2 # Parent e0069e7fe419f55636d1a64314985adadc9e0f20# Parent 8d4855fd9d7bb85e724deebca4ccb288c79677b1 Merge with upstream diff -r e0069e7fe419 -r a184cd0c2db9 mercurial/context.py --- a/mercurial/context.py Tue Oct 03 11:54:11 2006 +0200 +++ b/mercurial/context.py Tue Oct 03 12:14:33 2006 -0700 @@ -33,7 +33,7 @@ return short(self.node()) def __repr__(self): - return "" % short(self.node()) + return "" % str(self) def __eq__(self, other): return self._rev == other._rev @@ -41,26 +41,25 @@ def __nonzero__(self): return self._rev != -1 - def changeset(self): - try: - return self._changeset - except AttributeError: + def __getattr__(self, name): + if name == '_changeset': self._changeset = self._repo.changelog.read(self.node()) return self._changeset - - def manifest(self): - try: + elif name == '_manifest': + self._manifest = self._repo.manifest.read(self._changeset[0]) return self._manifest - except AttributeError: - self._manifest = self._repo.manifest.read(self.changeset()[0]) - return self._manifest + else: + raise AttributeError, name + + def changeset(self): return self._changeset + def manifest(self): return self._manifest def rev(self): return self._rev def node(self): return self._node - def user(self): return self.changeset()[1] - def date(self): return self.changeset()[2] - def files(self): return self.changeset()[3] - def description(self): return self.changeset()[4] + def user(self): return self._changeset[1] + def date(self): return self._changeset[2] + def files(self): return self._changeset[3] + def description(self): return self._changeset[4] def parents(self): """return contexts for each parent changeset""" @@ -73,17 +72,16 @@ return [ changectx(self._repo, x) for x in c ] def filenode(self, path): - node, flag = self._repo.manifest.find(self.changeset()[0], path) + if hasattr(self, "_manifest"): + return self._manifest[path] + node, flag = self._repo.manifest.find(self._changeset[0], path) return node def filectx(self, path, fileid=None): """get a file context from this changeset""" if fileid is None: fileid = self.filenode(path) - if not fileid: - raise repo.LookupError(_("'%s' does not exist in changeset %s") % - (path, hex(self.node()))) - return filectx(self._repo, path, fileid=fileid) + return filectx(self._repo, path, fileid=fileid, changectx=self) def filectxs(self): """generate a file context for each file in this changeset's @@ -104,34 +102,44 @@ class filectx(object): """A filecontext object makes access to data related to a particular filerevision convenient.""" - def __init__(self, repo_, path, changeid=None, fileid=None, filelog=None): + def __init__(self, repo, path, changeid=None, fileid=None, + filelog=None, changectx=None): """changeid can be a changeset revision, node, or tag. fileid can be a file revision or node.""" - self._repo = repo_ + self._repo = repo self._path = path assert changeid is not None or fileid is not None if filelog: self._filelog = filelog - else: - self._filelog = self._repo.file(self._path) + if changectx: + self._changectx = changectx + self._changeid = changectx.node() if fileid is None: self._changeid = changeid else: - try: - self._filenode = self._filelog.lookup(fileid) - except revlog.RevlogError, inst: - raise repo.LookupError(str(inst)) - self._changeid = self._filelog.linkrev(self._filenode) + self._fileid = fileid def __getattr__(self, name): if name == '_changectx': self._changectx = changectx(self._repo, self._changeid) return self._changectx + elif name == '_filelog': + self._filelog = self._repo.file(self._path) + return self._filelog + elif name == '_changeid': + self._changeid = self._filelog.linkrev(self._filenode) + return self._changeid elif name == '_filenode': - self._filenode = self._changectx.filenode(self._path) + try: + if hasattr(self, "_fileid"): + self._filenode = self._filelog.lookup(self._fileid) + else: + self._filenode = self._changectx.filenode(self._path) + except revlog.RevlogError, inst: + raise repo.LookupError(str(inst)) return self._filenode elif name == '_filerev': self._filerev = self._filelog.rev(self._filenode) @@ -146,7 +154,7 @@ return "%s@%s" % (self.path(), short(self.node())) def __repr__(self): - return "" % (self.path(), short(self.node())) + return "" % str(self) def __eq__(self, other): return self._path == other._path and self._changeid == other._changeid @@ -218,7 +226,10 @@ def parents(f): # we want to reuse filectx objects as much as possible p = f._path - pl = [ (p, r) for r in f._filelog.parentrevs(f._filerev) ] + if f._filerev is None: # working dir + pl = [ (n.path(), n.filerev()) for n in f.parents() ] + else: + pl = [ (p, n) for n in f._filelog.parentrevs(f._filerev) ] if follow: r = f.renamed() @@ -271,6 +282,13 @@ """ acache = {} + + # prime the ancestor cache for the working directory + for c in (self, fc2): + if c._filerev == None: + pl = [ (n.path(), n.filenode()) for n in c.parents() ] + acache[(c._path, None)] = pl + flcache = {self._path:self._filelog, fc2._path:fc2._filelog} def parents(vertex): if vertex in acache: @@ -293,3 +311,149 @@ return filectx(self._repo, f, fileid=n, filelog=flcache[f]) return None + +class workingctx(changectx): + """A workingctx object makes access to data related to + the current working directory convenient.""" + def __init__(self, repo): + self._repo = repo + self._rev = None + self._node = None + + def __str__(self): + return "." + + def __nonzero__(self): + return True + + def __getattr__(self, name): + if name == '_parents': + self._parents = self._repo.parents() + return self._parents + if name == '_status': + self._status = self._repo.status() + return self._status + if name == '_manifest': + self._buildmanifest() + return self._manifest + else: + raise AttributeError, name + + def _buildmanifest(self): + """generate a manifest corresponding to the working directory""" + + man = self._parents[0].manifest().copy() + copied = self._repo.dirstate.copies() + modified, added, removed, deleted, unknown = self._status[:5] + for i,l in (("a", added), ("m", modified), ("u", unknown)): + for f in l: + man[f] = man.get(copied.get(f, f), nullid) + i + man.set(f, util.is_exec(self._repo.wjoin(f), man.execf(f))) + + for f in deleted + removed: + del man[f] + + self._manifest = man + + def manifest(self): return self._manifest + + def user(self): return self._repo.ui.username() + def date(self): return util.makedate() + def description(self): return "" + def files(self): + f = self.modified() + self.added() + self.removed() + f.sort() + return f + + def modified(self): return self._status[0] + def added(self): return self._status[1] + def removed(self): return self._status[2] + def deleted(self): return self._status[3] + def unknown(self): return self._status[4] + def clean(self): return self._status[5] + + def parents(self): + """return contexts for each parent changeset""" + return self._parents + + def children(self): + return [] + + def filectx(self, path): + """get a file context from the working directory""" + return workingfilectx(self._repo, path, workingctx=self) + + def ancestor(self, c2): + """return the ancestor context of self and c2""" + return self._parents[0].ancestor(c2) # punt on two parents for now + +class workingfilectx(filectx): + """A workingfilectx object makes access to data related to a particular + file in the working directory convenient.""" + def __init__(self, repo, path, filelog=None, workingctx=None): + """changeid can be a changeset revision, node, or tag. + fileid can be a file revision or node.""" + self._repo = repo + self._path = path + self._changeid = None + self._filerev = self._filenode = None + + if filelog: + self._filelog = filelog + if workingctx: + self._changectx = workingctx + + def __getattr__(self, name): + if name == '_changectx': + self._changectx = workingctx(repo) + return self._changectx + elif name == '_repopath': + self._repopath = self._repo.dirstate.copied(p) or self._path + elif name == '_filelog': + self._filelog = self._repo.file(self._repopath) + return self._filelog + else: + raise AttributeError, name + + def __nonzero__(self): + return True + + def __str__(self): + return "%s@." % self.path() + + def filectx(self, fileid): + '''opens an arbitrary revision of the file without + opening a new filelog''' + return filectx(self._repo, self._repopath, fileid=fileid, + filelog=self._filelog) + + def rev(self): + if hasattr(self, "_changectx"): + return self._changectx.rev() + return self._filelog.linkrev(self._filenode) + + def data(self): return self._repo.wread(self._path) + def renamed(self): + rp = self._repopath + if rp == self._path: + return None + return rp, self._workingctx._parents._manifest.get(rp, nullid) + + def parents(self): + '''return parent filectxs, following copies if necessary''' + p = self._path + rp = self._repopath + pcl = self._workingctx._parents + fl = self._filelog + pl = [ (rp, pcl[0]._manifest.get(rp, nullid), fl) ] + if len(pcl) > 1: + if rp != p: + fl = None + pl.append((p, pcl[1]._manifest.get(p, nullid), fl)) + + return [ filectx(self._repo, p, fileid=n, filelog=l) + for p,n,l in pl if n != nullid ] + + def children(self): + return [] + diff -r e0069e7fe419 -r a184cd0c2db9 mercurial/localrepo.py --- a/mercurial/localrepo.py Tue Oct 03 11:54:11 2006 +0200 +++ b/mercurial/localrepo.py Tue Oct 03 12:14:33 2006 -0700 @@ -321,6 +321,9 @@ def changectx(self, changeid=None): return context.changectx(self, changeid) + def workingctx(self): + return context.workingctx(self) + def parents(self, changeid=None): ''' get list of changectxs for parents of changeid or working directory diff -r e0069e7fe419 -r a184cd0c2db9 mercurial/merge.py --- a/mercurial/merge.py Tue Oct 03 11:54:11 2006 +0200 +++ b/mercurial/merge.py Tue Oct 03 12:14:33 2006 -0700 @@ -10,72 +10,66 @@ from demandload import * demandload(globals(), "errno util os tempfile") -def merge3(repo, fn, my, other, p1, p2): - """perform a 3-way merge in the working directory""" +def filemerge(repo, fw, fo, fd, my, other, p1, p2, move): + """perform a 3-way merge in the working directory - def temp(prefix, node): - pre = "%s~%s." % (os.path.basename(fn), prefix) + fw = filename in the working directory and first parent + fo = filename in other parent + fd = destination filename + my = fileid in first parent + other = fileid in second parent + p1, p2 = hex changeset ids for merge command + move = whether to move or copy the file to the destination + + TODO: + if fw is copied in the working directory, we get confused + implement move and fd + """ + + def temp(prefix, ctx): + pre = "%s~%s." % (os.path.basename(ctx.path()), prefix) (fd, name) = tempfile.mkstemp(prefix=pre) f = os.fdopen(fd, "wb") - repo.wwrite(fn, fl.read(node), f) + repo.wwrite(ctx.path(), ctx.data(), 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) + fcm = repo.filectx(fw, fileid=my) + fco = repo.filectx(fo, fileid=other) + fca = fcm.ancestor(fco) + if not fca: + fca = repo.filectx(fw, fileid=-1) + a = repo.wjoin(fw) + b = temp("base", fca) + c = temp("other", fco) - 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))) + repo.ui.note(_("resolving %s\n") % fw) + repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca)) 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, + environ={'HG_FILE': fw, '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)}) + 'HG_OTHER_NODE': p2}) if r: - repo.ui.warn(_("merging %s failed!\n") % fn) + repo.ui.warn(_("merging %s failed!\n") % fw) os.unlink(b) os.unlink(c) return r -def checkunknown(repo, m2, status): +def checkunknown(repo, m2, wctx): """ check for collisions between unknown files and files in m2 """ - modified, added, removed, deleted, unknown = status[:5] - for f in unknown: + for f in wctx.unknown(): if f in m2: if repo.file(f).cmp(m2[f], repo.wread(f)): raise util.Abort(_("'%s' already exists in the working" " dir and differs from remote") % f) -def workingmanifest(repo, man, status): - """ - Update manifest to correspond to the working directory - """ - - copied = repo.dirstate.copies() - modified, added, removed, deleted, unknown = status[:5] - for i,l in (("a", added), ("m", modified), ("u", unknown)): - for f in l: - man[f] = man.get(copied.get(f, f), nullid) + i - man.set(f, util.is_exec(repo.wjoin(f), man.execf(f))) - - for f in deleted + removed: - del man[f] - - return man - -def forgetremoved(m2, status): +def forgetremoved(m2, wctx): """ Forget removed files @@ -86,10 +80,9 @@ manifest. """ - modified, added, removed, deleted, unknown = status[:5] action = [] - for f in deleted + removed: + for f in wctx.deleted() + wctx.removed(): if f not in m2: action.append((f, "f")) @@ -261,7 +254,7 @@ elif m == "m": # merge flag, my, other = a[2:] repo.ui.status(_("merging %s\n") % f) - if merge3(repo, f, my, other, xp1, xp2): + if filemerge(repo, f, f, f, my, other, xp1, xp2, False): unresolved += 1 util.set_exec(repo.wjoin(f), flag) merged += 1 @@ -320,7 +313,8 @@ ### check phase - pl = repo.parents() + wc = repo.workingctx() + pl = wc.parents() if not overwrite and len(pl) > 1: raise util.Abort(_("outstanding uncommitted merges")) @@ -339,13 +333,11 @@ raise util.Abort(_("update spans branches, use 'hg merge' " "or 'hg update -C' to lose changes")) - status = repo.status() - modified, added, removed, deleted, unknown = status[:5] if branchmerge and not forcemerge: - if modified or added or removed: + if wc.modified() or wc.added() or wc.removed(): raise util.Abort(_("outstanding uncommitted changes")) - m1 = p1.manifest().copy() + m1 = wc.manifest().copy() m2 = p2.manifest().copy() ma = pa.manifest() @@ -359,14 +351,13 @@ action = [] copy = {} - m1 = workingmanifest(repo, m1, status) filtermanifest(m1, partial) filtermanifest(m2, partial) if not force: - checkunknown(repo, m2, status) + checkunknown(repo, m2, wc) if not branchmerge: - action += forgetremoved(m2, status) + action += forgetremoved(m2, wc) if not (backwards or overwrite): copy = findcopies(repo, m1, m2, pa.rev()) diff -r e0069e7fe419 -r a184cd0c2db9 tests/test-merge7.out --- a/tests/test-merge7.out Tue Oct 03 11:54:11 2006 +0200 +++ b/tests/test-merge7.out Tue Oct 03 12:14:33 2006 -0700 @@ -27,7 +27,7 @@ test.txt: versions differ -> m merging test.txt resolving test.txt -file test.txt: my fc3148072371 other d40249267ae3 ancestor 8fe46a3eb557 +my test.txt@451c744aabcc other test.txt@a070d41e8360 ancestor test.txt@faaea63e63a9 merging test.txt failed! 0 files updated, 0 files merged, 0 files removed, 1 files unresolved There are unresolved merges, you can redo the full merge using: diff -r e0069e7fe419 -r a184cd0c2db9 tests/test-up-local-change.out --- a/tests/test-up-local-change.out Tue Oct 03 11:54:11 2006 +0200 +++ b/tests/test-up-local-change.out Tue Oct 03 12:14:33 2006 -0700 @@ -21,7 +21,7 @@ b: remote created -> g merging a resolving a -file a: my b789fdd96dc2 other d730145abbf9 ancestor b789fdd96dc2 +my a@33aaa84a386b other a@802f095af299 ancestor a@33aaa84a386b getting b 1 files updated, 1 files merged, 0 files removed, 0 files unresolved changeset: 1:802f095af299 @@ -55,7 +55,7 @@ b: remote created -> g merging a resolving a -file a: my b789fdd96dc2 other d730145abbf9 ancestor b789fdd96dc2 +my a@33aaa84a386b other a@802f095af299 ancestor a@33aaa84a386b getting b 1 files updated, 1 files merged, 0 files removed, 0 files unresolved changeset: 1:802f095af299 @@ -106,10 +106,10 @@ b: versions differ -> m merging a resolving a -file a: my d730145abbf9 other 13e0d5f949fa ancestor b789fdd96dc2 +my a@802f095af299 other a@030602aee63d ancestor a@33aaa84a386b merging b resolving b -file b: my 1e88685f5dde other 61de8c7723ca ancestor 000000000000 +my b@802f095af299 other b@030602aee63d ancestor b@000000000000 0 files updated, 2 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) changeset: 1:802f095af299