##// END OF EJS Templates
normallookup: during merges, restore the state saved by remove
Alexis S. L. Carvalho -
r6298:53cbb33e default
parent child Browse files
Show More
@@ -1,609 +1,624 b''
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, strutil, util, errno, ignore
13 13 import cStringIO, osutil
14 14
15 15 _unknown = ('?', 0, 0, 0)
16 16 _format = ">cllll"
17 17
18 18 class dirstate(object):
19 19
20 20 def __init__(self, opener, ui, root):
21 21 self._opener = opener
22 22 self._root = root
23 23 self._dirty = False
24 24 self._dirtypl = False
25 25 self._ui = ui
26 26
27 27 def __getattr__(self, name):
28 28 if name == '_map':
29 29 self._read()
30 30 return self._map
31 31 elif name == '_copymap':
32 32 self._read()
33 33 return self._copymap
34 34 elif name == '_branch':
35 35 try:
36 36 self._branch = (self._opener("branch").read().strip()
37 37 or "default")
38 38 except IOError:
39 39 self._branch = "default"
40 40 return self._branch
41 41 elif name == '_pl':
42 42 self._pl = [nullid, nullid]
43 43 try:
44 44 st = self._opener("dirstate").read(40)
45 45 if len(st) == 40:
46 46 self._pl = st[:20], st[20:40]
47 47 except IOError, err:
48 48 if err.errno != errno.ENOENT: raise
49 49 return self._pl
50 50 elif name == '_dirs':
51 51 self._dirs = {}
52 52 for f in self._map:
53 53 if self[f] != 'r':
54 54 self._incpath(f)
55 55 return self._dirs
56 56 elif name == '_ignore':
57 57 files = [self._join('.hgignore')]
58 58 for name, path in self._ui.configitems("ui"):
59 59 if name == 'ignore' or name.startswith('ignore.'):
60 60 files.append(os.path.expanduser(path))
61 61 self._ignore = ignore.ignore(self._root, files, self._ui.warn)
62 62 return self._ignore
63 63 elif name == '_slash':
64 64 self._slash = self._ui.configbool('ui', 'slash') and os.sep != '/'
65 65 return self._slash
66 66 elif name == '_checkexec':
67 67 self._checkexec = util.checkexec(self._root)
68 68 return self._checkexec
69 69 else:
70 70 raise AttributeError, name
71 71
72 72 def _join(self, f):
73 73 return os.path.join(self._root, f)
74 74
75 75 def getcwd(self):
76 76 cwd = os.getcwd()
77 77 if cwd == self._root: return ''
78 78 # self._root ends with a path separator if self._root is '/' or 'C:\'
79 79 rootsep = self._root
80 80 if not util.endswithsep(rootsep):
81 81 rootsep += os.sep
82 82 if cwd.startswith(rootsep):
83 83 return cwd[len(rootsep):]
84 84 else:
85 85 # we're outside the repo. return an absolute path.
86 86 return cwd
87 87
88 88 def pathto(self, f, cwd=None):
89 89 if cwd is None:
90 90 cwd = self.getcwd()
91 91 path = util.pathto(self._root, cwd, f)
92 92 if self._slash:
93 93 return util.normpath(path)
94 94 return path
95 95
96 96 def __getitem__(self, key):
97 97 ''' current states:
98 98 n normal
99 99 m needs merging
100 100 r marked for removal
101 101 a marked for addition
102 102 ? not tracked'''
103 103 return self._map.get(key, ("?",))[0]
104 104
105 105 def __contains__(self, key):
106 106 return key in self._map
107 107
108 108 def __iter__(self):
109 109 a = self._map.keys()
110 110 a.sort()
111 111 for x in a:
112 112 yield x
113 113
114 114 def parents(self):
115 115 return self._pl
116 116
117 117 def branch(self):
118 118 return self._branch
119 119
120 120 def setparents(self, p1, p2=nullid):
121 121 self._dirty = self._dirtypl = True
122 122 self._pl = p1, p2
123 123
124 124 def setbranch(self, branch):
125 125 self._branch = branch
126 126 self._opener("branch", "w").write(branch + '\n')
127 127
128 128 def _read(self):
129 129 self._map = {}
130 130 self._copymap = {}
131 131 if not self._dirtypl:
132 132 self._pl = [nullid, nullid]
133 133 try:
134 134 st = self._opener("dirstate").read()
135 135 except IOError, err:
136 136 if err.errno != errno.ENOENT: raise
137 137 return
138 138 if not st:
139 139 return
140 140
141 141 if not self._dirtypl:
142 142 self._pl = [st[:20], st[20: 40]]
143 143
144 144 # deref fields so they will be local in loop
145 145 dmap = self._map
146 146 copymap = self._copymap
147 147 unpack = struct.unpack
148 148 e_size = struct.calcsize(_format)
149 149 pos1 = 40
150 150 l = len(st)
151 151
152 152 # the inner loop
153 153 while pos1 < l:
154 154 pos2 = pos1 + e_size
155 155 e = unpack(">cllll", st[pos1:pos2]) # a literal here is faster
156 156 pos1 = pos2 + e[4]
157 157 f = st[pos2:pos1]
158 158 if '\0' in f:
159 159 f, c = f.split('\0')
160 160 copymap[f] = c
161 161 dmap[f] = e # we hold onto e[4] because making a subtuple is slow
162 162
163 163 def invalidate(self):
164 164 for a in "_map _copymap _branch _pl _dirs _ignore".split():
165 165 if a in self.__dict__:
166 166 delattr(self, a)
167 167 self._dirty = False
168 168
169 169 def copy(self, source, dest):
170 170 self._dirty = True
171 171 self._copymap[dest] = source
172 172
173 173 def copied(self, file):
174 174 return self._copymap.get(file, None)
175 175
176 176 def copies(self):
177 177 return self._copymap
178 178
179 179 def _incpath(self, path):
180 180 c = path.rfind('/')
181 181 if c >= 0:
182 182 dirs = self._dirs
183 183 base = path[:c]
184 184 if base not in dirs:
185 185 self._incpath(base)
186 186 dirs[base] = 1
187 187 else:
188 188 dirs[base] += 1
189 189
190 190 def _decpath(self, path):
191 191 c = path.rfind('/')
192 192 if c >= 0:
193 193 base = path[:c]
194 194 dirs = self._dirs
195 195 if dirs[base] == 1:
196 196 del dirs[base]
197 197 self._decpath(base)
198 198 else:
199 199 dirs[base] -= 1
200 200
201 201 def _incpathcheck(self, f):
202 202 if '\r' in f or '\n' in f:
203 203 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
204 204 % f)
205 205 # shadows
206 206 if f in self._dirs:
207 207 raise util.Abort(_('directory %r already in dirstate') % f)
208 208 for c in strutil.rfindall(f, '/'):
209 209 d = f[:c]
210 210 if d in self._dirs:
211 211 break
212 212 if d in self._map and self[d] != 'r':
213 213 raise util.Abort(_('file %r in dirstate clashes with %r') %
214 214 (d, f))
215 215 self._incpath(f)
216 216
217 217 def _changepath(self, f, newstate, relaxed=False):
218 218 # handle upcoming path changes
219 219 oldstate = self[f]
220 220 if oldstate not in "?r" and newstate in "?r":
221 221 if "_dirs" in self.__dict__:
222 222 self._decpath(f)
223 223 return
224 224 if oldstate in "?r" and newstate not in "?r":
225 225 if relaxed and oldstate == '?':
226 226 # XXX
227 227 # in relaxed mode we assume the caller knows
228 228 # what it is doing, workaround for updating
229 229 # dir-to-file revisions
230 230 if "_dirs" in self.__dict__:
231 231 self._incpath(f)
232 232 return
233 233 self._incpathcheck(f)
234 234 return
235 235
236 236 def normal(self, f):
237 237 'mark a file normal and clean'
238 238 self._dirty = True
239 239 self._changepath(f, 'n', True)
240 240 s = os.lstat(self._join(f))
241 241 self._map[f] = ('n', s.st_mode, s.st_size, s.st_mtime, 0)
242 242 if f in self._copymap:
243 243 del self._copymap[f]
244 244
245 245 def normallookup(self, f):
246 246 'mark a file normal, but possibly dirty'
247 if self._pl[1] != nullid and f in self._map:
248 # if there is a merge going on and the file was either
249 # in state 'm' or dirty before being removed, restore that state.
250 entry = self._map[f]
251 if entry[0] == 'r' and entry[2] in (-1, -2):
252 source = self._copymap.get(f)
253 if entry[2] == -1:
254 self.merge(f)
255 elif entry[2] == -2:
256 self.normaldirty(f)
257 if source:
258 self.copy(source, f)
259 return
260 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
261 return
247 262 self._dirty = True
248 263 self._changepath(f, 'n', True)
249 264 self._map[f] = ('n', 0, -1, -1, 0)
250 265 if f in self._copymap:
251 266 del self._copymap[f]
252 267
253 268 def normaldirty(self, f):
254 269 'mark a file normal, but dirty'
255 270 self._dirty = True
256 271 self._changepath(f, 'n', True)
257 272 self._map[f] = ('n', 0, -2, -1, 0)
258 273 if f in self._copymap:
259 274 del self._copymap[f]
260 275
261 276 def add(self, f):
262 277 'mark a file added'
263 278 self._dirty = True
264 279 self._changepath(f, 'a')
265 280 self._map[f] = ('a', 0, -1, -1, 0)
266 281 if f in self._copymap:
267 282 del self._copymap[f]
268 283
269 284 def remove(self, f):
270 285 'mark a file removed'
271 286 self._dirty = True
272 287 self._changepath(f, 'r')
273 288 size = 0
274 289 if self._pl[1] != nullid and f in self._map:
275 290 entry = self._map[f]
276 291 if entry[0] == 'm':
277 292 size = -1
278 293 elif entry[0] == 'n' and entry[2] == -2:
279 294 size = -2
280 295 self._map[f] = ('r', 0, size, 0, 0)
281 296 if size == 0 and f in self._copymap:
282 297 del self._copymap[f]
283 298
284 299 def merge(self, f):
285 300 'mark a file merged'
286 301 self._dirty = True
287 302 s = os.lstat(self._join(f))
288 303 self._changepath(f, 'm', True)
289 304 self._map[f] = ('m', s.st_mode, s.st_size, s.st_mtime, 0)
290 305 if f in self._copymap:
291 306 del self._copymap[f]
292 307
293 308 def forget(self, f):
294 309 'forget a file'
295 310 self._dirty = True
296 311 try:
297 312 self._changepath(f, '?')
298 313 del self._map[f]
299 314 except KeyError:
300 315 self._ui.warn(_("not in dirstate: %s\n") % f)
301 316
302 317 def clear(self):
303 318 self._map = {}
304 319 if "_dirs" in self.__dict__:
305 320 delattr(self, "_dirs");
306 321 self._copymap = {}
307 322 self._pl = [nullid, nullid]
308 323 self._dirty = True
309 324
310 325 def rebuild(self, parent, files):
311 326 self.clear()
312 327 for f in files:
313 328 if files.execf(f):
314 329 self._map[f] = ('n', 0777, -1, 0, 0)
315 330 else:
316 331 self._map[f] = ('n', 0666, -1, 0, 0)
317 332 self._pl = (parent, nullid)
318 333 self._dirty = True
319 334
320 335 def write(self):
321 336 if not self._dirty:
322 337 return
323 338 cs = cStringIO.StringIO()
324 339 copymap = self._copymap
325 340 pack = struct.pack
326 341 write = cs.write
327 342 write("".join(self._pl))
328 343 for f, e in self._map.iteritems():
329 344 if f in copymap:
330 345 f = "%s\0%s" % (f, copymap[f])
331 346 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
332 347 write(e)
333 348 write(f)
334 349 st = self._opener("dirstate", "w", atomictemp=True)
335 350 st.write(cs.getvalue())
336 351 st.rename()
337 352 self._dirty = self._dirtypl = False
338 353
339 354 def _filter(self, files):
340 355 ret = {}
341 356 unknown = []
342 357
343 358 for x in files:
344 359 if x == '.':
345 360 return self._map.copy()
346 361 if x not in self._map:
347 362 unknown.append(x)
348 363 else:
349 364 ret[x] = self._map[x]
350 365
351 366 if not unknown:
352 367 return ret
353 368
354 369 b = self._map.keys()
355 370 b.sort()
356 371 blen = len(b)
357 372
358 373 for x in unknown:
359 374 bs = bisect.bisect(b, "%s%s" % (x, '/'))
360 375 while bs < blen:
361 376 s = b[bs]
362 377 if len(s) > len(x) and s.startswith(x):
363 378 ret[s] = self._map[s]
364 379 else:
365 380 break
366 381 bs += 1
367 382 return ret
368 383
369 384 def _supported(self, f, mode, verbose=False):
370 385 if stat.S_ISREG(mode) or stat.S_ISLNK(mode):
371 386 return True
372 387 if verbose:
373 388 kind = 'unknown'
374 389 if stat.S_ISCHR(mode): kind = _('character device')
375 390 elif stat.S_ISBLK(mode): kind = _('block device')
376 391 elif stat.S_ISFIFO(mode): kind = _('fifo')
377 392 elif stat.S_ISSOCK(mode): kind = _('socket')
378 393 elif stat.S_ISDIR(mode): kind = _('directory')
379 394 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
380 395 % (self.pathto(f), kind))
381 396 return False
382 397
383 398 def _dirignore(self, f):
384 399 if self._ignore(f):
385 400 return True
386 401 for c in strutil.findall(f, '/'):
387 402 if self._ignore(f[:c]):
388 403 return True
389 404 return False
390 405
391 406 def walk(self, files=None, match=util.always, badmatch=None):
392 407 # filter out the stat
393 408 for src, f, st in self.statwalk(files, match, badmatch=badmatch):
394 409 yield src, f
395 410
396 411 def statwalk(self, files=None, match=util.always, unknown=True,
397 412 ignored=False, badmatch=None, directories=False):
398 413 '''
399 414 walk recursively through the directory tree, finding all files
400 415 matched by the match function
401 416
402 417 results are yielded in a tuple (src, filename, st), where src
403 418 is one of:
404 419 'f' the file was found in the directory tree
405 420 'd' the file is a directory of the tree
406 421 'm' the file was only in the dirstate and not in the tree
407 422 'b' file was not found and matched badmatch
408 423
409 424 and st is the stat result if the file was found in the directory.
410 425 '''
411 426
412 427 # walk all files by default
413 428 if not files:
414 429 files = ['.']
415 430 dc = self._map.copy()
416 431 else:
417 432 files = util.unique(files)
418 433 dc = self._filter(files)
419 434
420 435 def imatch(file_):
421 436 if file_ not in dc and self._ignore(file_):
422 437 return False
423 438 return match(file_)
424 439
425 440 # TODO: don't walk unknown directories if unknown and ignored are False
426 441 ignore = self._ignore
427 442 dirignore = self._dirignore
428 443 if ignored:
429 444 imatch = match
430 445 ignore = util.never
431 446 dirignore = util.never
432 447
433 448 # self._root may end with a path separator when self._root == '/'
434 449 common_prefix_len = len(self._root)
435 450 if not util.endswithsep(self._root):
436 451 common_prefix_len += 1
437 452
438 453 normpath = util.normpath
439 454 listdir = osutil.listdir
440 455 lstat = os.lstat
441 456 bisect_left = bisect.bisect_left
442 457 isdir = os.path.isdir
443 458 pconvert = util.pconvert
444 459 join = os.path.join
445 460 s_isdir = stat.S_ISDIR
446 461 supported = self._supported
447 462 _join = self._join
448 463 known = {'.hg': 1}
449 464
450 465 # recursion free walker, faster than os.walk.
451 466 def findfiles(s):
452 467 work = [s]
453 468 wadd = work.append
454 469 found = []
455 470 add = found.append
456 471 if directories:
457 472 add((normpath(s[common_prefix_len:]), 'd', lstat(s)))
458 473 while work:
459 474 top = work.pop()
460 475 entries = listdir(top, stat=True)
461 476 # nd is the top of the repository dir tree
462 477 nd = normpath(top[common_prefix_len:])
463 478 if nd == '.':
464 479 nd = ''
465 480 else:
466 481 # do not recurse into a repo contained in this
467 482 # one. use bisect to find .hg directory so speed
468 483 # is good on big directory.
469 484 names = [e[0] for e in entries]
470 485 hg = bisect_left(names, '.hg')
471 486 if hg < len(names) and names[hg] == '.hg':
472 487 if isdir(join(top, '.hg')):
473 488 continue
474 489 for f, kind, st in entries:
475 490 np = pconvert(join(nd, f))
476 491 if np in known:
477 492 continue
478 493 known[np] = 1
479 494 p = join(top, f)
480 495 # don't trip over symlinks
481 496 if kind == stat.S_IFDIR:
482 497 if not ignore(np):
483 498 wadd(p)
484 499 if directories:
485 500 add((np, 'd', st))
486 501 if np in dc and match(np):
487 502 add((np, 'm', st))
488 503 elif imatch(np):
489 504 if supported(np, st.st_mode):
490 505 add((np, 'f', st))
491 506 elif np in dc:
492 507 add((np, 'm', st))
493 508 found.sort()
494 509 return found
495 510
496 511 # step one, find all files that match our criteria
497 512 files.sort()
498 513 for ff in files:
499 514 nf = normpath(ff)
500 515 f = _join(ff)
501 516 try:
502 517 st = lstat(f)
503 518 except OSError, inst:
504 519 found = False
505 520 for fn in dc:
506 521 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
507 522 found = True
508 523 break
509 524 if not found:
510 525 if inst.errno != errno.ENOENT or not badmatch:
511 526 self._ui.warn('%s: %s\n' %
512 527 (self.pathto(ff), inst.strerror))
513 528 elif badmatch and badmatch(ff) and imatch(nf):
514 529 yield 'b', ff, None
515 530 continue
516 531 if s_isdir(st.st_mode):
517 532 if not dirignore(nf):
518 533 for f, src, st in findfiles(f):
519 534 yield src, f, st
520 535 else:
521 536 if nf in known:
522 537 continue
523 538 known[nf] = 1
524 539 if match(nf):
525 540 if supported(ff, st.st_mode, verbose=True):
526 541 yield 'f', nf, st
527 542 elif ff in dc:
528 543 yield 'm', nf, st
529 544
530 545 # step two run through anything left in the dc hash and yield
531 546 # if we haven't already seen it
532 547 ks = dc.keys()
533 548 ks.sort()
534 549 for k in ks:
535 550 if k in known:
536 551 continue
537 552 known[k] = 1
538 553 if imatch(k):
539 554 yield 'm', k, None
540 555
541 556 def status(self, files, match, list_ignored, list_clean, list_unknown=True):
542 557 lookup, modified, added, unknown, ignored = [], [], [], [], []
543 558 removed, deleted, clean = [], [], []
544 559
545 560 files = files or []
546 561 _join = self._join
547 562 lstat = os.lstat
548 563 cmap = self._copymap
549 564 dmap = self._map
550 565 ladd = lookup.append
551 566 madd = modified.append
552 567 aadd = added.append
553 568 uadd = unknown.append
554 569 iadd = ignored.append
555 570 radd = removed.append
556 571 dadd = deleted.append
557 572 cadd = clean.append
558 573
559 574 for src, fn, st in self.statwalk(files, match, unknown=list_unknown,
560 575 ignored=list_ignored):
561 576 if fn in dmap:
562 577 type_, mode, size, time, foo = dmap[fn]
563 578 else:
564 579 if (list_ignored or fn in files) and self._dirignore(fn):
565 580 if list_ignored:
566 581 iadd(fn)
567 582 elif list_unknown:
568 583 uadd(fn)
569 584 continue
570 585 if src == 'm':
571 586 nonexistent = True
572 587 if not st:
573 588 try:
574 589 st = lstat(_join(fn))
575 590 except OSError, inst:
576 591 if inst.errno not in (errno.ENOENT, errno.ENOTDIR):
577 592 raise
578 593 st = None
579 594 # We need to re-check that it is a valid file
580 595 if st and self._supported(fn, st.st_mode):
581 596 nonexistent = False
582 597 # XXX: what to do with file no longer present in the fs
583 598 # who are not removed in the dirstate ?
584 599 if nonexistent and type_ in "nma":
585 600 dadd(fn)
586 601 continue
587 602 # check the common case first
588 603 if type_ == 'n':
589 604 if not st:
590 605 st = lstat(_join(fn))
591 606 if (size >= 0 and
592 607 (size != st.st_size
593 608 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
594 609 or size == -2
595 610 or fn in self._copymap):
596 611 madd(fn)
597 612 elif time != int(st.st_mtime):
598 613 ladd(fn)
599 614 elif list_clean:
600 615 cadd(fn)
601 616 elif type_ == 'm':
602 617 madd(fn)
603 618 elif type_ == 'a':
604 619 aadd(fn)
605 620 elif type_ == 'r':
606 621 radd(fn)
607 622
608 623 return (lookup, modified, added, removed, deleted, unknown, ignored,
609 624 clean)
@@ -1,27 +1,28 b''
1 1 %%% should show a removed and b added
2 2 A b
3 3 R a
4 4 reverting...
5 5 undeleting a
6 6 forgetting b
7 7 %%% should show b unknown and a back to normal
8 8 ? b
9 9 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
10 10 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
11 11 merging a
12 12 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
13 13 (branch merge, don't forget to commit)
14 14 %%% should show foo-b
15 15 foo-b
16 16 %%% should show a removed and b added
17 17 A b
18 18 R a
19 19 %%% revert should fail
20 20 abort: uncommitted merge - please provide a specific revision
21 21 %%% revert should be ok now
22 22 undeleting a
23 23 forgetting b
24 24 %%% should show b unknown and a marked modified (merged)
25 M a
25 26 ? b
26 27 %%% should show foo-b
27 28 foo-b
@@ -1,29 +1,36 b''
1 1 #!/bin/sh
2 2
3 3 hg init repo
4 4 cd repo
5 5
6 6 echo foo > foo
7 7 echo bar > bar
8 8 hg ci -qAm 'add foo bar'
9 9
10 10 echo foo2 >> foo
11 11 echo bleh > bar
12 12 hg ci -m 'change foo bar'
13 13
14 14 hg up -qC 0
15 15 hg mv foo foo1
16 16 echo foo1 > foo1
17 17 hg cat foo >> foo1
18 18 hg ci -m 'mv foo foo1'
19 19
20 20 hg merge
21 21 hg debugstate --nodates
22 22 hg st -q
23 23
24 24 echo '% removing foo1 and bar'
25 25 cp foo1 F
26 26 cp bar B
27 27 hg rm -f foo1 bar
28 28 hg debugstate --nodates
29 29 hg st -qC
30
31 echo '% readding foo1 and bar'
32 cp F foo1
33 cp B bar
34 hg add -v foo1 bar
35 hg debugstate --nodates
36 hg st -qC
@@ -1,15 +1,24 b''
1 1 merging foo1 and foo
2 2 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
3 3 (branch merge, don't forget to commit)
4 4 n 0 -2 bar
5 5 m 644 14 foo1
6 6 copy: foo -> foo1
7 7 M bar
8 8 M foo1
9 9 % removing foo1 and bar
10 10 r 0 -2 bar
11 11 r 0 -1 foo1
12 12 copy: foo -> foo1
13 13 R bar
14 14 R foo1
15 15 foo
16 % readding foo1 and bar
17 adding bar
18 adding foo1
19 n 0 -2 bar
20 m 644 14 foo1
21 copy: foo -> foo1
22 M bar
23 M foo1
24 foo
General Comments 0
You need to be logged in to leave comments. Login now