1import hglib.client # Circular dependency. 2from hglib import util, templates 3from hglib.error import CommandError 4from hglib.util import b, strtobytes, integertypes 5 6_nullcset = [b('-1'), b('0000000000000000000000000000000000000000'), b(''), 7 b(''), b(''), b(''), b('')] 8 9class changectx(object): 10 """A changecontext object makes access to data related to a particular 11 changeset convenient.""" 12 def __init__(self, repo, changeid=b('')): 13 """changeid is a revision number, node, or tag""" 14 if changeid == b(''): 15 changeid = b('.') 16 self._repo = repo 17 if isinstance(changeid, hglib.client.revision): 18 cset = changeid 19 elif changeid == -1: 20 cset = _nullcset 21 else: 22 if isinstance(changeid, integertypes): 23 changeid = b('rev(') + strtobytes(changeid) + b(')') 24 25 notfound = False 26 try: 27 cset = self._repo.log(changeid) 28 # hg bbf4f3dfd700 gave a null result for tip+1 29 if (cset and cset[0][1] == _nullcset[1] 30 and cset[0][0] != _nullcset[0]): 31 notfound = True 32 except CommandError: 33 notfound = True 34 35 if notfound or not len(cset): 36 raise ValueError('changeid %r not found in repo' % changeid) 37 if len(cset) > 1: 38 raise ValueError('changeid must yield a single changeset') 39 cset = cset[0] 40 41 self._rev, self._node, self._tags = cset[:3] 42 self._branch, self._author, self._description, self._date = cset[3:] 43 44 self._rev = int(self._rev) 45 46 self._tags = self._tags.split() 47 try: 48 self._tags.remove(b('tip')) 49 except ValueError: 50 pass 51 52 self._ignored = None 53 self._clean = None 54 55 def __str__(self): 56 return self._node[:12].decode('latin-1') 57 58 def __int__(self): 59 return self._rev 60 61 def __repr__(self): 62 return "<changectx %s>" % str(self) 63 64 def __hash__(self): 65 try: 66 return hash(self._rev) 67 except AttributeError: 68 return id(self) 69 70 def __eq__(self, other): 71 try: 72 return self._rev == other._rev 73 except AttributeError: 74 return False 75 76 def __ne__(self, other): 77 return not (self == other) 78 79 def __nonzero__(self): 80 return self._rev != -1 81 82 def __bool__(self): 83 return self.__nonzero__() 84 85 def __contains__(self, key): 86 return key in self._manifest 87 88 def __iter__(self): 89 for f in sorted(self._manifest): 90 yield f 91 92 @util.propertycache 93 def _status(self): 94 return self._parsestatus(self._repo.status(change=strtobytes(self)))[:4] 95 96 def _parsestatus(self, stat): 97 d = dict((c, []) 98 for c in (b('M'), b('A'), b('R'), b('!'), b('?'), b('I'), 99 b('C'), b(' '))) 100 for k, path in stat: 101 d[k].append(path) 102 return (d[b('M')], d[b('A')], d[b('R')], d[b('!')], d[b('?')], 103 d[b('I')], d[b('C')]) 104 105 def status(self, ignored=False, clean=False): 106 """Explicit status query 107 Unless this method is used to query the working copy status, the 108 _status property will implicitly read the status using its default 109 arguments.""" 110 stat = self._parsestatus(self._repo.status(change=strtobytes(self), 111 ignored=ignored, 112 clean=clean)) 113 self._unknown = self._ignored = self._clean = None 114 if ignored: 115 self._ignored = stat[5] 116 if clean: 117 self._clean = stat[6] 118 self._status = stat[:4] 119 return stat 120 121 def rev(self): 122 return self._rev 123 124 def node(self): 125 return self._node 126 127 def tags(self): 128 return self._tags 129 130 def branch(self): 131 return self._branch 132 133 def author(self): 134 return self._author 135 136 def user(self): 137 return self._author 138 139 def date(self): 140 return self._date 141 142 def description(self): 143 return self._description 144 145 def files(self): 146 return sorted(self._status[0] + self._status[1] + self._status[2]) 147 148 def modified(self): 149 return self._status[0] 150 151 def added(self): 152 return self._status[1] 153 154 def removed(self): 155 return self._status[2] 156 157 def ignored(self): 158 if self._ignored is None: 159 self.status(ignored=True) 160 return self._ignored 161 162 def clean(self): 163 if self._clean is None: 164 self.status(clean=True) 165 return self._clean 166 167 @util.propertycache 168 def _manifest(self): 169 d = {} 170 for node, p, e, s, path in self._repo.manifest(rev=strtobytes(self)): 171 d[path] = node 172 return d 173 174 def manifest(self): 175 return self._manifest 176 177 def hex(self): 178 return hex(self._node) 179 180 @util.propertycache 181 def _parents(self): 182 """return contexts for each parent changeset""" 183 par = self._repo.parents(rev=strtobytes(self)) 184 if not par: 185 return [changectx(self._repo, -1)] 186 return [changectx(self._repo, int(cset.rev)) for cset in par] 187 188 def parents(self): 189 return self._parents 190 191 def p1(self): 192 return self._parents[0] 193 194 def p2(self): 195 if len(self._parents) == 2: 196 return self._parents[1] 197 return changectx(self._repo, -1) 198 199 @util.propertycache 200 def _bookmarks(self): 201 books = [bm for bm in self._repo.bookmarks()[0] if bm[1] == self._rev] 202 203 bms = [] 204 for name, r, n in books: 205 bms.append(name) 206 return bms 207 208 def bookmarks(self): 209 return self._bookmarks 210 211 def hidden(self): 212 """return True if the changeset is hidden, else False""" 213 return bool(self._repo.log(revrange=self._node + b(' and hidden()'), 214 hidden=True)) 215 216 def phase(self): 217 """return the phase of the changeset (public, draft or secret)""" 218 return self._repo.phase(strtobytes(self._rev))[0][1] 219 220 def children(self): 221 """return contexts for each child changeset""" 222 for c in self._repo.log(b('children(') + self._node + b(')')): 223 yield changectx(self._repo, c) 224 225 def ancestors(self): 226 for a in self._repo.log(b('ancestors(') + self._node + b(')')): 227 yield changectx(self._repo, a) 228 229 def descendants(self): 230 for d in self._repo.log(b('descendants(') + self._node + b(')')): 231 yield changectx(self._repo, d) 232 233 def ancestor(self, c2): 234 """ 235 return the ancestor context of self and c2 236 """ 237 return changectx(self._repo, 238 b('ancestor(') + self + b(', ') + c2 + b(')')) 239