##// END OF EJS Templates
dirstate.walk: eliminate src from yield...
Matt Mackall -
r6818:6e93fbd8 default
parent child Browse files
Show More
@@ -1,774 +1,774
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 nullid, nullrev, short, hex
9 9 from i18n import _
10 10 import ancestor, bdiff, 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=''):
16 16 """changeid is a revision number, node, or tag"""
17 17 if changeid == '':
18 18 changeid = '.'
19 19 self._repo = repo
20 20 self._node = self._repo.lookup(changeid)
21 21 self._rev = self._repo.changelog.rev(self._node)
22 22
23 23 def __str__(self):
24 24 return short(self.node())
25 25
26 26 def __int__(self):
27 27 return self.rev()
28 28
29 29 def __repr__(self):
30 30 return "<changectx %s>" % str(self)
31 31
32 32 def __hash__(self):
33 33 try:
34 34 return hash(self._rev)
35 35 except AttributeError:
36 36 return id(self)
37 37
38 38 def __eq__(self, other):
39 39 try:
40 40 return self._rev == other._rev
41 41 except AttributeError:
42 42 return False
43 43
44 44 def __ne__(self, other):
45 45 return not (self == other)
46 46
47 47 def __nonzero__(self):
48 48 return self._rev != nullrev
49 49
50 50 def __getattr__(self, name):
51 51 if name == '_changeset':
52 52 self._changeset = self._repo.changelog.read(self.node())
53 53 return self._changeset
54 54 elif name == '_manifest':
55 55 self._manifest = self._repo.manifest.read(self._changeset[0])
56 56 return self._manifest
57 57 elif name == '_manifestdelta':
58 58 md = self._repo.manifest.readdelta(self._changeset[0])
59 59 self._manifestdelta = md
60 60 return self._manifestdelta
61 61 elif name == '_parents':
62 62 p = self._repo.changelog.parents(self._node)
63 63 if p[1] == nullid:
64 64 p = p[:-1]
65 65 self._parents = [changectx(self._repo, x) for x in p]
66 66 return self._parents
67 67 else:
68 68 raise AttributeError, name
69 69
70 70 def __contains__(self, key):
71 71 return key in self._manifest
72 72
73 73 def __getitem__(self, key):
74 74 return self.filectx(key)
75 75
76 76 def __iter__(self):
77 77 for f in util.sort(self._manifest):
78 78 yield f
79 79
80 80 def changeset(self): return self._changeset
81 81 def manifest(self): return self._manifest
82 82
83 83 def rev(self): return self._rev
84 84 def node(self): return self._node
85 85 def hex(self): return hex(self._node)
86 86 def user(self): return self._changeset[1]
87 87 def date(self): return self._changeset[2]
88 88 def files(self): return self._changeset[3]
89 89 def description(self): return self._changeset[4]
90 90 def branch(self): return self._changeset[5].get("branch")
91 91 def extra(self): return self._changeset[5]
92 92 def tags(self): return self._repo.nodetags(self._node)
93 93
94 94 def parents(self):
95 95 """return contexts for each parent changeset"""
96 96 return self._parents
97 97
98 98 def children(self):
99 99 """return contexts for each child changeset"""
100 100 c = self._repo.changelog.children(self._node)
101 101 return [changectx(self._repo, x) for x in c]
102 102
103 103 def _fileinfo(self, path):
104 104 if '_manifest' in self.__dict__:
105 105 try:
106 106 return self._manifest[path], self._manifest.flags(path)
107 107 except KeyError:
108 108 raise revlog.LookupError(self._node, path,
109 109 _('not found in manifest'))
110 110 if '_manifestdelta' in self.__dict__ or path in self.files():
111 111 if path in self._manifestdelta:
112 112 return self._manifestdelta[path], self._manifestdelta.flags(path)
113 113 node, flag = self._repo.manifest.find(self._changeset[0], path)
114 114 if not node:
115 115 raise revlog.LookupError(self._node, path,
116 116 _('not found in manifest'))
117 117
118 118 return node, flag
119 119
120 120 def filenode(self, path):
121 121 return self._fileinfo(path)[0]
122 122
123 123 def flags(self, path):
124 124 try:
125 125 return self._fileinfo(path)[1]
126 126 except revlog.LookupError:
127 127 return ''
128 128
129 129 def filectx(self, path, fileid=None, filelog=None):
130 130 """get a file context from this changeset"""
131 131 if fileid is None:
132 132 fileid = self.filenode(path)
133 133 return filectx(self._repo, path, fileid=fileid,
134 134 changectx=self, filelog=filelog)
135 135
136 136 def filectxs(self):
137 137 """generate a file context for each file in this changeset's
138 138 manifest"""
139 139 for f in util.sort(mf):
140 140 yield self.filectx(f, fileid=mf[f])
141 141
142 142 def ancestor(self, c2):
143 143 """
144 144 return the ancestor context of self and c2
145 145 """
146 146 n = self._repo.changelog.ancestor(self._node, c2._node)
147 147 return changectx(self._repo, n)
148 148
149 149 def walk(self, match):
150 150 fdict = dict.fromkeys(match.files())
151 151 # for dirstate.walk, files=['.'] means "walk the whole tree".
152 152 # follow that here, too
153 153 fdict.pop('.', None)
154 154 for fn in self:
155 155 for ffn in fdict:
156 156 # match if the file is the exact name or a directory
157 157 if ffn == fn or fn.startswith("%s/" % ffn):
158 158 del fdict[ffn]
159 159 break
160 160 if match(fn):
161 161 yield fn
162 162 for fn in util.sort(fdict):
163 163 if match.bad(fn, 'No such file in rev ' + str(self)) and match(fn):
164 164 yield fn
165 165
166 166 class filectx(object):
167 167 """A filecontext object makes access to data related to a particular
168 168 filerevision convenient."""
169 169 def __init__(self, repo, path, changeid=None, fileid=None,
170 170 filelog=None, changectx=None):
171 171 """changeid can be a changeset revision, node, or tag.
172 172 fileid can be a file revision or node."""
173 173 self._repo = repo
174 174 self._path = path
175 175
176 176 assert (changeid is not None
177 177 or fileid is not None
178 178 or changectx is not None)
179 179
180 180 if filelog:
181 181 self._filelog = filelog
182 182
183 183 if changeid is not None:
184 184 self._changeid = changeid
185 185 if changectx is not None:
186 186 self._changectx = changectx
187 187 if fileid is not None:
188 188 self._fileid = fileid
189 189
190 190 def __getattr__(self, name):
191 191 if name == '_changectx':
192 192 self._changectx = changectx(self._repo, self._changeid)
193 193 return self._changectx
194 194 elif name == '_filelog':
195 195 self._filelog = self._repo.file(self._path)
196 196 return self._filelog
197 197 elif name == '_changeid':
198 198 if '_changectx' in self.__dict__:
199 199 self._changeid = self._changectx.rev()
200 200 else:
201 201 self._changeid = self._filelog.linkrev(self._filenode)
202 202 return self._changeid
203 203 elif name == '_filenode':
204 204 if '_fileid' in self.__dict__:
205 205 self._filenode = self._filelog.lookup(self._fileid)
206 206 else:
207 207 self._filenode = self._changectx.filenode(self._path)
208 208 return self._filenode
209 209 elif name == '_filerev':
210 210 self._filerev = self._filelog.rev(self._filenode)
211 211 return self._filerev
212 212 elif name == '_repopath':
213 213 self._repopath = self._path
214 214 return self._repopath
215 215 else:
216 216 raise AttributeError, name
217 217
218 218 def __nonzero__(self):
219 219 try:
220 220 n = self._filenode
221 221 return True
222 222 except revlog.LookupError:
223 223 # file is missing
224 224 return False
225 225
226 226 def __str__(self):
227 227 return "%s@%s" % (self.path(), short(self.node()))
228 228
229 229 def __repr__(self):
230 230 return "<filectx %s>" % str(self)
231 231
232 232 def __hash__(self):
233 233 try:
234 234 return hash((self._path, self._fileid))
235 235 except AttributeError:
236 236 return id(self)
237 237
238 238 def __eq__(self, other):
239 239 try:
240 240 return (self._path == other._path
241 241 and self._fileid == other._fileid)
242 242 except AttributeError:
243 243 return False
244 244
245 245 def __ne__(self, other):
246 246 return not (self == other)
247 247
248 248 def filectx(self, fileid):
249 249 '''opens an arbitrary revision of the file without
250 250 opening a new filelog'''
251 251 return filectx(self._repo, self._path, fileid=fileid,
252 252 filelog=self._filelog)
253 253
254 254 def filerev(self): return self._filerev
255 255 def filenode(self): return self._filenode
256 256 def flags(self): return self._changectx.flags(self._path)
257 257 def filelog(self): return self._filelog
258 258
259 259 def rev(self):
260 260 if '_changectx' in self.__dict__:
261 261 return self._changectx.rev()
262 262 if '_changeid' in self.__dict__:
263 263 return self._changectx.rev()
264 264 return self._filelog.linkrev(self._filenode)
265 265
266 266 def linkrev(self): return self._filelog.linkrev(self._filenode)
267 267 def node(self): return self._changectx.node()
268 268 def user(self): return self._changectx.user()
269 269 def date(self): return self._changectx.date()
270 270 def files(self): return self._changectx.files()
271 271 def description(self): return self._changectx.description()
272 272 def branch(self): return self._changectx.branch()
273 273 def manifest(self): return self._changectx.manifest()
274 274 def changectx(self): return self._changectx
275 275
276 276 def data(self): return self._filelog.read(self._filenode)
277 277 def path(self): return self._path
278 278 def size(self): return self._filelog.size(self._filerev)
279 279
280 280 def cmp(self, text): return self._filelog.cmp(self._filenode, text)
281 281
282 282 def renamed(self):
283 283 """check if file was actually renamed in this changeset revision
284 284
285 285 If rename logged in file revision, we report copy for changeset only
286 286 if file revisions linkrev points back to the changeset in question
287 287 or both changeset parents contain different file revisions.
288 288 """
289 289
290 290 renamed = self._filelog.renamed(self._filenode)
291 291 if not renamed:
292 292 return renamed
293 293
294 294 if self.rev() == self.linkrev():
295 295 return renamed
296 296
297 297 name = self.path()
298 298 fnode = self._filenode
299 299 for p in self._changectx.parents():
300 300 try:
301 301 if fnode == p.filenode(name):
302 302 return None
303 303 except revlog.LookupError:
304 304 pass
305 305 return renamed
306 306
307 307 def parents(self):
308 308 p = self._path
309 309 fl = self._filelog
310 310 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
311 311
312 312 r = self._filelog.renamed(self._filenode)
313 313 if r:
314 314 pl[0] = (r[0], r[1], None)
315 315
316 316 return [filectx(self._repo, p, fileid=n, filelog=l)
317 317 for p,n,l in pl if n != nullid]
318 318
319 319 def children(self):
320 320 # hard for renames
321 321 c = self._filelog.children(self._filenode)
322 322 return [filectx(self._repo, self._path, fileid=x,
323 323 filelog=self._filelog) for x in c]
324 324
325 325 def annotate(self, follow=False, linenumber=None):
326 326 '''returns a list of tuples of (ctx, line) for each line
327 327 in the file, where ctx is the filectx of the node where
328 328 that line was last changed.
329 329 This returns tuples of ((ctx, linenumber), line) for each line,
330 330 if "linenumber" parameter is NOT "None".
331 331 In such tuples, linenumber means one at the first appearance
332 332 in the managed file.
333 333 To reduce annotation cost,
334 334 this returns fixed value(False is used) as linenumber,
335 335 if "linenumber" parameter is "False".'''
336 336
337 337 def decorate_compat(text, rev):
338 338 return ([rev] * len(text.splitlines()), text)
339 339
340 340 def without_linenumber(text, rev):
341 341 return ([(rev, False)] * len(text.splitlines()), text)
342 342
343 343 def with_linenumber(text, rev):
344 344 size = len(text.splitlines())
345 345 return ([(rev, i) for i in xrange(1, size + 1)], text)
346 346
347 347 decorate = (((linenumber is None) and decorate_compat) or
348 348 (linenumber and with_linenumber) or
349 349 without_linenumber)
350 350
351 351 def pair(parent, child):
352 352 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
353 353 child[0][b1:b2] = parent[0][a1:a2]
354 354 return child
355 355
356 356 getlog = util.cachefunc(lambda x: self._repo.file(x))
357 357 def getctx(path, fileid):
358 358 log = path == self._path and self._filelog or getlog(path)
359 359 return filectx(self._repo, path, fileid=fileid, filelog=log)
360 360 getctx = util.cachefunc(getctx)
361 361
362 362 def parents(f):
363 363 # we want to reuse filectx objects as much as possible
364 364 p = f._path
365 365 if f._filerev is None: # working dir
366 366 pl = [(n.path(), n.filerev()) for n in f.parents()]
367 367 else:
368 368 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
369 369
370 370 if follow:
371 371 r = f.renamed()
372 372 if r:
373 373 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
374 374
375 375 return [getctx(p, n) for p, n in pl if n != nullrev]
376 376
377 377 # use linkrev to find the first changeset where self appeared
378 378 if self.rev() != self.linkrev():
379 379 base = self.filectx(self.filerev())
380 380 else:
381 381 base = self
382 382
383 383 # find all ancestors
384 384 needed = {base: 1}
385 385 visit = [base]
386 386 files = [base._path]
387 387 while visit:
388 388 f = visit.pop(0)
389 389 for p in parents(f):
390 390 if p not in needed:
391 391 needed[p] = 1
392 392 visit.append(p)
393 393 if p._path not in files:
394 394 files.append(p._path)
395 395 else:
396 396 # count how many times we'll use this
397 397 needed[p] += 1
398 398
399 399 # sort by revision (per file) which is a topological order
400 400 visit = []
401 401 for f in files:
402 402 fn = [(n.rev(), n) for n in needed if n._path == f]
403 403 visit.extend(fn)
404 404
405 405 hist = {}
406 406 for r, f in util.sort(visit):
407 407 curr = decorate(f.data(), f)
408 408 for p in parents(f):
409 409 if p != nullid:
410 410 curr = pair(hist[p], curr)
411 411 # trim the history of unneeded revs
412 412 needed[p] -= 1
413 413 if not needed[p]:
414 414 del hist[p]
415 415 hist[f] = curr
416 416
417 417 return zip(hist[f][0], hist[f][1].splitlines(1))
418 418
419 419 def ancestor(self, fc2):
420 420 """
421 421 find the common ancestor file context, if any, of self, and fc2
422 422 """
423 423
424 424 acache = {}
425 425
426 426 # prime the ancestor cache for the working directory
427 427 for c in (self, fc2):
428 428 if c._filerev == None:
429 429 pl = [(n.path(), n.filenode()) for n in c.parents()]
430 430 acache[(c._path, None)] = pl
431 431
432 432 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
433 433 def parents(vertex):
434 434 if vertex in acache:
435 435 return acache[vertex]
436 436 f, n = vertex
437 437 if f not in flcache:
438 438 flcache[f] = self._repo.file(f)
439 439 fl = flcache[f]
440 440 pl = [(f, p) for p in fl.parents(n) if p != nullid]
441 441 re = fl.renamed(n)
442 442 if re:
443 443 pl.append(re)
444 444 acache[vertex] = pl
445 445 return pl
446 446
447 447 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
448 448 v = ancestor.ancestor(a, b, parents)
449 449 if v:
450 450 f, n = v
451 451 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
452 452
453 453 return None
454 454
455 455 class workingctx(changectx):
456 456 """A workingctx object makes access to data related to
457 457 the current working directory convenient.
458 458 parents - a pair of parent nodeids, or None to use the dirstate.
459 459 date - any valid date string or (unixtime, offset), or None.
460 460 user - username string, or None.
461 461 extra - a dictionary of extra values, or None.
462 462 changes - a list of file lists as returned by localrepo.status()
463 463 or None to use the repository status.
464 464 """
465 465 def __init__(self, repo, parents=None, text="", user=None, date=None,
466 466 extra=None, changes=None):
467 467 self._repo = repo
468 468 self._rev = None
469 469 self._node = None
470 470 self._text = text
471 471 if date:
472 472 self._date = util.parsedate(date)
473 473 if user:
474 474 self._user = user
475 475 if parents:
476 476 self._parents = [changectx(self._repo, p) for p in parents]
477 477 if changes:
478 478 self._status = list(changes)
479 479
480 480 self._extra = {}
481 481 if extra:
482 482 self._extra = extra.copy()
483 483 if 'branch' not in self._extra:
484 484 branch = self._repo.dirstate.branch()
485 485 try:
486 486 branch = branch.decode('UTF-8').encode('UTF-8')
487 487 except UnicodeDecodeError:
488 488 raise util.Abort(_('branch name not in UTF-8!'))
489 489 self._extra['branch'] = branch
490 490 if self._extra['branch'] == '':
491 491 self._extra['branch'] = 'default'
492 492
493 493 def __str__(self):
494 494 return str(self._parents[0]) + "+"
495 495
496 496 def __nonzero__(self):
497 497 return True
498 498
499 499 def __contains__(self, key):
500 500 return self._dirstate[f] not in "?r"
501 501
502 502 def __getattr__(self, name):
503 503 if name == '_status':
504 504 self._status = self._repo.status(unknown=True)
505 505 return self._status
506 506 elif name == '_user':
507 507 self._user = self._repo.ui.username()
508 508 return self._user
509 509 elif name == '_date':
510 510 self._date = util.makedate()
511 511 return self._date
512 512 if name == '_manifest':
513 513 self._buildmanifest()
514 514 return self._manifest
515 515 elif name == '_parents':
516 516 p = self._repo.dirstate.parents()
517 517 if p[1] == nullid:
518 518 p = p[:-1]
519 519 self._parents = [changectx(self._repo, x) for x in p]
520 520 return self._parents
521 521 else:
522 522 raise AttributeError, name
523 523
524 524 def _buildmanifest(self):
525 525 """generate a manifest corresponding to the working directory"""
526 526
527 527 man = self._parents[0].manifest().copy()
528 528 copied = self._repo.dirstate.copies()
529 529 cf = lambda x: man.flags(copied.get(x, x))
530 530 ff = self._repo.dirstate.flagfunc(cf)
531 531 modified, added, removed, deleted, unknown = self._status[:5]
532 532 for i, l in (("a", added), ("m", modified), ("u", unknown)):
533 533 for f in l:
534 534 man[f] = man.get(copied.get(f, f), nullid) + i
535 535 try:
536 536 man.set(f, ff(f))
537 537 except OSError:
538 538 pass
539 539
540 540 for f in deleted + removed:
541 541 if f in man:
542 542 del man[f]
543 543
544 544 self._manifest = man
545 545
546 546 def manifest(self): return self._manifest
547 547
548 548 def user(self): return self._user or self._repo.ui.username()
549 549 def date(self): return self._date
550 550 def description(self): return self._text
551 551 def files(self):
552 552 return util.sort(self._status[0] + self._status[1] + self._status[2])
553 553
554 554 def modified(self): return self._status[0]
555 555 def added(self): return self._status[1]
556 556 def removed(self): return self._status[2]
557 557 def deleted(self): return self._status[3]
558 558 def unknown(self): return self._status[4]
559 559 def clean(self): return self._status[5]
560 560 def branch(self): return self._extra['branch']
561 561 def extra(self): return self._extra
562 562
563 563 def tags(self):
564 564 t = []
565 565 [t.extend(p.tags()) for p in self.parents()]
566 566 return t
567 567
568 568 def children(self):
569 569 return []
570 570
571 571 def flags(self, path):
572 572 if '_manifest' in self.__dict__:
573 573 try:
574 574 return self._manifest.flags(path)
575 575 except KeyError:
576 576 return ''
577 577
578 578 pnode = self._parents[0].changeset()[0]
579 579 orig = self._repo.dirstate.copies().get(path, path)
580 580 node, flag = self._repo.manifest.find(pnode, orig)
581 581 try:
582 582 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
583 583 return ff(path)
584 584 except OSError:
585 585 pass
586 586
587 587 if not node or path in self.deleted() or path in self.removed():
588 588 return ''
589 589 return flag
590 590
591 591 def filectx(self, path, filelog=None):
592 592 """get a file context from the working directory"""
593 593 return workingfilectx(self._repo, path, workingctx=self,
594 594 filelog=filelog)
595 595
596 596 def ancestor(self, c2):
597 597 """return the ancestor context of self and c2"""
598 598 return self._parents[0].ancestor(c2) # punt on two parents for now
599 599
600 600 def walk(self, match):
601 for src, fn, st in self._repo.dirstate.walk(match, True, False):
601 for fn, st in self._repo.dirstate.walk(match, True, False):
602 602 yield fn
603 603
604 604 class workingfilectx(filectx):
605 605 """A workingfilectx object makes access to data related to a particular
606 606 file in the working directory convenient."""
607 607 def __init__(self, repo, path, filelog=None, workingctx=None):
608 608 """changeid can be a changeset revision, node, or tag.
609 609 fileid can be a file revision or node."""
610 610 self._repo = repo
611 611 self._path = path
612 612 self._changeid = None
613 613 self._filerev = self._filenode = None
614 614
615 615 if filelog:
616 616 self._filelog = filelog
617 617 if workingctx:
618 618 self._changectx = workingctx
619 619
620 620 def __getattr__(self, name):
621 621 if name == '_changectx':
622 622 self._changectx = workingctx(self._repo)
623 623 return self._changectx
624 624 elif name == '_repopath':
625 625 self._repopath = (self._repo.dirstate.copied(self._path)
626 626 or self._path)
627 627 return self._repopath
628 628 elif name == '_filelog':
629 629 self._filelog = self._repo.file(self._repopath)
630 630 return self._filelog
631 631 else:
632 632 raise AttributeError, name
633 633
634 634 def __nonzero__(self):
635 635 return True
636 636
637 637 def __str__(self):
638 638 return "%s@%s" % (self.path(), self._changectx)
639 639
640 640 def filectx(self, fileid):
641 641 '''opens an arbitrary revision of the file without
642 642 opening a new filelog'''
643 643 return filectx(self._repo, self._repopath, fileid=fileid,
644 644 filelog=self._filelog)
645 645
646 646 def rev(self):
647 647 if '_changectx' in self.__dict__:
648 648 return self._changectx.rev()
649 649 return self._filelog.linkrev(self._filenode)
650 650
651 651 def data(self): return self._repo.wread(self._path)
652 652 def renamed(self):
653 653 rp = self._repopath
654 654 if rp == self._path:
655 655 return None
656 656 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
657 657
658 658 def parents(self):
659 659 '''return parent filectxs, following copies if necessary'''
660 660 p = self._path
661 661 rp = self._repopath
662 662 pcl = self._changectx._parents
663 663 fl = self._filelog
664 664 pl = [(rp, pcl[0]._manifest.get(rp, nullid), fl)]
665 665 if len(pcl) > 1:
666 666 if rp != p:
667 667 fl = None
668 668 pl.append((p, pcl[1]._manifest.get(p, nullid), fl))
669 669
670 670 return [filectx(self._repo, p, fileid=n, filelog=l)
671 671 for p,n,l in pl if n != nullid]
672 672
673 673 def children(self):
674 674 return []
675 675
676 676 def size(self): return os.stat(self._repo.wjoin(self._path)).st_size
677 677 def date(self):
678 678 t, tz = self._changectx.date()
679 679 try:
680 680 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
681 681 except OSError, err:
682 682 if err.errno != errno.ENOENT: raise
683 683 return (t, tz)
684 684
685 685 def cmp(self, text): return self._repo.wread(self._path) == text
686 686
687 687 class memctx(object):
688 688 """A memctx is a subset of changectx supposed to be built on memory
689 689 and passed to commit functions.
690 690
691 691 NOTE: this interface and the related memfilectx are experimental and
692 692 may change without notice.
693 693
694 694 parents - a pair of parent nodeids.
695 695 filectxfn - a callable taking (repo, memctx, path) arguments and
696 696 returning a memctx object.
697 697 date - any valid date string or (unixtime, offset), or None.
698 698 user - username string, or None.
699 699 extra - a dictionary of extra values, or None.
700 700 """
701 701 def __init__(self, repo, parents, text, files, filectxfn, user=None,
702 702 date=None, extra=None):
703 703 self._repo = repo
704 704 self._rev = None
705 705 self._node = None
706 706 self._text = text
707 707 self._date = date and util.parsedate(date) or util.makedate()
708 708 self._user = user
709 709 parents = [(p or nullid) for p in parents]
710 710 p1, p2 = parents
711 711 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
712 712 files = util.sort(list(files))
713 713 self._status = [files, [], [], [], []]
714 714 self._filectxfn = filectxfn
715 715
716 716 self._extra = extra and extra.copy() or {}
717 717 if 'branch' not in self._extra:
718 718 self._extra['branch'] = 'default'
719 719 elif self._extra.get('branch') == '':
720 720 self._extra['branch'] = 'default'
721 721
722 722 def __str__(self):
723 723 return str(self._parents[0]) + "+"
724 724
725 725 def __int__(self):
726 726 return self._rev
727 727
728 728 def __nonzero__(self):
729 729 return True
730 730
731 731 def user(self): return self._user or self._repo.ui.username()
732 732 def date(self): return self._date
733 733 def description(self): return self._text
734 734 def files(self): return self.modified()
735 735 def modified(self): return self._status[0]
736 736 def added(self): return self._status[1]
737 737 def removed(self): return self._status[2]
738 738 def deleted(self): return self._status[3]
739 739 def unknown(self): return self._status[4]
740 740 def clean(self): return self._status[5]
741 741 def branch(self): return self._extra['branch']
742 742 def extra(self): return self._extra
743 743 def flags(self, f): return self[f].flags()
744 744
745 745 def parents(self):
746 746 """return contexts for each parent changeset"""
747 747 return self._parents
748 748
749 749 def filectx(self, path, filelog=None):
750 750 """get a file context from the working directory"""
751 751 return self._filectxfn(self._repo, self, path)
752 752
753 753 class memfilectx(object):
754 754 """A memfilectx is a subset of filectx supposed to be built by client
755 755 code and passed to commit functions.
756 756 """
757 757 def __init__(self, path, data, islink, isexec, copied):
758 758 """copied is the source file path, or None."""
759 759 self._path = path
760 760 self._data = data
761 761 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
762 762 self._copied = None
763 763 if copied:
764 764 self._copied = (copied, nullid)
765 765
766 766 def __nonzero__(self): return True
767 767 def __str__(self): return "%s@%s" % (self.path(), self._changectx)
768 768 def path(self): return self._path
769 769 def data(self): return self._data
770 770 def flags(self): return self._flags
771 771 def isexec(self): return 'x' in self._flags
772 772 def islink(self): return 'l' in self._flags
773 773 def renamed(self): return self._copied
774 774
@@ -1,670 +1,658
1 1 """
2 2 dirstate.py - working directory tracking for mercurial
3 3
4 4 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5
6 6 This software may be used and distributed according to the terms
7 7 of the GNU General Public License, incorporated herein by reference.
8 8 """
9 9
10 10 from node import nullid
11 11 from i18n import _
12 12 import struct, os, bisect, stat, util, errno, ignore
13 13 import cStringIO, osutil, sys
14 14
15 15 _unknown = ('?', 0, 0, 0)
16 16 _format = ">cllll"
17 17
18 18 def _finddirs(path):
19 19 pos = len(path)
20 20 while 1:
21 21 pos = path.rfind('/', 0, pos)
22 22 if pos == -1:
23 23 break
24 24 yield path[:pos]
25 25
26 26 class dirstate(object):
27 27
28 28 def __init__(self, opener, ui, root):
29 29 self._opener = opener
30 30 self._root = root
31 31 self._dirty = False
32 32 self._dirtypl = False
33 33 self._ui = ui
34 34
35 35 def __getattr__(self, name):
36 36 if name == '_map':
37 37 self._read()
38 38 return self._map
39 39 elif name == '_copymap':
40 40 self._read()
41 41 return self._copymap
42 42 elif name == '_foldmap':
43 43 _foldmap = {}
44 44 for name in self._map:
45 45 norm = os.path.normcase(os.path.normpath(name))
46 46 _foldmap[norm] = name
47 47 self._foldmap = _foldmap
48 48 return self._foldmap
49 49 elif name == '_branch':
50 50 try:
51 51 self._branch = (self._opener("branch").read().strip()
52 52 or "default")
53 53 except IOError:
54 54 self._branch = "default"
55 55 return self._branch
56 56 elif name == '_pl':
57 57 self._pl = [nullid, nullid]
58 58 try:
59 59 st = self._opener("dirstate").read(40)
60 60 if len(st) == 40:
61 61 self._pl = st[:20], st[20:40]
62 62 except IOError, err:
63 63 if err.errno != errno.ENOENT: raise
64 64 return self._pl
65 65 elif name == '_dirs':
66 66 dirs = {}
67 67 for f,s in self._map.items():
68 68 if s[0] != 'r':
69 69 for base in _finddirs(f):
70 70 dirs[base] = dirs.get(base, 0) + 1
71 71 self._dirs = dirs
72 72 return self._dirs
73 73 elif name == '_ignore':
74 74 files = [self._join('.hgignore')]
75 75 for name, path in self._ui.configitems("ui"):
76 76 if name == 'ignore' or name.startswith('ignore.'):
77 77 files.append(os.path.expanduser(path))
78 78 self._ignore = ignore.ignore(self._root, files, self._ui.warn)
79 79 return self._ignore
80 80 elif name == '_slash':
81 81 self._slash = self._ui.configbool('ui', 'slash') and os.sep != '/'
82 82 return self._slash
83 83 elif name == '_checklink':
84 84 self._checklink = util.checklink(self._root)
85 85 return self._checklink
86 86 elif name == '_checkexec':
87 87 self._checkexec = util.checkexec(self._root)
88 88 return self._checkexec
89 89 elif name == '_checkcase':
90 90 self._checkcase = not util.checkcase(self._join('.hg'))
91 91 return self._checkcase
92 92 elif name == 'normalize':
93 93 if self._checkcase:
94 94 self.normalize = self._normalize
95 95 else:
96 96 self.normalize = lambda x: x
97 97 return self.normalize
98 98 else:
99 99 raise AttributeError, name
100 100
101 101 def _join(self, f):
102 102 return os.path.join(self._root, f)
103 103
104 104 def flagfunc(self, fallback):
105 105 if self._checklink:
106 106 if self._checkexec:
107 107 def f(x):
108 108 p = os.path.join(self._root, x)
109 109 if os.path.islink(p):
110 110 return 'l'
111 111 if util.is_exec(p):
112 112 return 'x'
113 113 return ''
114 114 return f
115 115 def f(x):
116 116 if os.path.islink(os.path.join(self._root, x)):
117 117 return 'l'
118 118 if 'x' in fallback(x):
119 119 return 'x'
120 120 return ''
121 121 return f
122 122 if self._checkexec:
123 123 def f(x):
124 124 if 'l' in fallback(x):
125 125 return 'l'
126 126 if util.is_exec(os.path.join(self._root, x)):
127 127 return 'x'
128 128 return ''
129 129 return f
130 130 return fallback
131 131
132 132 def getcwd(self):
133 133 cwd = os.getcwd()
134 134 if cwd == self._root: return ''
135 135 # self._root ends with a path separator if self._root is '/' or 'C:\'
136 136 rootsep = self._root
137 137 if not util.endswithsep(rootsep):
138 138 rootsep += os.sep
139 139 if cwd.startswith(rootsep):
140 140 return cwd[len(rootsep):]
141 141 else:
142 142 # we're outside the repo. return an absolute path.
143 143 return cwd
144 144
145 145 def pathto(self, f, cwd=None):
146 146 if cwd is None:
147 147 cwd = self.getcwd()
148 148 path = util.pathto(self._root, cwd, f)
149 149 if self._slash:
150 150 return util.normpath(path)
151 151 return path
152 152
153 153 def __getitem__(self, key):
154 154 ''' current states:
155 155 n normal
156 156 m needs merging
157 157 r marked for removal
158 158 a marked for addition
159 159 ? not tracked'''
160 160 return self._map.get(key, ("?",))[0]
161 161
162 162 def __contains__(self, key):
163 163 return key in self._map
164 164
165 165 def __iter__(self):
166 166 for x in util.sort(self._map):
167 167 yield x
168 168
169 169 def parents(self):
170 170 return self._pl
171 171
172 172 def branch(self):
173 173 return self._branch
174 174
175 175 def setparents(self, p1, p2=nullid):
176 176 self._dirty = self._dirtypl = True
177 177 self._pl = p1, p2
178 178
179 179 def setbranch(self, branch):
180 180 self._branch = branch
181 181 self._opener("branch", "w").write(branch + '\n')
182 182
183 183 def _read(self):
184 184 self._map = {}
185 185 self._copymap = {}
186 186 if not self._dirtypl:
187 187 self._pl = [nullid, nullid]
188 188 try:
189 189 st = self._opener("dirstate").read()
190 190 except IOError, err:
191 191 if err.errno != errno.ENOENT: raise
192 192 return
193 193 if not st:
194 194 return
195 195
196 196 if not self._dirtypl:
197 197 self._pl = [st[:20], st[20: 40]]
198 198
199 199 # deref fields so they will be local in loop
200 200 dmap = self._map
201 201 copymap = self._copymap
202 202 unpack = struct.unpack
203 203 e_size = struct.calcsize(_format)
204 204 pos1 = 40
205 205 l = len(st)
206 206
207 207 # the inner loop
208 208 while pos1 < l:
209 209 pos2 = pos1 + e_size
210 210 e = unpack(">cllll", st[pos1:pos2]) # a literal here is faster
211 211 pos1 = pos2 + e[4]
212 212 f = st[pos2:pos1]
213 213 if '\0' in f:
214 214 f, c = f.split('\0')
215 215 copymap[f] = c
216 216 dmap[f] = e # we hold onto e[4] because making a subtuple is slow
217 217
218 218 def invalidate(self):
219 219 for a in "_map _copymap _foldmap _branch _pl _dirs _ignore".split():
220 220 if a in self.__dict__:
221 221 delattr(self, a)
222 222 self._dirty = False
223 223
224 224 def copy(self, source, dest):
225 225 if source == dest:
226 226 return
227 227 self._dirty = True
228 228 self._copymap[dest] = source
229 229
230 230 def copied(self, file):
231 231 return self._copymap.get(file, None)
232 232
233 233 def copies(self):
234 234 return self._copymap
235 235
236 236 def _droppath(self, f):
237 237 if self[f] not in "?r" and "_dirs" in self.__dict__:
238 238 dirs = self._dirs
239 239 for base in _finddirs(f):
240 240 if dirs[base] == 1:
241 241 del dirs[base]
242 242 else:
243 243 dirs[base] -= 1
244 244
245 245 def _addpath(self, f, check=False):
246 246 oldstate = self[f]
247 247 if check or oldstate == "r":
248 248 if '\r' in f or '\n' in f:
249 249 raise util.Abort(
250 250 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
251 251 if f in self._dirs:
252 252 raise util.Abort(_('directory %r already in dirstate') % f)
253 253 # shadows
254 254 for d in _finddirs(f):
255 255 if d in self._dirs:
256 256 break
257 257 if d in self._map and self[d] != 'r':
258 258 raise util.Abort(
259 259 _('file %r in dirstate clashes with %r') % (d, f))
260 260 if oldstate in "?r" and "_dirs" in self.__dict__:
261 261 dirs = self._dirs
262 262 for base in _finddirs(f):
263 263 dirs[base] = dirs.get(base, 0) + 1
264 264
265 265 def normal(self, f):
266 266 'mark a file normal and clean'
267 267 self._dirty = True
268 268 self._addpath(f)
269 269 s = os.lstat(self._join(f))
270 270 self._map[f] = ('n', s.st_mode, s.st_size, s.st_mtime, 0)
271 271 if f in self._copymap:
272 272 del self._copymap[f]
273 273
274 274 def normallookup(self, f):
275 275 'mark a file normal, but possibly dirty'
276 276 if self._pl[1] != nullid and f in self._map:
277 277 # if there is a merge going on and the file was either
278 278 # in state 'm' or dirty before being removed, restore that state.
279 279 entry = self._map[f]
280 280 if entry[0] == 'r' and entry[2] in (-1, -2):
281 281 source = self._copymap.get(f)
282 282 if entry[2] == -1:
283 283 self.merge(f)
284 284 elif entry[2] == -2:
285 285 self.normaldirty(f)
286 286 if source:
287 287 self.copy(source, f)
288 288 return
289 289 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
290 290 return
291 291 self._dirty = True
292 292 self._addpath(f)
293 293 self._map[f] = ('n', 0, -1, -1, 0)
294 294 if f in self._copymap:
295 295 del self._copymap[f]
296 296
297 297 def normaldirty(self, f):
298 298 'mark a file normal, but dirty'
299 299 self._dirty = True
300 300 self._addpath(f)
301 301 self._map[f] = ('n', 0, -2, -1, 0)
302 302 if f in self._copymap:
303 303 del self._copymap[f]
304 304
305 305 def add(self, f):
306 306 'mark a file added'
307 307 self._dirty = True
308 308 self._addpath(f, True)
309 309 self._map[f] = ('a', 0, -1, -1, 0)
310 310 if f in self._copymap:
311 311 del self._copymap[f]
312 312
313 313 def remove(self, f):
314 314 'mark a file removed'
315 315 self._dirty = True
316 316 self._droppath(f)
317 317 size = 0
318 318 if self._pl[1] != nullid and f in self._map:
319 319 entry = self._map[f]
320 320 if entry[0] == 'm':
321 321 size = -1
322 322 elif entry[0] == 'n' and entry[2] == -2:
323 323 size = -2
324 324 self._map[f] = ('r', 0, size, 0, 0)
325 325 if size == 0 and f in self._copymap:
326 326 del self._copymap[f]
327 327
328 328 def merge(self, f):
329 329 'mark a file merged'
330 330 self._dirty = True
331 331 s = os.lstat(self._join(f))
332 332 self._addpath(f)
333 333 self._map[f] = ('m', s.st_mode, s.st_size, s.st_mtime, 0)
334 334 if f in self._copymap:
335 335 del self._copymap[f]
336 336
337 337 def forget(self, f):
338 338 'forget a file'
339 339 self._dirty = True
340 340 try:
341 341 self._droppath(f)
342 342 del self._map[f]
343 343 except KeyError:
344 344 self._ui.warn(_("not in dirstate: %s\n") % f)
345 345
346 346 def _normalize(self, path):
347 347 normpath = os.path.normcase(os.path.normpath(path))
348 348 if normpath in self._foldmap:
349 349 return self._foldmap[normpath]
350 350 elif os.path.exists(path):
351 351 self._foldmap[normpath] = util.fspath(path, self._root)
352 352 return self._foldmap[normpath]
353 353 else:
354 354 return path
355 355
356 356 def clear(self):
357 357 self._map = {}
358 358 if "_dirs" in self.__dict__:
359 359 delattr(self, "_dirs");
360 360 self._copymap = {}
361 361 self._pl = [nullid, nullid]
362 362 self._dirty = True
363 363
364 364 def rebuild(self, parent, files):
365 365 self.clear()
366 366 for f in files:
367 367 if 'x' in files.flags(f):
368 368 self._map[f] = ('n', 0777, -1, 0, 0)
369 369 else:
370 370 self._map[f] = ('n', 0666, -1, 0, 0)
371 371 self._pl = (parent, nullid)
372 372 self._dirty = True
373 373
374 374 def write(self):
375 375 if not self._dirty:
376 376 return
377 377 st = self._opener("dirstate", "w", atomictemp=True)
378 378
379 379 try:
380 380 gran = int(self._ui.config('dirstate', 'granularity', 1))
381 381 except ValueError:
382 382 gran = 1
383 383 limit = sys.maxint
384 384 if gran > 0:
385 385 limit = util.fstat(st).st_mtime - gran
386 386
387 387 cs = cStringIO.StringIO()
388 388 copymap = self._copymap
389 389 pack = struct.pack
390 390 write = cs.write
391 391 write("".join(self._pl))
392 392 for f, e in self._map.iteritems():
393 393 if f in copymap:
394 394 f = "%s\0%s" % (f, copymap[f])
395 395 if e[3] > limit and e[0] == 'n':
396 396 e = (e[0], 0, -1, -1, 0)
397 397 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
398 398 write(e)
399 399 write(f)
400 400 st.write(cs.getvalue())
401 401 st.rename()
402 402 self._dirty = self._dirtypl = False
403 403
404 404 def _filter(self, files):
405 405 ret = {}
406 406 unknown = []
407 407
408 408 for x in files:
409 409 if x == '.':
410 410 return self._map.copy()
411 411 if x not in self._map:
412 412 unknown.append(x)
413 413 else:
414 414 ret[x] = self._map[x]
415 415
416 416 if not unknown:
417 417 return ret
418 418
419 419 b = util.sort(self._map)
420 420 blen = len(b)
421 421
422 422 for x in unknown:
423 423 bs = bisect.bisect(b, "%s%s" % (x, '/'))
424 424 while bs < blen:
425 425 s = b[bs]
426 426 if len(s) > len(x) and s.startswith(x):
427 427 ret[s] = self._map[s]
428 428 else:
429 429 break
430 430 bs += 1
431 431 return ret
432 432
433 433 def _supported(self, f, mode, verbose=False):
434 434 if stat.S_ISREG(mode) or stat.S_ISLNK(mode):
435 435 return True
436 436 if verbose:
437 437 kind = 'unknown'
438 438 if stat.S_ISCHR(mode): kind = _('character device')
439 439 elif stat.S_ISBLK(mode): kind = _('block device')
440 440 elif stat.S_ISFIFO(mode): kind = _('fifo')
441 441 elif stat.S_ISSOCK(mode): kind = _('socket')
442 442 elif stat.S_ISDIR(mode): kind = _('directory')
443 443 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
444 444 % (self.pathto(f), kind))
445 445 return False
446 446
447 447 def _dirignore(self, f):
448 448 if f == '.':
449 449 return False
450 450 if self._ignore(f):
451 451 return True
452 452 for p in _finddirs(f):
453 453 if self._ignore(p):
454 454 return True
455 455 return False
456 456
457 457 def walk(self, match, unknown, ignored):
458 458 '''
459 459 walk recursively through the directory tree, finding all files
460 460 matched by the match function
461 461
462 results are yielded in a tuple (src, filename, st), where src
463 is one of:
464 'f' the file was found in the directory tree
465 'm' the file was only in the dirstate and not in the tree
466
462 results are yielded in a tuple (filename, stat), where stat
467 463 and st is the stat result if the file was found in the directory.
468 464 '''
469 465
470 466 def fwarn(f, msg):
471 467 self._ui.warn('%s: %s\n' % (self.pathto(ff), msg))
472 468 return False
473 469 badfn = fwarn
474 470 if hasattr(match, 'bad'):
475 471 badfn = match.bad
476 472
477 473 # walk all files by default
478 474 files = match.files()
479 475 if not files:
480 476 files = ['.']
481 477 dc = self._map.copy()
482 478 else:
483 479 files = util.unique(files)
484 480 dc = self._filter(files)
485 481
486 482 def imatch(file_):
487 483 if file_ not in dc and self._ignore(file_):
488 484 return False
489 485 return match(file_)
490 486
491 487 # TODO: don't walk unknown directories if unknown and ignored are False
492 488 ignore = self._ignore
493 489 dirignore = self._dirignore
494 490 if ignored:
495 491 imatch = match
496 492 ignore = util.never
497 493 dirignore = util.never
498 494
499 495 # self._root may end with a path separator when self._root == '/'
500 496 common_prefix_len = len(self._root)
501 497 if not util.endswithsep(self._root):
502 498 common_prefix_len += 1
503 499
504 500 normpath = util.normpath
505 501 listdir = osutil.listdir
506 502 lstat = os.lstat
507 503 bisect_left = bisect.bisect_left
508 504 isdir = os.path.isdir
509 505 pconvert = util.pconvert
510 506 join = os.path.join
511 507 s_isdir = stat.S_ISDIR
512 508 supported = self._supported
513 509 _join = self._join
514 510 known = {'.hg': 1}
515 511
516 512 # recursion free walker, faster than os.walk.
517 513 def findfiles(s):
518 514 work = [s]
519 515 wadd = work.append
520 516 found = []
521 517 add = found.append
522 518 if hasattr(match, 'dir'):
523 519 match.dir(normpath(s[common_prefix_len:]))
524 520 while work:
525 521 top = work.pop()
526 522 entries = listdir(top, stat=True)
527 523 # nd is the top of the repository dir tree
528 524 nd = normpath(top[common_prefix_len:])
529 525 if nd == '.':
530 526 nd = ''
531 527 else:
532 528 # do not recurse into a repo contained in this
533 529 # one. use bisect to find .hg directory so speed
534 530 # is good on big directory.
535 531 names = [e[0] for e in entries]
536 532 hg = bisect_left(names, '.hg')
537 533 if hg < len(names) and names[hg] == '.hg':
538 534 if isdir(join(top, '.hg')):
539 535 continue
540 536 for f, kind, st in entries:
541 537 np = pconvert(join(nd, f))
542 538 nn = self.normalize(np)
543 539 if np in known:
544 540 continue
545 541 known[nn] = 1
546 542 p = join(top, f)
547 543 # don't trip over symlinks
548 544 if kind == stat.S_IFDIR:
549 545 if not ignore(np):
550 546 wadd(p)
551 547 if hasattr(match, 'dir'):
552 548 match.dir(np)
553 549 if np in dc and match(np):
554 add((nn, 'm', st))
550 add((nn, None))
555 551 elif imatch(np):
556 552 if supported(np, st.st_mode):
557 add((nn, 'f', st))
553 add((nn, st))
558 554 elif np in dc:
559 add((nn, 'm', st))
555 add((nn, None))
560 556 return util.sort(found)
561 557
562 558 # step one, find all files that match our criteria
563 559 for ff in util.sort(files):
564 560 nf = normpath(ff)
565 561 nn = self.normalize(nf)
566 562 f = _join(ff)
567 563 try:
568 564 st = lstat(f)
569 565 except OSError, inst:
570 566 found = False
571 567 for fn in dc:
572 568 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
573 569 found = True
574 570 break
575 571 if not found:
576 572 if inst.errno != errno.ENOENT:
577 573 fwarn(ff, inst.strerror)
578 574 elif badfn(ff, inst.strerror) and imatch(nf):
579 yield 'f', ff, None
575 yield ff, None
580 576 continue
581 577 if s_isdir(st.st_mode):
582 578 if not dirignore(nf):
583 for f, src, st in findfiles(f):
584 yield src, f, st
579 for e in findfiles(f):
580 yield e
585 581 else:
586 582 if nn in known:
587 583 continue
588 584 known[nn] = 1
589 585 if match(nf):
590 586 if supported(ff, st.st_mode, verbose=True):
591 yield 'f', nn, st
587 yield nn, st
592 588 elif ff in dc:
593 yield 'm', nf, st
589 yield nf, None
594 590
595 591 # step two run through anything left in the dc hash and yield
596 592 # if we haven't already seen it
597 for k in util.sort(dc):
598 if k in known:
593 for f in util.sort(dc):
594 if f in known:
599 595 continue
600 known[k] = 1
601 if imatch(k):
602 yield 'm', k, None
596 known[f] = 1
597 if imatch(f):
598 try:
599 st = lstat(_join(f))
600 if supported(f, st.st_mode):
601 yield f, st
602 continue
603 except OSError, inst:
604 if inst.errno not in (errno.ENOENT, errno.ENOTDIR):
605 raise
606 yield f, None
603 607
604 608 def status(self, match, ignored, clean, unknown):
605 609 listignored, listclean, listunknown = ignored, clean, unknown
606 610 lookup, modified, added, unknown, ignored = [], [], [], [], []
607 611 removed, deleted, clean = [], [], []
608 612
609 613 _join = self._join
610 614 lstat = os.lstat
611 615 cmap = self._copymap
612 616 dmap = self._map
613 617 ladd = lookup.append
614 618 madd = modified.append
615 619 aadd = added.append
616 620 uadd = unknown.append
617 621 iadd = ignored.append
618 622 radd = removed.append
619 623 dadd = deleted.append
620 624 cadd = clean.append
621 625
622 for src, fn, st in self.walk(match, listunknown, listignored):
626 for fn, st in self.walk(match, listunknown, listignored):
623 627 if fn not in dmap:
624 628 if (listignored or match.exact(fn)) and self._dirignore(fn):
625 629 if listignored:
626 630 iadd(fn)
627 631 elif listunknown:
628 632 uadd(fn)
629 633 continue
630 634
631 635 state, mode, size, time, foo = dmap[fn]
632 636
633 if src == 'm':
634 nonexistent = True
635 if not st:
636 try:
637 st = lstat(_join(fn))
638 except OSError, inst:
639 if inst.errno not in (errno.ENOENT, errno.ENOTDIR):
640 raise
641 st = None
642 # We need to re-check that it is a valid file
643 if st and self._supported(fn, st.st_mode):
644 nonexistent = False
645 if nonexistent and state in "nma":
646 dadd(fn)
647 continue
648 # check the common case first
649 if state == 'n':
650 if not st:
651 st = lstat(_join(fn))
637 if not st and state in "nma":
638 dadd(fn)
639 elif state == 'n':
652 640 if (size >= 0 and
653 641 (size != st.st_size
654 642 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
655 643 or size == -2
656 644 or fn in self._copymap):
657 645 madd(fn)
658 646 elif time != int(st.st_mtime):
659 647 ladd(fn)
660 648 elif listclean:
661 649 cadd(fn)
662 650 elif state == 'm':
663 651 madd(fn)
664 652 elif state == 'a':
665 653 aadd(fn)
666 654 elif state == 'r':
667 655 radd(fn)
668 656
669 657 return (lookup, modified, added, removed, deleted, unknown, ignored,
670 658 clean)
General Comments 0
You need to be logged in to leave comments. Login now