##// END OF EJS Templates
Fix workingctx exec/link bit of copies on non-supporting systems
Patrick Mezard -
r5407:d7e7902b default
parent child Browse files
Show More
@@ -1,587 +1,590 b''
1 1 # context.py - changeset and file context objects for mercurial
2 2 #
3 3 # Copyright 2006, 2007 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 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 __ne__(self, other):
44 44 return not (self == other)
45 45
46 46 def __nonzero__(self):
47 47 return self._rev != nullrev
48 48
49 49 def __getattr__(self, name):
50 50 if name == '_changeset':
51 51 self._changeset = self._repo.changelog.read(self.node())
52 52 return self._changeset
53 53 elif name == '_manifest':
54 54 self._manifest = self._repo.manifest.read(self._changeset[0])
55 55 return self._manifest
56 56 elif name == '_manifestdelta':
57 57 md = self._repo.manifest.readdelta(self._changeset[0])
58 58 self._manifestdelta = md
59 59 return self._manifestdelta
60 60 else:
61 61 raise AttributeError, name
62 62
63 63 def __contains__(self, key):
64 64 return key in self._manifest
65 65
66 66 def __getitem__(self, key):
67 67 return self.filectx(key)
68 68
69 69 def __iter__(self):
70 70 a = self._manifest.keys()
71 71 a.sort()
72 72 for f in a:
73 73 return f
74 74
75 75 def changeset(self): return self._changeset
76 76 def manifest(self): return self._manifest
77 77
78 78 def rev(self): return self._rev
79 79 def node(self): return self._node
80 80 def user(self): return self._changeset[1]
81 81 def date(self): return self._changeset[2]
82 82 def files(self): return self._changeset[3]
83 83 def description(self): return self._changeset[4]
84 84 def branch(self): return self._changeset[5].get("branch")
85 85 def tags(self): return self._repo.nodetags(self._node)
86 86
87 87 def parents(self):
88 88 """return contexts for each parent changeset"""
89 89 p = self._repo.changelog.parents(self._node)
90 90 return [changectx(self._repo, x) for x in p]
91 91
92 92 def children(self):
93 93 """return contexts for each child changeset"""
94 94 c = self._repo.changelog.children(self._node)
95 95 return [changectx(self._repo, x) for x in c]
96 96
97 97 def _fileinfo(self, path):
98 98 if '_manifest' in self.__dict__:
99 99 try:
100 100 return self._manifest[path], self._manifest.flags(path)
101 101 except KeyError:
102 102 raise revlog.LookupError(_("'%s' not found in manifest") % path)
103 103 if '_manifestdelta' in self.__dict__ or path in self.files():
104 104 if path in self._manifestdelta:
105 105 return self._manifestdelta[path], self._manifestdelta.flags(path)
106 106 node, flag = self._repo.manifest.find(self._changeset[0], path)
107 107 if not node:
108 108 raise revlog.LookupError(_("'%s' not found in manifest") % path)
109 109
110 110 return node, flag
111 111
112 112 def filenode(self, path):
113 113 return self._fileinfo(path)[0]
114 114
115 115 def fileflags(self, path):
116 116 try:
117 117 return self._fileinfo(path)[1]
118 118 except revlog.LookupError:
119 119 return ''
120 120
121 121 def filectx(self, path, fileid=None, filelog=None):
122 122 """get a file context from this changeset"""
123 123 if fileid is None:
124 124 fileid = self.filenode(path)
125 125 return filectx(self._repo, path, fileid=fileid,
126 126 changectx=self, filelog=filelog)
127 127
128 128 def filectxs(self):
129 129 """generate a file context for each file in this changeset's
130 130 manifest"""
131 131 mf = self.manifest()
132 132 m = mf.keys()
133 133 m.sort()
134 134 for f in m:
135 135 yield self.filectx(f, fileid=mf[f])
136 136
137 137 def ancestor(self, c2):
138 138 """
139 139 return the ancestor context of self and c2
140 140 """
141 141 n = self._repo.changelog.ancestor(self._node, c2._node)
142 142 return changectx(self._repo, n)
143 143
144 144 class filectx(object):
145 145 """A filecontext object makes access to data related to a particular
146 146 filerevision convenient."""
147 147 def __init__(self, repo, path, changeid=None, fileid=None,
148 148 filelog=None, changectx=None):
149 149 """changeid can be a changeset revision, node, or tag.
150 150 fileid can be a file revision or node."""
151 151 self._repo = repo
152 152 self._path = path
153 153
154 154 assert (changeid is not None
155 155 or fileid is not None
156 156 or changectx is not None)
157 157
158 158 if filelog:
159 159 self._filelog = filelog
160 160
161 161 if fileid is None:
162 162 if changectx is None:
163 163 self._changeid = changeid
164 164 else:
165 165 self._changectx = changectx
166 166 else:
167 167 self._fileid = fileid
168 168
169 169 def __getattr__(self, name):
170 170 if name == '_changectx':
171 171 self._changectx = changectx(self._repo, self._changeid)
172 172 return self._changectx
173 173 elif name == '_filelog':
174 174 self._filelog = self._repo.file(self._path)
175 175 return self._filelog
176 176 elif name == '_changeid':
177 177 self._changeid = self._filelog.linkrev(self._filenode)
178 178 return self._changeid
179 179 elif name == '_filenode':
180 180 if '_fileid' in self.__dict__:
181 181 self._filenode = self._filelog.lookup(self._fileid)
182 182 else:
183 183 self._filenode = self._changectx.filenode(self._path)
184 184 return self._filenode
185 185 elif name == '_filerev':
186 186 self._filerev = self._filelog.rev(self._filenode)
187 187 return self._filerev
188 188 else:
189 189 raise AttributeError, name
190 190
191 191 def __nonzero__(self):
192 192 try:
193 193 n = self._filenode
194 194 return True
195 195 except revlog.LookupError:
196 196 # file is missing
197 197 return False
198 198
199 199 def __str__(self):
200 200 return "%s@%s" % (self.path(), short(self.node()))
201 201
202 202 def __repr__(self):
203 203 return "<filectx %s>" % str(self)
204 204
205 205 def __eq__(self, other):
206 206 try:
207 207 return (self._path == other._path
208 208 and self._fileid == other._fileid)
209 209 except AttributeError:
210 210 return False
211 211
212 212 def __ne__(self, other):
213 213 return not (self == other)
214 214
215 215 def filectx(self, fileid):
216 216 '''opens an arbitrary revision of the file without
217 217 opening a new filelog'''
218 218 return filectx(self._repo, self._path, fileid=fileid,
219 219 filelog=self._filelog)
220 220
221 221 def filerev(self): return self._filerev
222 222 def filenode(self): return self._filenode
223 223 def fileflags(self): return self._changectx.fileflags(self._path)
224 224 def isexec(self): return 'x' in self.fileflags()
225 225 def islink(self): return 'l' in self.fileflags()
226 226 def filelog(self): return self._filelog
227 227
228 228 def rev(self):
229 229 if '_changectx' in self.__dict__:
230 230 return self._changectx.rev()
231 231 return self._filelog.linkrev(self._filenode)
232 232
233 233 def node(self): return self._changectx.node()
234 234 def user(self): return self._changectx.user()
235 235 def date(self): return self._changectx.date()
236 236 def files(self): return self._changectx.files()
237 237 def description(self): return self._changectx.description()
238 238 def branch(self): return self._changectx.branch()
239 239 def manifest(self): return self._changectx.manifest()
240 240 def changectx(self): return self._changectx
241 241
242 242 def data(self): return self._filelog.read(self._filenode)
243 243 def renamed(self): return self._filelog.renamed(self._filenode)
244 244 def path(self): return self._path
245 245 def size(self): return self._filelog.size(self._filerev)
246 246
247 247 def cmp(self, text): return self._filelog.cmp(self._filenode, text)
248 248
249 249 def parents(self):
250 250 p = self._path
251 251 fl = self._filelog
252 252 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
253 253
254 254 r = self.renamed()
255 255 if r:
256 256 pl[0] = (r[0], r[1], None)
257 257
258 258 return [filectx(self._repo, p, fileid=n, filelog=l)
259 259 for p,n,l in pl if n != nullid]
260 260
261 261 def children(self):
262 262 # hard for renames
263 263 c = self._filelog.children(self._filenode)
264 264 return [filectx(self._repo, self._path, fileid=x,
265 265 filelog=self._filelog) for x in c]
266 266
267 267 def annotate(self, follow=False, linenumber=None):
268 268 '''returns a list of tuples of (ctx, line) for each line
269 269 in the file, where ctx is the filectx of the node where
270 270 that line was last changed.
271 271 This returns tuples of ((ctx, linenumber), line) for each line,
272 272 if "linenumber" parameter is NOT "None".
273 273 In such tuples, linenumber means one at the first appearance
274 274 in the managed file.
275 275 To reduce annotation cost,
276 276 this returns fixed value(False is used) as linenumber,
277 277 if "linenumber" parameter is "False".'''
278 278
279 279 def decorate_compat(text, rev):
280 280 return ([rev] * len(text.splitlines()), text)
281 281
282 282 def without_linenumber(text, rev):
283 283 return ([(rev, False)] * len(text.splitlines()), text)
284 284
285 285 def with_linenumber(text, rev):
286 286 size = len(text.splitlines())
287 287 return ([(rev, i) for i in xrange(1, size + 1)], text)
288 288
289 289 decorate = (((linenumber is None) and decorate_compat) or
290 290 (linenumber and with_linenumber) or
291 291 without_linenumber)
292 292
293 293 def pair(parent, child):
294 294 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
295 295 child[0][b1:b2] = parent[0][a1:a2]
296 296 return child
297 297
298 298 getlog = util.cachefunc(lambda x: self._repo.file(x))
299 299 def getctx(path, fileid):
300 300 log = path == self._path and self._filelog or getlog(path)
301 301 return filectx(self._repo, path, fileid=fileid, filelog=log)
302 302 getctx = util.cachefunc(getctx)
303 303
304 304 def parents(f):
305 305 # we want to reuse filectx objects as much as possible
306 306 p = f._path
307 307 if f._filerev is None: # working dir
308 308 pl = [(n.path(), n.filerev()) for n in f.parents()]
309 309 else:
310 310 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
311 311
312 312 if follow:
313 313 r = f.renamed()
314 314 if r:
315 315 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
316 316
317 317 return [getctx(p, n) for p, n in pl if n != nullrev]
318 318
319 319 # use linkrev to find the first changeset where self appeared
320 320 if self.rev() != self._filelog.linkrev(self._filenode):
321 321 base = self.filectx(self.filerev())
322 322 else:
323 323 base = self
324 324
325 325 # find all ancestors
326 326 needed = {base: 1}
327 327 visit = [base]
328 328 files = [base._path]
329 329 while visit:
330 330 f = visit.pop(0)
331 331 for p in parents(f):
332 332 if p not in needed:
333 333 needed[p] = 1
334 334 visit.append(p)
335 335 if p._path not in files:
336 336 files.append(p._path)
337 337 else:
338 338 # count how many times we'll use this
339 339 needed[p] += 1
340 340
341 341 # sort by revision (per file) which is a topological order
342 342 visit = []
343 343 for f in files:
344 344 fn = [(n.rev(), n) for n in needed.keys() if n._path == f]
345 345 visit.extend(fn)
346 346 visit.sort()
347 347 hist = {}
348 348
349 349 for r, f in visit:
350 350 curr = decorate(f.data(), f)
351 351 for p in parents(f):
352 352 if p != nullid:
353 353 curr = pair(hist[p], curr)
354 354 # trim the history of unneeded revs
355 355 needed[p] -= 1
356 356 if not needed[p]:
357 357 del hist[p]
358 358 hist[f] = curr
359 359
360 360 return zip(hist[f][0], hist[f][1].splitlines(1))
361 361
362 362 def ancestor(self, fc2):
363 363 """
364 364 find the common ancestor file context, if any, of self, and fc2
365 365 """
366 366
367 367 acache = {}
368 368
369 369 # prime the ancestor cache for the working directory
370 370 for c in (self, fc2):
371 371 if c._filerev == None:
372 372 pl = [(n.path(), n.filenode()) for n in c.parents()]
373 373 acache[(c._path, None)] = pl
374 374
375 375 flcache = {self._path:self._filelog, fc2._path:fc2._filelog}
376 376 def parents(vertex):
377 377 if vertex in acache:
378 378 return acache[vertex]
379 379 f, n = vertex
380 380 if f not in flcache:
381 381 flcache[f] = self._repo.file(f)
382 382 fl = flcache[f]
383 383 pl = [(f, p) for p in fl.parents(n) if p != nullid]
384 384 re = fl.renamed(n)
385 385 if re:
386 386 pl.append(re)
387 387 acache[vertex] = pl
388 388 return pl
389 389
390 390 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
391 391 v = ancestor.ancestor(a, b, parents)
392 392 if v:
393 393 f, n = v
394 394 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
395 395
396 396 return None
397 397
398 398 class workingctx(changectx):
399 399 """A workingctx object makes access to data related to
400 400 the current working directory convenient."""
401 401 def __init__(self, repo):
402 402 self._repo = repo
403 403 self._rev = None
404 404 self._node = None
405 405
406 406 def __str__(self):
407 407 return str(self._parents[0]) + "+"
408 408
409 409 def __nonzero__(self):
410 410 return True
411 411
412 412 def __getattr__(self, name):
413 413 if name == '_parents':
414 414 self._parents = self._repo.parents()
415 415 return self._parents
416 416 if name == '_status':
417 417 self._status = self._repo.status()
418 418 return self._status
419 419 if name == '_manifest':
420 420 self._buildmanifest()
421 421 return self._manifest
422 422 else:
423 423 raise AttributeError, name
424 424
425 425 def _buildmanifest(self):
426 426 """generate a manifest corresponding to the working directory"""
427 427
428 428 man = self._parents[0].manifest().copy()
429 is_exec = util.execfunc(self._repo.root, man.execf)
430 is_link = util.linkfunc(self._repo.root, man.linkf)
431 429 copied = self._repo.dirstate.copies()
430 is_exec = util.execfunc(self._repo.root,
431 lambda p: man.execf(copied.get(p,p)))
432 is_link = util.linkfunc(self._repo.root,
433 lambda p: man.linkf(copied.get(p,p)))
432 434 modified, added, removed, deleted, unknown = self._status[:5]
433 435 for i, l in (("a", added), ("m", modified), ("u", unknown)):
434 436 for f in l:
435 437 man[f] = man.get(copied.get(f, f), nullid) + i
436 438 try:
437 439 man.set(f, is_exec(f), is_link(f))
438 440 except OSError:
439 441 pass
440 442
441 443 for f in deleted + removed:
442 444 if f in man:
443 445 del man[f]
444 446
445 447 self._manifest = man
446 448
447 449 def manifest(self): return self._manifest
448 450
449 451 def user(self): return self._repo.ui.username()
450 452 def date(self): return util.makedate()
451 453 def description(self): return ""
452 454 def files(self):
453 455 f = self.modified() + self.added() + self.removed()
454 456 f.sort()
455 457 return f
456 458
457 459 def modified(self): return self._status[0]
458 460 def added(self): return self._status[1]
459 461 def removed(self): return self._status[2]
460 462 def deleted(self): return self._status[3]
461 463 def unknown(self): return self._status[4]
462 464 def clean(self): return self._status[5]
463 465 def branch(self): return self._repo.dirstate.branch()
464 466
465 467 def tags(self):
466 468 t = []
467 469 [t.extend(p.tags()) for p in self.parents()]
468 470 return t
469 471
470 472 def parents(self):
471 473 """return contexts for each parent changeset"""
472 474 return self._parents
473 475
474 476 def children(self):
475 477 return []
476 478
477 479 def fileflags(self, path):
478 480 if '_manifest' in self.__dict__:
479 481 try:
480 482 return self._manifest.flags(path)
481 483 except KeyError:
482 484 return ''
483 485
484 486 pnode = self._parents[0].changeset()[0]
485 node, flag = self._repo.manifest.find(pnode, path)
487 orig = self._repo.dirstate.copies().get(path, path)
488 node, flag = self._repo.manifest.find(pnode, orig)
486 489 is_link = util.linkfunc(self._repo.root, lambda p: 'l' in flag)
487 490 is_exec = util.execfunc(self._repo.root, lambda p: 'x' in flag)
488 491 try:
489 492 return (is_link(path) and 'l' or '') + (is_exec(path) and 'e' or '')
490 493 except OSError:
491 494 pass
492 495
493 496 if not node or path in self.deleted() or path in self.removed():
494 497 return ''
495 498 return flag
496 499
497 500 def filectx(self, path, filelog=None):
498 501 """get a file context from the working directory"""
499 502 return workingfilectx(self._repo, path, workingctx=self,
500 503 filelog=filelog)
501 504
502 505 def ancestor(self, c2):
503 506 """return the ancestor context of self and c2"""
504 507 return self._parents[0].ancestor(c2) # punt on two parents for now
505 508
506 509 class workingfilectx(filectx):
507 510 """A workingfilectx object makes access to data related to a particular
508 511 file in the working directory convenient."""
509 512 def __init__(self, repo, path, filelog=None, workingctx=None):
510 513 """changeid can be a changeset revision, node, or tag.
511 514 fileid can be a file revision or node."""
512 515 self._repo = repo
513 516 self._path = path
514 517 self._changeid = None
515 518 self._filerev = self._filenode = None
516 519
517 520 if filelog:
518 521 self._filelog = filelog
519 522 if workingctx:
520 523 self._changectx = workingctx
521 524
522 525 def __getattr__(self, name):
523 526 if name == '_changectx':
524 527 self._changectx = workingctx(self._repo)
525 528 return self._changectx
526 529 elif name == '_repopath':
527 530 self._repopath = (self._repo.dirstate.copied(self._path)
528 531 or self._path)
529 532 return self._repopath
530 533 elif name == '_filelog':
531 534 self._filelog = self._repo.file(self._repopath)
532 535 return self._filelog
533 536 else:
534 537 raise AttributeError, name
535 538
536 539 def __nonzero__(self):
537 540 return True
538 541
539 542 def __str__(self):
540 543 return "%s@%s" % (self.path(), self._changectx)
541 544
542 545 def filectx(self, fileid):
543 546 '''opens an arbitrary revision of the file without
544 547 opening a new filelog'''
545 548 return filectx(self._repo, self._repopath, fileid=fileid,
546 549 filelog=self._filelog)
547 550
548 551 def rev(self):
549 552 if '_changectx' in self.__dict__:
550 553 return self._changectx.rev()
551 554 return self._filelog.linkrev(self._filenode)
552 555
553 556 def data(self): return self._repo.wread(self._path)
554 557 def renamed(self):
555 558 rp = self._repopath
556 559 if rp == self._path:
557 560 return None
558 561 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
559 562
560 563 def parents(self):
561 564 '''return parent filectxs, following copies if necessary'''
562 565 p = self._path
563 566 rp = self._repopath
564 567 pcl = self._changectx._parents
565 568 fl = self._filelog
566 569 pl = [(rp, pcl[0]._manifest.get(rp, nullid), fl)]
567 570 if len(pcl) > 1:
568 571 if rp != p:
569 572 fl = None
570 573 pl.append((p, pcl[1]._manifest.get(p, nullid), fl))
571 574
572 575 return [filectx(self._repo, p, fileid=n, filelog=l)
573 576 for p,n,l in pl if n != nullid]
574 577
575 578 def children(self):
576 579 return []
577 580
578 581 def size(self): return os.stat(self._repo.wjoin(self._path)).st_size
579 582 def date(self):
580 583 t, tz = self._changectx.date()
581 584 try:
582 585 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
583 586 except OSError, err:
584 587 if err.errno != errno.ENOENT: raise
585 588 return (t, tz)
586 589
587 590 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