##// END OF EJS Templates
context: add a match builder method...
Matt Mackall -
r14669:2d2604ad default
parent child Browse files
Show More
@@ -1,1109 +1,1115
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 of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import nullid, nullrev, short, hex
9 9 from i18n import _
10 10 import ancestor, bdiff, error, util, scmutil, subrepo, patch, encoding
11 import match as matchmod
11 12 import os, errno, stat
12 13
13 14 propertycache = util.propertycache
14 15
15 16 class changectx(object):
16 17 """A changecontext object makes access to data related to a particular
17 18 changeset convenient."""
18 19 def __init__(self, repo, changeid=''):
19 20 """changeid is a revision number, node, or tag"""
20 21 if changeid == '':
21 22 changeid = '.'
22 23 self._repo = repo
23 24 if isinstance(changeid, (long, int)):
24 25 self._rev = changeid
25 26 self._node = self._repo.changelog.node(changeid)
26 27 else:
27 28 self._node = self._repo.lookup(changeid)
28 29 self._rev = self._repo.changelog.rev(self._node)
29 30
30 31 def __str__(self):
31 32 return short(self.node())
32 33
33 34 def __int__(self):
34 35 return self.rev()
35 36
36 37 def __repr__(self):
37 38 return "<changectx %s>" % str(self)
38 39
39 40 def __hash__(self):
40 41 try:
41 42 return hash(self._rev)
42 43 except AttributeError:
43 44 return id(self)
44 45
45 46 def __eq__(self, other):
46 47 try:
47 48 return self._rev == other._rev
48 49 except AttributeError:
49 50 return False
50 51
51 52 def __ne__(self, other):
52 53 return not (self == other)
53 54
54 55 def __nonzero__(self):
55 56 return self._rev != nullrev
56 57
57 58 @propertycache
58 59 def _changeset(self):
59 60 return self._repo.changelog.read(self.node())
60 61
61 62 @propertycache
62 63 def _manifest(self):
63 64 return self._repo.manifest.read(self._changeset[0])
64 65
65 66 @propertycache
66 67 def _manifestdelta(self):
67 68 return self._repo.manifest.readdelta(self._changeset[0])
68 69
69 70 @propertycache
70 71 def _parents(self):
71 72 p = self._repo.changelog.parentrevs(self._rev)
72 73 if p[1] == nullrev:
73 74 p = p[:-1]
74 75 return [changectx(self._repo, x) for x in p]
75 76
76 77 @propertycache
77 78 def substate(self):
78 79 return subrepo.state(self, self._repo.ui)
79 80
80 81 def __contains__(self, key):
81 82 return key in self._manifest
82 83
83 84 def __getitem__(self, key):
84 85 return self.filectx(key)
85 86
86 87 def __iter__(self):
87 88 for f in sorted(self._manifest):
88 89 yield f
89 90
90 91 def changeset(self):
91 92 return self._changeset
92 93 def manifest(self):
93 94 return self._manifest
94 95 def manifestnode(self):
95 96 return self._changeset[0]
96 97
97 98 def rev(self):
98 99 return self._rev
99 100 def node(self):
100 101 return self._node
101 102 def hex(self):
102 103 return hex(self._node)
103 104 def user(self):
104 105 return self._changeset[1]
105 106 def date(self):
106 107 return self._changeset[2]
107 108 def files(self):
108 109 return self._changeset[3]
109 110 def description(self):
110 111 return self._changeset[4]
111 112 def branch(self):
112 113 return encoding.tolocal(self._changeset[5].get("branch"))
113 114 def extra(self):
114 115 return self._changeset[5]
115 116 def tags(self):
116 117 return self._repo.nodetags(self._node)
117 118 def bookmarks(self):
118 119 return self._repo.nodebookmarks(self._node)
119 120 def hidden(self):
120 121 return self._rev in self._repo.changelog.hiddenrevs
121 122
122 123 def parents(self):
123 124 """return contexts for each parent changeset"""
124 125 return self._parents
125 126
126 127 def p1(self):
127 128 return self._parents[0]
128 129
129 130 def p2(self):
130 131 if len(self._parents) == 2:
131 132 return self._parents[1]
132 133 return changectx(self._repo, -1)
133 134
134 135 def children(self):
135 136 """return contexts for each child changeset"""
136 137 c = self._repo.changelog.children(self._node)
137 138 return [changectx(self._repo, x) for x in c]
138 139
139 140 def ancestors(self):
140 141 for a in self._repo.changelog.ancestors(self._rev):
141 142 yield changectx(self._repo, a)
142 143
143 144 def descendants(self):
144 145 for d in self._repo.changelog.descendants(self._rev):
145 146 yield changectx(self._repo, d)
146 147
147 148 def _fileinfo(self, path):
148 149 if '_manifest' in self.__dict__:
149 150 try:
150 151 return self._manifest[path], self._manifest.flags(path)
151 152 except KeyError:
152 153 raise error.LookupError(self._node, path,
153 154 _('not found in manifest'))
154 155 if '_manifestdelta' in self.__dict__ or path in self.files():
155 156 if path in self._manifestdelta:
156 157 return self._manifestdelta[path], self._manifestdelta.flags(path)
157 158 node, flag = self._repo.manifest.find(self._changeset[0], path)
158 159 if not node:
159 160 raise error.LookupError(self._node, path,
160 161 _('not found in manifest'))
161 162
162 163 return node, flag
163 164
164 165 def filenode(self, path):
165 166 return self._fileinfo(path)[0]
166 167
167 168 def flags(self, path):
168 169 try:
169 170 return self._fileinfo(path)[1]
170 171 except error.LookupError:
171 172 return ''
172 173
173 174 def filectx(self, path, fileid=None, filelog=None):
174 175 """get a file context from this changeset"""
175 176 if fileid is None:
176 177 fileid = self.filenode(path)
177 178 return filectx(self._repo, path, fileid=fileid,
178 179 changectx=self, filelog=filelog)
179 180
180 181 def ancestor(self, c2):
181 182 """
182 183 return the ancestor context of self and c2
183 184 """
184 185 # deal with workingctxs
185 186 n2 = c2._node
186 187 if n2 is None:
187 188 n2 = c2._parents[0]._node
188 189 n = self._repo.changelog.ancestor(self._node, n2)
189 190 return changectx(self._repo, n)
190 191
191 192 def walk(self, match):
192 193 fset = set(match.files())
193 194 # for dirstate.walk, files=['.'] means "walk the whole tree".
194 195 # follow that here, too
195 196 fset.discard('.')
196 197 for fn in self:
197 198 for ffn in fset:
198 199 # match if the file is the exact name or a directory
199 200 if ffn == fn or fn.startswith("%s/" % ffn):
200 201 fset.remove(ffn)
201 202 break
202 203 if match(fn):
203 204 yield fn
204 205 for fn in sorted(fset):
205 206 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
206 207 yield fn
207 208
208 209 def sub(self, path):
209 210 return subrepo.subrepo(self, path)
210 211
212 def match(self, pats=[], include=None, exclude=None, default='glob'):
213 r = self._repo
214 return matchmod.match(r.root, r.getcwd(), pats,
215 include, exclude, default, auditor=r.auditor)
216
211 217 def diff(self, ctx2=None, match=None, **opts):
212 218 """Returns a diff generator for the given contexts and matcher"""
213 219 if ctx2 is None:
214 220 ctx2 = self.p1()
215 221 if ctx2 is not None and not isinstance(ctx2, changectx):
216 222 ctx2 = self._repo[ctx2]
217 223 diffopts = patch.diffopts(self._repo.ui, opts)
218 224 return patch.diff(self._repo, ctx2.node(), self.node(),
219 225 match=match, opts=diffopts)
220 226
221 227 class filectx(object):
222 228 """A filecontext object makes access to data related to a particular
223 229 filerevision convenient."""
224 230 def __init__(self, repo, path, changeid=None, fileid=None,
225 231 filelog=None, changectx=None):
226 232 """changeid can be a changeset revision, node, or tag.
227 233 fileid can be a file revision or node."""
228 234 self._repo = repo
229 235 self._path = path
230 236
231 237 assert (changeid is not None
232 238 or fileid is not None
233 239 or changectx is not None), \
234 240 ("bad args: changeid=%r, fileid=%r, changectx=%r"
235 241 % (changeid, fileid, changectx))
236 242
237 243 if filelog:
238 244 self._filelog = filelog
239 245
240 246 if changeid is not None:
241 247 self._changeid = changeid
242 248 if changectx is not None:
243 249 self._changectx = changectx
244 250 if fileid is not None:
245 251 self._fileid = fileid
246 252
247 253 @propertycache
248 254 def _changectx(self):
249 255 return changectx(self._repo, self._changeid)
250 256
251 257 @propertycache
252 258 def _filelog(self):
253 259 return self._repo.file(self._path)
254 260
255 261 @propertycache
256 262 def _changeid(self):
257 263 if '_changectx' in self.__dict__:
258 264 return self._changectx.rev()
259 265 else:
260 266 return self._filelog.linkrev(self._filerev)
261 267
262 268 @propertycache
263 269 def _filenode(self):
264 270 if '_fileid' in self.__dict__:
265 271 return self._filelog.lookup(self._fileid)
266 272 else:
267 273 return self._changectx.filenode(self._path)
268 274
269 275 @propertycache
270 276 def _filerev(self):
271 277 return self._filelog.rev(self._filenode)
272 278
273 279 @propertycache
274 280 def _repopath(self):
275 281 return self._path
276 282
277 283 def __nonzero__(self):
278 284 try:
279 285 self._filenode
280 286 return True
281 287 except error.LookupError:
282 288 # file is missing
283 289 return False
284 290
285 291 def __str__(self):
286 292 return "%s@%s" % (self.path(), short(self.node()))
287 293
288 294 def __repr__(self):
289 295 return "<filectx %s>" % str(self)
290 296
291 297 def __hash__(self):
292 298 try:
293 299 return hash((self._path, self._filenode))
294 300 except AttributeError:
295 301 return id(self)
296 302
297 303 def __eq__(self, other):
298 304 try:
299 305 return (self._path == other._path
300 306 and self._filenode == other._filenode)
301 307 except AttributeError:
302 308 return False
303 309
304 310 def __ne__(self, other):
305 311 return not (self == other)
306 312
307 313 def filectx(self, fileid):
308 314 '''opens an arbitrary revision of the file without
309 315 opening a new filelog'''
310 316 return filectx(self._repo, self._path, fileid=fileid,
311 317 filelog=self._filelog)
312 318
313 319 def filerev(self):
314 320 return self._filerev
315 321 def filenode(self):
316 322 return self._filenode
317 323 def flags(self):
318 324 return self._changectx.flags(self._path)
319 325 def filelog(self):
320 326 return self._filelog
321 327
322 328 def rev(self):
323 329 if '_changectx' in self.__dict__:
324 330 return self._changectx.rev()
325 331 if '_changeid' in self.__dict__:
326 332 return self._changectx.rev()
327 333 return self._filelog.linkrev(self._filerev)
328 334
329 335 def linkrev(self):
330 336 return self._filelog.linkrev(self._filerev)
331 337 def node(self):
332 338 return self._changectx.node()
333 339 def hex(self):
334 340 return hex(self.node())
335 341 def user(self):
336 342 return self._changectx.user()
337 343 def date(self):
338 344 return self._changectx.date()
339 345 def files(self):
340 346 return self._changectx.files()
341 347 def description(self):
342 348 return self._changectx.description()
343 349 def branch(self):
344 350 return self._changectx.branch()
345 351 def extra(self):
346 352 return self._changectx.extra()
347 353 def manifest(self):
348 354 return self._changectx.manifest()
349 355 def changectx(self):
350 356 return self._changectx
351 357
352 358 def data(self):
353 359 return self._filelog.read(self._filenode)
354 360 def path(self):
355 361 return self._path
356 362 def size(self):
357 363 return self._filelog.size(self._filerev)
358 364
359 365 def cmp(self, fctx):
360 366 """compare with other file context
361 367
362 368 returns True if different than fctx.
363 369 """
364 370 if (fctx._filerev is None and self._repo._encodefilterpats
365 371 or self.size() == fctx.size()):
366 372 return self._filelog.cmp(self._filenode, fctx.data())
367 373
368 374 return True
369 375
370 376 def renamed(self):
371 377 """check if file was actually renamed in this changeset revision
372 378
373 379 If rename logged in file revision, we report copy for changeset only
374 380 if file revisions linkrev points back to the changeset in question
375 381 or both changeset parents contain different file revisions.
376 382 """
377 383
378 384 renamed = self._filelog.renamed(self._filenode)
379 385 if not renamed:
380 386 return renamed
381 387
382 388 if self.rev() == self.linkrev():
383 389 return renamed
384 390
385 391 name = self.path()
386 392 fnode = self._filenode
387 393 for p in self._changectx.parents():
388 394 try:
389 395 if fnode == p.filenode(name):
390 396 return None
391 397 except error.LookupError:
392 398 pass
393 399 return renamed
394 400
395 401 def parents(self):
396 402 p = self._path
397 403 fl = self._filelog
398 404 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
399 405
400 406 r = self._filelog.renamed(self._filenode)
401 407 if r:
402 408 pl[0] = (r[0], r[1], None)
403 409
404 410 return [filectx(self._repo, p, fileid=n, filelog=l)
405 411 for p, n, l in pl if n != nullid]
406 412
407 413 def p1(self):
408 414 return self.parents()[0]
409 415
410 416 def p2(self):
411 417 p = self.parents()
412 418 if len(p) == 2:
413 419 return p[1]
414 420 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
415 421
416 422 def children(self):
417 423 # hard for renames
418 424 c = self._filelog.children(self._filenode)
419 425 return [filectx(self._repo, self._path, fileid=x,
420 426 filelog=self._filelog) for x in c]
421 427
422 428 def annotate(self, follow=False, linenumber=None):
423 429 '''returns a list of tuples of (ctx, line) for each line
424 430 in the file, where ctx is the filectx of the node where
425 431 that line was last changed.
426 432 This returns tuples of ((ctx, linenumber), line) for each line,
427 433 if "linenumber" parameter is NOT "None".
428 434 In such tuples, linenumber means one at the first appearance
429 435 in the managed file.
430 436 To reduce annotation cost,
431 437 this returns fixed value(False is used) as linenumber,
432 438 if "linenumber" parameter is "False".'''
433 439
434 440 def decorate_compat(text, rev):
435 441 return ([rev] * len(text.splitlines()), text)
436 442
437 443 def without_linenumber(text, rev):
438 444 return ([(rev, False)] * len(text.splitlines()), text)
439 445
440 446 def with_linenumber(text, rev):
441 447 size = len(text.splitlines())
442 448 return ([(rev, i) for i in xrange(1, size + 1)], text)
443 449
444 450 decorate = (((linenumber is None) and decorate_compat) or
445 451 (linenumber and with_linenumber) or
446 452 without_linenumber)
447 453
448 454 def pair(parent, child):
449 455 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
450 456 child[0][b1:b2] = parent[0][a1:a2]
451 457 return child
452 458
453 459 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
454 460 def getctx(path, fileid):
455 461 log = path == self._path and self._filelog or getlog(path)
456 462 return filectx(self._repo, path, fileid=fileid, filelog=log)
457 463 getctx = util.lrucachefunc(getctx)
458 464
459 465 def parents(f):
460 466 # we want to reuse filectx objects as much as possible
461 467 p = f._path
462 468 if f._filerev is None: # working dir
463 469 pl = [(n.path(), n.filerev()) for n in f.parents()]
464 470 else:
465 471 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
466 472
467 473 if follow:
468 474 r = f.renamed()
469 475 if r:
470 476 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
471 477
472 478 return [getctx(p, n) for p, n in pl if n != nullrev]
473 479
474 480 # use linkrev to find the first changeset where self appeared
475 481 if self.rev() != self.linkrev():
476 482 base = self.filectx(self.filerev())
477 483 else:
478 484 base = self
479 485
480 486 # This algorithm would prefer to be recursive, but Python is a
481 487 # bit recursion-hostile. Instead we do an iterative
482 488 # depth-first search.
483 489
484 490 visit = [base]
485 491 hist = {}
486 492 pcache = {}
487 493 needed = {base: 1}
488 494 while visit:
489 495 f = visit[-1]
490 496 if f not in pcache:
491 497 pcache[f] = parents(f)
492 498
493 499 ready = True
494 500 pl = pcache[f]
495 501 for p in pl:
496 502 if p not in hist:
497 503 ready = False
498 504 visit.append(p)
499 505 needed[p] = needed.get(p, 0) + 1
500 506 if ready:
501 507 visit.pop()
502 508 curr = decorate(f.data(), f)
503 509 for p in pl:
504 510 curr = pair(hist[p], curr)
505 511 if needed[p] == 1:
506 512 del hist[p]
507 513 else:
508 514 needed[p] -= 1
509 515
510 516 hist[f] = curr
511 517 pcache[f] = []
512 518
513 519 return zip(hist[base][0], hist[base][1].splitlines(True))
514 520
515 521 def ancestor(self, fc2, actx=None):
516 522 """
517 523 find the common ancestor file context, if any, of self, and fc2
518 524
519 525 If actx is given, it must be the changectx of the common ancestor
520 526 of self's and fc2's respective changesets.
521 527 """
522 528
523 529 if actx is None:
524 530 actx = self.changectx().ancestor(fc2.changectx())
525 531
526 532 # the trivial case: changesets are unrelated, files must be too
527 533 if not actx:
528 534 return None
529 535
530 536 # the easy case: no (relevant) renames
531 537 if fc2.path() == self.path() and self.path() in actx:
532 538 return actx[self.path()]
533 539 acache = {}
534 540
535 541 # prime the ancestor cache for the working directory
536 542 for c in (self, fc2):
537 543 if c._filerev is None:
538 544 pl = [(n.path(), n.filenode()) for n in c.parents()]
539 545 acache[(c._path, None)] = pl
540 546
541 547 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
542 548 def parents(vertex):
543 549 if vertex in acache:
544 550 return acache[vertex]
545 551 f, n = vertex
546 552 if f not in flcache:
547 553 flcache[f] = self._repo.file(f)
548 554 fl = flcache[f]
549 555 pl = [(f, p) for p in fl.parents(n) if p != nullid]
550 556 re = fl.renamed(n)
551 557 if re:
552 558 pl.append(re)
553 559 acache[vertex] = pl
554 560 return pl
555 561
556 562 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
557 563 v = ancestor.ancestor(a, b, parents)
558 564 if v:
559 565 f, n = v
560 566 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
561 567
562 568 return None
563 569
564 570 def ancestors(self):
565 571 visit = {}
566 572 c = self
567 573 while True:
568 574 for parent in c.parents():
569 575 visit[(parent.rev(), parent.node())] = parent
570 576 if not visit:
571 577 break
572 578 c = visit.pop(max(visit))
573 579 yield c
574 580
575 581 class workingctx(changectx):
576 582 """A workingctx object makes access to data related to
577 583 the current working directory convenient.
578 584 date - any valid date string or (unixtime, offset), or None.
579 585 user - username string, or None.
580 586 extra - a dictionary of extra values, or None.
581 587 changes - a list of file lists as returned by localrepo.status()
582 588 or None to use the repository status.
583 589 """
584 590 def __init__(self, repo, text="", user=None, date=None, extra=None,
585 591 changes=None):
586 592 self._repo = repo
587 593 self._rev = None
588 594 self._node = None
589 595 self._text = text
590 596 if date:
591 597 self._date = util.parsedate(date)
592 598 if user:
593 599 self._user = user
594 600 if changes:
595 601 self._status = list(changes[:4])
596 602 self._unknown = changes[4]
597 603 self._ignored = changes[5]
598 604 self._clean = changes[6]
599 605 else:
600 606 self._unknown = None
601 607 self._ignored = None
602 608 self._clean = None
603 609
604 610 self._extra = {}
605 611 if extra:
606 612 self._extra = extra.copy()
607 613 if 'branch' not in self._extra:
608 614 try:
609 615 branch = encoding.fromlocal(self._repo.dirstate.branch())
610 616 except UnicodeDecodeError:
611 617 raise util.Abort(_('branch name not in UTF-8!'))
612 618 self._extra['branch'] = branch
613 619 if self._extra['branch'] == '':
614 620 self._extra['branch'] = 'default'
615 621
616 622 def __str__(self):
617 623 return str(self._parents[0]) + "+"
618 624
619 625 def __repr__(self):
620 626 return "<workingctx %s>" % str(self)
621 627
622 628 def __nonzero__(self):
623 629 return True
624 630
625 631 def __contains__(self, key):
626 632 return self._repo.dirstate[key] not in "?r"
627 633
628 634 @propertycache
629 635 def _manifest(self):
630 636 """generate a manifest corresponding to the working directory"""
631 637
632 638 if self._unknown is None:
633 639 self.status(unknown=True)
634 640
635 641 man = self._parents[0].manifest().copy()
636 642 copied = self._repo.dirstate.copies()
637 643 if len(self._parents) > 1:
638 644 man2 = self.p2().manifest()
639 645 def getman(f):
640 646 if f in man:
641 647 return man
642 648 return man2
643 649 else:
644 650 getman = lambda f: man
645 651 def cf(f):
646 652 f = copied.get(f, f)
647 653 return getman(f).flags(f)
648 654 ff = self._repo.dirstate.flagfunc(cf)
649 655 modified, added, removed, deleted = self._status
650 656 unknown = self._unknown
651 657 for i, l in (("a", added), ("m", modified), ("u", unknown)):
652 658 for f in l:
653 659 orig = copied.get(f, f)
654 660 man[f] = getman(orig).get(orig, nullid) + i
655 661 try:
656 662 man.set(f, ff(f))
657 663 except OSError:
658 664 pass
659 665
660 666 for f in deleted + removed:
661 667 if f in man:
662 668 del man[f]
663 669
664 670 return man
665 671
666 672 def __iter__(self):
667 673 d = self._repo.dirstate
668 674 for f in d:
669 675 if d[f] != 'r':
670 676 yield f
671 677
672 678 @propertycache
673 679 def _status(self):
674 680 return self._repo.status()[:4]
675 681
676 682 @propertycache
677 683 def _user(self):
678 684 return self._repo.ui.username()
679 685
680 686 @propertycache
681 687 def _date(self):
682 688 return util.makedate()
683 689
684 690 @propertycache
685 691 def _parents(self):
686 692 p = self._repo.dirstate.parents()
687 693 if p[1] == nullid:
688 694 p = p[:-1]
689 695 self._parents = [changectx(self._repo, x) for x in p]
690 696 return self._parents
691 697
692 698 def status(self, ignored=False, clean=False, unknown=False):
693 699 """Explicit status query
694 700 Unless this method is used to query the working copy status, the
695 701 _status property will implicitly read the status using its default
696 702 arguments."""
697 703 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
698 704 self._unknown = self._ignored = self._clean = None
699 705 if unknown:
700 706 self._unknown = stat[4]
701 707 if ignored:
702 708 self._ignored = stat[5]
703 709 if clean:
704 710 self._clean = stat[6]
705 711 self._status = stat[:4]
706 712 return stat
707 713
708 714 def manifest(self):
709 715 return self._manifest
710 716 def user(self):
711 717 return self._user or self._repo.ui.username()
712 718 def date(self):
713 719 return self._date
714 720 def description(self):
715 721 return self._text
716 722 def files(self):
717 723 return sorted(self._status[0] + self._status[1] + self._status[2])
718 724
719 725 def modified(self):
720 726 return self._status[0]
721 727 def added(self):
722 728 return self._status[1]
723 729 def removed(self):
724 730 return self._status[2]
725 731 def deleted(self):
726 732 return self._status[3]
727 733 def unknown(self):
728 734 assert self._unknown is not None # must call status first
729 735 return self._unknown
730 736 def ignored(self):
731 737 assert self._ignored is not None # must call status first
732 738 return self._ignored
733 739 def clean(self):
734 740 assert self._clean is not None # must call status first
735 741 return self._clean
736 742 def branch(self):
737 743 return encoding.tolocal(self._extra['branch'])
738 744 def extra(self):
739 745 return self._extra
740 746
741 747 def tags(self):
742 748 t = []
743 749 for p in self.parents():
744 750 t.extend(p.tags())
745 751 return t
746 752
747 753 def bookmarks(self):
748 754 b = []
749 755 for p in self.parents():
750 756 b.extend(p.bookmarks())
751 757 return b
752 758
753 759 def children(self):
754 760 return []
755 761
756 762 def flags(self, path):
757 763 if '_manifest' in self.__dict__:
758 764 try:
759 765 return self._manifest.flags(path)
760 766 except KeyError:
761 767 return ''
762 768
763 769 orig = self._repo.dirstate.copies().get(path, path)
764 770
765 771 def findflag(ctx):
766 772 mnode = ctx.changeset()[0]
767 773 node, flag = self._repo.manifest.find(mnode, orig)
768 774 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
769 775 try:
770 776 return ff(path)
771 777 except OSError:
772 778 pass
773 779
774 780 flag = findflag(self._parents[0])
775 781 if flag is None and len(self.parents()) > 1:
776 782 flag = findflag(self._parents[1])
777 783 if flag is None or self._repo.dirstate[path] == 'r':
778 784 return ''
779 785 return flag
780 786
781 787 def filectx(self, path, filelog=None):
782 788 """get a file context from the working directory"""
783 789 return workingfilectx(self._repo, path, workingctx=self,
784 790 filelog=filelog)
785 791
786 792 def ancestor(self, c2):
787 793 """return the ancestor context of self and c2"""
788 794 return self._parents[0].ancestor(c2) # punt on two parents for now
789 795
790 796 def walk(self, match):
791 797 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
792 798 True, False))
793 799
794 800 def dirty(self, missing=False):
795 801 "check whether a working directory is modified"
796 802 # check subrepos first
797 803 for s in self.substate:
798 804 if self.sub(s).dirty():
799 805 return True
800 806 # check current working dir
801 807 return (self.p2() or self.branch() != self.p1().branch() or
802 808 self.modified() or self.added() or self.removed() or
803 809 (missing and self.deleted()))
804 810
805 811 def add(self, list, prefix=""):
806 812 join = lambda f: os.path.join(prefix, f)
807 813 wlock = self._repo.wlock()
808 814 ui, ds = self._repo.ui, self._repo.dirstate
809 815 try:
810 816 rejected = []
811 817 for f in list:
812 818 scmutil.checkportable(ui, join(f))
813 819 p = self._repo.wjoin(f)
814 820 try:
815 821 st = os.lstat(p)
816 822 except OSError:
817 823 ui.warn(_("%s does not exist!\n") % join(f))
818 824 rejected.append(f)
819 825 continue
820 826 if st.st_size > 10000000:
821 827 ui.warn(_("%s: up to %d MB of RAM may be required "
822 828 "to manage this file\n"
823 829 "(use 'hg revert %s' to cancel the "
824 830 "pending addition)\n")
825 831 % (f, 3 * st.st_size // 1000000, join(f)))
826 832 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
827 833 ui.warn(_("%s not added: only files and symlinks "
828 834 "supported currently\n") % join(f))
829 835 rejected.append(p)
830 836 elif ds[f] in 'amn':
831 837 ui.warn(_("%s already tracked!\n") % join(f))
832 838 elif ds[f] == 'r':
833 839 ds.normallookup(f)
834 840 else:
835 841 ds.add(f)
836 842 return rejected
837 843 finally:
838 844 wlock.release()
839 845
840 846 def forget(self, files):
841 847 wlock = self._repo.wlock()
842 848 try:
843 849 for f in files:
844 850 if self._repo.dirstate[f] != 'a':
845 851 self._repo.dirstate.remove(f)
846 852 elif f not in self._repo.dirstate:
847 853 self._repo.ui.warn(_("%s not tracked!\n") % f)
848 854 else:
849 855 self._repo.dirstate.drop(f)
850 856 finally:
851 857 wlock.release()
852 858
853 859 def ancestors(self):
854 860 for a in self._repo.changelog.ancestors(
855 861 *[p.rev() for p in self._parents]):
856 862 yield changectx(self._repo, a)
857 863
858 864 def undelete(self, list):
859 865 pctxs = self.parents()
860 866 wlock = self._repo.wlock()
861 867 try:
862 868 for f in list:
863 869 if self._repo.dirstate[f] != 'r':
864 870 self._repo.ui.warn(_("%s not removed!\n") % f)
865 871 else:
866 872 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
867 873 t = fctx.data()
868 874 self._repo.wwrite(f, t, fctx.flags())
869 875 self._repo.dirstate.normal(f)
870 876 finally:
871 877 wlock.release()
872 878
873 879 def copy(self, source, dest):
874 880 p = self._repo.wjoin(dest)
875 881 if not os.path.lexists(p):
876 882 self._repo.ui.warn(_("%s does not exist!\n") % dest)
877 883 elif not (os.path.isfile(p) or os.path.islink(p)):
878 884 self._repo.ui.warn(_("copy failed: %s is not a file or a "
879 885 "symbolic link\n") % dest)
880 886 else:
881 887 wlock = self._repo.wlock()
882 888 try:
883 889 if self._repo.dirstate[dest] in '?r':
884 890 self._repo.dirstate.add(dest)
885 891 self._repo.dirstate.copy(source, dest)
886 892 finally:
887 893 wlock.release()
888 894
889 895 class workingfilectx(filectx):
890 896 """A workingfilectx object makes access to data related to a particular
891 897 file in the working directory convenient."""
892 898 def __init__(self, repo, path, filelog=None, workingctx=None):
893 899 """changeid can be a changeset revision, node, or tag.
894 900 fileid can be a file revision or node."""
895 901 self._repo = repo
896 902 self._path = path
897 903 self._changeid = None
898 904 self._filerev = self._filenode = None
899 905
900 906 if filelog:
901 907 self._filelog = filelog
902 908 if workingctx:
903 909 self._changectx = workingctx
904 910
905 911 @propertycache
906 912 def _changectx(self):
907 913 return workingctx(self._repo)
908 914
909 915 def __nonzero__(self):
910 916 return True
911 917
912 918 def __str__(self):
913 919 return "%s@%s" % (self.path(), self._changectx)
914 920
915 921 def __repr__(self):
916 922 return "<workingfilectx %s>" % str(self)
917 923
918 924 def data(self):
919 925 return self._repo.wread(self._path)
920 926 def renamed(self):
921 927 rp = self._repo.dirstate.copied(self._path)
922 928 if not rp:
923 929 return None
924 930 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
925 931
926 932 def parents(self):
927 933 '''return parent filectxs, following copies if necessary'''
928 934 def filenode(ctx, path):
929 935 return ctx._manifest.get(path, nullid)
930 936
931 937 path = self._path
932 938 fl = self._filelog
933 939 pcl = self._changectx._parents
934 940 renamed = self.renamed()
935 941
936 942 if renamed:
937 943 pl = [renamed + (None,)]
938 944 else:
939 945 pl = [(path, filenode(pcl[0], path), fl)]
940 946
941 947 for pc in pcl[1:]:
942 948 pl.append((path, filenode(pc, path), fl))
943 949
944 950 return [filectx(self._repo, p, fileid=n, filelog=l)
945 951 for p, n, l in pl if n != nullid]
946 952
947 953 def children(self):
948 954 return []
949 955
950 956 def size(self):
951 957 return os.lstat(self._repo.wjoin(self._path)).st_size
952 958 def date(self):
953 959 t, tz = self._changectx.date()
954 960 try:
955 961 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
956 962 except OSError, err:
957 963 if err.errno != errno.ENOENT:
958 964 raise
959 965 return (t, tz)
960 966
961 967 def cmp(self, fctx):
962 968 """compare with other file context
963 969
964 970 returns True if different than fctx.
965 971 """
966 972 # fctx should be a filectx (not a wfctx)
967 973 # invert comparison to reuse the same code path
968 974 return fctx.cmp(self)
969 975
970 976 class memctx(object):
971 977 """Use memctx to perform in-memory commits via localrepo.commitctx().
972 978
973 979 Revision information is supplied at initialization time while
974 980 related files data and is made available through a callback
975 981 mechanism. 'repo' is the current localrepo, 'parents' is a
976 982 sequence of two parent revisions identifiers (pass None for every
977 983 missing parent), 'text' is the commit message and 'files' lists
978 984 names of files touched by the revision (normalized and relative to
979 985 repository root).
980 986
981 987 filectxfn(repo, memctx, path) is a callable receiving the
982 988 repository, the current memctx object and the normalized path of
983 989 requested file, relative to repository root. It is fired by the
984 990 commit function for every file in 'files', but calls order is
985 991 undefined. If the file is available in the revision being
986 992 committed (updated or added), filectxfn returns a memfilectx
987 993 object. If the file was removed, filectxfn raises an
988 994 IOError. Moved files are represented by marking the source file
989 995 removed and the new file added with copy information (see
990 996 memfilectx).
991 997
992 998 user receives the committer name and defaults to current
993 999 repository username, date is the commit date in any format
994 1000 supported by util.parsedate() and defaults to current date, extra
995 1001 is a dictionary of metadata or is left empty.
996 1002 """
997 1003 def __init__(self, repo, parents, text, files, filectxfn, user=None,
998 1004 date=None, extra=None):
999 1005 self._repo = repo
1000 1006 self._rev = None
1001 1007 self._node = None
1002 1008 self._text = text
1003 1009 self._date = date and util.parsedate(date) or util.makedate()
1004 1010 self._user = user
1005 1011 parents = [(p or nullid) for p in parents]
1006 1012 p1, p2 = parents
1007 1013 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1008 1014 files = sorted(set(files))
1009 1015 self._status = [files, [], [], [], []]
1010 1016 self._filectxfn = filectxfn
1011 1017
1012 1018 self._extra = extra and extra.copy() or {}
1013 1019 if self._extra.get('branch', '') == '':
1014 1020 self._extra['branch'] = 'default'
1015 1021
1016 1022 def __str__(self):
1017 1023 return str(self._parents[0]) + "+"
1018 1024
1019 1025 def __int__(self):
1020 1026 return self._rev
1021 1027
1022 1028 def __nonzero__(self):
1023 1029 return True
1024 1030
1025 1031 def __getitem__(self, key):
1026 1032 return self.filectx(key)
1027 1033
1028 1034 def p1(self):
1029 1035 return self._parents[0]
1030 1036 def p2(self):
1031 1037 return self._parents[1]
1032 1038
1033 1039 def user(self):
1034 1040 return self._user or self._repo.ui.username()
1035 1041 def date(self):
1036 1042 return self._date
1037 1043 def description(self):
1038 1044 return self._text
1039 1045 def files(self):
1040 1046 return self.modified()
1041 1047 def modified(self):
1042 1048 return self._status[0]
1043 1049 def added(self):
1044 1050 return self._status[1]
1045 1051 def removed(self):
1046 1052 return self._status[2]
1047 1053 def deleted(self):
1048 1054 return self._status[3]
1049 1055 def unknown(self):
1050 1056 return self._status[4]
1051 1057 def ignored(self):
1052 1058 return self._status[5]
1053 1059 def clean(self):
1054 1060 return self._status[6]
1055 1061 def branch(self):
1056 1062 return encoding.tolocal(self._extra['branch'])
1057 1063 def extra(self):
1058 1064 return self._extra
1059 1065 def flags(self, f):
1060 1066 return self[f].flags()
1061 1067
1062 1068 def parents(self):
1063 1069 """return contexts for each parent changeset"""
1064 1070 return self._parents
1065 1071
1066 1072 def filectx(self, path, filelog=None):
1067 1073 """get a file context from the working directory"""
1068 1074 return self._filectxfn(self._repo, self, path)
1069 1075
1070 1076 def commit(self):
1071 1077 """commit context to the repo"""
1072 1078 return self._repo.commitctx(self)
1073 1079
1074 1080 class memfilectx(object):
1075 1081 """memfilectx represents an in-memory file to commit.
1076 1082
1077 1083 See memctx for more details.
1078 1084 """
1079 1085 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1080 1086 """
1081 1087 path is the normalized file path relative to repository root.
1082 1088 data is the file content as a string.
1083 1089 islink is True if the file is a symbolic link.
1084 1090 isexec is True if the file is executable.
1085 1091 copied is the source file path if current file was copied in the
1086 1092 revision being committed, or None."""
1087 1093 self._path = path
1088 1094 self._data = data
1089 1095 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1090 1096 self._copied = None
1091 1097 if copied:
1092 1098 self._copied = (copied, nullid)
1093 1099
1094 1100 def __nonzero__(self):
1095 1101 return True
1096 1102 def __str__(self):
1097 1103 return "%s@%s" % (self.path(), self._changectx)
1098 1104 def path(self):
1099 1105 return self._path
1100 1106 def data(self):
1101 1107 return self._data
1102 1108 def flags(self):
1103 1109 return self._flags
1104 1110 def isexec(self):
1105 1111 return 'x' in self._flags
1106 1112 def islink(self):
1107 1113 return 'l' in self._flags
1108 1114 def renamed(self):
1109 1115 return self._copied
@@ -1,705 +1,704
1 1 # scmutil.py - Mercurial core utility functions
2 2 #
3 3 # Copyright Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import util, error, osutil, revset, similar
10 10 import match as matchmod
11 11 import os, errno, stat, sys, glob
12 12
13 13 def checkfilename(f):
14 14 '''Check that the filename f is an acceptable filename for a tracked file'''
15 15 if '\r' in f or '\n' in f:
16 16 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
17 17
18 18 def checkportable(ui, f):
19 19 '''Check if filename f is portable and warn or abort depending on config'''
20 20 checkfilename(f)
21 21 abort, warn = checkportabilityalert(ui)
22 22 if abort or warn:
23 23 msg = util.checkwinfilename(f)
24 24 if msg:
25 25 msg = "%s: %r" % (msg, f)
26 26 if abort:
27 27 raise util.Abort(msg)
28 28 ui.warn(_("warning: %s\n") % msg)
29 29
30 30 def checkportabilityalert(ui):
31 31 '''check if the user's config requests nothing, a warning, or abort for
32 32 non-portable filenames'''
33 33 val = ui.config('ui', 'portablefilenames', 'warn')
34 34 lval = val.lower()
35 35 bval = util.parsebool(val)
36 36 abort = os.name == 'nt' or lval == 'abort'
37 37 warn = bval or lval == 'warn'
38 38 if bval is None and not (warn or abort or lval == 'ignore'):
39 39 raise error.ConfigError(
40 40 _("ui.portablefilenames value is invalid ('%s')") % val)
41 41 return abort, warn
42 42
43 43 class casecollisionauditor(object):
44 44 def __init__(self, ui, abort, existingiter):
45 45 self._ui = ui
46 46 self._abort = abort
47 47 self._map = {}
48 48 for f in existingiter:
49 49 self._map[f.lower()] = f
50 50
51 51 def __call__(self, f):
52 52 fl = f.lower()
53 53 map = self._map
54 54 if fl in map and map[fl] != f:
55 55 msg = _('possible case-folding collision for %s') % f
56 56 if self._abort:
57 57 raise util.Abort(msg)
58 58 self._ui.warn(_("warning: %s\n") % msg)
59 59 map[fl] = f
60 60
61 61 class pathauditor(object):
62 62 '''ensure that a filesystem path contains no banned components.
63 63 the following properties of a path are checked:
64 64
65 65 - ends with a directory separator
66 66 - under top-level .hg
67 67 - starts at the root of a windows drive
68 68 - contains ".."
69 69 - traverses a symlink (e.g. a/symlink_here/b)
70 70 - inside a nested repository (a callback can be used to approve
71 71 some nested repositories, e.g., subrepositories)
72 72 '''
73 73
74 74 def __init__(self, root, callback=None):
75 75 self.audited = set()
76 76 self.auditeddir = set()
77 77 self.root = root
78 78 self.callback = callback
79 79
80 80 def __call__(self, path):
81 81 '''Check the relative path.
82 82 path may contain a pattern (e.g. foodir/**.txt)'''
83 83
84 84 if path in self.audited:
85 85 return
86 86 # AIX ignores "/" at end of path, others raise EISDIR.
87 87 if util.endswithsep(path):
88 88 raise util.Abort(_("path ends in directory separator: %s") % path)
89 89 normpath = os.path.normcase(path)
90 90 parts = util.splitpath(normpath)
91 91 if (os.path.splitdrive(path)[0]
92 92 or parts[0].lower() in ('.hg', '.hg.', '')
93 93 or os.pardir in parts):
94 94 raise util.Abort(_("path contains illegal component: %s") % path)
95 95 if '.hg' in path.lower():
96 96 lparts = [p.lower() for p in parts]
97 97 for p in '.hg', '.hg.':
98 98 if p in lparts[1:]:
99 99 pos = lparts.index(p)
100 100 base = os.path.join(*parts[:pos])
101 101 raise util.Abort(_('path %r is inside nested repo %r')
102 102 % (path, base))
103 103
104 104 parts.pop()
105 105 prefixes = []
106 106 while parts:
107 107 prefix = os.sep.join(parts)
108 108 if prefix in self.auditeddir:
109 109 break
110 110 curpath = os.path.join(self.root, prefix)
111 111 try:
112 112 st = os.lstat(curpath)
113 113 except OSError, err:
114 114 # EINVAL can be raised as invalid path syntax under win32.
115 115 # They must be ignored for patterns can be checked too.
116 116 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
117 117 raise
118 118 else:
119 119 if stat.S_ISLNK(st.st_mode):
120 120 raise util.Abort(
121 121 _('path %r traverses symbolic link %r')
122 122 % (path, prefix))
123 123 elif (stat.S_ISDIR(st.st_mode) and
124 124 os.path.isdir(os.path.join(curpath, '.hg'))):
125 125 if not self.callback or not self.callback(curpath):
126 126 raise util.Abort(_('path %r is inside nested repo %r') %
127 127 (path, prefix))
128 128 prefixes.append(prefix)
129 129 parts.pop()
130 130
131 131 self.audited.add(path)
132 132 # only add prefixes to the cache after checking everything: we don't
133 133 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
134 134 self.auditeddir.update(prefixes)
135 135
136 136 class abstractopener(object):
137 137 """Abstract base class; cannot be instantiated"""
138 138
139 139 def __init__(self, *args, **kwargs):
140 140 '''Prevent instantiation; don't call this from subclasses.'''
141 141 raise NotImplementedError('attempted instantiating ' + str(type(self)))
142 142
143 143 def read(self, path):
144 144 fp = self(path, 'rb')
145 145 try:
146 146 return fp.read()
147 147 finally:
148 148 fp.close()
149 149
150 150 def write(self, path, data):
151 151 fp = self(path, 'wb')
152 152 try:
153 153 return fp.write(data)
154 154 finally:
155 155 fp.close()
156 156
157 157 def append(self, path, data):
158 158 fp = self(path, 'ab')
159 159 try:
160 160 return fp.write(data)
161 161 finally:
162 162 fp.close()
163 163
164 164 class opener(abstractopener):
165 165 '''Open files relative to a base directory
166 166
167 167 This class is used to hide the details of COW semantics and
168 168 remote file access from higher level code.
169 169 '''
170 170 def __init__(self, base, audit=True):
171 171 self.base = base
172 172 if audit:
173 173 self.auditor = pathauditor(base)
174 174 else:
175 175 self.auditor = util.always
176 176 self.createmode = None
177 177 self._trustnlink = None
178 178
179 179 @util.propertycache
180 180 def _cansymlink(self):
181 181 return util.checklink(self.base)
182 182
183 183 def _fixfilemode(self, name):
184 184 if self.createmode is None:
185 185 return
186 186 os.chmod(name, self.createmode & 0666)
187 187
188 188 def __call__(self, path, mode="r", text=False, atomictemp=False):
189 189 r = util.checkosfilename(path)
190 190 if r:
191 191 raise util.Abort("%s: %r" % (r, path))
192 192 self.auditor(path)
193 193 f = os.path.join(self.base, path)
194 194
195 195 if not text and "b" not in mode:
196 196 mode += "b" # for that other OS
197 197
198 198 nlink = -1
199 199 dirname, basename = os.path.split(f)
200 200 # If basename is empty, then the path is malformed because it points
201 201 # to a directory. Let the posixfile() call below raise IOError.
202 202 if basename and mode not in ('r', 'rb'):
203 203 if atomictemp:
204 204 if not os.path.isdir(dirname):
205 205 util.makedirs(dirname, self.createmode)
206 206 return util.atomictempfile(f, mode, self.createmode)
207 207 try:
208 208 if 'w' in mode:
209 209 util.unlink(f)
210 210 nlink = 0
211 211 else:
212 212 # nlinks() may behave differently for files on Windows
213 213 # shares if the file is open.
214 214 fd = util.posixfile(f)
215 215 nlink = util.nlinks(f)
216 216 if nlink < 1:
217 217 nlink = 2 # force mktempcopy (issue1922)
218 218 fd.close()
219 219 except (OSError, IOError), e:
220 220 if e.errno != errno.ENOENT:
221 221 raise
222 222 nlink = 0
223 223 if not os.path.isdir(dirname):
224 224 util.makedirs(dirname, self.createmode)
225 225 if nlink > 0:
226 226 if self._trustnlink is None:
227 227 self._trustnlink = nlink > 1 or util.checknlink(f)
228 228 if nlink > 1 or not self._trustnlink:
229 229 util.rename(util.mktempcopy(f), f)
230 230 fp = util.posixfile(f, mode)
231 231 if nlink == 0:
232 232 self._fixfilemode(f)
233 233 return fp
234 234
235 235 def symlink(self, src, dst):
236 236 self.auditor(dst)
237 237 linkname = os.path.join(self.base, dst)
238 238 try:
239 239 os.unlink(linkname)
240 240 except OSError:
241 241 pass
242 242
243 243 dirname = os.path.dirname(linkname)
244 244 if not os.path.exists(dirname):
245 245 util.makedirs(dirname, self.createmode)
246 246
247 247 if self._cansymlink:
248 248 try:
249 249 os.symlink(src, linkname)
250 250 except OSError, err:
251 251 raise OSError(err.errno, _('could not symlink to %r: %s') %
252 252 (src, err.strerror), linkname)
253 253 else:
254 254 f = self(dst, "w")
255 255 f.write(src)
256 256 f.close()
257 257 self._fixfilemode(dst)
258 258
259 259 def audit(self, path):
260 260 self.auditor(path)
261 261
262 262 class filteropener(abstractopener):
263 263 '''Wrapper opener for filtering filenames with a function.'''
264 264
265 265 def __init__(self, opener, filter):
266 266 self._filter = filter
267 267 self._orig = opener
268 268
269 269 def __call__(self, path, *args, **kwargs):
270 270 return self._orig(self._filter(path), *args, **kwargs)
271 271
272 272 def canonpath(root, cwd, myname, auditor=None):
273 273 '''return the canonical path of myname, given cwd and root'''
274 274 if util.endswithsep(root):
275 275 rootsep = root
276 276 else:
277 277 rootsep = root + os.sep
278 278 name = myname
279 279 if not os.path.isabs(name):
280 280 name = os.path.join(root, cwd, name)
281 281 name = os.path.normpath(name)
282 282 if auditor is None:
283 283 auditor = pathauditor(root)
284 284 if name != rootsep and name.startswith(rootsep):
285 285 name = name[len(rootsep):]
286 286 auditor(name)
287 287 return util.pconvert(name)
288 288 elif name == root:
289 289 return ''
290 290 else:
291 291 # Determine whether `name' is in the hierarchy at or beneath `root',
292 292 # by iterating name=dirname(name) until that causes no change (can't
293 293 # check name == '/', because that doesn't work on windows). For each
294 294 # `name', compare dev/inode numbers. If they match, the list `rel'
295 295 # holds the reversed list of components making up the relative file
296 296 # name we want.
297 297 root_st = os.stat(root)
298 298 rel = []
299 299 while True:
300 300 try:
301 301 name_st = os.stat(name)
302 302 except OSError:
303 303 break
304 304 if util.samestat(name_st, root_st):
305 305 if not rel:
306 306 # name was actually the same as root (maybe a symlink)
307 307 return ''
308 308 rel.reverse()
309 309 name = os.path.join(*rel)
310 310 auditor(name)
311 311 return util.pconvert(name)
312 312 dirname, basename = os.path.split(name)
313 313 rel.append(basename)
314 314 if dirname == name:
315 315 break
316 316 name = dirname
317 317
318 318 raise util.Abort('%s not under root' % myname)
319 319
320 320 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
321 321 '''yield every hg repository under path, recursively.'''
322 322 def errhandler(err):
323 323 if err.filename == path:
324 324 raise err
325 325 if followsym and hasattr(os.path, 'samestat'):
326 326 def adddir(dirlst, dirname):
327 327 match = False
328 328 samestat = os.path.samestat
329 329 dirstat = os.stat(dirname)
330 330 for lstdirstat in dirlst:
331 331 if samestat(dirstat, lstdirstat):
332 332 match = True
333 333 break
334 334 if not match:
335 335 dirlst.append(dirstat)
336 336 return not match
337 337 else:
338 338 followsym = False
339 339
340 340 if (seen_dirs is None) and followsym:
341 341 seen_dirs = []
342 342 adddir(seen_dirs, path)
343 343 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
344 344 dirs.sort()
345 345 if '.hg' in dirs:
346 346 yield root # found a repository
347 347 qroot = os.path.join(root, '.hg', 'patches')
348 348 if os.path.isdir(os.path.join(qroot, '.hg')):
349 349 yield qroot # we have a patch queue repo here
350 350 if recurse:
351 351 # avoid recursing inside the .hg directory
352 352 dirs.remove('.hg')
353 353 else:
354 354 dirs[:] = [] # don't descend further
355 355 elif followsym:
356 356 newdirs = []
357 357 for d in dirs:
358 358 fname = os.path.join(root, d)
359 359 if adddir(seen_dirs, fname):
360 360 if os.path.islink(fname):
361 361 for hgname in walkrepos(fname, True, seen_dirs):
362 362 yield hgname
363 363 else:
364 364 newdirs.append(d)
365 365 dirs[:] = newdirs
366 366
367 367 def osrcpath():
368 368 '''return default os-specific hgrc search path'''
369 369 path = systemrcpath()
370 370 path.extend(userrcpath())
371 371 path = [os.path.normpath(f) for f in path]
372 372 return path
373 373
374 374 _rcpath = None
375 375
376 376 def rcpath():
377 377 '''return hgrc search path. if env var HGRCPATH is set, use it.
378 378 for each item in path, if directory, use files ending in .rc,
379 379 else use item.
380 380 make HGRCPATH empty to only look in .hg/hgrc of current repo.
381 381 if no HGRCPATH, use default os-specific path.'''
382 382 global _rcpath
383 383 if _rcpath is None:
384 384 if 'HGRCPATH' in os.environ:
385 385 _rcpath = []
386 386 for p in os.environ['HGRCPATH'].split(os.pathsep):
387 387 if not p:
388 388 continue
389 389 p = util.expandpath(p)
390 390 if os.path.isdir(p):
391 391 for f, kind in osutil.listdir(p):
392 392 if f.endswith('.rc'):
393 393 _rcpath.append(os.path.join(p, f))
394 394 else:
395 395 _rcpath.append(p)
396 396 else:
397 397 _rcpath = osrcpath()
398 398 return _rcpath
399 399
400 400 if os.name != 'nt':
401 401
402 402 def rcfiles(path):
403 403 rcs = [os.path.join(path, 'hgrc')]
404 404 rcdir = os.path.join(path, 'hgrc.d')
405 405 try:
406 406 rcs.extend([os.path.join(rcdir, f)
407 407 for f, kind in osutil.listdir(rcdir)
408 408 if f.endswith(".rc")])
409 409 except OSError:
410 410 pass
411 411 return rcs
412 412
413 413 def systemrcpath():
414 414 path = []
415 415 # old mod_python does not set sys.argv
416 416 if len(getattr(sys, 'argv', [])) > 0:
417 417 p = os.path.dirname(os.path.dirname(sys.argv[0]))
418 418 path.extend(rcfiles(os.path.join(p, 'etc/mercurial')))
419 419 path.extend(rcfiles('/etc/mercurial'))
420 420 return path
421 421
422 422 def userrcpath():
423 423 return [os.path.expanduser('~/.hgrc')]
424 424
425 425 else:
426 426
427 427 _HKEY_LOCAL_MACHINE = 0x80000002L
428 428
429 429 def systemrcpath():
430 430 '''return default os-specific hgrc search path'''
431 431 rcpath = []
432 432 filename = util.executablepath()
433 433 # Use mercurial.ini found in directory with hg.exe
434 434 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
435 435 if os.path.isfile(progrc):
436 436 rcpath.append(progrc)
437 437 return rcpath
438 438 # Use hgrc.d found in directory with hg.exe
439 439 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
440 440 if os.path.isdir(progrcd):
441 441 for f, kind in osutil.listdir(progrcd):
442 442 if f.endswith('.rc'):
443 443 rcpath.append(os.path.join(progrcd, f))
444 444 return rcpath
445 445 # else look for a system rcpath in the registry
446 446 value = util.lookupreg('SOFTWARE\\Mercurial', None,
447 447 _HKEY_LOCAL_MACHINE)
448 448 if not isinstance(value, str) or not value:
449 449 return rcpath
450 450 value = value.replace('/', os.sep)
451 451 for p in value.split(os.pathsep):
452 452 if p.lower().endswith('mercurial.ini'):
453 453 rcpath.append(p)
454 454 elif os.path.isdir(p):
455 455 for f, kind in osutil.listdir(p):
456 456 if f.endswith('.rc'):
457 457 rcpath.append(os.path.join(p, f))
458 458 return rcpath
459 459
460 460 def userrcpath():
461 461 '''return os-specific hgrc search path to the user dir'''
462 462 home = os.path.expanduser('~')
463 463 path = [os.path.join(home, 'mercurial.ini'),
464 464 os.path.join(home, '.hgrc')]
465 465 userprofile = os.environ.get('USERPROFILE')
466 466 if userprofile:
467 467 path.append(os.path.join(userprofile, 'mercurial.ini'))
468 468 path.append(os.path.join(userprofile, '.hgrc'))
469 469 return path
470 470
471 471 def revsingle(repo, revspec, default='.'):
472 472 if not revspec:
473 473 return repo[default]
474 474
475 475 l = revrange(repo, [revspec])
476 476 if len(l) < 1:
477 477 raise util.Abort(_('empty revision set'))
478 478 return repo[l[-1]]
479 479
480 480 def revpair(repo, revs):
481 481 if not revs:
482 482 return repo.dirstate.p1(), None
483 483
484 484 l = revrange(repo, revs)
485 485
486 486 if len(l) == 0:
487 487 return repo.dirstate.p1(), None
488 488
489 489 if len(l) == 1:
490 490 return repo.lookup(l[0]), None
491 491
492 492 return repo.lookup(l[0]), repo.lookup(l[-1])
493 493
494 494 _revrangesep = ':'
495 495
496 496 def revrange(repo, revs):
497 497 """Yield revision as strings from a list of revision specifications."""
498 498
499 499 def revfix(repo, val, defval):
500 500 if not val and val != 0 and defval is not None:
501 501 return defval
502 502 return repo.changelog.rev(repo.lookup(val))
503 503
504 504 seen, l = set(), []
505 505 for spec in revs:
506 506 # attempt to parse old-style ranges first to deal with
507 507 # things like old-tag which contain query metacharacters
508 508 try:
509 509 if isinstance(spec, int):
510 510 seen.add(spec)
511 511 l.append(spec)
512 512 continue
513 513
514 514 if _revrangesep in spec:
515 515 start, end = spec.split(_revrangesep, 1)
516 516 start = revfix(repo, start, 0)
517 517 end = revfix(repo, end, len(repo) - 1)
518 518 step = start > end and -1 or 1
519 519 for rev in xrange(start, end + step, step):
520 520 if rev in seen:
521 521 continue
522 522 seen.add(rev)
523 523 l.append(rev)
524 524 continue
525 525 elif spec and spec in repo: # single unquoted rev
526 526 rev = revfix(repo, spec, None)
527 527 if rev in seen:
528 528 continue
529 529 seen.add(rev)
530 530 l.append(rev)
531 531 continue
532 532 except error.RepoLookupError:
533 533 pass
534 534
535 535 # fall through to new-style queries if old-style fails
536 536 m = revset.match(repo.ui, spec)
537 537 for r in m(repo, range(len(repo))):
538 538 if r not in seen:
539 539 l.append(r)
540 540 seen.update(l)
541 541
542 542 return l
543 543
544 544 def expandpats(pats):
545 545 if not util.expandglobs:
546 546 return list(pats)
547 547 ret = []
548 548 for p in pats:
549 549 kind, name = matchmod._patsplit(p, None)
550 550 if kind is None:
551 551 try:
552 552 globbed = glob.glob(name)
553 553 except re.error:
554 554 globbed = [name]
555 555 if globbed:
556 556 ret.extend(globbed)
557 557 continue
558 558 ret.append(p)
559 559 return ret
560 560
561 561 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
562 562 if pats == ("",):
563 563 pats = []
564 564 if not globbed and default == 'relpath':
565 565 pats = expandpats(pats or [])
566 m = matchmod.match(repo.root, repo.getcwd(), pats,
567 opts.get('include'), opts.get('exclude'), default,
568 auditor=repo.auditor)
566 m = repo[None].match(pats, opts.get('include'), opts.get('exclude'),
567 default)
569 568 def badfn(f, msg):
570 569 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
571 570 m.bad = badfn
572 571 return m
573 572
574 573 def matchall(repo):
575 574 return matchmod.always(repo.root, repo.getcwd())
576 575
577 576 def matchfiles(repo, files):
578 577 return matchmod.exact(repo.root, repo.getcwd(), files)
579 578
580 579 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
581 580 if dry_run is None:
582 581 dry_run = opts.get('dry_run')
583 582 if similarity is None:
584 583 similarity = float(opts.get('similarity') or 0)
585 584 # we'd use status here, except handling of symlinks and ignore is tricky
586 585 added, unknown, deleted, removed = [], [], [], []
587 586 audit_path = pathauditor(repo.root)
588 587 m = match(repo, pats, opts)
589 588 for abs in repo.walk(m):
590 589 target = repo.wjoin(abs)
591 590 good = True
592 591 try:
593 592 audit_path(abs)
594 593 except (OSError, util.Abort):
595 594 good = False
596 595 rel = m.rel(abs)
597 596 exact = m.exact(abs)
598 597 if good and abs not in repo.dirstate:
599 598 unknown.append(abs)
600 599 if repo.ui.verbose or not exact:
601 600 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
602 601 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
603 602 or (os.path.isdir(target) and not os.path.islink(target))):
604 603 deleted.append(abs)
605 604 if repo.ui.verbose or not exact:
606 605 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
607 606 # for finding renames
608 607 elif repo.dirstate[abs] == 'r':
609 608 removed.append(abs)
610 609 elif repo.dirstate[abs] == 'a':
611 610 added.append(abs)
612 611 copies = {}
613 612 if similarity > 0:
614 613 for old, new, score in similar.findrenames(repo,
615 614 added + unknown, removed + deleted, similarity):
616 615 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
617 616 repo.ui.status(_('recording removal of %s as rename to %s '
618 617 '(%d%% similar)\n') %
619 618 (m.rel(old), m.rel(new), score * 100))
620 619 copies[new] = old
621 620
622 621 if not dry_run:
623 622 wctx = repo[None]
624 623 wlock = repo.wlock()
625 624 try:
626 625 wctx.forget(deleted)
627 626 wctx.add(unknown)
628 627 for new, old in copies.iteritems():
629 628 wctx.copy(old, new)
630 629 finally:
631 630 wlock.release()
632 631
633 632 def updatedir(ui, repo, patches, similarity=0):
634 633 '''Update dirstate after patch application according to metadata'''
635 634 if not patches:
636 635 return []
637 636 copies = []
638 637 removes = set()
639 638 cfiles = patches.keys()
640 639 cwd = repo.getcwd()
641 640 if cwd:
642 641 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
643 642 for f in patches:
644 643 gp = patches[f]
645 644 if not gp:
646 645 continue
647 646 if gp.op == 'RENAME':
648 647 copies.append((gp.oldpath, gp.path))
649 648 removes.add(gp.oldpath)
650 649 elif gp.op == 'COPY':
651 650 copies.append((gp.oldpath, gp.path))
652 651 elif gp.op == 'DELETE':
653 652 removes.add(gp.path)
654 653
655 654 wctx = repo[None]
656 655 for src, dst in copies:
657 656 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
658 657 if (not similarity) and removes:
659 658 wctx.remove(sorted(removes), True)
660 659
661 660 for f in patches:
662 661 gp = patches[f]
663 662 if gp and gp.mode:
664 663 islink, isexec = gp.mode
665 664 dst = repo.wjoin(gp.path)
666 665 # patch won't create empty files
667 666 if gp.op == 'ADD' and not os.path.lexists(dst):
668 667 flags = (isexec and 'x' or '') + (islink and 'l' or '')
669 668 repo.wwrite(gp.path, '', flags)
670 669 util.setflags(dst, islink, isexec)
671 670 addremove(repo, cfiles, similarity=similarity)
672 671 files = patches.keys()
673 672 files.extend([r for r in removes if r not in files])
674 673 return sorted(files)
675 674
676 675 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
677 676 """Update the dirstate to reflect the intent of copying src to dst. For
678 677 different reasons it might not end with dst being marked as copied from src.
679 678 """
680 679 origsrc = repo.dirstate.copied(src) or src
681 680 if dst == origsrc: # copying back a copy?
682 681 if repo.dirstate[dst] not in 'mn' and not dryrun:
683 682 repo.dirstate.normallookup(dst)
684 683 else:
685 684 if repo.dirstate[origsrc] == 'a' and origsrc == src:
686 685 if not ui.quiet:
687 686 ui.warn(_("%s has not been committed yet, so no copy "
688 687 "data will be stored for %s.\n")
689 688 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
690 689 if repo.dirstate[dst] in '?r' and not dryrun:
691 690 wctx.add([dst])
692 691 elif not dryrun:
693 692 wctx.copy(origsrc, dst)
694 693
695 694 def readrequires(opener, supported):
696 695 '''Reads and parses .hg/requires and checks if all entries found
697 696 are in the list of supported features.'''
698 697 requirements = set(opener.read("requires").splitlines())
699 698 for r in requirements:
700 699 if r not in supported:
701 700 if not r or not r[0].isalnum():
702 701 raise error.RequirementError(_(".hg/requires file is corrupt"))
703 702 raise error.RequirementError(_("unknown repository format: "
704 703 "requires feature '%s' (upgrade Mercurial)") % r)
705 704 return requirements
General Comments 0
You need to be logged in to leave comments. Login now