##// END OF EJS Templates
add date attribute to workingfilectx
Benoit Boissinot -
r3962:2b8825c9 default
parent child Browse files
Show More
@@ -1,505 +1,512 b''
1 1 # context.py - changeset and file context objects for mercurial
2 2 #
3 3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import *
9 9 from i18n import _
10 import ancestor, bdiff, repo, revlog, util, os
10 import ancestor, bdiff, repo, revlog, util, os, errno
11 11
12 12 class changectx(object):
13 13 """A changecontext object makes access to data related to a particular
14 14 changeset convenient."""
15 15 def __init__(self, repo, changeid=None):
16 16 """changeid is a revision number, node, or tag"""
17 17 self._repo = repo
18 18
19 19 if not changeid and changeid != 0:
20 20 p1, p2 = self._repo.dirstate.parents()
21 21 self._rev = self._repo.changelog.rev(p1)
22 22 if self._rev == -1:
23 23 changeid = 'tip'
24 24 else:
25 25 self._node = p1
26 26 return
27 27
28 28 self._node = self._repo.lookup(changeid)
29 29 self._rev = self._repo.changelog.rev(self._node)
30 30
31 31 def __str__(self):
32 32 return short(self.node())
33 33
34 34 def __repr__(self):
35 35 return "<changectx %s>" % str(self)
36 36
37 37 def __eq__(self, other):
38 38 try:
39 39 return self._rev == other._rev
40 40 except AttributeError:
41 41 return False
42 42
43 43 def __nonzero__(self):
44 44 return self._rev != nullrev
45 45
46 46 def __getattr__(self, name):
47 47 if name == '_changeset':
48 48 self._changeset = self._repo.changelog.read(self.node())
49 49 return self._changeset
50 50 elif name == '_manifest':
51 51 self._manifest = self._repo.manifest.read(self._changeset[0])
52 52 return self._manifest
53 53 elif name == '_manifestdelta':
54 54 md = self._repo.manifest.readdelta(self._changeset[0])
55 55 self._manifestdelta = md
56 56 return self._manifestdelta
57 57 else:
58 58 raise AttributeError, name
59 59
60 60 def changeset(self): return self._changeset
61 61 def manifest(self): return self._manifest
62 62
63 63 def rev(self): return self._rev
64 64 def node(self): return self._node
65 65 def user(self): return self._changeset[1]
66 66 def date(self): return self._changeset[2]
67 67 def files(self): return self._changeset[3]
68 68 def description(self): return self._changeset[4]
69 69 def branch(self): return self._changeset[5].get("branch", "")
70 70
71 71 def parents(self):
72 72 """return contexts for each parent changeset"""
73 73 p = self._repo.changelog.parents(self._node)
74 74 return [changectx(self._repo, x) for x in p]
75 75
76 76 def children(self):
77 77 """return contexts for each child changeset"""
78 78 c = self._repo.changelog.children(self._node)
79 79 return [changectx(self._repo, x) for x in c]
80 80
81 81 def filenode(self, path):
82 82 if '_manifest' in self.__dict__:
83 83 try:
84 84 return self._manifest[path]
85 85 except KeyError:
86 86 raise revlog.LookupError(_("'%s' not found in manifest") % path)
87 87 if '_manifestdelta' in self.__dict__ or path in self.files():
88 88 if path in self._manifestdelta:
89 89 return self._manifestdelta[path]
90 90 node, flag = self._repo.manifest.find(self._changeset[0], path)
91 91 if not node:
92 92 raise revlog.LookupError(_("'%s' not found in manifest") % path)
93 93
94 94 return node
95 95
96 96 def filectx(self, path, fileid=None):
97 97 """get a file context from this changeset"""
98 98 if fileid is None:
99 99 fileid = self.filenode(path)
100 100 return filectx(self._repo, path, fileid=fileid, changectx=self)
101 101
102 102 def filectxs(self):
103 103 """generate a file context for each file in this changeset's
104 104 manifest"""
105 105 mf = self.manifest()
106 106 m = mf.keys()
107 107 m.sort()
108 108 for f in m:
109 109 yield self.filectx(f, fileid=mf[f])
110 110
111 111 def ancestor(self, c2):
112 112 """
113 113 return the ancestor context of self and c2
114 114 """
115 115 n = self._repo.changelog.ancestor(self._node, c2._node)
116 116 return changectx(self._repo, n)
117 117
118 118 class filectx(object):
119 119 """A filecontext object makes access to data related to a particular
120 120 filerevision convenient."""
121 121 def __init__(self, repo, path, changeid=None, fileid=None,
122 122 filelog=None, changectx=None):
123 123 """changeid can be a changeset revision, node, or tag.
124 124 fileid can be a file revision or node."""
125 125 self._repo = repo
126 126 self._path = path
127 127
128 128 assert changeid is not None or fileid is not None
129 129
130 130 if filelog:
131 131 self._filelog = filelog
132 132 if changectx:
133 133 self._changectx = changectx
134 134 self._changeid = changectx.node()
135 135
136 136 if fileid is None:
137 137 self._changeid = changeid
138 138 else:
139 139 self._fileid = fileid
140 140
141 141 def __getattr__(self, name):
142 142 if name == '_changectx':
143 143 self._changectx = changectx(self._repo, self._changeid)
144 144 return self._changectx
145 145 elif name == '_filelog':
146 146 self._filelog = self._repo.file(self._path)
147 147 return self._filelog
148 148 elif name == '_changeid':
149 149 self._changeid = self._filelog.linkrev(self._filenode)
150 150 return self._changeid
151 151 elif name == '_filenode':
152 152 if '_fileid' in self.__dict__:
153 153 self._filenode = self._filelog.lookup(self._fileid)
154 154 else:
155 155 self._filenode = self._changectx.filenode(self._path)
156 156 return self._filenode
157 157 elif name == '_filerev':
158 158 self._filerev = self._filelog.rev(self._filenode)
159 159 return self._filerev
160 160 else:
161 161 raise AttributeError, name
162 162
163 163 def __nonzero__(self):
164 164 try:
165 165 n = self._filenode
166 166 return True
167 167 except revlog.LookupError:
168 168 # file is missing
169 169 return False
170 170
171 171 def __str__(self):
172 172 return "%s@%s" % (self.path(), short(self.node()))
173 173
174 174 def __repr__(self):
175 175 return "<filectx %s>" % str(self)
176 176
177 177 def __eq__(self, other):
178 178 try:
179 179 return (self._path == other._path
180 180 and self._changeid == other._changeid)
181 181 except AttributeError:
182 182 return False
183 183
184 184 def filectx(self, fileid):
185 185 '''opens an arbitrary revision of the file without
186 186 opening a new filelog'''
187 187 return filectx(self._repo, self._path, fileid=fileid,
188 188 filelog=self._filelog)
189 189
190 190 def filerev(self): return self._filerev
191 191 def filenode(self): return self._filenode
192 192 def filelog(self): return self._filelog
193 193
194 194 def rev(self):
195 195 if '_changectx' in self.__dict__:
196 196 return self._changectx.rev()
197 197 return self._filelog.linkrev(self._filenode)
198 198
199 199 def node(self): return self._changectx.node()
200 200 def user(self): return self._changectx.user()
201 201 def date(self): return self._changectx.date()
202 202 def files(self): return self._changectx.files()
203 203 def description(self): return self._changectx.description()
204 204 def branch(self): return self._changectx.branch()
205 205 def manifest(self): return self._changectx.manifest()
206 206 def changectx(self): return self._changectx
207 207
208 208 def data(self): return self._filelog.read(self._filenode)
209 209 def renamed(self): return self._filelog.renamed(self._filenode)
210 210 def path(self): return self._path
211 211 def size(self): return self._filelog.size(self._filerev)
212 212
213 213 def cmp(self, text): return self._filelog.cmp(self._filenode, text)
214 214
215 215 def parents(self):
216 216 p = self._path
217 217 fl = self._filelog
218 218 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
219 219
220 220 r = self.renamed()
221 221 if r:
222 222 pl[0] = (r[0], r[1], None)
223 223
224 224 return [filectx(self._repo, p, fileid=n, filelog=l)
225 225 for p,n,l in pl if n != nullid]
226 226
227 227 def children(self):
228 228 # hard for renames
229 229 c = self._filelog.children(self._filenode)
230 230 return [filectx(self._repo, self._path, fileid=x,
231 231 filelog=self._filelog) for x in c]
232 232
233 233 def annotate(self, follow=False):
234 234 '''returns a list of tuples of (ctx, line) for each line
235 235 in the file, where ctx is the filectx of the node where
236 236 that line was last changed'''
237 237
238 238 def decorate(text, rev):
239 239 return ([rev] * len(text.splitlines()), text)
240 240
241 241 def pair(parent, child):
242 242 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
243 243 child[0][b1:b2] = parent[0][a1:a2]
244 244 return child
245 245
246 246 getlog = util.cachefunc(lambda x: self._repo.file(x))
247 247 def getctx(path, fileid):
248 248 log = path == self._path and self._filelog or getlog(path)
249 249 return filectx(self._repo, path, fileid=fileid, filelog=log)
250 250 getctx = util.cachefunc(getctx)
251 251
252 252 def parents(f):
253 253 # we want to reuse filectx objects as much as possible
254 254 p = f._path
255 255 if f._filerev is None: # working dir
256 256 pl = [(n.path(), n.filerev()) for n in f.parents()]
257 257 else:
258 258 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
259 259
260 260 if follow:
261 261 r = f.renamed()
262 262 if r:
263 263 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
264 264
265 265 return [getctx(p, n) for p, n in pl if n != nullrev]
266 266
267 267 # use linkrev to find the first changeset where self appeared
268 268 if self.rev() != self._filelog.linkrev(self._filenode):
269 269 base = self.filectx(self.filerev())
270 270 else:
271 271 base = self
272 272
273 273 # find all ancestors
274 274 needed = {base: 1}
275 275 visit = [base]
276 276 files = [base._path]
277 277 while visit:
278 278 f = visit.pop(0)
279 279 for p in parents(f):
280 280 if p not in needed:
281 281 needed[p] = 1
282 282 visit.append(p)
283 283 if p._path not in files:
284 284 files.append(p._path)
285 285 else:
286 286 # count how many times we'll use this
287 287 needed[p] += 1
288 288
289 289 # sort by revision (per file) which is a topological order
290 290 visit = []
291 291 files.reverse()
292 292 for f in files:
293 293 fn = [(n._filerev, n) for n in needed.keys() if n._path == f]
294 294 fn.sort()
295 295 visit.extend(fn)
296 296 hist = {}
297 297
298 298 for r, f in visit:
299 299 curr = decorate(f.data(), f)
300 300 for p in parents(f):
301 301 if p != nullid:
302 302 curr = pair(hist[p], curr)
303 303 # trim the history of unneeded revs
304 304 needed[p] -= 1
305 305 if not needed[p]:
306 306 del hist[p]
307 307 hist[f] = curr
308 308
309 309 return zip(hist[f][0], hist[f][1].splitlines(1))
310 310
311 311 def ancestor(self, fc2):
312 312 """
313 313 find the common ancestor file context, if any, of self, and fc2
314 314 """
315 315
316 316 acache = {}
317 317
318 318 # prime the ancestor cache for the working directory
319 319 for c in (self, fc2):
320 320 if c._filerev == None:
321 321 pl = [(n.path(), n.filenode()) for n in c.parents()]
322 322 acache[(c._path, None)] = pl
323 323
324 324 flcache = {self._path:self._filelog, fc2._path:fc2._filelog}
325 325 def parents(vertex):
326 326 if vertex in acache:
327 327 return acache[vertex]
328 328 f, n = vertex
329 329 if f not in flcache:
330 330 flcache[f] = self._repo.file(f)
331 331 fl = flcache[f]
332 332 pl = [(f, p) for p in fl.parents(n) if p != nullid]
333 333 re = fl.renamed(n)
334 334 if re:
335 335 pl.append(re)
336 336 acache[vertex] = pl
337 337 return pl
338 338
339 339 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
340 340 v = ancestor.ancestor(a, b, parents)
341 341 if v:
342 342 f, n = v
343 343 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
344 344
345 345 return None
346 346
347 347 class workingctx(changectx):
348 348 """A workingctx object makes access to data related to
349 349 the current working directory convenient."""
350 350 def __init__(self, repo):
351 351 self._repo = repo
352 352 self._rev = None
353 353 self._node = None
354 354
355 355 def __str__(self):
356 356 return str(self._parents[0]) + "+"
357 357
358 358 def __nonzero__(self):
359 359 return True
360 360
361 361 def __getattr__(self, name):
362 362 if name == '_parents':
363 363 self._parents = self._repo.parents()
364 364 return self._parents
365 365 if name == '_status':
366 366 self._status = self._repo.status()
367 367 return self._status
368 368 if name == '_manifest':
369 369 self._buildmanifest()
370 370 return self._manifest
371 371 else:
372 372 raise AttributeError, name
373 373
374 374 def _buildmanifest(self):
375 375 """generate a manifest corresponding to the working directory"""
376 376
377 377 man = self._parents[0].manifest().copy()
378 378 copied = self._repo.dirstate.copies()
379 379 modified, added, removed, deleted, unknown = self._status[:5]
380 380 for i, l in (("a", added), ("m", modified), ("u", unknown)):
381 381 for f in l:
382 382 man[f] = man.get(copied.get(f, f), nullid) + i
383 383 try:
384 384 man.set(f, util.is_exec(self._repo.wjoin(f), man.execf(f)))
385 385 except OSError:
386 386 pass
387 387
388 388 for f in deleted + removed:
389 389 if f in man:
390 390 del man[f]
391 391
392 392 self._manifest = man
393 393
394 394 def manifest(self): return self._manifest
395 395
396 396 def user(self): return self._repo.ui.username()
397 397 def date(self): return util.makedate()
398 398 def description(self): return ""
399 399 def files(self):
400 400 f = self.modified() + self.added() + self.removed()
401 401 f.sort()
402 402 return f
403 403
404 404 def modified(self): return self._status[0]
405 405 def added(self): return self._status[1]
406 406 def removed(self): return self._status[2]
407 407 def deleted(self): return self._status[3]
408 408 def unknown(self): return self._status[4]
409 409 def clean(self): return self._status[5]
410 410 def branch(self):
411 411 try:
412 412 return self._repo.opener("branch").read().strip()
413 413 except IOError:
414 414 return ""
415 415
416 416 def parents(self):
417 417 """return contexts for each parent changeset"""
418 418 return self._parents
419 419
420 420 def children(self):
421 421 return []
422 422
423 423 def filectx(self, path):
424 424 """get a file context from the working directory"""
425 425 return workingfilectx(self._repo, path, workingctx=self)
426 426
427 427 def ancestor(self, c2):
428 428 """return the ancestor context of self and c2"""
429 429 return self._parents[0].ancestor(c2) # punt on two parents for now
430 430
431 431 class workingfilectx(filectx):
432 432 """A workingfilectx object makes access to data related to a particular
433 433 file in the working directory convenient."""
434 434 def __init__(self, repo, path, filelog=None, workingctx=None):
435 435 """changeid can be a changeset revision, node, or tag.
436 436 fileid can be a file revision or node."""
437 437 self._repo = repo
438 438 self._path = path
439 439 self._changeid = None
440 440 self._filerev = self._filenode = None
441 441
442 442 if filelog:
443 443 self._filelog = filelog
444 444 if workingctx:
445 445 self._changectx = workingctx
446 446
447 447 def __getattr__(self, name):
448 448 if name == '_changectx':
449 449 self._changectx = workingctx(repo)
450 450 return self._changectx
451 451 elif name == '_repopath':
452 452 self._repopath = (self._repo.dirstate.copied(self._path)
453 453 or self._path)
454 454 return self._repopath
455 455 elif name == '_filelog':
456 456 self._filelog = self._repo.file(self._repopath)
457 457 return self._filelog
458 458 else:
459 459 raise AttributeError, name
460 460
461 461 def __nonzero__(self):
462 462 return True
463 463
464 464 def __str__(self):
465 465 return "%s@%s" % (self.path(), self._changectx)
466 466
467 467 def filectx(self, fileid):
468 468 '''opens an arbitrary revision of the file without
469 469 opening a new filelog'''
470 470 return filectx(self._repo, self._repopath, fileid=fileid,
471 471 filelog=self._filelog)
472 472
473 473 def rev(self):
474 474 if '_changectx' in self.__dict__:
475 475 return self._changectx.rev()
476 476 return self._filelog.linkrev(self._filenode)
477 477
478 478 def data(self): return self._repo.wread(self._path)
479 479 def renamed(self):
480 480 rp = self._repopath
481 481 if rp == self._path:
482 482 return None
483 483 return rp, self._workingctx._parents._manifest.get(rp, nullid)
484 484
485 485 def parents(self):
486 486 '''return parent filectxs, following copies if necessary'''
487 487 p = self._path
488 488 rp = self._repopath
489 489 pcl = self._changectx._parents
490 490 fl = self._filelog
491 491 pl = [(rp, pcl[0]._manifest.get(rp, nullid), fl)]
492 492 if len(pcl) > 1:
493 493 if rp != p:
494 494 fl = None
495 495 pl.append((p, pcl[1]._manifest.get(p, nullid), fl))
496 496
497 497 return [filectx(self._repo, p, fileid=n, filelog=l)
498 498 for p,n,l in pl if n != nullid]
499 499
500 500 def children(self):
501 501 return []
502 502
503 503 def size(self): return os.stat(self._repo.wjoin(self._path)).st_size
504 def date(self):
505 t, tz = self._changectx.date()
506 try:
507 return (os.lstat(repo.wjoin(self._path)).st_mtime, tz)
508 except OSError, err:
509 if err.errno != errno.ENOENT: raise
510 return (t, tz)
504 511
505 512 def cmp(self, text): return self._repo.wread(self._path) == text
General Comments 0
You need to be logged in to leave comments. Login now