--- a/mercurial/context.py Sun Aug 10 18:01:03 2008 -0500
+++ b/mercurial/context.py Sun Aug 10 18:38:43 2008 -0500
@@ -5,35 +5,36 @@
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.
-from node import nullid, nullrev, short
+from node import nullid, nullrev, short, hex
from i18n import _
import ancestor, bdiff, revlog, util, os, errno
class changectx(object):
"""A changecontext object makes access to data related to a particular
changeset convenient."""
- def __init__(self, repo, changeid=None):
+ def __init__(self, repo, changeid=''):
"""changeid is a revision number, node, or tag"""
+ if changeid == '':
+ changeid = '.'
self._repo = repo
-
- if not changeid and changeid != 0:
- p1, p2 = self._repo.dirstate.parents()
- self._rev = self._repo.changelog.rev(p1)
- if self._rev == -1:
- changeid = 'tip'
- else:
- self._node = p1
- return
-
self._node = self._repo.lookup(changeid)
self._rev = self._repo.changelog.rev(self._node)
def __str__(self):
return short(self.node())
+ def __int__(self):
+ return self.rev()
+
def __repr__(self):
return "<changectx %s>" % str(self)
+ def __hash__(self):
+ try:
+ return hash(self._rev)
+ except AttributeError:
+ return id(self)
+
def __eq__(self, other):
try:
return self._rev == other._rev
@@ -57,6 +58,12 @@
md = self._repo.manifest.readdelta(self._changeset[0])
self._manifestdelta = md
return self._manifestdelta
+ elif name == '_parents':
+ p = self._repo.changelog.parents(self._node)
+ if p[1] == nullid:
+ p = p[:-1]
+ self._parents = [changectx(self._repo, x) for x in p]
+ return self._parents
else:
raise AttributeError, name
@@ -67,9 +74,7 @@
return self.filectx(key)
def __iter__(self):
- a = self._manifest.keys()
- a.sort()
- for f in a:
+ for f in util.sort(self._manifest):
yield f
def changeset(self): return self._changeset
@@ -77,6 +82,7 @@
def rev(self): return self._rev
def node(self): return self._node
+ def hex(self): return hex(self._node)
def user(self): return self._changeset[1]
def date(self): return self._changeset[2]
def files(self): return self._changeset[3]
@@ -87,14 +93,21 @@
def parents(self):
"""return contexts for each parent changeset"""
- p = self._repo.changelog.parents(self._node)
- return [changectx(self._repo, x) for x in p]
+ return self._parents
def children(self):
"""return contexts for each child changeset"""
c = self._repo.changelog.children(self._node)
return [changectx(self._repo, x) for x in c]
+ def ancestors(self):
+ for a in self._repo.changelog.ancestors(self._rev):
+ yield changectx(self._repo, a)
+
+ def descendants(self):
+ for d in self._repo.changelog.descendants(self._rev):
+ yield changectx(self._repo, d)
+
def _fileinfo(self, path):
if '_manifest' in self.__dict__:
try:
@@ -115,7 +128,7 @@
def filenode(self, path):
return self._fileinfo(path)[0]
- def fileflags(self, path):
+ def flags(self, path):
try:
return self._fileinfo(path)[1]
except revlog.LookupError:
@@ -128,15 +141,6 @@
return filectx(self._repo, path, fileid=fileid,
changectx=self, filelog=filelog)
- def filectxs(self):
- """generate a file context for each file in this changeset's
- manifest"""
- mf = self.manifest()
- m = mf.keys()
- m.sort()
- for f in m:
- yield self.filectx(f, fileid=mf[f])
-
def ancestor(self, c2):
"""
return the ancestor context of self and c2
@@ -144,6 +148,23 @@
n = self._repo.changelog.ancestor(self._node, c2._node)
return changectx(self._repo, n)
+ def walk(self, match):
+ fdict = dict.fromkeys(match.files())
+ # for dirstate.walk, files=['.'] means "walk the whole tree".
+ # follow that here, too
+ fdict.pop('.', None)
+ for fn in self:
+ for ffn in fdict:
+ # match if the file is the exact name or a directory
+ if ffn == fn or fn.startswith("%s/" % ffn):
+ del fdict[ffn]
+ break
+ if match(fn):
+ yield fn
+ for fn in util.sort(fdict):
+ if match.bad(fn, 'No such file in rev ' + str(self)) and match(fn):
+ yield fn
+
class filectx(object):
"""A filecontext object makes access to data related to a particular
filerevision convenient."""
@@ -210,6 +231,12 @@
def __repr__(self):
return "<filectx %s>" % str(self)
+ def __hash__(self):
+ try:
+ return hash((self._path, self._fileid))
+ except AttributeError:
+ return id(self)
+
def __eq__(self, other):
try:
return (self._path == other._path
@@ -228,9 +255,7 @@
def filerev(self): return self._filerev
def filenode(self): return self._filenode
- def fileflags(self): return self._changectx.fileflags(self._path)
- def isexec(self): return 'x' in self.fileflags()
- def islink(self): return 'l' in self.fileflags()
+ def flags(self): return self._changectx.flags(self._path)
def filelog(self): return self._filelog
def rev(self):
@@ -376,12 +401,11 @@
# sort by revision (per file) which is a topological order
visit = []
for f in files:
- fn = [(n.rev(), n) for n in needed.keys() if n._path == f]
+ fn = [(n.rev(), n) for n in needed if n._path == f]
visit.extend(fn)
- visit.sort()
+
hist = {}
-
- for r, f in visit:
+ for r, f in util.sort(visit):
curr = decorate(f.data(), f)
for p in parents(f):
if p != nullid:
@@ -432,11 +456,41 @@
class workingctx(changectx):
"""A workingctx object makes access to data related to
- the current working directory convenient."""
- def __init__(self, repo):
+ the current working directory convenient.
+ parents - a pair of parent nodeids, or None to use the dirstate.
+ date - any valid date string or (unixtime, offset), or None.
+ user - username string, or None.
+ extra - a dictionary of extra values, or None.
+ changes - a list of file lists as returned by localrepo.status()
+ or None to use the repository status.
+ """
+ def __init__(self, repo, parents=None, text="", user=None, date=None,
+ extra=None, changes=None):
self._repo = repo
self._rev = None
self._node = None
+ self._text = text
+ if date:
+ self._date = util.parsedate(date)
+ if user:
+ self._user = user
+ if parents:
+ self._parents = [changectx(self._repo, p) for p in parents]
+ if changes:
+ self._status = list(changes)
+
+ self._extra = {}
+ if extra:
+ self._extra = extra.copy()
+ if 'branch' not in self._extra:
+ branch = self._repo.dirstate.branch()
+ try:
+ branch = branch.decode('UTF-8').encode('UTF-8')
+ except UnicodeDecodeError:
+ raise util.Abort(_('branch name not in UTF-8!'))
+ self._extra['branch'] = branch
+ if self._extra['branch'] == '':
+ self._extra['branch'] = 'default'
def __str__(self):
return str(self._parents[0]) + "+"
@@ -444,16 +498,28 @@
def __nonzero__(self):
return True
+ def __contains__(self, key):
+ return self._dirstate[key] not in "?r"
+
def __getattr__(self, name):
- if name == '_parents':
- self._parents = self._repo.parents()
- return self._parents
if name == '_status':
- self._status = self._repo.status()
+ self._status = self._repo.status(unknown=True)
return self._status
+ elif name == '_user':
+ self._user = self._repo.ui.username()
+ return self._user
+ elif name == '_date':
+ self._date = util.makedate()
+ return self._date
if name == '_manifest':
self._buildmanifest()
return self._manifest
+ elif name == '_parents':
+ p = self._repo.dirstate.parents()
+ if p[1] == nullid:
+ p = p[:-1]
+ self._parents = [changectx(self._repo, x) for x in p]
+ return self._parents
else:
raise AttributeError, name
@@ -462,16 +528,14 @@
man = self._parents[0].manifest().copy()
copied = self._repo.dirstate.copies()
- is_exec = util.execfunc(self._repo.root,
- lambda p: man.execf(copied.get(p,p)))
- is_link = util.linkfunc(self._repo.root,
- lambda p: man.linkf(copied.get(p,p)))
+ cf = lambda x: man.flags(copied.get(x, x))
+ ff = self._repo.dirstate.flagfunc(cf)
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
try:
- man.set(f, is_exec(f), is_link(f))
+ man.set(f, ff(f))
except OSError:
pass
@@ -483,13 +547,11 @@
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 user(self): return self._user or self._repo.ui.username()
+ def date(self): return self._date
+ def description(self): return self._text
def files(self):
- f = self.modified() + self.added() + self.removed()
- f.sort()
- return f
+ return util.sort(self._status[0] + self._status[1] + self._status[2])
def modified(self): return self._status[0]
def added(self): return self._status[1]
@@ -497,21 +559,18 @@
def deleted(self): return self._status[3]
def unknown(self): return self._status[4]
def clean(self): return self._status[5]
- def branch(self): return self._repo.dirstate.branch()
+ def branch(self): return self._extra['branch']
+ def extra(self): return self._extra
def tags(self):
t = []
[t.extend(p.tags()) for p in self.parents()]
return t
- def parents(self):
- """return contexts for each parent changeset"""
- return self._parents
-
def children(self):
return []
- def fileflags(self, path):
+ def flags(self, path):
if '_manifest' in self.__dict__:
try:
return self._manifest.flags(path)
@@ -521,12 +580,9 @@
pnode = self._parents[0].changeset()[0]
orig = self._repo.dirstate.copies().get(path, path)
node, flag = self._repo.manifest.find(pnode, orig)
- is_link = util.linkfunc(self._repo.root,
- lambda p: flag and 'l' in flag)
- is_exec = util.execfunc(self._repo.root,
- lambda p: flag and 'x' in flag)
try:
- return (is_link(path) and 'l' or '') + (is_exec(path) and 'x' or '')
+ ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
+ return ff(path)
except OSError:
pass
@@ -543,6 +599,9 @@
"""return the ancestor context of self and c2"""
return self._parents[0].ancestor(c2) # punt on two parents for now
+ def walk(self, match):
+ return util.sort(self._repo.dirstate.walk(match, True, False).keys())
+
class workingfilectx(filectx):
"""A workingfilectx object makes access to data related to a particular
file in the working directory convenient."""
@@ -625,3 +684,92 @@
return (t, tz)
def cmp(self, text): return self._repo.wread(self._path) == text
+
+class memctx(object):
+ """A memctx is a subset of changectx supposed to be built on memory
+ and passed to commit functions.
+
+ NOTE: this interface and the related memfilectx are experimental and
+ may change without notice.
+
+ parents - a pair of parent nodeids.
+ filectxfn - a callable taking (repo, memctx, path) arguments and
+ returning a memctx object.
+ date - any valid date string or (unixtime, offset), or None.
+ user - username string, or None.
+ extra - a dictionary of extra values, or None.
+ """
+ def __init__(self, repo, parents, text, files, filectxfn, user=None,
+ date=None, extra=None):
+ self._repo = repo
+ self._rev = None
+ self._node = None
+ self._text = text
+ self._date = date and util.parsedate(date) or util.makedate()
+ self._user = user
+ parents = [(p or nullid) for p in parents]
+ p1, p2 = parents
+ self._parents = [changectx(self._repo, p) for p in (p1, p2)]
+ files = util.sort(list(files))
+ self._status = [files, [], [], [], []]
+ self._filectxfn = filectxfn
+
+ self._extra = extra and extra.copy() or {}
+ if 'branch' not in self._extra:
+ self._extra['branch'] = 'default'
+ elif self._extra.get('branch') == '':
+ self._extra['branch'] = 'default'
+
+ def __str__(self):
+ return str(self._parents[0]) + "+"
+
+ def __int__(self):
+ return self._rev
+
+ def __nonzero__(self):
+ return True
+
+ def user(self): return self._user or self._repo.ui.username()
+ def date(self): return self._date
+ def description(self): return self._text
+ def files(self): return self.modified()
+ 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 branch(self): return self._extra['branch']
+ def extra(self): return self._extra
+ def flags(self, f): return self[f].flags()
+
+ def parents(self):
+ """return contexts for each parent changeset"""
+ return self._parents
+
+ def filectx(self, path, filelog=None):
+ """get a file context from the working directory"""
+ return self._filectxfn(self._repo, self, path)
+
+class memfilectx(object):
+ """A memfilectx is a subset of filectx supposed to be built by client
+ code and passed to commit functions.
+ """
+ def __init__(self, path, data, islink, isexec, copied):
+ """copied is the source file path, or None."""
+ self._path = path
+ self._data = data
+ self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
+ self._copied = None
+ if copied:
+ self._copied = (copied, nullid)
+
+ def __nonzero__(self): return True
+ def __str__(self): return "%s@%s" % (self.path(), self._changectx)
+ def path(self): return self._path
+ def data(self): return self._data
+ def flags(self): return self._flags
+ def isexec(self): return 'x' in self._flags
+ def islink(self): return 'l' in self._flags
+ def renamed(self): return self._copied
+