##// END OF EJS Templates
dirstate: use 'm' state in otherparent to reduce ambiguity...
Matt Mackall -
r22896:7e9cbb9c default
parent child Browse files
Show More
@@ -1,928 +1,934 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, pathutil
11 11 import os, stat, errno, gc
12 12
13 13 propertycache = util.propertycache
14 14 filecache = scmutil.filecache
15 15 _rangemask = 0x7fffffff
16 16
17 17 dirstatetuple = parsers.dirstatetuple
18 18
19 19 class repocache(filecache):
20 20 """filecache for files in .hg/"""
21 21 def join(self, obj, fname):
22 22 return obj._opener.join(fname)
23 23
24 24 class rootcache(filecache):
25 25 """filecache for files in the repository root"""
26 26 def join(self, obj, fname):
27 27 return obj._join(fname)
28 28
29 29 class dirstate(object):
30 30
31 31 def __init__(self, opener, ui, root, validate):
32 32 '''Create a new dirstate object.
33 33
34 34 opener is an open()-like callable that can be used to open the
35 35 dirstate file; root is the root of the directory tracked by
36 36 the dirstate.
37 37 '''
38 38 self._opener = opener
39 39 self._validate = validate
40 40 self._root = root
41 41 self._rootdir = os.path.join(root, '')
42 42 self._dirty = False
43 43 self._dirtypl = False
44 44 self._lastnormaltime = 0
45 45 self._ui = ui
46 46 self._filecache = {}
47 47 self._parentwriters = 0
48 48
49 49 def beginparentchange(self):
50 50 '''Marks the beginning of a set of changes that involve changing
51 51 the dirstate parents. If there is an exception during this time,
52 52 the dirstate will not be written when the wlock is released. This
53 53 prevents writing an incoherent dirstate where the parent doesn't
54 54 match the contents.
55 55 '''
56 56 self._parentwriters += 1
57 57
58 58 def endparentchange(self):
59 59 '''Marks the end of a set of changes that involve changing the
60 60 dirstate parents. Once all parent changes have been marked done,
61 61 the wlock will be free to write the dirstate on release.
62 62 '''
63 63 if self._parentwriters > 0:
64 64 self._parentwriters -= 1
65 65
66 66 def pendingparentchange(self):
67 67 '''Returns true if the dirstate is in the middle of a set of changes
68 68 that modify the dirstate parent.
69 69 '''
70 70 return self._parentwriters > 0
71 71
72 72 @propertycache
73 73 def _map(self):
74 74 '''Return the dirstate contents as a map from filename to
75 75 (state, mode, size, time).'''
76 76 self._read()
77 77 return self._map
78 78
79 79 @propertycache
80 80 def _copymap(self):
81 81 self._read()
82 82 return self._copymap
83 83
84 84 @propertycache
85 85 def _foldmap(self):
86 86 f = {}
87 87 normcase = util.normcase
88 88 for name, s in self._map.iteritems():
89 89 if s[0] != 'r':
90 90 f[normcase(name)] = name
91 91 for name in self._dirs:
92 92 f[normcase(name)] = name
93 93 f['.'] = '.' # prevents useless util.fspath() invocation
94 94 return f
95 95
96 96 @repocache('branch')
97 97 def _branch(self):
98 98 try:
99 99 return self._opener.read("branch").strip() or "default"
100 100 except IOError, inst:
101 101 if inst.errno != errno.ENOENT:
102 102 raise
103 103 return "default"
104 104
105 105 @propertycache
106 106 def _pl(self):
107 107 try:
108 108 fp = self._opener("dirstate")
109 109 st = fp.read(40)
110 110 fp.close()
111 111 l = len(st)
112 112 if l == 40:
113 113 return st[:20], st[20:40]
114 114 elif l > 0 and l < 40:
115 115 raise util.Abort(_('working directory state appears damaged!'))
116 116 except IOError, err:
117 117 if err.errno != errno.ENOENT:
118 118 raise
119 119 return [nullid, nullid]
120 120
121 121 @propertycache
122 122 def _dirs(self):
123 123 return scmutil.dirs(self._map, 'r')
124 124
125 125 def dirs(self):
126 126 return self._dirs
127 127
128 128 @rootcache('.hgignore')
129 129 def _ignore(self):
130 130 files = [self._join('.hgignore')]
131 131 for name, path in self._ui.configitems("ui"):
132 132 if name == 'ignore' or name.startswith('ignore.'):
133 133 files.append(util.expandpath(path))
134 134 return ignore.ignore(self._root, files, self._ui.warn)
135 135
136 136 @propertycache
137 137 def _slash(self):
138 138 return self._ui.configbool('ui', 'slash') and os.sep != '/'
139 139
140 140 @propertycache
141 141 def _checklink(self):
142 142 return util.checklink(self._root)
143 143
144 144 @propertycache
145 145 def _checkexec(self):
146 146 return util.checkexec(self._root)
147 147
148 148 @propertycache
149 149 def _checkcase(self):
150 150 return not util.checkcase(self._join('.hg'))
151 151
152 152 def _join(self, f):
153 153 # much faster than os.path.join()
154 154 # it's safe because f is always a relative path
155 155 return self._rootdir + f
156 156
157 157 def flagfunc(self, buildfallback):
158 158 if self._checklink and self._checkexec:
159 159 def f(x):
160 160 try:
161 161 st = os.lstat(self._join(x))
162 162 if util.statislink(st):
163 163 return 'l'
164 164 if util.statisexec(st):
165 165 return 'x'
166 166 except OSError:
167 167 pass
168 168 return ''
169 169 return f
170 170
171 171 fallback = buildfallback()
172 172 if self._checklink:
173 173 def f(x):
174 174 if os.path.islink(self._join(x)):
175 175 return 'l'
176 176 if 'x' in fallback(x):
177 177 return 'x'
178 178 return ''
179 179 return f
180 180 if self._checkexec:
181 181 def f(x):
182 182 if 'l' in fallback(x):
183 183 return 'l'
184 184 if util.isexec(self._join(x)):
185 185 return 'x'
186 186 return ''
187 187 return f
188 188 else:
189 189 return fallback
190 190
191 191 @propertycache
192 192 def _cwd(self):
193 193 return os.getcwd()
194 194
195 195 def getcwd(self):
196 196 cwd = self._cwd
197 197 if cwd == self._root:
198 198 return ''
199 199 # self._root ends with a path separator if self._root is '/' or 'C:\'
200 200 rootsep = self._root
201 201 if not util.endswithsep(rootsep):
202 202 rootsep += os.sep
203 203 if cwd.startswith(rootsep):
204 204 return cwd[len(rootsep):]
205 205 else:
206 206 # we're outside the repo. return an absolute path.
207 207 return cwd
208 208
209 209 def pathto(self, f, cwd=None):
210 210 if cwd is None:
211 211 cwd = self.getcwd()
212 212 path = util.pathto(self._root, cwd, f)
213 213 if self._slash:
214 214 return util.pconvert(path)
215 215 return path
216 216
217 217 def __getitem__(self, key):
218 218 '''Return the current state of key (a filename) in the dirstate.
219 219
220 220 States are:
221 221 n normal
222 222 m needs merging
223 223 r marked for removal
224 224 a marked for addition
225 225 ? not tracked
226 226 '''
227 227 return self._map.get(key, ("?",))[0]
228 228
229 229 def __contains__(self, key):
230 230 return key in self._map
231 231
232 232 def __iter__(self):
233 233 for x in sorted(self._map):
234 234 yield x
235 235
236 236 def iteritems(self):
237 237 return self._map.iteritems()
238 238
239 239 def parents(self):
240 240 return [self._validate(p) for p in self._pl]
241 241
242 242 def p1(self):
243 243 return self._validate(self._pl[0])
244 244
245 245 def p2(self):
246 246 return self._validate(self._pl[1])
247 247
248 248 def branch(self):
249 249 return encoding.tolocal(self._branch)
250 250
251 251 def setparents(self, p1, p2=nullid):
252 252 """Set dirstate parents to p1 and p2.
253 253
254 254 When moving from two parents to one, 'm' merged entries a
255 255 adjusted to normal and previous copy records discarded and
256 256 returned by the call.
257 257
258 258 See localrepo.setparents()
259 259 """
260 260 if self._parentwriters == 0:
261 261 raise ValueError("cannot set dirstate parent without "
262 262 "calling dirstate.beginparentchange")
263 263
264 264 self._dirty = self._dirtypl = True
265 265 oldp2 = self._pl[1]
266 266 self._pl = p1, p2
267 267 copies = {}
268 268 if oldp2 != nullid and p2 == nullid:
269 269 for f, s in self._map.iteritems():
270 270 # Discard 'm' markers when moving away from a merge state
271 271 if s[0] == 'm':
272 272 if f in self._copymap:
273 273 copies[f] = self._copymap[f]
274 274 self.normallookup(f)
275 275 # Also fix up otherparent markers
276 276 elif s[0] == 'n' and s[2] == -2:
277 277 if f in self._copymap:
278 278 copies[f] = self._copymap[f]
279 279 self.add(f)
280 280 return copies
281 281
282 282 def setbranch(self, branch):
283 283 self._branch = encoding.fromlocal(branch)
284 284 f = self._opener('branch', 'w', atomictemp=True)
285 285 try:
286 286 f.write(self._branch + '\n')
287 287 f.close()
288 288
289 289 # make sure filecache has the correct stat info for _branch after
290 290 # replacing the underlying file
291 291 ce = self._filecache['_branch']
292 292 if ce:
293 293 ce.refresh()
294 294 except: # re-raises
295 295 f.discard()
296 296 raise
297 297
298 298 def _read(self):
299 299 self._map = {}
300 300 self._copymap = {}
301 301 try:
302 302 st = self._opener.read("dirstate")
303 303 except IOError, err:
304 304 if err.errno != errno.ENOENT:
305 305 raise
306 306 return
307 307 if not st:
308 308 return
309 309
310 310 # Python's garbage collector triggers a GC each time a certain number
311 311 # of container objects (the number being defined by
312 312 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
313 313 # for each file in the dirstate. The C version then immediately marks
314 314 # them as not to be tracked by the collector. However, this has no
315 315 # effect on when GCs are triggered, only on what objects the GC looks
316 316 # into. This means that O(number of files) GCs are unavoidable.
317 317 # Depending on when in the process's lifetime the dirstate is parsed,
318 318 # this can get very expensive. As a workaround, disable GC while
319 319 # parsing the dirstate.
320 320 gcenabled = gc.isenabled()
321 321 gc.disable()
322 322 try:
323 323 p = parsers.parse_dirstate(self._map, self._copymap, st)
324 324 finally:
325 325 if gcenabled:
326 326 gc.enable()
327 327 if not self._dirtypl:
328 328 self._pl = p
329 329
330 330 def invalidate(self):
331 331 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
332 332 "_ignore"):
333 333 if a in self.__dict__:
334 334 delattr(self, a)
335 335 self._lastnormaltime = 0
336 336 self._dirty = False
337 337 self._parentwriters = 0
338 338
339 339 def copy(self, source, dest):
340 340 """Mark dest as a copy of source. Unmark dest if source is None."""
341 341 if source == dest:
342 342 return
343 343 self._dirty = True
344 344 if source is not None:
345 345 self._copymap[dest] = source
346 346 elif dest in self._copymap:
347 347 del self._copymap[dest]
348 348
349 349 def copied(self, file):
350 350 return self._copymap.get(file, None)
351 351
352 352 def copies(self):
353 353 return self._copymap
354 354
355 355 def _droppath(self, f):
356 356 if self[f] not in "?r" and "_dirs" in self.__dict__:
357 357 self._dirs.delpath(f)
358 358
359 359 def _addpath(self, f, state, mode, size, mtime):
360 360 oldstate = self[f]
361 361 if state == 'a' or oldstate == 'r':
362 362 scmutil.checkfilename(f)
363 363 if f in self._dirs:
364 364 raise util.Abort(_('directory %r already in dirstate') % f)
365 365 # shadows
366 366 for d in scmutil.finddirs(f):
367 367 if d in self._dirs:
368 368 break
369 369 if d in self._map and self[d] != 'r':
370 370 raise util.Abort(
371 371 _('file %r in dirstate clashes with %r') % (d, f))
372 372 if oldstate in "?r" and "_dirs" in self.__dict__:
373 373 self._dirs.addpath(f)
374 374 self._dirty = True
375 375 self._map[f] = dirstatetuple(state, mode, size, mtime)
376 376
377 377 def normal(self, f):
378 378 '''Mark a file normal and clean.'''
379 379 s = os.lstat(self._join(f))
380 380 mtime = int(s.st_mtime)
381 381 self._addpath(f, 'n', s.st_mode,
382 382 s.st_size & _rangemask, mtime & _rangemask)
383 383 if f in self._copymap:
384 384 del self._copymap[f]
385 385 if mtime > self._lastnormaltime:
386 386 # Remember the most recent modification timeslot for status(),
387 387 # to make sure we won't miss future size-preserving file content
388 388 # modifications that happen within the same timeslot.
389 389 self._lastnormaltime = mtime
390 390
391 391 def normallookup(self, f):
392 392 '''Mark a file normal, but possibly dirty.'''
393 393 if self._pl[1] != nullid and f in self._map:
394 394 # if there is a merge going on and the file was either
395 395 # in state 'm' (-1) or coming from other parent (-2) before
396 396 # being removed, restore that state.
397 397 entry = self._map[f]
398 398 if entry[0] == 'r' and entry[2] in (-1, -2):
399 399 source = self._copymap.get(f)
400 400 if entry[2] == -1:
401 401 self.merge(f)
402 402 elif entry[2] == -2:
403 403 self.otherparent(f)
404 404 if source:
405 405 self.copy(source, f)
406 406 return
407 407 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
408 408 return
409 409 self._addpath(f, 'n', 0, -1, -1)
410 410 if f in self._copymap:
411 411 del self._copymap[f]
412 412
413 413 def otherparent(self, f):
414 414 '''Mark as coming from the other parent, always dirty.'''
415 415 if self._pl[1] == nullid:
416 416 raise util.Abort(_("setting %r to other parent "
417 417 "only allowed in merges") % f)
418 if f in self and self[f] == 'n':
419 # merge-like
420 self._addpath(f, 'm', 0, -2, -1)
421 else:
422 # add-like
418 423 self._addpath(f, 'n', 0, -2, -1)
424
419 425 if f in self._copymap:
420 426 del self._copymap[f]
421 427
422 428 def add(self, f):
423 429 '''Mark a file added.'''
424 430 self._addpath(f, 'a', 0, -1, -1)
425 431 if f in self._copymap:
426 432 del self._copymap[f]
427 433
428 434 def remove(self, f):
429 435 '''Mark a file removed.'''
430 436 self._dirty = True
431 437 self._droppath(f)
432 438 size = 0
433 439 if self._pl[1] != nullid and f in self._map:
434 440 # backup the previous state
435 441 entry = self._map[f]
436 442 if entry[0] == 'm': # merge
437 443 size = -1
438 444 elif entry[0] == 'n' and entry[2] == -2: # other parent
439 445 size = -2
440 446 self._map[f] = dirstatetuple('r', 0, size, 0)
441 447 if size == 0 and f in self._copymap:
442 448 del self._copymap[f]
443 449
444 450 def merge(self, f):
445 451 '''Mark a file merged.'''
446 452 if self._pl[1] == nullid:
447 453 return self.normallookup(f)
448 454 s = os.lstat(self._join(f))
449 455 self._addpath(f, 'm', s.st_mode,
450 456 s.st_size & _rangemask, int(s.st_mtime) & _rangemask)
451 457 if f in self._copymap:
452 458 del self._copymap[f]
453 459
454 460 def drop(self, f):
455 461 '''Drop a file from the dirstate'''
456 462 if f in self._map:
457 463 self._dirty = True
458 464 self._droppath(f)
459 465 del self._map[f]
460 466
461 467 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
462 468 normed = util.normcase(path)
463 469 folded = self._foldmap.get(normed, None)
464 470 if folded is None:
465 471 if isknown:
466 472 folded = path
467 473 else:
468 474 if exists is None:
469 475 exists = os.path.lexists(os.path.join(self._root, path))
470 476 if not exists:
471 477 # Maybe a path component exists
472 478 if not ignoremissing and '/' in path:
473 479 d, f = path.rsplit('/', 1)
474 480 d = self._normalize(d, isknown, ignoremissing, None)
475 481 folded = d + "/" + f
476 482 else:
477 483 # No path components, preserve original case
478 484 folded = path
479 485 else:
480 486 # recursively normalize leading directory components
481 487 # against dirstate
482 488 if '/' in normed:
483 489 d, f = normed.rsplit('/', 1)
484 490 d = self._normalize(d, isknown, ignoremissing, True)
485 491 r = self._root + "/" + d
486 492 folded = d + "/" + util.fspath(f, r)
487 493 else:
488 494 folded = util.fspath(normed, self._root)
489 495 self._foldmap[normed] = folded
490 496
491 497 return folded
492 498
493 499 def normalize(self, path, isknown=False, ignoremissing=False):
494 500 '''
495 501 normalize the case of a pathname when on a casefolding filesystem
496 502
497 503 isknown specifies whether the filename came from walking the
498 504 disk, to avoid extra filesystem access.
499 505
500 506 If ignoremissing is True, missing path are returned
501 507 unchanged. Otherwise, we try harder to normalize possibly
502 508 existing path components.
503 509
504 510 The normalized case is determined based on the following precedence:
505 511
506 512 - version of name already stored in the dirstate
507 513 - version of name stored on disk
508 514 - version provided via command arguments
509 515 '''
510 516
511 517 if self._checkcase:
512 518 return self._normalize(path, isknown, ignoremissing)
513 519 return path
514 520
515 521 def clear(self):
516 522 self._map = {}
517 523 if "_dirs" in self.__dict__:
518 524 delattr(self, "_dirs")
519 525 self._copymap = {}
520 526 self._pl = [nullid, nullid]
521 527 self._lastnormaltime = 0
522 528 self._dirty = True
523 529
524 530 def rebuild(self, parent, allfiles, changedfiles=None):
525 531 changedfiles = changedfiles or allfiles
526 532 oldmap = self._map
527 533 self.clear()
528 534 for f in allfiles:
529 535 if f not in changedfiles:
530 536 self._map[f] = oldmap[f]
531 537 else:
532 538 if 'x' in allfiles.flags(f):
533 539 self._map[f] = dirstatetuple('n', 0777, -1, 0)
534 540 else:
535 541 self._map[f] = dirstatetuple('n', 0666, -1, 0)
536 542 self._pl = (parent, nullid)
537 543 self._dirty = True
538 544
539 545 def write(self):
540 546 if not self._dirty:
541 547 return
542 548
543 549 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
544 550 # timestamp of each entries in dirstate, because of 'now > mtime'
545 551 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
546 552 if delaywrite:
547 553 import time # to avoid useless import
548 554 time.sleep(delaywrite)
549 555
550 556 st = self._opener("dirstate", "w", atomictemp=True)
551 557 # use the modification time of the newly created temporary file as the
552 558 # filesystem's notion of 'now'
553 559 now = util.fstat(st).st_mtime
554 560 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
555 561 st.close()
556 562 self._lastnormaltime = 0
557 563 self._dirty = self._dirtypl = False
558 564
559 565 def _dirignore(self, f):
560 566 if f == '.':
561 567 return False
562 568 if self._ignore(f):
563 569 return True
564 570 for p in scmutil.finddirs(f):
565 571 if self._ignore(p):
566 572 return True
567 573 return False
568 574
569 575 def _walkexplicit(self, match, subrepos):
570 576 '''Get stat data about the files explicitly specified by match.
571 577
572 578 Return a triple (results, dirsfound, dirsnotfound).
573 579 - results is a mapping from filename to stat result. It also contains
574 580 listings mapping subrepos and .hg to None.
575 581 - dirsfound is a list of files found to be directories.
576 582 - dirsnotfound is a list of files that the dirstate thinks are
577 583 directories and that were not found.'''
578 584
579 585 def badtype(mode):
580 586 kind = _('unknown')
581 587 if stat.S_ISCHR(mode):
582 588 kind = _('character device')
583 589 elif stat.S_ISBLK(mode):
584 590 kind = _('block device')
585 591 elif stat.S_ISFIFO(mode):
586 592 kind = _('fifo')
587 593 elif stat.S_ISSOCK(mode):
588 594 kind = _('socket')
589 595 elif stat.S_ISDIR(mode):
590 596 kind = _('directory')
591 597 return _('unsupported file type (type is %s)') % kind
592 598
593 599 matchedir = match.explicitdir
594 600 badfn = match.bad
595 601 dmap = self._map
596 602 normpath = util.normpath
597 603 lstat = os.lstat
598 604 getkind = stat.S_IFMT
599 605 dirkind = stat.S_IFDIR
600 606 regkind = stat.S_IFREG
601 607 lnkkind = stat.S_IFLNK
602 608 join = self._join
603 609 dirsfound = []
604 610 foundadd = dirsfound.append
605 611 dirsnotfound = []
606 612 notfoundadd = dirsnotfound.append
607 613
608 614 if match.matchfn != match.exact and self._checkcase:
609 615 normalize = self._normalize
610 616 else:
611 617 normalize = None
612 618
613 619 files = sorted(match.files())
614 620 subrepos.sort()
615 621 i, j = 0, 0
616 622 while i < len(files) and j < len(subrepos):
617 623 subpath = subrepos[j] + "/"
618 624 if files[i] < subpath:
619 625 i += 1
620 626 continue
621 627 while i < len(files) and files[i].startswith(subpath):
622 628 del files[i]
623 629 j += 1
624 630
625 631 if not files or '.' in files:
626 632 files = ['']
627 633 results = dict.fromkeys(subrepos)
628 634 results['.hg'] = None
629 635
630 636 for ff in files:
631 637 if normalize:
632 638 nf = normalize(normpath(ff), False, True)
633 639 else:
634 640 nf = normpath(ff)
635 641 if nf in results:
636 642 continue
637 643
638 644 try:
639 645 st = lstat(join(nf))
640 646 kind = getkind(st.st_mode)
641 647 if kind == dirkind:
642 648 if nf in dmap:
643 649 # file replaced by dir on disk but still in dirstate
644 650 results[nf] = None
645 651 if matchedir:
646 652 matchedir(nf)
647 653 foundadd(nf)
648 654 elif kind == regkind or kind == lnkkind:
649 655 results[nf] = st
650 656 else:
651 657 badfn(ff, badtype(kind))
652 658 if nf in dmap:
653 659 results[nf] = None
654 660 except OSError, inst: # nf not found on disk - it is dirstate only
655 661 if nf in dmap: # does it exactly match a missing file?
656 662 results[nf] = None
657 663 else: # does it match a missing directory?
658 664 prefix = nf + "/"
659 665 for fn in dmap:
660 666 if fn.startswith(prefix):
661 667 if matchedir:
662 668 matchedir(nf)
663 669 notfoundadd(nf)
664 670 break
665 671 else:
666 672 badfn(ff, inst.strerror)
667 673
668 674 return results, dirsfound, dirsnotfound
669 675
670 676 def walk(self, match, subrepos, unknown, ignored, full=True):
671 677 '''
672 678 Walk recursively through the directory tree, finding all files
673 679 matched by match.
674 680
675 681 If full is False, maybe skip some known-clean files.
676 682
677 683 Return a dict mapping filename to stat-like object (either
678 684 mercurial.osutil.stat instance or return value of os.stat()).
679 685
680 686 '''
681 687 # full is a flag that extensions that hook into walk can use -- this
682 688 # implementation doesn't use it at all. This satisfies the contract
683 689 # because we only guarantee a "maybe".
684 690
685 691 if ignored:
686 692 ignore = util.never
687 693 dirignore = util.never
688 694 elif unknown:
689 695 ignore = self._ignore
690 696 dirignore = self._dirignore
691 697 else:
692 698 # if not unknown and not ignored, drop dir recursion and step 2
693 699 ignore = util.always
694 700 dirignore = util.always
695 701
696 702 matchfn = match.matchfn
697 703 matchalways = match.always()
698 704 matchtdir = match.traversedir
699 705 dmap = self._map
700 706 listdir = osutil.listdir
701 707 lstat = os.lstat
702 708 dirkind = stat.S_IFDIR
703 709 regkind = stat.S_IFREG
704 710 lnkkind = stat.S_IFLNK
705 711 join = self._join
706 712
707 713 exact = skipstep3 = False
708 714 if matchfn == match.exact: # match.exact
709 715 exact = True
710 716 dirignore = util.always # skip step 2
711 717 elif match.files() and not match.anypats(): # match.match, no patterns
712 718 skipstep3 = True
713 719
714 720 if not exact and self._checkcase:
715 721 normalize = self._normalize
716 722 skipstep3 = False
717 723 else:
718 724 normalize = None
719 725
720 726 # step 1: find all explicit files
721 727 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
722 728
723 729 skipstep3 = skipstep3 and not (work or dirsnotfound)
724 730 work = [d for d in work if not dirignore(d)]
725 731 wadd = work.append
726 732
727 733 # step 2: visit subdirectories
728 734 while work:
729 735 nd = work.pop()
730 736 skip = None
731 737 if nd == '.':
732 738 nd = ''
733 739 else:
734 740 skip = '.hg'
735 741 try:
736 742 entries = listdir(join(nd), stat=True, skip=skip)
737 743 except OSError, inst:
738 744 if inst.errno in (errno.EACCES, errno.ENOENT):
739 745 match.bad(self.pathto(nd), inst.strerror)
740 746 continue
741 747 raise
742 748 for f, kind, st in entries:
743 749 if normalize:
744 750 nf = normalize(nd and (nd + "/" + f) or f, True, True)
745 751 else:
746 752 nf = nd and (nd + "/" + f) or f
747 753 if nf not in results:
748 754 if kind == dirkind:
749 755 if not ignore(nf):
750 756 if matchtdir:
751 757 matchtdir(nf)
752 758 wadd(nf)
753 759 if nf in dmap and (matchalways or matchfn(nf)):
754 760 results[nf] = None
755 761 elif kind == regkind or kind == lnkkind:
756 762 if nf in dmap:
757 763 if matchalways or matchfn(nf):
758 764 results[nf] = st
759 765 elif (matchalways or matchfn(nf)) and not ignore(nf):
760 766 results[nf] = st
761 767 elif nf in dmap and (matchalways or matchfn(nf)):
762 768 results[nf] = None
763 769
764 770 for s in subrepos:
765 771 del results[s]
766 772 del results['.hg']
767 773
768 774 # step 3: visit remaining files from dmap
769 775 if not skipstep3 and not exact:
770 776 # If a dmap file is not in results yet, it was either
771 777 # a) not matching matchfn b) ignored, c) missing, or d) under a
772 778 # symlink directory.
773 779 if not results and matchalways:
774 780 visit = dmap.keys()
775 781 else:
776 782 visit = [f for f in dmap if f not in results and matchfn(f)]
777 783 visit.sort()
778 784
779 785 if unknown:
780 786 # unknown == True means we walked all dirs under the roots
781 787 # that wasn't ignored, and everything that matched was stat'ed
782 788 # and is already in results.
783 789 # The rest must thus be ignored or under a symlink.
784 790 audit_path = pathutil.pathauditor(self._root)
785 791
786 792 for nf in iter(visit):
787 793 # Report ignored items in the dmap as long as they are not
788 794 # under a symlink directory.
789 795 if audit_path.check(nf):
790 796 try:
791 797 results[nf] = lstat(join(nf))
792 798 # file was just ignored, no links, and exists
793 799 except OSError:
794 800 # file doesn't exist
795 801 results[nf] = None
796 802 else:
797 803 # It's either missing or under a symlink directory
798 804 # which we in this case report as missing
799 805 results[nf] = None
800 806 else:
801 807 # We may not have walked the full directory tree above,
802 808 # so stat and check everything we missed.
803 809 nf = iter(visit).next
804 810 for st in util.statfiles([join(i) for i in visit]):
805 811 results[nf()] = st
806 812 return results
807 813
808 814 def status(self, match, subrepos, ignored, clean, unknown):
809 815 '''Determine the status of the working copy relative to the
810 816 dirstate and return a tuple of lists (unsure, modified, added,
811 817 removed, deleted, unknown, ignored, clean), where:
812 818
813 819 unsure:
814 820 files that might have been modified since the dirstate was
815 821 written, but need to be read to be sure (size is the same
816 822 but mtime differs)
817 823 modified:
818 824 files that have definitely been modified since the dirstate
819 825 was written (different size or mode)
820 826 added:
821 827 files that have been explicitly added with hg add
822 828 removed:
823 829 files that have been explicitly removed with hg remove
824 830 deleted:
825 831 files that have been deleted through other means ("missing")
826 832 unknown:
827 833 files not in the dirstate that are not ignored
828 834 ignored:
829 835 files not in the dirstate that are ignored
830 836 (by _dirignore())
831 837 clean:
832 838 files that have definitely not been modified since the
833 839 dirstate was written
834 840 '''
835 841 listignored, listclean, listunknown = ignored, clean, unknown
836 842 lookup, modified, added, unknown, ignored = [], [], [], [], []
837 843 removed, deleted, clean = [], [], []
838 844
839 845 dmap = self._map
840 846 ladd = lookup.append # aka "unsure"
841 847 madd = modified.append
842 848 aadd = added.append
843 849 uadd = unknown.append
844 850 iadd = ignored.append
845 851 radd = removed.append
846 852 dadd = deleted.append
847 853 cadd = clean.append
848 854 mexact = match.exact
849 855 dirignore = self._dirignore
850 856 checkexec = self._checkexec
851 857 copymap = self._copymap
852 858 lastnormaltime = self._lastnormaltime
853 859
854 860 # We need to do full walks when either
855 861 # - we're listing all clean files, or
856 862 # - match.traversedir does something, because match.traversedir should
857 863 # be called for every dir in the working dir
858 864 full = listclean or match.traversedir is not None
859 865 for fn, st in self.walk(match, subrepos, listunknown, listignored,
860 866 full=full).iteritems():
861 867 if fn not in dmap:
862 868 if (listignored or mexact(fn)) and dirignore(fn):
863 869 if listignored:
864 870 iadd(fn)
865 871 else:
866 872 uadd(fn)
867 873 continue
868 874
869 875 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
870 876 # written like that for performance reasons. dmap[fn] is not a
871 877 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
872 878 # opcode has fast paths when the value to be unpacked is a tuple or
873 879 # a list, but falls back to creating a full-fledged iterator in
874 880 # general. That is much slower than simply accessing and storing the
875 881 # tuple members one by one.
876 882 t = dmap[fn]
877 883 state = t[0]
878 884 mode = t[1]
879 885 size = t[2]
880 886 time = t[3]
881 887
882 888 if not st and state in "nma":
883 889 dadd(fn)
884 890 elif state == 'n':
885 891 mtime = int(st.st_mtime)
886 892 if (size >= 0 and
887 893 ((size != st.st_size and size != st.st_size & _rangemask)
888 894 or ((mode ^ st.st_mode) & 0100 and checkexec))
889 895 or size == -2 # other parent
890 896 or fn in copymap):
891 897 madd(fn)
892 898 elif time != mtime and time != mtime & _rangemask:
893 899 ladd(fn)
894 900 elif mtime == lastnormaltime:
895 901 # fn may have been changed in the same timeslot without
896 902 # changing its size. This can happen if we quickly do
897 903 # multiple commits in a single transaction.
898 904 # Force lookup, so we don't miss such a racy file change.
899 905 ladd(fn)
900 906 elif listclean:
901 907 cadd(fn)
902 908 elif state == 'm':
903 909 madd(fn)
904 910 elif state == 'a':
905 911 aadd(fn)
906 912 elif state == 'r':
907 913 radd(fn)
908 914
909 915 return (lookup, modified, added, removed, deleted, unknown, ignored,
910 916 clean)
911 917
912 918 def matches(self, match):
913 919 '''
914 920 return files in the dirstate (in whatever state) filtered by match
915 921 '''
916 922 dmap = self._map
917 923 if match.always():
918 924 return dmap.keys()
919 925 files = match.files()
920 926 if match.matchfn == match.exact:
921 927 # fast path -- filter the other way around, since typically files is
922 928 # much smaller than dmap
923 929 return [f for f in files if f in dmap]
924 930 if not match.anypats() and util.all(fn in dmap for fn in files):
925 931 # fast path -- all the values are known to be files, so just return
926 932 # that
927 933 return list(files)
928 934 return [f for f in dmap if match(f)]
@@ -1,149 +1,144 b''
1 1 This test makes sure that we don't mark a file as merged with its ancestor
2 2 when we do a merge.
3 3
4 4 $ cat <<EOF > merge
5 5 > import sys, os
6 6 > print "merging for", os.path.basename(sys.argv[1])
7 7 > EOF
8 8 $ HGMERGE="python ../merge"; export HGMERGE
9 9
10 10 Creating base:
11 11
12 12 $ hg init a
13 13 $ cd a
14 14 $ echo 1 > foo
15 15 $ echo 1 > bar
16 16 $ echo 1 > baz
17 17 $ echo 1 > quux
18 18 $ hg add foo bar baz quux
19 19 $ hg commit -m "base"
20 20
21 21 $ cd ..
22 22 $ hg clone a b
23 23 updating to branch default
24 24 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
25 25
26 26 Creating branch a:
27 27
28 28 $ cd a
29 29 $ echo 2a > foo
30 30 $ echo 2a > bar
31 31 $ hg commit -m "branch a"
32 32
33 33 Creating branch b:
34 34
35 35 $ cd ..
36 36 $ cd b
37 37 $ echo 2b > foo
38 38 $ echo 2b > baz
39 39 $ hg commit -m "branch b"
40 40
41 41 We shouldn't have anything but n state here:
42 42
43 43 $ hg debugstate --nodates | grep -v "^n"
44 44 [1]
45 45
46 46 Merging:
47 47
48 48 $ hg pull ../a
49 49 pulling from ../a
50 50 searching for changes
51 51 adding changesets
52 52 adding manifests
53 53 adding file changes
54 54 added 1 changesets with 2 changes to 2 files (+1 heads)
55 55 (run 'hg heads' to see heads, 'hg merge' to merge)
56 56
57 57 $ hg merge -v
58 58 resolving manifests
59 59 getting bar
60 60 merging foo
61 61 merging for foo
62 62 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
63 63 (branch merge, don't forget to commit)
64 64
65 65 $ echo 2m > foo
66 66 $ echo 2b > baz
67 67 $ echo new > quux
68 68
69 We shouldn't have anything but foo in merge state here:
70
71 $ hg debugstate --nodates | grep "^m"
72 m 644 3 foo
73
74 69 $ hg ci -m "merge"
75 70
76 71 main: we should have a merge here:
77 72
78 73 $ hg debugindex --changelog
79 74 rev offset length ..... linkrev nodeid p1 p2 (re)
80 75 0 0 73 ..... 0 cdca01651b96 000000000000 000000000000 (re)
81 76 1 73 68 ..... 1 f6718a9cb7f3 cdca01651b96 000000000000 (re)
82 77 2 141 68 ..... 2 bdd988058d16 cdca01651b96 000000000000 (re)
83 78 3 209 66 ..... 3 d8a521142a3c f6718a9cb7f3 bdd988058d16 (re)
84 79
85 80 log should show foo and quux changed:
86 81
87 82 $ hg log -v -r tip
88 83 changeset: 3:d8a521142a3c
89 84 tag: tip
90 85 parent: 1:f6718a9cb7f3
91 86 parent: 2:bdd988058d16
92 87 user: test
93 88 date: Thu Jan 01 00:00:00 1970 +0000
94 89 files: foo quux
95 90 description:
96 91 merge
97 92
98 93
99 94
100 95 foo: we should have a merge here:
101 96
102 97 $ hg debugindex foo
103 98 rev offset length ..... linkrev nodeid p1 p2 (re)
104 99 0 0 3 ..... 0 b8e02f643373 000000000000 000000000000 (re)
105 100 1 3 4 ..... 1 2ffeddde1b65 b8e02f643373 000000000000 (re)
106 101 2 7 4 ..... 2 33d1fb69067a b8e02f643373 000000000000 (re)
107 102 3 11 4 ..... 3 aa27919ee430 2ffeddde1b65 33d1fb69067a (re)
108 103
109 104 bar: we should not have a merge here:
110 105
111 106 $ hg debugindex bar
112 107 rev offset length ..... linkrev nodeid p1 p2 (re)
113 108 0 0 3 ..... 0 b8e02f643373 000000000000 000000000000 (re)
114 109 1 3 4 ..... 2 33d1fb69067a b8e02f643373 000000000000 (re)
115 110
116 111 baz: we should not have a merge here:
117 112
118 113 $ hg debugindex baz
119 114 rev offset length ..... linkrev nodeid p1 p2 (re)
120 115 0 0 3 ..... 0 b8e02f643373 000000000000 000000000000 (re)
121 116 1 3 4 ..... 1 2ffeddde1b65 b8e02f643373 000000000000 (re)
122 117
123 118 quux: we should not have a merge here:
124 119
125 120 $ hg debugindex quux
126 121 rev offset length ..... linkrev nodeid p1 p2 (re)
127 122 0 0 3 ..... 0 b8e02f643373 000000000000 000000000000 (re)
128 123 1 3 5 ..... 3 6128c0f33108 b8e02f643373 000000000000 (re)
129 124
130 125 Manifest entries should match tips of all files:
131 126
132 127 $ hg manifest --debug
133 128 33d1fb69067a0139622a3fa3b7ba1cdb1367972e 644 bar
134 129 2ffeddde1b65b4827f6746174a145474129fa2ce 644 baz
135 130 aa27919ee4303cfd575e1fb932dd64d75aa08be4 644 foo
136 131 6128c0f33108e8cfbb4e0824d13ae48b466d7280 644 quux
137 132
138 133 Everything should be clean now:
139 134
140 135 $ hg status
141 136
142 137 $ hg verify
143 138 checking changesets
144 139 checking manifests
145 140 crosschecking files in changesets and manifests
146 141 checking files
147 142 4 files, 4 changesets, 10 total revisions
148 143
149 144 $ cd ..
@@ -1,56 +1,56 b''
1 1 http://mercurial.selenic.com/bts/issue522
2 2
3 3 In the merge below, the file "foo" has the same contents in both
4 4 parents, but if we look at the file-level history, we'll notice that
5 5 the version in p1 is an ancestor of the version in p2. This test makes
6 6 sure that we'll use the version from p2 in the manifest of the merge
7 7 revision.
8 8
9 9 $ hg init
10 10
11 11 $ echo foo > foo
12 12 $ hg ci -qAm 'add foo'
13 13
14 14 $ echo bar >> foo
15 15 $ hg ci -m 'change foo'
16 16
17 17 $ hg backout -r tip -m 'backout changed foo'
18 18 reverting foo
19 19 changeset 2:4d9e78aaceee backs out changeset 1:b515023e500e
20 20
21 21 $ hg up -C 0
22 22 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
23 23
24 24 $ touch bar
25 25 $ hg ci -qAm 'add bar'
26 26
27 27 $ hg merge --debug
28 28 searching for copies back to rev 1
29 29 unmatched files in local:
30 30 bar
31 31 resolving manifests
32 32 branchmerge: True, force: False, partial: False
33 33 ancestor: bbd179dfa0a7, local: 71766447bdbb+, remote: 4d9e78aaceee
34 34 foo: remote is newer -> g
35 35 getting foo
36 36 updating: foo 1/1 files (100.00%)
37 37 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
38 38 (branch merge, don't forget to commit)
39 39
40 40 $ hg debugstate | grep foo
41 n 0 -2 unset foo
41 m 0 -2 unset foo
42 42
43 43 $ hg st -A foo
44 44 M foo
45 45
46 46 $ hg ci -m 'merge'
47 47
48 48 $ hg manifest --debug | grep foo
49 49 c6fc755d7e68f49f880599da29f15add41f42f5a 644 foo
50 50
51 51 $ hg debugindex foo
52 52 rev offset length ..... linkrev nodeid p1 p2 (re)
53 53 0 0 5 ..... 0 2ed2a3912a0b 000000000000 000000000000 (re)
54 54 1 5 9 ..... 1 6f4310b00b9a 2ed2a3912a0b 000000000000 (re)
55 55 2 14 5 ..... 2 c6fc755d7e68 6f4310b00b9a 000000000000 (re)
56 56
@@ -1,114 +1,114 b''
1 1 $ hg init
2 2
3 3 $ echo foo > foo
4 4 $ echo bar > bar
5 5 $ hg ci -qAm 'add foo bar'
6 6
7 7 $ echo foo2 >> foo
8 8 $ echo bleh > bar
9 9 $ hg ci -m 'change foo bar'
10 10
11 11 $ hg up -qC 0
12 12 $ hg mv foo foo1
13 13 $ echo foo1 > foo1
14 14 $ hg cat foo >> foo1
15 15 $ hg ci -m 'mv foo foo1'
16 16 created new head
17 17
18 18 $ hg merge
19 19 merging foo1 and foo to foo1
20 20 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
21 21 (branch merge, don't forget to commit)
22 22
23 23 $ hg debugstate --nodates
24 n 0 -2 bar
24 m 0 -2 bar
25 25 m 644 14 foo1
26 26 copy: foo -> foo1
27 27
28 28 $ hg st -q
29 29 M bar
30 30 M foo1
31 31
32 32
33 33 Removing foo1 and bar:
34 34
35 35 $ cp foo1 F
36 36 $ cp bar B
37 37 $ hg rm -f foo1 bar
38 38
39 39 $ hg debugstate --nodates
40 r 0 -2 bar
40 r 0 -1 bar
41 41 r 0 -1 foo1
42 42 copy: foo -> foo1
43 43
44 44 $ hg st -qC
45 45 R bar
46 46 R foo1
47 47
48 48
49 49 Re-adding foo1 and bar:
50 50
51 51 $ cp F foo1
52 52 $ cp B bar
53 53 $ hg add -v foo1 bar
54 54 adding bar
55 55 adding foo1
56 56
57 57 $ hg debugstate --nodates
58 n 0 -2 bar
58 m 644 5 bar
59 59 m 644 14 foo1
60 60 copy: foo -> foo1
61 61
62 62 $ hg st -qC
63 63 M bar
64 64 M foo1
65 65 foo
66 66
67 67
68 68 Reverting foo1 and bar:
69 69
70 70 $ hg revert -vr . foo1 bar
71 71 saving current version of bar as bar.orig
72 72 reverting bar
73 73 saving current version of foo1 as foo1.orig
74 74 reverting foo1
75 75
76 76 $ hg debugstate --nodates
77 n 0 -2 bar
77 m 644 5 bar
78 78 m 644 14 foo1
79 79 copy: foo -> foo1
80 80
81 81 $ hg st -qC
82 82 M bar
83 83 M foo1
84 84 foo
85 85
86 86 $ hg diff
87 87
88 88 Merge should not overwrite local file that is untracked after remove
89 89
90 90 $ rm *
91 91 $ hg up -qC
92 92 $ hg rm bar
93 93 $ hg ci -m 'remove bar'
94 94 $ echo 'memories of buried pirate treasure' > bar
95 95 $ hg merge
96 96 bar: untracked file differs
97 97 abort: untracked files in working directory differ from files in requested revision
98 98 [255]
99 99 $ cat bar
100 100 memories of buried pirate treasure
101 101
102 102 Those who use force will lose
103 103
104 104 $ hg merge -f
105 105 remote changed bar which local deleted
106 106 use (c)hanged version or leave (d)eleted? c
107 107 merging foo1 and foo to foo1
108 108 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
109 109 (branch merge, don't forget to commit)
110 110 $ cat bar
111 111 bleh
112 112 $ hg st
113 113 M bar
114 114 M foo1
General Comments 0
You need to be logged in to leave comments. Login now