##// END OF EJS Templates
dirstate: don't fail when dropping a not-tracked file (issue3080)...
Matt Mackall -
r15399:41453d55 2.0 stable
parent child Browse files
Show More
@@ -1,724 +1,725 b''
1 1 # dirstate.py - working directory tracking for mercurial
2 2 #
3 3 # Copyright 2005-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
9 9 from i18n import _
10 10 import scmutil, util, ignore, osutil, parsers, encoding
11 11 import struct, os, stat, errno
12 12 import cStringIO
13 13
14 14 _format = ">cllll"
15 15 propertycache = util.propertycache
16 16
17 17 def _finddirs(path):
18 18 pos = path.rfind('/')
19 19 while pos != -1:
20 20 yield path[:pos]
21 21 pos = path.rfind('/', 0, pos)
22 22
23 23 def _incdirs(dirs, path):
24 24 for base in _finddirs(path):
25 25 if base in dirs:
26 26 dirs[base] += 1
27 27 return
28 28 dirs[base] = 1
29 29
30 30 def _decdirs(dirs, path):
31 31 for base in _finddirs(path):
32 32 if dirs[base] > 1:
33 33 dirs[base] -= 1
34 34 return
35 35 del dirs[base]
36 36
37 37 class dirstate(object):
38 38
39 39 def __init__(self, opener, ui, root, validate):
40 40 '''Create a new dirstate object.
41 41
42 42 opener is an open()-like callable that can be used to open the
43 43 dirstate file; root is the root of the directory tracked by
44 44 the dirstate.
45 45 '''
46 46 self._opener = opener
47 47 self._validate = validate
48 48 self._root = root
49 49 self._rootdir = os.path.join(root, '')
50 50 self._dirty = False
51 51 self._dirtypl = False
52 52 self._lastnormaltime = None
53 53 self._ui = ui
54 54
55 55 @propertycache
56 56 def _map(self):
57 57 '''Return the dirstate contents as a map from filename to
58 58 (state, mode, size, time).'''
59 59 self._read()
60 60 return self._map
61 61
62 62 @propertycache
63 63 def _copymap(self):
64 64 self._read()
65 65 return self._copymap
66 66
67 67 @propertycache
68 68 def _foldmap(self):
69 69 f = {}
70 70 for name in self._map:
71 71 f[os.path.normcase(name)] = name
72 72 return f
73 73
74 74 @propertycache
75 75 def _branch(self):
76 76 try:
77 77 return self._opener.read("branch").strip() or "default"
78 78 except IOError:
79 79 return "default"
80 80
81 81 @propertycache
82 82 def _pl(self):
83 83 try:
84 84 fp = self._opener("dirstate")
85 85 st = fp.read(40)
86 86 fp.close()
87 87 l = len(st)
88 88 if l == 40:
89 89 return st[:20], st[20:40]
90 90 elif l > 0 and l < 40:
91 91 raise util.Abort(_('working directory state appears damaged!'))
92 92 except IOError, err:
93 93 if err.errno != errno.ENOENT:
94 94 raise
95 95 return [nullid, nullid]
96 96
97 97 @propertycache
98 98 def _dirs(self):
99 99 dirs = {}
100 100 for f, s in self._map.iteritems():
101 101 if s[0] != 'r':
102 102 _incdirs(dirs, f)
103 103 return dirs
104 104
105 105 @propertycache
106 106 def _ignore(self):
107 107 files = [self._join('.hgignore')]
108 108 for name, path in self._ui.configitems("ui"):
109 109 if name == 'ignore' or name.startswith('ignore.'):
110 110 files.append(util.expandpath(path))
111 111 return ignore.ignore(self._root, files, self._ui.warn)
112 112
113 113 @propertycache
114 114 def _slash(self):
115 115 return self._ui.configbool('ui', 'slash') and os.sep != '/'
116 116
117 117 @propertycache
118 118 def _checklink(self):
119 119 return util.checklink(self._root)
120 120
121 121 @propertycache
122 122 def _checkexec(self):
123 123 return util.checkexec(self._root)
124 124
125 125 @propertycache
126 126 def _checkcase(self):
127 127 return not util.checkcase(self._join('.hg'))
128 128
129 129 def _join(self, f):
130 130 # much faster than os.path.join()
131 131 # it's safe because f is always a relative path
132 132 return self._rootdir + f
133 133
134 134 def flagfunc(self, buildfallback):
135 135 if self._checklink and self._checkexec:
136 136 def f(x):
137 137 p = self._join(x)
138 138 if os.path.islink(p):
139 139 return 'l'
140 140 if util.isexec(p):
141 141 return 'x'
142 142 return ''
143 143 return f
144 144
145 145 fallback = buildfallback()
146 146 if self._checklink:
147 147 def f(x):
148 148 if os.path.islink(self._join(x)):
149 149 return 'l'
150 150 if 'x' in fallback(x):
151 151 return 'x'
152 152 return ''
153 153 return f
154 154 if self._checkexec:
155 155 def f(x):
156 156 if 'l' in fallback(x):
157 157 return 'l'
158 158 if util.isexec(self._join(x)):
159 159 return 'x'
160 160 return ''
161 161 return f
162 162 else:
163 163 return fallback
164 164
165 165 def getcwd(self):
166 166 cwd = os.getcwd()
167 167 if cwd == self._root:
168 168 return ''
169 169 # self._root ends with a path separator if self._root is '/' or 'C:\'
170 170 rootsep = self._root
171 171 if not util.endswithsep(rootsep):
172 172 rootsep += os.sep
173 173 if cwd.startswith(rootsep):
174 174 return cwd[len(rootsep):]
175 175 else:
176 176 # we're outside the repo. return an absolute path.
177 177 return cwd
178 178
179 179 def pathto(self, f, cwd=None):
180 180 if cwd is None:
181 181 cwd = self.getcwd()
182 182 path = util.pathto(self._root, cwd, f)
183 183 if self._slash:
184 184 return util.normpath(path)
185 185 return path
186 186
187 187 def __getitem__(self, key):
188 188 '''Return the current state of key (a filename) in the dirstate.
189 189
190 190 States are:
191 191 n normal
192 192 m needs merging
193 193 r marked for removal
194 194 a marked for addition
195 195 ? not tracked
196 196 '''
197 197 return self._map.get(key, ("?",))[0]
198 198
199 199 def __contains__(self, key):
200 200 return key in self._map
201 201
202 202 def __iter__(self):
203 203 for x in sorted(self._map):
204 204 yield x
205 205
206 206 def parents(self):
207 207 return [self._validate(p) for p in self._pl]
208 208
209 209 def p1(self):
210 210 return self._validate(self._pl[0])
211 211
212 212 def p2(self):
213 213 return self._validate(self._pl[1])
214 214
215 215 def branch(self):
216 216 return encoding.tolocal(self._branch)
217 217
218 218 def setparents(self, p1, p2=nullid):
219 219 self._dirty = self._dirtypl = True
220 220 self._pl = p1, p2
221 221
222 222 def setbranch(self, branch):
223 223 if branch in ['tip', '.', 'null']:
224 224 raise util.Abort(_('the name \'%s\' is reserved') % branch)
225 225 self._branch = encoding.fromlocal(branch)
226 226 self._opener.write("branch", self._branch + '\n')
227 227
228 228 def _read(self):
229 229 self._map = {}
230 230 self._copymap = {}
231 231 try:
232 232 st = self._opener.read("dirstate")
233 233 except IOError, err:
234 234 if err.errno != errno.ENOENT:
235 235 raise
236 236 return
237 237 if not st:
238 238 return
239 239
240 240 p = parsers.parse_dirstate(self._map, self._copymap, st)
241 241 if not self._dirtypl:
242 242 self._pl = p
243 243
244 244 def invalidate(self):
245 245 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
246 246 "_ignore"):
247 247 if a in self.__dict__:
248 248 delattr(self, a)
249 249 self._lastnormaltime = None
250 250 self._dirty = False
251 251
252 252 def copy(self, source, dest):
253 253 """Mark dest as a copy of source. Unmark dest if source is None."""
254 254 if source == dest:
255 255 return
256 256 self._dirty = True
257 257 if source is not None:
258 258 self._copymap[dest] = source
259 259 elif dest in self._copymap:
260 260 del self._copymap[dest]
261 261
262 262 def copied(self, file):
263 263 return self._copymap.get(file, None)
264 264
265 265 def copies(self):
266 266 return self._copymap
267 267
268 268 def _droppath(self, f):
269 269 if self[f] not in "?r" and "_dirs" in self.__dict__:
270 270 _decdirs(self._dirs, f)
271 271
272 272 def _addpath(self, f, check=False):
273 273 oldstate = self[f]
274 274 if check or oldstate == "r":
275 275 scmutil.checkfilename(f)
276 276 if f in self._dirs:
277 277 raise util.Abort(_('directory %r already in dirstate') % f)
278 278 # shadows
279 279 for d in _finddirs(f):
280 280 if d in self._dirs:
281 281 break
282 282 if d in self._map and self[d] != 'r':
283 283 raise util.Abort(
284 284 _('file %r in dirstate clashes with %r') % (d, f))
285 285 if oldstate in "?r" and "_dirs" in self.__dict__:
286 286 _incdirs(self._dirs, f)
287 287
288 288 def normal(self, f):
289 289 '''Mark a file normal and clean.'''
290 290 self._dirty = True
291 291 self._addpath(f)
292 292 s = os.lstat(self._join(f))
293 293 mtime = int(s.st_mtime)
294 294 self._map[f] = ('n', s.st_mode, s.st_size, mtime)
295 295 if f in self._copymap:
296 296 del self._copymap[f]
297 297 if mtime > self._lastnormaltime:
298 298 # Remember the most recent modification timeslot for status(),
299 299 # to make sure we won't miss future size-preserving file content
300 300 # modifications that happen within the same timeslot.
301 301 self._lastnormaltime = mtime
302 302
303 303 def normallookup(self, f):
304 304 '''Mark a file normal, but possibly dirty.'''
305 305 if self._pl[1] != nullid and f in self._map:
306 306 # if there is a merge going on and the file was either
307 307 # in state 'm' (-1) or coming from other parent (-2) before
308 308 # being removed, restore that state.
309 309 entry = self._map[f]
310 310 if entry[0] == 'r' and entry[2] in (-1, -2):
311 311 source = self._copymap.get(f)
312 312 if entry[2] == -1:
313 313 self.merge(f)
314 314 elif entry[2] == -2:
315 315 self.otherparent(f)
316 316 if source:
317 317 self.copy(source, f)
318 318 return
319 319 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
320 320 return
321 321 self._dirty = True
322 322 self._addpath(f)
323 323 self._map[f] = ('n', 0, -1, -1)
324 324 if f in self._copymap:
325 325 del self._copymap[f]
326 326
327 327 def otherparent(self, f):
328 328 '''Mark as coming from the other parent, always dirty.'''
329 329 if self._pl[1] == nullid:
330 330 raise util.Abort(_("setting %r to other parent "
331 331 "only allowed in merges") % f)
332 332 self._dirty = True
333 333 self._addpath(f)
334 334 self._map[f] = ('n', 0, -2, -1)
335 335 if f in self._copymap:
336 336 del self._copymap[f]
337 337
338 338 def add(self, f):
339 339 '''Mark a file added.'''
340 340 self._dirty = True
341 341 self._addpath(f, True)
342 342 self._map[f] = ('a', 0, -1, -1)
343 343 if f in self._copymap:
344 344 del self._copymap[f]
345 345
346 346 def remove(self, f):
347 347 '''Mark a file removed.'''
348 348 self._dirty = True
349 349 self._droppath(f)
350 350 size = 0
351 351 if self._pl[1] != nullid and f in self._map:
352 352 # backup the previous state
353 353 entry = self._map[f]
354 354 if entry[0] == 'm': # merge
355 355 size = -1
356 356 elif entry[0] == 'n' and entry[2] == -2: # other parent
357 357 size = -2
358 358 self._map[f] = ('r', 0, size, 0)
359 359 if size == 0 and f in self._copymap:
360 360 del self._copymap[f]
361 361
362 362 def merge(self, f):
363 363 '''Mark a file merged.'''
364 364 self._dirty = True
365 365 s = os.lstat(self._join(f))
366 366 self._addpath(f)
367 367 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
368 368 if f in self._copymap:
369 369 del self._copymap[f]
370 370
371 371 def drop(self, f):
372 372 '''Drop a file from the dirstate'''
373 self._dirty = True
374 self._droppath(f)
375 del self._map[f]
373 if f in self._map:
374 self._dirty = True
375 self._droppath(f)
376 del self._map[f]
376 377
377 378 def _normalize(self, path, isknown):
378 379 normed = os.path.normcase(path)
379 380 folded = self._foldmap.get(normed, None)
380 381 if folded is None:
381 382 if isknown or not os.path.lexists(os.path.join(self._root, path)):
382 383 folded = path
383 384 else:
384 385 folded = self._foldmap.setdefault(normed,
385 386 util.fspath(path, self._root))
386 387 return folded
387 388
388 389 def normalize(self, path, isknown=False):
389 390 '''
390 391 normalize the case of a pathname when on a casefolding filesystem
391 392
392 393 isknown specifies whether the filename came from walking the
393 394 disk, to avoid extra filesystem access
394 395
395 396 The normalized case is determined based on the following precedence:
396 397
397 398 - version of name already stored in the dirstate
398 399 - version of name stored on disk
399 400 - version provided via command arguments
400 401 '''
401 402
402 403 if self._checkcase:
403 404 return self._normalize(path, isknown)
404 405 return path
405 406
406 407 def clear(self):
407 408 self._map = {}
408 409 if "_dirs" in self.__dict__:
409 410 delattr(self, "_dirs")
410 411 self._copymap = {}
411 412 self._pl = [nullid, nullid]
412 413 self._lastnormaltime = None
413 414 self._dirty = True
414 415
415 416 def rebuild(self, parent, files):
416 417 self.clear()
417 418 for f in files:
418 419 if 'x' in files.flags(f):
419 420 self._map[f] = ('n', 0777, -1, 0)
420 421 else:
421 422 self._map[f] = ('n', 0666, -1, 0)
422 423 self._pl = (parent, nullid)
423 424 self._dirty = True
424 425
425 426 def write(self):
426 427 if not self._dirty:
427 428 return
428 429 st = self._opener("dirstate", "w", atomictemp=True)
429 430
430 431 # use the modification time of the newly created temporary file as the
431 432 # filesystem's notion of 'now'
432 433 now = int(util.fstat(st).st_mtime)
433 434
434 435 cs = cStringIO.StringIO()
435 436 copymap = self._copymap
436 437 pack = struct.pack
437 438 write = cs.write
438 439 write("".join(self._pl))
439 440 for f, e in self._map.iteritems():
440 441 if e[0] == 'n' and e[3] == now:
441 442 # The file was last modified "simultaneously" with the current
442 443 # write to dirstate (i.e. within the same second for file-
443 444 # systems with a granularity of 1 sec). This commonly happens
444 445 # for at least a couple of files on 'update'.
445 446 # The user could change the file without changing its size
446 447 # within the same second. Invalidate the file's stat data in
447 448 # dirstate, forcing future 'status' calls to compare the
448 449 # contents of the file. This prevents mistakenly treating such
449 450 # files as clean.
450 451 e = (e[0], 0, -1, -1) # mark entry as 'unset'
451 452 self._map[f] = e
452 453
453 454 if f in copymap:
454 455 f = "%s\0%s" % (f, copymap[f])
455 456 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
456 457 write(e)
457 458 write(f)
458 459 st.write(cs.getvalue())
459 460 st.close()
460 461 self._lastnormaltime = None
461 462 self._dirty = self._dirtypl = False
462 463
463 464 def _dirignore(self, f):
464 465 if f == '.':
465 466 return False
466 467 if self._ignore(f):
467 468 return True
468 469 for p in _finddirs(f):
469 470 if self._ignore(p):
470 471 return True
471 472 return False
472 473
473 474 def walk(self, match, subrepos, unknown, ignored):
474 475 '''
475 476 Walk recursively through the directory tree, finding all files
476 477 matched by match.
477 478
478 479 Return a dict mapping filename to stat-like object (either
479 480 mercurial.osutil.stat instance or return value of os.stat()).
480 481 '''
481 482
482 483 def fwarn(f, msg):
483 484 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
484 485 return False
485 486
486 487 def badtype(mode):
487 488 kind = _('unknown')
488 489 if stat.S_ISCHR(mode):
489 490 kind = _('character device')
490 491 elif stat.S_ISBLK(mode):
491 492 kind = _('block device')
492 493 elif stat.S_ISFIFO(mode):
493 494 kind = _('fifo')
494 495 elif stat.S_ISSOCK(mode):
495 496 kind = _('socket')
496 497 elif stat.S_ISDIR(mode):
497 498 kind = _('directory')
498 499 return _('unsupported file type (type is %s)') % kind
499 500
500 501 ignore = self._ignore
501 502 dirignore = self._dirignore
502 503 if ignored:
503 504 ignore = util.never
504 505 dirignore = util.never
505 506 elif not unknown:
506 507 # if unknown and ignored are False, skip step 2
507 508 ignore = util.always
508 509 dirignore = util.always
509 510
510 511 matchfn = match.matchfn
511 512 badfn = match.bad
512 513 dmap = self._map
513 514 normpath = util.normpath
514 515 listdir = osutil.listdir
515 516 lstat = os.lstat
516 517 getkind = stat.S_IFMT
517 518 dirkind = stat.S_IFDIR
518 519 regkind = stat.S_IFREG
519 520 lnkkind = stat.S_IFLNK
520 521 join = self._join
521 522 work = []
522 523 wadd = work.append
523 524
524 525 exact = skipstep3 = False
525 526 if matchfn == match.exact: # match.exact
526 527 exact = True
527 528 dirignore = util.always # skip step 2
528 529 elif match.files() and not match.anypats(): # match.match, no patterns
529 530 skipstep3 = True
530 531
531 532 if self._checkcase:
532 533 normalize = self._normalize
533 534 skipstep3 = False
534 535 else:
535 536 normalize = lambda x, y: x
536 537
537 538 files = sorted(match.files())
538 539 subrepos.sort()
539 540 i, j = 0, 0
540 541 while i < len(files) and j < len(subrepos):
541 542 subpath = subrepos[j] + "/"
542 543 if files[i] < subpath:
543 544 i += 1
544 545 continue
545 546 while i < len(files) and files[i].startswith(subpath):
546 547 del files[i]
547 548 j += 1
548 549
549 550 if not files or '.' in files:
550 551 files = ['']
551 552 results = dict.fromkeys(subrepos)
552 553 results['.hg'] = None
553 554
554 555 # step 1: find all explicit files
555 556 for ff in files:
556 557 nf = normalize(normpath(ff), False)
557 558 if nf in results:
558 559 continue
559 560
560 561 try:
561 562 st = lstat(join(nf))
562 563 kind = getkind(st.st_mode)
563 564 if kind == dirkind:
564 565 skipstep3 = False
565 566 if nf in dmap:
566 567 #file deleted on disk but still in dirstate
567 568 results[nf] = None
568 569 match.dir(nf)
569 570 if not dirignore(nf):
570 571 wadd(nf)
571 572 elif kind == regkind or kind == lnkkind:
572 573 results[nf] = st
573 574 else:
574 575 badfn(ff, badtype(kind))
575 576 if nf in dmap:
576 577 results[nf] = None
577 578 except OSError, inst:
578 579 if nf in dmap: # does it exactly match a file?
579 580 results[nf] = None
580 581 else: # does it match a directory?
581 582 prefix = nf + "/"
582 583 for fn in dmap:
583 584 if fn.startswith(prefix):
584 585 match.dir(nf)
585 586 skipstep3 = False
586 587 break
587 588 else:
588 589 badfn(ff, inst.strerror)
589 590
590 591 # step 2: visit subdirectories
591 592 while work:
592 593 nd = work.pop()
593 594 skip = None
594 595 if nd == '.':
595 596 nd = ''
596 597 else:
597 598 skip = '.hg'
598 599 try:
599 600 entries = listdir(join(nd), stat=True, skip=skip)
600 601 except OSError, inst:
601 602 if inst.errno == errno.EACCES:
602 603 fwarn(nd, inst.strerror)
603 604 continue
604 605 raise
605 606 for f, kind, st in entries:
606 607 nf = normalize(nd and (nd + "/" + f) or f, True)
607 608 if nf not in results:
608 609 if kind == dirkind:
609 610 if not ignore(nf):
610 611 match.dir(nf)
611 612 wadd(nf)
612 613 if nf in dmap and matchfn(nf):
613 614 results[nf] = None
614 615 elif kind == regkind or kind == lnkkind:
615 616 if nf in dmap:
616 617 if matchfn(nf):
617 618 results[nf] = st
618 619 elif matchfn(nf) and not ignore(nf):
619 620 results[nf] = st
620 621 elif nf in dmap and matchfn(nf):
621 622 results[nf] = None
622 623
623 624 # step 3: report unseen items in the dmap hash
624 625 if not skipstep3 and not exact:
625 626 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
626 627 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
627 628 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
628 629 st = None
629 630 results[nf] = st
630 631 for s in subrepos:
631 632 del results[s]
632 633 del results['.hg']
633 634 return results
634 635
635 636 def status(self, match, subrepos, ignored, clean, unknown):
636 637 '''Determine the status of the working copy relative to the
637 638 dirstate and return a tuple of lists (unsure, modified, added,
638 639 removed, deleted, unknown, ignored, clean), where:
639 640
640 641 unsure:
641 642 files that might have been modified since the dirstate was
642 643 written, but need to be read to be sure (size is the same
643 644 but mtime differs)
644 645 modified:
645 646 files that have definitely been modified since the dirstate
646 647 was written (different size or mode)
647 648 added:
648 649 files that have been explicitly added with hg add
649 650 removed:
650 651 files that have been explicitly removed with hg remove
651 652 deleted:
652 653 files that have been deleted through other means ("missing")
653 654 unknown:
654 655 files not in the dirstate that are not ignored
655 656 ignored:
656 657 files not in the dirstate that are ignored
657 658 (by _dirignore())
658 659 clean:
659 660 files that have definitely not been modified since the
660 661 dirstate was written
661 662 '''
662 663 listignored, listclean, listunknown = ignored, clean, unknown
663 664 lookup, modified, added, unknown, ignored = [], [], [], [], []
664 665 removed, deleted, clean = [], [], []
665 666
666 667 dmap = self._map
667 668 ladd = lookup.append # aka "unsure"
668 669 madd = modified.append
669 670 aadd = added.append
670 671 uadd = unknown.append
671 672 iadd = ignored.append
672 673 radd = removed.append
673 674 dadd = deleted.append
674 675 cadd = clean.append
675 676
676 677 lnkkind = stat.S_IFLNK
677 678
678 679 for fn, st in self.walk(match, subrepos, listunknown,
679 680 listignored).iteritems():
680 681 if fn not in dmap:
681 682 if (listignored or match.exact(fn)) and self._dirignore(fn):
682 683 if listignored:
683 684 iadd(fn)
684 685 elif listunknown:
685 686 uadd(fn)
686 687 continue
687 688
688 689 state, mode, size, time = dmap[fn]
689 690
690 691 if not st and state in "nma":
691 692 dadd(fn)
692 693 elif state == 'n':
693 694 # The "mode & lnkkind != lnkkind or self._checklink"
694 695 # lines are an expansion of "islink => checklink"
695 696 # where islink means "is this a link?" and checklink
696 697 # means "can we check links?".
697 698 mtime = int(st.st_mtime)
698 699 if (size >= 0 and
699 700 (size != st.st_size
700 701 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
701 702 and (mode & lnkkind != lnkkind or self._checklink)
702 703 or size == -2 # other parent
703 704 or fn in self._copymap):
704 705 madd(fn)
705 706 elif (mtime != time
706 707 and (mode & lnkkind != lnkkind or self._checklink)):
707 708 ladd(fn)
708 709 elif mtime == self._lastnormaltime:
709 710 # fn may have been changed in the same timeslot without
710 711 # changing its size. This can happen if we quickly do
711 712 # multiple commits in a single transaction.
712 713 # Force lookup, so we don't miss such a racy file change.
713 714 ladd(fn)
714 715 elif listclean:
715 716 cadd(fn)
716 717 elif state == 'm':
717 718 madd(fn)
718 719 elif state == 'a':
719 720 aadd(fn)
720 721 elif state == 'r':
721 722 radd(fn)
722 723
723 724 return (lookup, modified, added, removed, deleted, unknown, ignored,
724 725 clean)
General Comments 0
You need to be logged in to leave comments. Login now