[PATCH 2 of 3 hglib] context: initial implementation of changectx

Idan Kamara idankk86 at gmail.com
Sat Oct 15 14:47:46 CDT 2011


# HG changeset patch
# User Idan Kamara <idankk86 at gmail.com>
# Date 1318708040 -7200
# Node ID be88c3cb597f4368903b6641c9e874eb8eb0b6a0
# Parent  5574413bce099f120fbf1ada7af59853c69f78db
context: initial implementation of changectx

diff --git a/hglib/context.py b/hglib/context.py
new file mode 100644
--- /dev/null
+++ b/hglib/context.py
@@ -0,0 +1,192 @@
+import client, util, templates
+
+nullcset = ['-1', '000000000000000000000000000000000000000', '', '', '', '']
+
+class changectx(object):
+    null = changectx(None, -1)
+
+    """A changecontext object makes access to data related to a particular
+    changeset convenient."""
+    def __init__(self, repo, changeid=''):
+        """changeid is a revision number, node, or tag"""
+        if changeid == '':
+            changeid = '.'
+        self._repo = repo
+        if isinstance(changeid, client.hgclient.revision):
+            cset = changeid
+        elif changeid == -1:
+            cset = nullcset
+        else:
+            if isinstance(changeid, (long, int)):
+                changeid = 'rev(%d)' % changeid
+
+            cset = self._repo.log(changeid)
+            if not len(cset):
+                raise ValueError('changeid %r not found in repo' % changeid)
+            if len(cset) > 1:
+                raise ValueError('changeid must yield a single changeset')
+            cset = cset[0]
+
+        self.rev, self.node, self.tags = cset[:3]
+        self.branch, self.author, self.description = cset[3:]
+
+        self.user = self.author
+        self.rev = int(self.rev)
+
+        self.tags = self.tags.split()
+        try:
+            self.tags.remove('tip')
+        except ValueError:
+            pass
+
+        self._ignored = None
+        self._clean = None
+
+    def __str__(self):
+        return self.node[:12]
+
+    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
+        except AttributeError:
+            return False
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def __nonzero__(self):
+        return self.rev != -1
+
+    def __contains__(self, key):
+        return key in self.manifest
+
+    def __getitem__(self, key):
+        return self.filectx(key)
+
+    def __iter__(self):
+        for f in sorted(self.manifest):
+            yield f
+
+    @util.propertycache
+    def _status(self):
+        return self._parsestatus(self._repo.status(change=self))[:4]
+
+    def _parsestatus(self, stat):
+        d = dict((c, []) for c in 'MAR!?IC ')
+        for k, path in stat:
+            d[k].append(path)
+        return d['M'], d['A'], d['R'], d['!'], d['?'], d['I'], d['C']
+
+    def status(self, ignored=False, clean=False):
+        """Explicit status query
+        Unless this method is used to query the working copy status, the
+        _status property will implicitly read the status using its default
+        arguments."""
+        stat = self._parsestatus(self._repo.status(change=self, ignored=ignored,
+                                                   clean=clean))
+        self._unknown = self._ignored = self._clean = None
+        if ignored:
+            self._ignored = stat[5]
+        if clean:
+            self._clean = stat[6]
+        self._status = stat[:4]
+        return stat
+
+    @property
+    def date(self):
+        return self._date
+    @property
+    def files(self):
+        return sorted(self._status[0] + self._status[1] + self._status[2])
+
+    @property
+    def modified(self):
+        return self._status[0]
+    @property
+    def added(self):
+        return self._status[1]
+    @property
+    def removed(self):
+        return self._status[2]
+    @property
+    def ignored(self):
+        if self._ignored is None:
+            self.status(ignored=True)
+        return self._ignored
+    @property
+    def clean(self):
+        if self._clean is None:
+            self.status(clean=True)
+        return self._clean
+
+    @util.propertycache
+    def manifest(self):
+        d = {}
+        for node, p, e, s, path in self._repo.manifest(rev=self):
+            d[path] = node
+        return d
+
+    @property
+    def hex(self):
+        return hex(self.node)
+
+    @util.propertycache
+    def parents(self):
+        """return contexts for each parent changeset"""
+        par = self._repo.parents(rev=self)
+        if not par:
+            return [changectx(self._repo, -1)]
+        return [changectx(self._repo, int(cset.rev)) for cset in par]
+
+    @property
+    def p1(self):
+        return self.parents[0]
+
+    @property
+    def p2(self):
+        if len(self.parents) == 2:
+            return self.parents[1]
+        return changectx(self._repo, -1)
+
+    @util.propertycache
+    def bookmarks(self):
+        books = [bm for bm in self._repo.bookmarks()[0] if bm[1] == self.rev]
+
+        bms = []
+        for name, r, n in books:
+            bms.append(name)
+        return bms
+
+    @property
+    def children(self):
+        """return contexts for each child changeset"""
+        for c in self._repo.log('children(%s)' % self.node):
+            yield changectx(self._repo, c)
+
+    @property
+    def ancestors(self):
+        for a in self._repo.log('ancestors(%s)' % self.node):
+            yield changectx(self._repo, a)
+
+    @property
+    def descendants(self):
+        for d in self._repo.log('descendants(%s)' % self.node):
+            yield changectx(self._repo, d)
+
+    def ancestor(self, c2):
+        """
+        return the ancestor context of self and c2
+        """
+        return changectx(self._repo, 'ancestor(%s, %s)' % (self, n2))
diff --git a/tests/test-context.py b/tests/test-context.py
new file mode 100644
--- /dev/null
+++ b/tests/test-context.py
@@ -0,0 +1,53 @@
+import common, hglib
+from hglib import context
+
+class test_context(common.basetest):
+    def test_non_existent(self):
+        self.assertRaises(ValueError, context.changectx, self.client, 'foo')
+
+    def test_basic(self):
+        self.append('a', 'a')
+        self.append('b', 'b')
+        rev0, node0 = self.client.commit('first', addremove=True)
+
+        self.append('c', 'c')
+        rev1, node1 = self.client.commit('second', addremove=True)
+
+        ctx = context.changectx(self.client, node0)
+
+        self.assertEquals(str(ctx), node0[:12])
+        self.assertEquals(ctx.node, node0)
+        self.assertEquals(int(ctx), rev0)
+        self.assertEquals(ctx.rev, rev0)
+
+        self.assertTrue(ctx)
+
+        self.assertTrue('a' in ctx and 'b' in ctx)
+        self.assertFalse('c' in ctx)
+        self.assertEquals(list(ctx), ['a', 'b'])
+        self.assertEquals(ctx.files, ['a', 'b'])
+
+        self.assertEquals(ctx.modified, [])
+        self.assertEquals(ctx.added, ['a', 'b'])
+        self.assertEquals(ctx.removed, [])
+        self.assertEquals(ctx.ignored, [])
+        self.assertEquals(ctx.clean, [])
+
+        man = {'a' : '047b75c6d7a3ef6a2243bd0e99f94f6ea6683597',
+               'b' : '62452855512f5b81522aa3895892760bb8da9f3f'}
+        self.assertEquals(ctx.manifest, man)
+
+        self.assertEquals([int(c) for c in ctx.parents], [-1])
+        self.assertEquals(int(ctx.p1), -1)
+        self.assertEquals(int(ctx.p2), -1)
+
+        self.assertEquals([int(c) for c in ctx.children], [1])
+        self.assertEquals([int(c) for c in ctx.descendants], [0, 1])
+        self.assertEquals([int(c) for c in ctx.ancestors], [0])
+
+        self.client.bookmark('bookmark', inactive=True, rev=node0)
+        self.assertEquals(ctx.bookmarks, ['bookmark'])
+
+        self.client.tag('tag', rev=node0)
+        # tags are read on construction
+        self.assertEquals(context.changectx(self.client, node0).tags, ['tag'])


More information about the Mercurial-devel mailing list