##// END OF EJS Templates
rename: properly report removed and added file as modified (issue4458)...
Pierre-Yves David -
r23402:2963d5c9 stable
parent child Browse files
Show More
@@ -1,1731 +1,1733 b''
1 1 # context.py - changeset and file context objects for mercurial
2 2 #
3 3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import nullid, nullrev, short, hex, bin
9 9 from i18n import _
10 10 import mdiff, error, util, scmutil, subrepo, patch, encoding, phases
11 11 import match as matchmod
12 12 import os, errno, stat
13 13 import obsolete as obsmod
14 14 import repoview
15 15 import fileset
16 16 import revlog
17 17
18 18 propertycache = util.propertycache
19 19
20 20 class basectx(object):
21 21 """A basectx object represents the common logic for its children:
22 22 changectx: read-only context that is already present in the repo,
23 23 workingctx: a context that represents the working directory and can
24 24 be committed,
25 25 memctx: a context that represents changes in-memory and can also
26 26 be committed."""
27 27 def __new__(cls, repo, changeid='', *args, **kwargs):
28 28 if isinstance(changeid, basectx):
29 29 return changeid
30 30
31 31 o = super(basectx, cls).__new__(cls)
32 32
33 33 o._repo = repo
34 34 o._rev = nullrev
35 35 o._node = nullid
36 36
37 37 return o
38 38
39 39 def __str__(self):
40 40 return short(self.node())
41 41
42 42 def __int__(self):
43 43 return self.rev()
44 44
45 45 def __repr__(self):
46 46 return "<%s %s>" % (type(self).__name__, str(self))
47 47
48 48 def __eq__(self, other):
49 49 try:
50 50 return type(self) == type(other) and self._rev == other._rev
51 51 except AttributeError:
52 52 return False
53 53
54 54 def __ne__(self, other):
55 55 return not (self == other)
56 56
57 57 def __contains__(self, key):
58 58 return key in self._manifest
59 59
60 60 def __getitem__(self, key):
61 61 return self.filectx(key)
62 62
63 63 def __iter__(self):
64 64 for f in sorted(self._manifest):
65 65 yield f
66 66
67 67 def _manifestmatches(self, match, s):
68 68 """generate a new manifest filtered by the match argument
69 69
70 70 This method is for internal use only and mainly exists to provide an
71 71 object oriented way for other contexts to customize the manifest
72 72 generation.
73 73 """
74 74 if match.always():
75 75 return self.manifest().copy()
76 76
77 77 files = match.files()
78 78 if (match.matchfn == match.exact or
79 79 (not match.anypats() and util.all(fn in self for fn in files))):
80 80 return self.manifest().intersectfiles(files)
81 81
82 82 mf = self.manifest().copy()
83 83 for fn in mf.keys():
84 84 if not match(fn):
85 85 del mf[fn]
86 86 return mf
87 87
88 88 def _matchstatus(self, other, s, match, listignored, listclean,
89 89 listunknown):
90 90 """return match.always if match is none
91 91
92 92 This internal method provides a way for child objects to override the
93 93 match operator.
94 94 """
95 95 return match or matchmod.always(self._repo.root, self._repo.getcwd())
96 96
97 97 def _prestatus(self, other, s, match, listignored, listclean, listunknown):
98 98 """provide a hook to allow child objects to preprocess status results
99 99
100 100 For example, this allows other contexts, such as workingctx, to query
101 101 the dirstate before comparing the manifests.
102 102 """
103 103 # load earliest manifest first for caching reasons
104 104 if self.rev() < other.rev():
105 105 self.manifest()
106 106 return s
107 107
108 108 def _poststatus(self, other, s, match, listignored, listclean, listunknown):
109 109 """provide a hook to allow child objects to postprocess status results
110 110
111 111 For example, this allows other contexts, such as workingctx, to filter
112 112 suspect symlinks in the case of FAT32 and NTFS filesytems.
113 113 """
114 114 return s
115 115
116 116 def _buildstatus(self, other, s, match, listignored, listclean,
117 117 listunknown):
118 118 """build a status with respect to another context"""
119 119 mf1 = other._manifestmatches(match, s)
120 120 mf2 = self._manifestmatches(match, s)
121 121
122 122 modified, added, clean = [], [], []
123 123 deleted, unknown, ignored = s[3], s[4], s[5]
124 124 deletedset = set(deleted)
125 125 withflags = mf1.withflags() | mf2.withflags()
126 126 for fn, mf2node in mf2.iteritems():
127 127 if fn in mf1:
128 128 if (fn not in deletedset and
129 129 ((fn in withflags and mf1.flags(fn) != mf2.flags(fn)) or
130 130 (mf1[fn] != mf2node and
131 131 (mf2node or self[fn].cmp(other[fn]))))):
132 132 modified.append(fn)
133 133 elif listclean:
134 134 clean.append(fn)
135 135 del mf1[fn]
136 136 elif fn not in deletedset:
137 137 added.append(fn)
138 138 removed = mf1.keys()
139 139 if removed:
140 140 # need to filter files if they are already reported as removed
141 141 unknown = [fn for fn in unknown if fn not in mf1]
142 142 ignored = [fn for fn in ignored if fn not in mf1]
143 143
144 144 return [modified, added, removed, deleted, unknown, ignored, clean]
145 145
146 146 @propertycache
147 147 def substate(self):
148 148 return subrepo.state(self, self._repo.ui)
149 149
150 150 def subrev(self, subpath):
151 151 return self.substate[subpath][1]
152 152
153 153 def rev(self):
154 154 return self._rev
155 155 def node(self):
156 156 return self._node
157 157 def hex(self):
158 158 return hex(self.node())
159 159 def manifest(self):
160 160 return self._manifest
161 161 def phasestr(self):
162 162 return phases.phasenames[self.phase()]
163 163 def mutable(self):
164 164 return self.phase() > phases.public
165 165
166 166 def getfileset(self, expr):
167 167 return fileset.getfileset(self, expr)
168 168
169 169 def obsolete(self):
170 170 """True if the changeset is obsolete"""
171 171 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
172 172
173 173 def extinct(self):
174 174 """True if the changeset is extinct"""
175 175 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
176 176
177 177 def unstable(self):
178 178 """True if the changeset is not obsolete but it's ancestor are"""
179 179 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
180 180
181 181 def bumped(self):
182 182 """True if the changeset try to be a successor of a public changeset
183 183
184 184 Only non-public and non-obsolete changesets may be bumped.
185 185 """
186 186 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
187 187
188 188 def divergent(self):
189 189 """Is a successors of a changeset with multiple possible successors set
190 190
191 191 Only non-public and non-obsolete changesets may be divergent.
192 192 """
193 193 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
194 194
195 195 def troubled(self):
196 196 """True if the changeset is either unstable, bumped or divergent"""
197 197 return self.unstable() or self.bumped() or self.divergent()
198 198
199 199 def troubles(self):
200 200 """return the list of troubles affecting this changesets.
201 201
202 202 Troubles are returned as strings. possible values are:
203 203 - unstable,
204 204 - bumped,
205 205 - divergent.
206 206 """
207 207 troubles = []
208 208 if self.unstable():
209 209 troubles.append('unstable')
210 210 if self.bumped():
211 211 troubles.append('bumped')
212 212 if self.divergent():
213 213 troubles.append('divergent')
214 214 return troubles
215 215
216 216 def parents(self):
217 217 """return contexts for each parent changeset"""
218 218 return self._parents
219 219
220 220 def p1(self):
221 221 return self._parents[0]
222 222
223 223 def p2(self):
224 224 if len(self._parents) == 2:
225 225 return self._parents[1]
226 226 return changectx(self._repo, -1)
227 227
228 228 def _fileinfo(self, path):
229 229 if '_manifest' in self.__dict__:
230 230 try:
231 231 return self._manifest[path], self._manifest.flags(path)
232 232 except KeyError:
233 233 raise error.ManifestLookupError(self._node, path,
234 234 _('not found in manifest'))
235 235 if '_manifestdelta' in self.__dict__ or path in self.files():
236 236 if path in self._manifestdelta:
237 237 return (self._manifestdelta[path],
238 238 self._manifestdelta.flags(path))
239 239 node, flag = self._repo.manifest.find(self._changeset[0], path)
240 240 if not node:
241 241 raise error.ManifestLookupError(self._node, path,
242 242 _('not found in manifest'))
243 243
244 244 return node, flag
245 245
246 246 def filenode(self, path):
247 247 return self._fileinfo(path)[0]
248 248
249 249 def flags(self, path):
250 250 try:
251 251 return self._fileinfo(path)[1]
252 252 except error.LookupError:
253 253 return ''
254 254
255 255 def sub(self, path):
256 256 return subrepo.subrepo(self, path)
257 257
258 258 def match(self, pats=[], include=None, exclude=None, default='glob'):
259 259 r = self._repo
260 260 return matchmod.match(r.root, r.getcwd(), pats,
261 261 include, exclude, default,
262 262 auditor=r.auditor, ctx=self)
263 263
264 264 def diff(self, ctx2=None, match=None, **opts):
265 265 """Returns a diff generator for the given contexts and matcher"""
266 266 if ctx2 is None:
267 267 ctx2 = self.p1()
268 268 if ctx2 is not None:
269 269 ctx2 = self._repo[ctx2]
270 270 diffopts = patch.diffopts(self._repo.ui, opts)
271 271 return patch.diff(self._repo, ctx2, self, match=match, opts=diffopts)
272 272
273 273 @propertycache
274 274 def _dirs(self):
275 275 return scmutil.dirs(self._manifest)
276 276
277 277 def dirs(self):
278 278 return self._dirs
279 279
280 280 def dirty(self, missing=False, merge=True, branch=True):
281 281 return False
282 282
283 283 def status(self, other=None, match=None, listignored=False,
284 284 listclean=False, listunknown=False, listsubrepos=False):
285 285 """return status of files between two nodes or node and working
286 286 directory.
287 287
288 288 If other is None, compare this node with working directory.
289 289
290 290 returns (modified, added, removed, deleted, unknown, ignored, clean)
291 291 """
292 292
293 293 ctx1 = self
294 294 ctx2 = self._repo[other]
295 295
296 296 # This next code block is, admittedly, fragile logic that tests for
297 297 # reversing the contexts and wouldn't need to exist if it weren't for
298 298 # the fast (and common) code path of comparing the working directory
299 299 # with its first parent.
300 300 #
301 301 # What we're aiming for here is the ability to call:
302 302 #
303 303 # workingctx.status(parentctx)
304 304 #
305 305 # If we always built the manifest for each context and compared those,
306 306 # then we'd be done. But the special case of the above call means we
307 307 # just copy the manifest of the parent.
308 308 reversed = False
309 309 if (not isinstance(ctx1, changectx)
310 310 and isinstance(ctx2, changectx)):
311 311 reversed = True
312 312 ctx1, ctx2 = ctx2, ctx1
313 313
314 314 r = [[], [], [], [], [], [], []]
315 315 match = ctx2._matchstatus(ctx1, r, match, listignored, listclean,
316 316 listunknown)
317 317 r = ctx2._prestatus(ctx1, r, match, listignored, listclean, listunknown)
318 318 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
319 319 listunknown)
320 320 r = ctx2._poststatus(ctx1, r, match, listignored, listclean,
321 321 listunknown)
322 322
323 323 if reversed:
324 324 # reverse added and removed
325 325 r[1], r[2] = r[2], r[1]
326 326
327 327 if listsubrepos:
328 328 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
329 329 rev2 = ctx2.subrev(subpath)
330 330 try:
331 331 submatch = matchmod.narrowmatcher(subpath, match)
332 332 s = sub.status(rev2, match=submatch, ignored=listignored,
333 333 clean=listclean, unknown=listunknown,
334 334 listsubrepos=True)
335 335 for rfiles, sfiles in zip(r, s):
336 336 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
337 337 except error.LookupError:
338 338 self._repo.ui.status(_("skipping missing "
339 339 "subrepository: %s\n") % subpath)
340 340
341 341 for l in r:
342 342 l.sort()
343 343
344 344 # we return a tuple to signify that this list isn't changing
345 345 return scmutil.status(*r)
346 346
347 347
348 348 def makememctx(repo, parents, text, user, date, branch, files, store,
349 349 editor=None):
350 350 def getfilectx(repo, memctx, path):
351 351 data, mode, copied = store.getfile(path)
352 352 if data is None:
353 353 return None
354 354 islink, isexec = mode
355 355 return memfilectx(repo, path, data, islink=islink, isexec=isexec,
356 356 copied=copied, memctx=memctx)
357 357 extra = {}
358 358 if branch:
359 359 extra['branch'] = encoding.fromlocal(branch)
360 360 ctx = memctx(repo, parents, text, files, getfilectx, user,
361 361 date, extra, editor)
362 362 return ctx
363 363
364 364 class changectx(basectx):
365 365 """A changecontext object makes access to data related to a particular
366 366 changeset convenient. It represents a read-only context already present in
367 367 the repo."""
368 368 def __init__(self, repo, changeid=''):
369 369 """changeid is a revision number, node, or tag"""
370 370
371 371 # since basectx.__new__ already took care of copying the object, we
372 372 # don't need to do anything in __init__, so we just exit here
373 373 if isinstance(changeid, basectx):
374 374 return
375 375
376 376 if changeid == '':
377 377 changeid = '.'
378 378 self._repo = repo
379 379
380 380 try:
381 381 if isinstance(changeid, int):
382 382 self._node = repo.changelog.node(changeid)
383 383 self._rev = changeid
384 384 return
385 385 if isinstance(changeid, long):
386 386 changeid = str(changeid)
387 387 if changeid == '.':
388 388 self._node = repo.dirstate.p1()
389 389 self._rev = repo.changelog.rev(self._node)
390 390 return
391 391 if changeid == 'null':
392 392 self._node = nullid
393 393 self._rev = nullrev
394 394 return
395 395 if changeid == 'tip':
396 396 self._node = repo.changelog.tip()
397 397 self._rev = repo.changelog.rev(self._node)
398 398 return
399 399 if len(changeid) == 20:
400 400 try:
401 401 self._node = changeid
402 402 self._rev = repo.changelog.rev(changeid)
403 403 return
404 404 except error.FilteredRepoLookupError:
405 405 raise
406 406 except LookupError:
407 407 pass
408 408
409 409 try:
410 410 r = int(changeid)
411 411 if str(r) != changeid:
412 412 raise ValueError
413 413 l = len(repo.changelog)
414 414 if r < 0:
415 415 r += l
416 416 if r < 0 or r >= l:
417 417 raise ValueError
418 418 self._rev = r
419 419 self._node = repo.changelog.node(r)
420 420 return
421 421 except error.FilteredIndexError:
422 422 raise
423 423 except (ValueError, OverflowError, IndexError):
424 424 pass
425 425
426 426 if len(changeid) == 40:
427 427 try:
428 428 self._node = bin(changeid)
429 429 self._rev = repo.changelog.rev(self._node)
430 430 return
431 431 except error.FilteredLookupError:
432 432 raise
433 433 except (TypeError, LookupError):
434 434 pass
435 435
436 436 if changeid in repo._bookmarks:
437 437 self._node = repo._bookmarks[changeid]
438 438 self._rev = repo.changelog.rev(self._node)
439 439 return
440 440 if changeid in repo._tagscache.tags:
441 441 self._node = repo._tagscache.tags[changeid]
442 442 self._rev = repo.changelog.rev(self._node)
443 443 return
444 444 try:
445 445 self._node = repo.branchtip(changeid)
446 446 self._rev = repo.changelog.rev(self._node)
447 447 return
448 448 except error.FilteredRepoLookupError:
449 449 raise
450 450 except error.RepoLookupError:
451 451 pass
452 452
453 453 self._node = repo.unfiltered().changelog._partialmatch(changeid)
454 454 if self._node is not None:
455 455 self._rev = repo.changelog.rev(self._node)
456 456 return
457 457
458 458 # lookup failed
459 459 # check if it might have come from damaged dirstate
460 460 #
461 461 # XXX we could avoid the unfiltered if we had a recognizable
462 462 # exception for filtered changeset access
463 463 if changeid in repo.unfiltered().dirstate.parents():
464 464 msg = _("working directory has unknown parent '%s'!")
465 465 raise error.Abort(msg % short(changeid))
466 466 try:
467 467 if len(changeid) == 20:
468 468 changeid = hex(changeid)
469 469 except TypeError:
470 470 pass
471 471 except (error.FilteredIndexError, error.FilteredLookupError,
472 472 error.FilteredRepoLookupError):
473 473 if repo.filtername == 'visible':
474 474 msg = _("hidden revision '%s'") % changeid
475 475 hint = _('use --hidden to access hidden revisions')
476 476 raise error.FilteredRepoLookupError(msg, hint=hint)
477 477 msg = _("filtered revision '%s' (not in '%s' subset)")
478 478 msg %= (changeid, repo.filtername)
479 479 raise error.FilteredRepoLookupError(msg)
480 480 except IndexError:
481 481 pass
482 482 raise error.RepoLookupError(
483 483 _("unknown revision '%s'") % changeid)
484 484
485 485 def __hash__(self):
486 486 try:
487 487 return hash(self._rev)
488 488 except AttributeError:
489 489 return id(self)
490 490
491 491 def __nonzero__(self):
492 492 return self._rev != nullrev
493 493
494 494 @propertycache
495 495 def _changeset(self):
496 496 return self._repo.changelog.read(self.rev())
497 497
498 498 @propertycache
499 499 def _manifest(self):
500 500 return self._repo.manifest.read(self._changeset[0])
501 501
502 502 @propertycache
503 503 def _manifestdelta(self):
504 504 return self._repo.manifest.readdelta(self._changeset[0])
505 505
506 506 @propertycache
507 507 def _parents(self):
508 508 p = self._repo.changelog.parentrevs(self._rev)
509 509 if p[1] == nullrev:
510 510 p = p[:-1]
511 511 return [changectx(self._repo, x) for x in p]
512 512
513 513 def changeset(self):
514 514 return self._changeset
515 515 def manifestnode(self):
516 516 return self._changeset[0]
517 517
518 518 def user(self):
519 519 return self._changeset[1]
520 520 def date(self):
521 521 return self._changeset[2]
522 522 def files(self):
523 523 return self._changeset[3]
524 524 def description(self):
525 525 return self._changeset[4]
526 526 def branch(self):
527 527 return encoding.tolocal(self._changeset[5].get("branch"))
528 528 def closesbranch(self):
529 529 return 'close' in self._changeset[5]
530 530 def extra(self):
531 531 return self._changeset[5]
532 532 def tags(self):
533 533 return self._repo.nodetags(self._node)
534 534 def bookmarks(self):
535 535 return self._repo.nodebookmarks(self._node)
536 536 def phase(self):
537 537 return self._repo._phasecache.phase(self._repo, self._rev)
538 538 def hidden(self):
539 539 return self._rev in repoview.filterrevs(self._repo, 'visible')
540 540
541 541 def children(self):
542 542 """return contexts for each child changeset"""
543 543 c = self._repo.changelog.children(self._node)
544 544 return [changectx(self._repo, x) for x in c]
545 545
546 546 def ancestors(self):
547 547 for a in self._repo.changelog.ancestors([self._rev]):
548 548 yield changectx(self._repo, a)
549 549
550 550 def descendants(self):
551 551 for d in self._repo.changelog.descendants([self._rev]):
552 552 yield changectx(self._repo, d)
553 553
554 554 def filectx(self, path, fileid=None, filelog=None):
555 555 """get a file context from this changeset"""
556 556 if fileid is None:
557 557 fileid = self.filenode(path)
558 558 return filectx(self._repo, path, fileid=fileid,
559 559 changectx=self, filelog=filelog)
560 560
561 561 def ancestor(self, c2, warn=False):
562 562 """return the "best" ancestor context of self and c2
563 563
564 564 If there are multiple candidates, it will show a message and check
565 565 merge.preferancestor configuration before falling back to the
566 566 revlog ancestor."""
567 567 # deal with workingctxs
568 568 n2 = c2._node
569 569 if n2 is None:
570 570 n2 = c2._parents[0]._node
571 571 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
572 572 if not cahs:
573 573 anc = nullid
574 574 elif len(cahs) == 1:
575 575 anc = cahs[0]
576 576 else:
577 577 for r in self._repo.ui.configlist('merge', 'preferancestor'):
578 578 try:
579 579 ctx = changectx(self._repo, r)
580 580 except error.RepoLookupError:
581 581 continue
582 582 anc = ctx.node()
583 583 if anc in cahs:
584 584 break
585 585 else:
586 586 anc = self._repo.changelog.ancestor(self._node, n2)
587 587 if warn:
588 588 self._repo.ui.status(
589 589 (_("note: using %s as ancestor of %s and %s\n") %
590 590 (short(anc), short(self._node), short(n2))) +
591 591 ''.join(_(" alternatively, use --config "
592 592 "merge.preferancestor=%s\n") %
593 593 short(n) for n in sorted(cahs) if n != anc))
594 594 return changectx(self._repo, anc)
595 595
596 596 def descendant(self, other):
597 597 """True if other is descendant of this changeset"""
598 598 return self._repo.changelog.descendant(self._rev, other._rev)
599 599
600 600 def walk(self, match):
601 601 fset = set(match.files())
602 602 # for dirstate.walk, files=['.'] means "walk the whole tree".
603 603 # follow that here, too
604 604 fset.discard('.')
605 605
606 606 # avoid the entire walk if we're only looking for specific files
607 607 if fset and not match.anypats():
608 608 if util.all([fn in self for fn in fset]):
609 609 for fn in sorted(fset):
610 610 if match(fn):
611 611 yield fn
612 612 raise StopIteration
613 613
614 614 for fn in self:
615 615 if fn in fset:
616 616 # specified pattern is the exact name
617 617 fset.remove(fn)
618 618 if match(fn):
619 619 yield fn
620 620 for fn in sorted(fset):
621 621 if fn in self._dirs:
622 622 # specified pattern is a directory
623 623 continue
624 624 match.bad(fn, _('no such file in rev %s') % self)
625 625
626 626 def matches(self, match):
627 627 return self.walk(match)
628 628
629 629 class basefilectx(object):
630 630 """A filecontext object represents the common logic for its children:
631 631 filectx: read-only access to a filerevision that is already present
632 632 in the repo,
633 633 workingfilectx: a filecontext that represents files from the working
634 634 directory,
635 635 memfilectx: a filecontext that represents files in-memory."""
636 636 def __new__(cls, repo, path, *args, **kwargs):
637 637 return super(basefilectx, cls).__new__(cls)
638 638
639 639 @propertycache
640 640 def _filelog(self):
641 641 return self._repo.file(self._path)
642 642
643 643 @propertycache
644 644 def _changeid(self):
645 645 if '_changeid' in self.__dict__:
646 646 return self._changeid
647 647 elif '_changectx' in self.__dict__:
648 648 return self._changectx.rev()
649 649 else:
650 650 return self._filelog.linkrev(self._filerev)
651 651
652 652 @propertycache
653 653 def _filenode(self):
654 654 if '_fileid' in self.__dict__:
655 655 return self._filelog.lookup(self._fileid)
656 656 else:
657 657 return self._changectx.filenode(self._path)
658 658
659 659 @propertycache
660 660 def _filerev(self):
661 661 return self._filelog.rev(self._filenode)
662 662
663 663 @propertycache
664 664 def _repopath(self):
665 665 return self._path
666 666
667 667 def __nonzero__(self):
668 668 try:
669 669 self._filenode
670 670 return True
671 671 except error.LookupError:
672 672 # file is missing
673 673 return False
674 674
675 675 def __str__(self):
676 676 return "%s@%s" % (self.path(), self._changectx)
677 677
678 678 def __repr__(self):
679 679 return "<%s %s>" % (type(self).__name__, str(self))
680 680
681 681 def __hash__(self):
682 682 try:
683 683 return hash((self._path, self._filenode))
684 684 except AttributeError:
685 685 return id(self)
686 686
687 687 def __eq__(self, other):
688 688 try:
689 689 return (type(self) == type(other) and self._path == other._path
690 690 and self._filenode == other._filenode)
691 691 except AttributeError:
692 692 return False
693 693
694 694 def __ne__(self, other):
695 695 return not (self == other)
696 696
697 697 def filerev(self):
698 698 return self._filerev
699 699 def filenode(self):
700 700 return self._filenode
701 701 def flags(self):
702 702 return self._changectx.flags(self._path)
703 703 def filelog(self):
704 704 return self._filelog
705 705 def rev(self):
706 706 return self._changeid
707 707 def linkrev(self):
708 708 return self._filelog.linkrev(self._filerev)
709 709 def node(self):
710 710 return self._changectx.node()
711 711 def hex(self):
712 712 return self._changectx.hex()
713 713 def user(self):
714 714 return self._changectx.user()
715 715 def date(self):
716 716 return self._changectx.date()
717 717 def files(self):
718 718 return self._changectx.files()
719 719 def description(self):
720 720 return self._changectx.description()
721 721 def branch(self):
722 722 return self._changectx.branch()
723 723 def extra(self):
724 724 return self._changectx.extra()
725 725 def phase(self):
726 726 return self._changectx.phase()
727 727 def phasestr(self):
728 728 return self._changectx.phasestr()
729 729 def manifest(self):
730 730 return self._changectx.manifest()
731 731 def changectx(self):
732 732 return self._changectx
733 733
734 734 def path(self):
735 735 return self._path
736 736
737 737 def isbinary(self):
738 738 try:
739 739 return util.binary(self.data())
740 740 except IOError:
741 741 return False
742 742 def isexec(self):
743 743 return 'x' in self.flags()
744 744 def islink(self):
745 745 return 'l' in self.flags()
746 746
747 747 def cmp(self, fctx):
748 748 """compare with other file context
749 749
750 750 returns True if different than fctx.
751 751 """
752 752 if (fctx._filerev is None
753 753 and (self._repo._encodefilterpats
754 754 # if file data starts with '\1\n', empty metadata block is
755 755 # prepended, which adds 4 bytes to filelog.size().
756 756 or self.size() - 4 == fctx.size())
757 757 or self.size() == fctx.size()):
758 758 return self._filelog.cmp(self._filenode, fctx.data())
759 759
760 760 return True
761 761
762 762 def parents(self):
763 763 _path = self._path
764 764 fl = self._filelog
765 765 pl = [(_path, n, fl) for n in self._filelog.parents(self._filenode)]
766 766
767 767 r = self._filelog.renamed(self._filenode)
768 768 if r:
769 769 pl[0] = (r[0], r[1], None)
770 770
771 771 return [filectx(self._repo, p, fileid=n, filelog=l)
772 772 for p, n, l in pl if n != nullid]
773 773
774 774 def p1(self):
775 775 return self.parents()[0]
776 776
777 777 def p2(self):
778 778 p = self.parents()
779 779 if len(p) == 2:
780 780 return p[1]
781 781 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
782 782
783 783 def annotate(self, follow=False, linenumber=None, diffopts=None):
784 784 '''returns a list of tuples of (ctx, line) for each line
785 785 in the file, where ctx is the filectx of the node where
786 786 that line was last changed.
787 787 This returns tuples of ((ctx, linenumber), line) for each line,
788 788 if "linenumber" parameter is NOT "None".
789 789 In such tuples, linenumber means one at the first appearance
790 790 in the managed file.
791 791 To reduce annotation cost,
792 792 this returns fixed value(False is used) as linenumber,
793 793 if "linenumber" parameter is "False".'''
794 794
795 795 if linenumber is None:
796 796 def decorate(text, rev):
797 797 return ([rev] * len(text.splitlines()), text)
798 798 elif linenumber:
799 799 def decorate(text, rev):
800 800 size = len(text.splitlines())
801 801 return ([(rev, i) for i in xrange(1, size + 1)], text)
802 802 else:
803 803 def decorate(text, rev):
804 804 return ([(rev, False)] * len(text.splitlines()), text)
805 805
806 806 def pair(parent, child):
807 807 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
808 808 refine=True)
809 809 for (a1, a2, b1, b2), t in blocks:
810 810 # Changed blocks ('!') or blocks made only of blank lines ('~')
811 811 # belong to the child.
812 812 if t == '=':
813 813 child[0][b1:b2] = parent[0][a1:a2]
814 814 return child
815 815
816 816 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
817 817
818 818 def parents(f):
819 819 pl = f.parents()
820 820
821 821 # Don't return renamed parents if we aren't following.
822 822 if not follow:
823 823 pl = [p for p in pl if p.path() == f.path()]
824 824
825 825 # renamed filectx won't have a filelog yet, so set it
826 826 # from the cache to save time
827 827 for p in pl:
828 828 if not '_filelog' in p.__dict__:
829 829 p._filelog = getlog(p.path())
830 830
831 831 return pl
832 832
833 833 # use linkrev to find the first changeset where self appeared
834 834 if self.rev() != self.linkrev():
835 835 base = self.filectx(self.filenode())
836 836 else:
837 837 base = self
838 838
839 839 # This algorithm would prefer to be recursive, but Python is a
840 840 # bit recursion-hostile. Instead we do an iterative
841 841 # depth-first search.
842 842
843 843 visit = [base]
844 844 hist = {}
845 845 pcache = {}
846 846 needed = {base: 1}
847 847 while visit:
848 848 f = visit[-1]
849 849 pcached = f in pcache
850 850 if not pcached:
851 851 pcache[f] = parents(f)
852 852
853 853 ready = True
854 854 pl = pcache[f]
855 855 for p in pl:
856 856 if p not in hist:
857 857 ready = False
858 858 visit.append(p)
859 859 if not pcached:
860 860 needed[p] = needed.get(p, 0) + 1
861 861 if ready:
862 862 visit.pop()
863 863 reusable = f in hist
864 864 if reusable:
865 865 curr = hist[f]
866 866 else:
867 867 curr = decorate(f.data(), f)
868 868 for p in pl:
869 869 if not reusable:
870 870 curr = pair(hist[p], curr)
871 871 if needed[p] == 1:
872 872 del hist[p]
873 873 del needed[p]
874 874 else:
875 875 needed[p] -= 1
876 876
877 877 hist[f] = curr
878 878 pcache[f] = []
879 879
880 880 return zip(hist[base][0], hist[base][1].splitlines(True))
881 881
882 882 def ancestors(self, followfirst=False):
883 883 visit = {}
884 884 c = self
885 885 cut = followfirst and 1 or None
886 886 while True:
887 887 for parent in c.parents()[:cut]:
888 888 visit[(parent.rev(), parent.node())] = parent
889 889 if not visit:
890 890 break
891 891 c = visit.pop(max(visit))
892 892 yield c
893 893
894 894 class filectx(basefilectx):
895 895 """A filecontext object makes access to data related to a particular
896 896 filerevision convenient."""
897 897 def __init__(self, repo, path, changeid=None, fileid=None,
898 898 filelog=None, changectx=None):
899 899 """changeid can be a changeset revision, node, or tag.
900 900 fileid can be a file revision or node."""
901 901 self._repo = repo
902 902 self._path = path
903 903
904 904 assert (changeid is not None
905 905 or fileid is not None
906 906 or changectx is not None), \
907 907 ("bad args: changeid=%r, fileid=%r, changectx=%r"
908 908 % (changeid, fileid, changectx))
909 909
910 910 if filelog is not None:
911 911 self._filelog = filelog
912 912
913 913 if changeid is not None:
914 914 self._changeid = changeid
915 915 if changectx is not None:
916 916 self._changectx = changectx
917 917 if fileid is not None:
918 918 self._fileid = fileid
919 919
920 920 @propertycache
921 921 def _changectx(self):
922 922 try:
923 923 return changectx(self._repo, self._changeid)
924 924 except error.RepoLookupError:
925 925 # Linkrev may point to any revision in the repository. When the
926 926 # repository is filtered this may lead to `filectx` trying to build
927 927 # `changectx` for filtered revision. In such case we fallback to
928 928 # creating `changectx` on the unfiltered version of the reposition.
929 929 # This fallback should not be an issue because `changectx` from
930 930 # `filectx` are not used in complex operations that care about
931 931 # filtering.
932 932 #
933 933 # This fallback is a cheap and dirty fix that prevent several
934 934 # crashes. It does not ensure the behavior is correct. However the
935 935 # behavior was not correct before filtering either and "incorrect
936 936 # behavior" is seen as better as "crash"
937 937 #
938 938 # Linkrevs have several serious troubles with filtering that are
939 939 # complicated to solve. Proper handling of the issue here should be
940 940 # considered when solving linkrev issue are on the table.
941 941 return changectx(self._repo.unfiltered(), self._changeid)
942 942
943 943 def filectx(self, fileid):
944 944 '''opens an arbitrary revision of the file without
945 945 opening a new filelog'''
946 946 return filectx(self._repo, self._path, fileid=fileid,
947 947 filelog=self._filelog)
948 948
949 949 def data(self):
950 950 try:
951 951 return self._filelog.read(self._filenode)
952 952 except error.CensoredNodeError:
953 953 if self._repo.ui.config("censor", "policy", "abort") == "ignore":
954 954 return ""
955 955 raise util.Abort(_("censored node: %s") % short(self._filenode),
956 956 hint=_("set censor.policy to ignore errors"))
957 957
958 958 def size(self):
959 959 return self._filelog.size(self._filerev)
960 960
961 961 def renamed(self):
962 962 """check if file was actually renamed in this changeset revision
963 963
964 964 If rename logged in file revision, we report copy for changeset only
965 965 if file revisions linkrev points back to the changeset in question
966 966 or both changeset parents contain different file revisions.
967 967 """
968 968
969 969 renamed = self._filelog.renamed(self._filenode)
970 970 if not renamed:
971 971 return renamed
972 972
973 973 if self.rev() == self.linkrev():
974 974 return renamed
975 975
976 976 name = self.path()
977 977 fnode = self._filenode
978 978 for p in self._changectx.parents():
979 979 try:
980 980 if fnode == p.filenode(name):
981 981 return None
982 982 except error.LookupError:
983 983 pass
984 984 return renamed
985 985
986 986 def children(self):
987 987 # hard for renames
988 988 c = self._filelog.children(self._filenode)
989 989 return [filectx(self._repo, self._path, fileid=x,
990 990 filelog=self._filelog) for x in c]
991 991
992 992 class committablectx(basectx):
993 993 """A committablectx object provides common functionality for a context that
994 994 wants the ability to commit, e.g. workingctx or memctx."""
995 995 def __init__(self, repo, text="", user=None, date=None, extra=None,
996 996 changes=None):
997 997 self._repo = repo
998 998 self._rev = None
999 999 self._node = None
1000 1000 self._text = text
1001 1001 if date:
1002 1002 self._date = util.parsedate(date)
1003 1003 if user:
1004 1004 self._user = user
1005 1005 if changes:
1006 1006 self._status = changes
1007 1007
1008 1008 self._extra = {}
1009 1009 if extra:
1010 1010 self._extra = extra.copy()
1011 1011 if 'branch' not in self._extra:
1012 1012 try:
1013 1013 branch = encoding.fromlocal(self._repo.dirstate.branch())
1014 1014 except UnicodeDecodeError:
1015 1015 raise util.Abort(_('branch name not in UTF-8!'))
1016 1016 self._extra['branch'] = branch
1017 1017 if self._extra['branch'] == '':
1018 1018 self._extra['branch'] = 'default'
1019 1019
1020 1020 def __str__(self):
1021 1021 return str(self._parents[0]) + "+"
1022 1022
1023 1023 def __nonzero__(self):
1024 1024 return True
1025 1025
1026 1026 def _buildflagfunc(self):
1027 1027 # Create a fallback function for getting file flags when the
1028 1028 # filesystem doesn't support them
1029 1029
1030 1030 copiesget = self._repo.dirstate.copies().get
1031 1031
1032 1032 if len(self._parents) < 2:
1033 1033 # when we have one parent, it's easy: copy from parent
1034 1034 man = self._parents[0].manifest()
1035 1035 def func(f):
1036 1036 f = copiesget(f, f)
1037 1037 return man.flags(f)
1038 1038 else:
1039 1039 # merges are tricky: we try to reconstruct the unstored
1040 1040 # result from the merge (issue1802)
1041 1041 p1, p2 = self._parents
1042 1042 pa = p1.ancestor(p2)
1043 1043 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1044 1044
1045 1045 def func(f):
1046 1046 f = copiesget(f, f) # may be wrong for merges with copies
1047 1047 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1048 1048 if fl1 == fl2:
1049 1049 return fl1
1050 1050 if fl1 == fla:
1051 1051 return fl2
1052 1052 if fl2 == fla:
1053 1053 return fl1
1054 1054 return '' # punt for conflicts
1055 1055
1056 1056 return func
1057 1057
1058 1058 @propertycache
1059 1059 def _flagfunc(self):
1060 1060 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1061 1061
1062 1062 @propertycache
1063 1063 def _manifest(self):
1064 1064 """generate a manifest corresponding to the values in self._status"""
1065 1065
1066 1066 man1 = self._parents[0].manifest()
1067 1067 man = man1.copy()
1068 1068 if len(self._parents) > 1:
1069 1069 man2 = self.p2().manifest()
1070 1070 def getman(f):
1071 1071 if f in man1:
1072 1072 return man1
1073 1073 return man2
1074 1074 else:
1075 1075 getman = lambda f: man1
1076 1076
1077 1077 copied = self._repo.dirstate.copies()
1078 1078 ff = self._flagfunc
1079 1079 for i, l in (("a", self._status.added), ("m", self._status.modified)):
1080 1080 for f in l:
1081 1081 orig = copied.get(f, f)
1082 1082 man[f] = getman(orig).get(orig, nullid) + i
1083 1083 try:
1084 1084 man.setflag(f, ff(f))
1085 1085 except OSError:
1086 1086 pass
1087 1087
1088 1088 for f in self._status.deleted + self._status.removed:
1089 1089 if f in man:
1090 1090 del man[f]
1091 1091
1092 1092 return man
1093 1093
1094 1094 @propertycache
1095 1095 def _status(self):
1096 1096 return self._repo.status()
1097 1097
1098 1098 @propertycache
1099 1099 def _user(self):
1100 1100 return self._repo.ui.username()
1101 1101
1102 1102 @propertycache
1103 1103 def _date(self):
1104 1104 return util.makedate()
1105 1105
1106 1106 def subrev(self, subpath):
1107 1107 return None
1108 1108
1109 1109 def user(self):
1110 1110 return self._user or self._repo.ui.username()
1111 1111 def date(self):
1112 1112 return self._date
1113 1113 def description(self):
1114 1114 return self._text
1115 1115 def files(self):
1116 1116 return sorted(self._status.modified + self._status.added +
1117 1117 self._status.removed)
1118 1118
1119 1119 def modified(self):
1120 1120 return self._status.modified
1121 1121 def added(self):
1122 1122 return self._status.added
1123 1123 def removed(self):
1124 1124 return self._status.removed
1125 1125 def deleted(self):
1126 1126 return self._status.deleted
1127 1127 def unknown(self):
1128 1128 return self._status.unknown
1129 1129 def ignored(self):
1130 1130 return self._status.ignored
1131 1131 def clean(self):
1132 1132 return self._status.clean
1133 1133 def branch(self):
1134 1134 return encoding.tolocal(self._extra['branch'])
1135 1135 def closesbranch(self):
1136 1136 return 'close' in self._extra
1137 1137 def extra(self):
1138 1138 return self._extra
1139 1139
1140 1140 def tags(self):
1141 1141 t = []
1142 1142 for p in self.parents():
1143 1143 t.extend(p.tags())
1144 1144 return t
1145 1145
1146 1146 def bookmarks(self):
1147 1147 b = []
1148 1148 for p in self.parents():
1149 1149 b.extend(p.bookmarks())
1150 1150 return b
1151 1151
1152 1152 def phase(self):
1153 1153 phase = phases.draft # default phase to draft
1154 1154 for p in self.parents():
1155 1155 phase = max(phase, p.phase())
1156 1156 return phase
1157 1157
1158 1158 def hidden(self):
1159 1159 return False
1160 1160
1161 1161 def children(self):
1162 1162 return []
1163 1163
1164 1164 def flags(self, path):
1165 1165 if '_manifest' in self.__dict__:
1166 1166 try:
1167 1167 return self._manifest.flags(path)
1168 1168 except KeyError:
1169 1169 return ''
1170 1170
1171 1171 try:
1172 1172 return self._flagfunc(path)
1173 1173 except OSError:
1174 1174 return ''
1175 1175
1176 1176 def ancestor(self, c2):
1177 1177 """return the "best" ancestor context of self and c2"""
1178 1178 return self._parents[0].ancestor(c2) # punt on two parents for now
1179 1179
1180 1180 def walk(self, match):
1181 1181 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1182 1182 True, False))
1183 1183
1184 1184 def matches(self, match):
1185 1185 return sorted(self._repo.dirstate.matches(match))
1186 1186
1187 1187 def ancestors(self):
1188 1188 for a in self._repo.changelog.ancestors(
1189 1189 [p.rev() for p in self._parents]):
1190 1190 yield changectx(self._repo, a)
1191 1191
1192 1192 def markcommitted(self, node):
1193 1193 """Perform post-commit cleanup necessary after committing this ctx
1194 1194
1195 1195 Specifically, this updates backing stores this working context
1196 1196 wraps to reflect the fact that the changes reflected by this
1197 1197 workingctx have been committed. For example, it marks
1198 1198 modified and added files as normal in the dirstate.
1199 1199
1200 1200 """
1201 1201
1202 1202 self._repo.dirstate.beginparentchange()
1203 1203 for f in self.modified() + self.added():
1204 1204 self._repo.dirstate.normal(f)
1205 1205 for f in self.removed():
1206 1206 self._repo.dirstate.drop(f)
1207 1207 self._repo.dirstate.setparents(node)
1208 1208 self._repo.dirstate.endparentchange()
1209 1209
1210 1210 def dirs(self):
1211 1211 return self._repo.dirstate.dirs()
1212 1212
1213 1213 class workingctx(committablectx):
1214 1214 """A workingctx object makes access to data related to
1215 1215 the current working directory convenient.
1216 1216 date - any valid date string or (unixtime, offset), or None.
1217 1217 user - username string, or None.
1218 1218 extra - a dictionary of extra values, or None.
1219 1219 changes - a list of file lists as returned by localrepo.status()
1220 1220 or None to use the repository status.
1221 1221 """
1222 1222 def __init__(self, repo, text="", user=None, date=None, extra=None,
1223 1223 changes=None):
1224 1224 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1225 1225
1226 1226 def __iter__(self):
1227 1227 d = self._repo.dirstate
1228 1228 for f in d:
1229 1229 if d[f] != 'r':
1230 1230 yield f
1231 1231
1232 1232 def __contains__(self, key):
1233 1233 return self._repo.dirstate[key] not in "?r"
1234 1234
1235 1235 @propertycache
1236 1236 def _parents(self):
1237 1237 p = self._repo.dirstate.parents()
1238 1238 if p[1] == nullid:
1239 1239 p = p[:-1]
1240 1240 return [changectx(self._repo, x) for x in p]
1241 1241
1242 1242 def filectx(self, path, filelog=None):
1243 1243 """get a file context from the working directory"""
1244 1244 return workingfilectx(self._repo, path, workingctx=self,
1245 1245 filelog=filelog)
1246 1246
1247 1247 def dirty(self, missing=False, merge=True, branch=True):
1248 1248 "check whether a working directory is modified"
1249 1249 # check subrepos first
1250 1250 for s in sorted(self.substate):
1251 1251 if self.sub(s).dirty():
1252 1252 return True
1253 1253 # check current working dir
1254 1254 return ((merge and self.p2()) or
1255 1255 (branch and self.branch() != self.p1().branch()) or
1256 1256 self.modified() or self.added() or self.removed() or
1257 1257 (missing and self.deleted()))
1258 1258
1259 1259 def add(self, list, prefix=""):
1260 1260 join = lambda f: os.path.join(prefix, f)
1261 1261 wlock = self._repo.wlock()
1262 1262 ui, ds = self._repo.ui, self._repo.dirstate
1263 1263 try:
1264 1264 rejected = []
1265 1265 lstat = self._repo.wvfs.lstat
1266 1266 for f in list:
1267 1267 scmutil.checkportable(ui, join(f))
1268 1268 try:
1269 1269 st = lstat(f)
1270 1270 except OSError:
1271 1271 ui.warn(_("%s does not exist!\n") % join(f))
1272 1272 rejected.append(f)
1273 1273 continue
1274 1274 if st.st_size > 10000000:
1275 1275 ui.warn(_("%s: up to %d MB of RAM may be required "
1276 1276 "to manage this file\n"
1277 1277 "(use 'hg revert %s' to cancel the "
1278 1278 "pending addition)\n")
1279 1279 % (f, 3 * st.st_size // 1000000, join(f)))
1280 1280 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1281 1281 ui.warn(_("%s not added: only files and symlinks "
1282 1282 "supported currently\n") % join(f))
1283 1283 rejected.append(f)
1284 1284 elif ds[f] in 'amn':
1285 1285 ui.warn(_("%s already tracked!\n") % join(f))
1286 1286 elif ds[f] == 'r':
1287 1287 ds.normallookup(f)
1288 1288 else:
1289 1289 ds.add(f)
1290 1290 return rejected
1291 1291 finally:
1292 1292 wlock.release()
1293 1293
1294 1294 def forget(self, files, prefix=""):
1295 1295 join = lambda f: os.path.join(prefix, f)
1296 1296 wlock = self._repo.wlock()
1297 1297 try:
1298 1298 rejected = []
1299 1299 for f in files:
1300 1300 if f not in self._repo.dirstate:
1301 1301 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1302 1302 rejected.append(f)
1303 1303 elif self._repo.dirstate[f] != 'a':
1304 1304 self._repo.dirstate.remove(f)
1305 1305 else:
1306 1306 self._repo.dirstate.drop(f)
1307 1307 return rejected
1308 1308 finally:
1309 1309 wlock.release()
1310 1310
1311 1311 def undelete(self, list):
1312 1312 pctxs = self.parents()
1313 1313 wlock = self._repo.wlock()
1314 1314 try:
1315 1315 for f in list:
1316 1316 if self._repo.dirstate[f] != 'r':
1317 1317 self._repo.ui.warn(_("%s not removed!\n") % f)
1318 1318 else:
1319 1319 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1320 1320 t = fctx.data()
1321 1321 self._repo.wwrite(f, t, fctx.flags())
1322 1322 self._repo.dirstate.normal(f)
1323 1323 finally:
1324 1324 wlock.release()
1325 1325
1326 1326 def copy(self, source, dest):
1327 1327 try:
1328 1328 st = self._repo.wvfs.lstat(dest)
1329 1329 except OSError, err:
1330 1330 if err.errno != errno.ENOENT:
1331 1331 raise
1332 1332 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1333 1333 return
1334 1334 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1335 1335 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1336 1336 "symbolic link\n") % dest)
1337 1337 else:
1338 1338 wlock = self._repo.wlock()
1339 1339 try:
1340 if self._repo.dirstate[dest] in '?r':
1340 if self._repo.dirstate[dest] in '?':
1341 1341 self._repo.dirstate.add(dest)
1342 elif self._repo.dirstate[dest] in 'r':
1343 self._repo.dirstate.normallookup(dest)
1342 1344 self._repo.dirstate.copy(source, dest)
1343 1345 finally:
1344 1346 wlock.release()
1345 1347
1346 1348 def _filtersuspectsymlink(self, files):
1347 1349 if not files or self._repo.dirstate._checklink:
1348 1350 return files
1349 1351
1350 1352 # Symlink placeholders may get non-symlink-like contents
1351 1353 # via user error or dereferencing by NFS or Samba servers,
1352 1354 # so we filter out any placeholders that don't look like a
1353 1355 # symlink
1354 1356 sane = []
1355 1357 for f in files:
1356 1358 if self.flags(f) == 'l':
1357 1359 d = self[f].data()
1358 1360 if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d):
1359 1361 self._repo.ui.debug('ignoring suspect symlink placeholder'
1360 1362 ' "%s"\n' % f)
1361 1363 continue
1362 1364 sane.append(f)
1363 1365 return sane
1364 1366
1365 1367 def _checklookup(self, files):
1366 1368 # check for any possibly clean files
1367 1369 if not files:
1368 1370 return [], []
1369 1371
1370 1372 modified = []
1371 1373 fixup = []
1372 1374 pctx = self._parents[0]
1373 1375 # do a full compare of any files that might have changed
1374 1376 for f in sorted(files):
1375 1377 if (f not in pctx or self.flags(f) != pctx.flags(f)
1376 1378 or pctx[f].cmp(self[f])):
1377 1379 modified.append(f)
1378 1380 else:
1379 1381 fixup.append(f)
1380 1382
1381 1383 # update dirstate for files that are actually clean
1382 1384 if fixup:
1383 1385 try:
1384 1386 # updating the dirstate is optional
1385 1387 # so we don't wait on the lock
1386 1388 # wlock can invalidate the dirstate, so cache normal _after_
1387 1389 # taking the lock
1388 1390 wlock = self._repo.wlock(False)
1389 1391 normal = self._repo.dirstate.normal
1390 1392 try:
1391 1393 for f in fixup:
1392 1394 normal(f)
1393 1395 finally:
1394 1396 wlock.release()
1395 1397 except error.LockError:
1396 1398 pass
1397 1399 return modified, fixup
1398 1400
1399 1401 def _manifestmatches(self, match, s):
1400 1402 """Slow path for workingctx
1401 1403
1402 1404 The fast path is when we compare the working directory to its parent
1403 1405 which means this function is comparing with a non-parent; therefore we
1404 1406 need to build a manifest and return what matches.
1405 1407 """
1406 1408 mf = self._repo['.']._manifestmatches(match, s)
1407 1409 modified, added, removed = s[0:3]
1408 1410 for f in modified + added:
1409 1411 mf[f] = None
1410 1412 mf.setflag(f, self.flags(f))
1411 1413 for f in removed:
1412 1414 if f in mf:
1413 1415 del mf[f]
1414 1416 return mf
1415 1417
1416 1418 def _prestatus(self, other, s, match, listignored, listclean, listunknown):
1417 1419 """override the parent hook with a dirstate query
1418 1420
1419 1421 We use this prestatus hook to populate the status with information from
1420 1422 the dirstate.
1421 1423 """
1422 1424 # doesn't need to call super; if that changes, be aware that super
1423 1425 # calls self.manifest which would slow down the common case of calling
1424 1426 # status against a workingctx's parent
1425 1427 return self._dirstatestatus(match, listignored, listclean, listunknown)
1426 1428
1427 1429 def _poststatus(self, other, s, match, listignored, listclean, listunknown):
1428 1430 """override the parent hook with a filter for suspect symlinks
1429 1431
1430 1432 We use this poststatus hook to filter out symlinks that might have
1431 1433 accidentally ended up with the entire contents of the file they are
1432 1434 susposed to be linking to.
1433 1435 """
1434 1436 s[0] = self._filtersuspectsymlink(s[0])
1435 1437 self._status = scmutil.status(*s)
1436 1438 return s
1437 1439
1438 1440 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1439 1441 unknown=False):
1440 1442 '''Gets the status from the dirstate -- internal use only.'''
1441 1443 listignored, listclean, listunknown = ignored, clean, unknown
1442 1444 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1443 1445 subrepos = []
1444 1446 if '.hgsub' in self:
1445 1447 subrepos = sorted(self.substate)
1446 1448 cmp, s = self._repo.dirstate.status(match, subrepos, listignored,
1447 1449 listclean, listunknown)
1448 1450 modified, added, removed, deleted, unknown, ignored, clean = s
1449 1451
1450 1452 # check for any possibly clean files
1451 1453 if cmp:
1452 1454 modified2, fixup = self._checklookup(cmp)
1453 1455 modified += modified2
1454 1456
1455 1457 # update dirstate for files that are actually clean
1456 1458 if fixup and listclean:
1457 1459 clean += fixup
1458 1460
1459 1461 return [modified, added, removed, deleted, unknown, ignored, clean]
1460 1462
1461 1463 def _buildstatus(self, other, s, match, listignored, listclean,
1462 1464 listunknown):
1463 1465 """build a status with respect to another context
1464 1466
1465 1467 This includes logic for maintaining the fast path of status when
1466 1468 comparing the working directory against its parent, which is to skip
1467 1469 building a new manifest if self (working directory) is not comparing
1468 1470 against its parent (repo['.']).
1469 1471 """
1470 1472 if other != self._repo['.']:
1471 1473 s = super(workingctx, self)._buildstatus(other, s, match,
1472 1474 listignored, listclean,
1473 1475 listunknown)
1474 1476 return s
1475 1477
1476 1478 def _matchstatus(self, other, s, match, listignored, listclean,
1477 1479 listunknown):
1478 1480 """override the match method with a filter for directory patterns
1479 1481
1480 1482 We use inheritance to customize the match.bad method only in cases of
1481 1483 workingctx since it belongs only to the working directory when
1482 1484 comparing against the parent changeset.
1483 1485
1484 1486 If we aren't comparing against the working directory's parent, then we
1485 1487 just use the default match object sent to us.
1486 1488 """
1487 1489 superself = super(workingctx, self)
1488 1490 match = superself._matchstatus(other, s, match, listignored, listclean,
1489 1491 listunknown)
1490 1492 if other != self._repo['.']:
1491 1493 def bad(f, msg):
1492 1494 # 'f' may be a directory pattern from 'match.files()',
1493 1495 # so 'f not in ctx1' is not enough
1494 1496 if f not in other and f not in other.dirs():
1495 1497 self._repo.ui.warn('%s: %s\n' %
1496 1498 (self._repo.dirstate.pathto(f), msg))
1497 1499 match.bad = bad
1498 1500 return match
1499 1501
1500 1502 def status(self, other='.', match=None, listignored=False,
1501 1503 listclean=False, listunknown=False, listsubrepos=False):
1502 1504 # yet to be determined: what to do if 'other' is a 'workingctx' or a
1503 1505 # 'memctx'?
1504 1506 return super(workingctx, self).status(other, match, listignored,
1505 1507 listclean, listunknown,
1506 1508 listsubrepos)
1507 1509
1508 1510 class committablefilectx(basefilectx):
1509 1511 """A committablefilectx provides common functionality for a file context
1510 1512 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1511 1513 def __init__(self, repo, path, filelog=None, ctx=None):
1512 1514 self._repo = repo
1513 1515 self._path = path
1514 1516 self._changeid = None
1515 1517 self._filerev = self._filenode = None
1516 1518
1517 1519 if filelog is not None:
1518 1520 self._filelog = filelog
1519 1521 if ctx:
1520 1522 self._changectx = ctx
1521 1523
1522 1524 def __nonzero__(self):
1523 1525 return True
1524 1526
1525 1527 def parents(self):
1526 1528 '''return parent filectxs, following copies if necessary'''
1527 1529 def filenode(ctx, path):
1528 1530 return ctx._manifest.get(path, nullid)
1529 1531
1530 1532 path = self._path
1531 1533 fl = self._filelog
1532 1534 pcl = self._changectx._parents
1533 1535 renamed = self.renamed()
1534 1536
1535 1537 if renamed:
1536 1538 pl = [renamed + (None,)]
1537 1539 else:
1538 1540 pl = [(path, filenode(pcl[0], path), fl)]
1539 1541
1540 1542 for pc in pcl[1:]:
1541 1543 pl.append((path, filenode(pc, path), fl))
1542 1544
1543 1545 return [filectx(self._repo, p, fileid=n, filelog=l)
1544 1546 for p, n, l in pl if n != nullid]
1545 1547
1546 1548 def children(self):
1547 1549 return []
1548 1550
1549 1551 class workingfilectx(committablefilectx):
1550 1552 """A workingfilectx object makes access to data related to a particular
1551 1553 file in the working directory convenient."""
1552 1554 def __init__(self, repo, path, filelog=None, workingctx=None):
1553 1555 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1554 1556
1555 1557 @propertycache
1556 1558 def _changectx(self):
1557 1559 return workingctx(self._repo)
1558 1560
1559 1561 def data(self):
1560 1562 return self._repo.wread(self._path)
1561 1563 def renamed(self):
1562 1564 rp = self._repo.dirstate.copied(self._path)
1563 1565 if not rp:
1564 1566 return None
1565 1567 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1566 1568
1567 1569 def size(self):
1568 1570 return self._repo.wvfs.lstat(self._path).st_size
1569 1571 def date(self):
1570 1572 t, tz = self._changectx.date()
1571 1573 try:
1572 1574 return (int(self._repo.wvfs.lstat(self._path).st_mtime), tz)
1573 1575 except OSError, err:
1574 1576 if err.errno != errno.ENOENT:
1575 1577 raise
1576 1578 return (t, tz)
1577 1579
1578 1580 def cmp(self, fctx):
1579 1581 """compare with other file context
1580 1582
1581 1583 returns True if different than fctx.
1582 1584 """
1583 1585 # fctx should be a filectx (not a workingfilectx)
1584 1586 # invert comparison to reuse the same code path
1585 1587 return fctx.cmp(self)
1586 1588
1587 1589 def remove(self, ignoremissing=False):
1588 1590 """wraps unlink for a repo's working directory"""
1589 1591 util.unlinkpath(self._repo.wjoin(self._path), ignoremissing)
1590 1592
1591 1593 def write(self, data, flags):
1592 1594 """wraps repo.wwrite"""
1593 1595 self._repo.wwrite(self._path, data, flags)
1594 1596
1595 1597 class memctx(committablectx):
1596 1598 """Use memctx to perform in-memory commits via localrepo.commitctx().
1597 1599
1598 1600 Revision information is supplied at initialization time while
1599 1601 related files data and is made available through a callback
1600 1602 mechanism. 'repo' is the current localrepo, 'parents' is a
1601 1603 sequence of two parent revisions identifiers (pass None for every
1602 1604 missing parent), 'text' is the commit message and 'files' lists
1603 1605 names of files touched by the revision (normalized and relative to
1604 1606 repository root).
1605 1607
1606 1608 filectxfn(repo, memctx, path) is a callable receiving the
1607 1609 repository, the current memctx object and the normalized path of
1608 1610 requested file, relative to repository root. It is fired by the
1609 1611 commit function for every file in 'files', but calls order is
1610 1612 undefined. If the file is available in the revision being
1611 1613 committed (updated or added), filectxfn returns a memfilectx
1612 1614 object. If the file was removed, filectxfn raises an
1613 1615 IOError. Moved files are represented by marking the source file
1614 1616 removed and the new file added with copy information (see
1615 1617 memfilectx).
1616 1618
1617 1619 user receives the committer name and defaults to current
1618 1620 repository username, date is the commit date in any format
1619 1621 supported by util.parsedate() and defaults to current date, extra
1620 1622 is a dictionary of metadata or is left empty.
1621 1623 """
1622 1624
1623 1625 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
1624 1626 # Extensions that need to retain compatibility across Mercurial 3.1 can use
1625 1627 # this field to determine what to do in filectxfn.
1626 1628 _returnnoneformissingfiles = True
1627 1629
1628 1630 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1629 1631 date=None, extra=None, editor=False):
1630 1632 super(memctx, self).__init__(repo, text, user, date, extra)
1631 1633 self._rev = None
1632 1634 self._node = None
1633 1635 parents = [(p or nullid) for p in parents]
1634 1636 p1, p2 = parents
1635 1637 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1636 1638 files = sorted(set(files))
1637 1639 self._status = scmutil.status(files, [], [], [], [], [], [])
1638 1640 self._filectxfn = filectxfn
1639 1641 self.substate = {}
1640 1642
1641 1643 # if store is not callable, wrap it in a function
1642 1644 if not callable(filectxfn):
1643 1645 def getfilectx(repo, memctx, path):
1644 1646 fctx = filectxfn[path]
1645 1647 # this is weird but apparently we only keep track of one parent
1646 1648 # (why not only store that instead of a tuple?)
1647 1649 copied = fctx.renamed()
1648 1650 if copied:
1649 1651 copied = copied[0]
1650 1652 return memfilectx(repo, path, fctx.data(),
1651 1653 islink=fctx.islink(), isexec=fctx.isexec(),
1652 1654 copied=copied, memctx=memctx)
1653 1655 self._filectxfn = getfilectx
1654 1656
1655 1657 self._extra = extra and extra.copy() or {}
1656 1658 if self._extra.get('branch', '') == '':
1657 1659 self._extra['branch'] = 'default'
1658 1660
1659 1661 if editor:
1660 1662 self._text = editor(self._repo, self, [])
1661 1663 self._repo.savecommitmessage(self._text)
1662 1664
1663 1665 def filectx(self, path, filelog=None):
1664 1666 """get a file context from the working directory
1665 1667
1666 1668 Returns None if file doesn't exist and should be removed."""
1667 1669 return self._filectxfn(self._repo, self, path)
1668 1670
1669 1671 def commit(self):
1670 1672 """commit context to the repo"""
1671 1673 return self._repo.commitctx(self)
1672 1674
1673 1675 @propertycache
1674 1676 def _manifest(self):
1675 1677 """generate a manifest based on the return values of filectxfn"""
1676 1678
1677 1679 # keep this simple for now; just worry about p1
1678 1680 pctx = self._parents[0]
1679 1681 man = pctx.manifest().copy()
1680 1682
1681 1683 for f, fnode in man.iteritems():
1682 1684 p1node = nullid
1683 1685 p2node = nullid
1684 1686 p = pctx[f].parents() # if file isn't in pctx, check p2?
1685 1687 if len(p) > 0:
1686 1688 p1node = p[0].node()
1687 1689 if len(p) > 1:
1688 1690 p2node = p[1].node()
1689 1691 man[f] = revlog.hash(self[f].data(), p1node, p2node)
1690 1692
1691 1693 return man
1692 1694
1693 1695
1694 1696 class memfilectx(committablefilectx):
1695 1697 """memfilectx represents an in-memory file to commit.
1696 1698
1697 1699 See memctx and commitablefilectx for more details.
1698 1700 """
1699 1701 def __init__(self, repo, path, data, islink=False,
1700 1702 isexec=False, copied=None, memctx=None):
1701 1703 """
1702 1704 path is the normalized file path relative to repository root.
1703 1705 data is the file content as a string.
1704 1706 islink is True if the file is a symbolic link.
1705 1707 isexec is True if the file is executable.
1706 1708 copied is the source file path if current file was copied in the
1707 1709 revision being committed, or None."""
1708 1710 super(memfilectx, self).__init__(repo, path, None, memctx)
1709 1711 self._data = data
1710 1712 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1711 1713 self._copied = None
1712 1714 if copied:
1713 1715 self._copied = (copied, nullid)
1714 1716
1715 1717 def data(self):
1716 1718 return self._data
1717 1719 def size(self):
1718 1720 return len(self.data())
1719 1721 def flags(self):
1720 1722 return self._flags
1721 1723 def renamed(self):
1722 1724 return self._copied
1723 1725
1724 1726 def remove(self, ignoremissing=False):
1725 1727 """wraps unlink for a repo's working directory"""
1726 1728 # need to figure out what to do here
1727 1729 del self._changectx[self._path]
1728 1730
1729 1731 def write(self, data, flags):
1730 1732 """wraps repo.wwrite"""
1731 1733 self._data = data
@@ -1,125 +1,125 b''
1 1
2 2 $ echo "[extensions]" >> $HGRCPATH
3 3 $ echo "mq=" >> $HGRCPATH
4 4
5 5 $ hg init a
6 6 $ cd a
7 7
8 8 $ echo 'base' > base
9 9 $ hg ci -Ambase
10 10 adding base
11 11
12 12 $ hg qnew -mmqbase mqbase
13 13
14 14 $ hg qrename mqbase renamed
15 15 $ mkdir .hg/patches/foo
16 16 $ hg qrename renamed foo
17 17
18 18 $ hg qseries
19 19 foo/renamed
20 20
21 21 $ ls .hg/patches/foo
22 22 renamed
23 23
24 24 $ mkdir .hg/patches/bar
25 25 $ hg qrename foo/renamed bar
26 26
27 27 $ hg qseries
28 28 bar/renamed
29 29
30 30 $ ls .hg/patches/bar
31 31 renamed
32 32
33 33 $ hg qrename bar/renamed baz
34 34
35 35 $ hg qseries
36 36 baz
37 37
38 38 $ ls .hg/patches/baz
39 39 .hg/patches/baz
40 40
41 41 $ hg qrename baz new/dir
42 42
43 43 $ hg qseries
44 44 new/dir
45 45
46 46 $ ls .hg/patches/new/dir
47 47 .hg/patches/new/dir
48 48
49 49 $ cd ..
50 50
51 51 Test patch being renamed before committed:
52 52
53 53 $ hg init b
54 54 $ cd b
55 55 $ hg qinit -c
56 56 $ hg qnew x
57 57 $ hg qrename y
58 58 $ hg qcommit -m rename
59 59
60 60 $ cd ..
61 61
62 62 Test overlapping renames (issue2388)
63 63
64 64 $ hg init c
65 65 $ cd c
66 66 $ hg qinit -c
67 67 $ echo a > a
68 68 $ hg add
69 69 adding a
70 70 $ hg qnew patcha
71 71 $ echo b > b
72 72 $ hg add
73 73 adding b
74 74 $ hg qnew patchb
75 75 $ hg ci --mq -m c1
76 76 $ hg qrename patchb patchc
77 77 $ hg qrename patcha patchb
78 78 $ hg st --mq
79 M patchb
79 80 M series
80 A patchb
81 81 A patchc
82 82 R patcha
83 83 $ cd ..
84 84
85 85 Test renames with mq repo (issue2097)
86 86
87 87 $ hg init issue2097
88 88 $ cd issue2097
89 89 $ hg qnew p0
90 90 $ (cd .hg/patches && hg init)
91 91 $ hg qren p0 p1
92 92 $ hg debugstate --mq
93 93 $ hg ci --mq -mq0
94 94 nothing changed
95 95 [1]
96 96 $ cd ..
97 97
98 98 Test renaming to a folded patch (issue3058)
99 99
100 100 $ hg init issue3058
101 101 $ cd issue3058
102 102 $ hg init --mq
103 103 $ echo a > a
104 104 $ hg add a
105 105 $ hg qnew adda
106 106 $ echo b >> a
107 107 $ hg qnew addb
108 108 $ hg qpop
109 109 popping addb
110 110 now at: adda
111 111 $ hg ci --mq -m "save mq"
112 112 $ hg qfold addb
113 113 $ hg qmv addb
114 114 $ cat .hg/patches/addb
115 115 # HG changeset patch
116 116 # Parent 0000000000000000000000000000000000000000
117 117
118 118 diff -r 000000000000 a
119 119 --- /dev/null * (glob)
120 120 +++ b/a * (glob)
121 121 @@ -0,0 +1,2 @@
122 122 +a
123 123 +b
124 124 $ cd ..
125 125
@@ -1,638 +1,647 b''
1 1 $ hg init
2 2 $ mkdir d1 d1/d11 d2
3 3 $ echo d1/a > d1/a
4 4 $ echo d1/ba > d1/ba
5 5 $ echo d1/a1 > d1/d11/a1
6 6 $ echo d1/b > d1/b
7 7 $ echo d2/b > d2/b
8 8 $ hg add d1/a d1/b d1/ba d1/d11/a1 d2/b
9 9 $ hg commit -m "1"
10 10
11 11 rename a single file
12 12
13 13 $ hg rename d1/d11/a1 d2/c
14 14 $ hg --config ui.portablefilenames=abort rename d1/a d1/con.xml
15 15 abort: filename contains 'con', which is reserved on Windows: 'd1/con.xml'
16 16 [255]
17 17 $ hg sum
18 18 parent: 0:9b4b6e7b2c26 tip
19 19 1
20 20 branch: default
21 21 commit: 1 renamed
22 22 update: (current)
23 23 $ hg status -C
24 24 A d2/c
25 25 d1/d11/a1
26 26 R d1/d11/a1
27 27 $ hg update -C
28 28 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
29 29 $ rm d2/c
30 30
31 31 rename a single file using absolute paths
32 32
33 33 $ hg rename `pwd`/d1/d11/a1 `pwd`/d2/c
34 34 $ hg status -C
35 35 A d2/c
36 36 d1/d11/a1
37 37 R d1/d11/a1
38 38 $ hg update -C
39 39 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
40 40 $ rm d2/c
41 41
42 42 rename --after a single file
43 43
44 44 $ mv d1/d11/a1 d2/c
45 45 $ hg rename --after d1/d11/a1 d2/c
46 46 $ hg status -C
47 47 A d2/c
48 48 d1/d11/a1
49 49 R d1/d11/a1
50 50 $ hg update -C
51 51 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
52 52 $ rm d2/c
53 53
54 54 rename --after a single file when src and tgt already tracked
55 55
56 56 $ mv d1/d11/a1 d2/c
57 57 $ hg addrem -s 0
58 58 removing d1/d11/a1
59 59 adding d2/c
60 60 $ hg rename --after d1/d11/a1 d2/c
61 61 $ hg status -C
62 62 A d2/c
63 63 d1/d11/a1
64 64 R d1/d11/a1
65 65 $ hg update -C
66 66 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
67 67 $ rm d2/c
68 68
69 69 rename --after a single file to a nonexistent target filename
70 70
71 71 $ hg rename --after d1/a dummy
72 72 d1/a: not recording move - dummy does not exist (glob)
73 73
74 74 move a single file to an existing directory
75 75
76 76 $ hg rename d1/d11/a1 d2
77 77 $ hg status -C
78 78 A d2/a1
79 79 d1/d11/a1
80 80 R d1/d11/a1
81 81 $ hg update -C
82 82 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
83 83 $ rm d2/a1
84 84
85 85 move --after a single file to an existing directory
86 86
87 87 $ mv d1/d11/a1 d2
88 88 $ hg rename --after d1/d11/a1 d2
89 89 $ hg status -C
90 90 A d2/a1
91 91 d1/d11/a1
92 92 R d1/d11/a1
93 93 $ hg update -C
94 94 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
95 95 $ rm d2/a1
96 96
97 97 rename a file using a relative path
98 98
99 99 $ (cd d1/d11; hg rename ../../d2/b e)
100 100 $ hg status -C
101 101 A d1/d11/e
102 102 d2/b
103 103 R d2/b
104 104 $ hg update -C
105 105 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
106 106 $ rm d1/d11/e
107 107
108 108 rename --after a file using a relative path
109 109
110 110 $ (cd d1/d11; mv ../../d2/b e; hg rename --after ../../d2/b e)
111 111 $ hg status -C
112 112 A d1/d11/e
113 113 d2/b
114 114 R d2/b
115 115 $ hg update -C
116 116 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
117 117 $ rm d1/d11/e
118 118
119 119 rename directory d1 as d3
120 120
121 121 $ hg rename d1/ d3
122 122 moving d1/a to d3/a (glob)
123 123 moving d1/b to d3/b (glob)
124 124 moving d1/ba to d3/ba (glob)
125 125 moving d1/d11/a1 to d3/d11/a1 (glob)
126 126 $ hg status -C
127 127 A d3/a
128 128 d1/a
129 129 A d3/b
130 130 d1/b
131 131 A d3/ba
132 132 d1/ba
133 133 A d3/d11/a1
134 134 d1/d11/a1
135 135 R d1/a
136 136 R d1/b
137 137 R d1/ba
138 138 R d1/d11/a1
139 139 $ hg update -C
140 140 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
141 141 $ rm -rf d3
142 142
143 143 rename --after directory d1 as d3
144 144
145 145 $ mv d1 d3
146 146 $ hg rename --after d1 d3
147 147 moving d1/a to d3/a (glob)
148 148 moving d1/b to d3/b (glob)
149 149 moving d1/ba to d3/ba (glob)
150 150 moving d1/d11/a1 to d3/d11/a1 (glob)
151 151 $ hg status -C
152 152 A d3/a
153 153 d1/a
154 154 A d3/b
155 155 d1/b
156 156 A d3/ba
157 157 d1/ba
158 158 A d3/d11/a1
159 159 d1/d11/a1
160 160 R d1/a
161 161 R d1/b
162 162 R d1/ba
163 163 R d1/d11/a1
164 164 $ hg update -C
165 165 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
166 166 $ rm -rf d3
167 167
168 168 move a directory using a relative path
169 169
170 170 $ (cd d2; mkdir d3; hg rename ../d1/d11 d3)
171 171 moving ../d1/d11/a1 to d3/d11/a1 (glob)
172 172 $ hg status -C
173 173 A d2/d3/d11/a1
174 174 d1/d11/a1
175 175 R d1/d11/a1
176 176 $ hg update -C
177 177 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
178 178 $ rm -rf d2/d3
179 179
180 180 move --after a directory using a relative path
181 181
182 182 $ (cd d2; mkdir d3; mv ../d1/d11 d3; hg rename --after ../d1/d11 d3)
183 183 moving ../d1/d11/a1 to d3/d11/a1 (glob)
184 184 $ hg status -C
185 185 A d2/d3/d11/a1
186 186 d1/d11/a1
187 187 R d1/d11/a1
188 188 $ hg update -C
189 189 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
190 190 $ rm -rf d2/d3
191 191
192 192 move directory d1/d11 to an existing directory d2 (removes empty d1)
193 193
194 194 $ hg rename d1/d11/ d2
195 195 moving d1/d11/a1 to d2/d11/a1 (glob)
196 196 $ hg status -C
197 197 A d2/d11/a1
198 198 d1/d11/a1
199 199 R d1/d11/a1
200 200 $ hg update -C
201 201 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
202 202 $ rm -rf d2/d11
203 203
204 204 move directories d1 and d2 to a new directory d3
205 205
206 206 $ mkdir d3
207 207 $ hg rename d1 d2 d3
208 208 moving d1/a to d3/d1/a (glob)
209 209 moving d1/b to d3/d1/b (glob)
210 210 moving d1/ba to d3/d1/ba (glob)
211 211 moving d1/d11/a1 to d3/d1/d11/a1 (glob)
212 212 moving d2/b to d3/d2/b (glob)
213 213 $ hg status -C
214 214 A d3/d1/a
215 215 d1/a
216 216 A d3/d1/b
217 217 d1/b
218 218 A d3/d1/ba
219 219 d1/ba
220 220 A d3/d1/d11/a1
221 221 d1/d11/a1
222 222 A d3/d2/b
223 223 d2/b
224 224 R d1/a
225 225 R d1/b
226 226 R d1/ba
227 227 R d1/d11/a1
228 228 R d2/b
229 229 $ hg update -C
230 230 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
231 231 $ rm -rf d3
232 232
233 233 move --after directories d1 and d2 to a new directory d3
234 234
235 235 $ mkdir d3
236 236 $ mv d1 d2 d3
237 237 $ hg rename --after d1 d2 d3
238 238 moving d1/a to d3/d1/a (glob)
239 239 moving d1/b to d3/d1/b (glob)
240 240 moving d1/ba to d3/d1/ba (glob)
241 241 moving d1/d11/a1 to d3/d1/d11/a1 (glob)
242 242 moving d2/b to d3/d2/b (glob)
243 243 $ hg status -C
244 244 A d3/d1/a
245 245 d1/a
246 246 A d3/d1/b
247 247 d1/b
248 248 A d3/d1/ba
249 249 d1/ba
250 250 A d3/d1/d11/a1
251 251 d1/d11/a1
252 252 A d3/d2/b
253 253 d2/b
254 254 R d1/a
255 255 R d1/b
256 256 R d1/ba
257 257 R d1/d11/a1
258 258 R d2/b
259 259 $ hg update -C
260 260 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
261 261 $ rm -rf d3
262 262
263 263 move everything under directory d1 to existing directory d2, do not
264 264 overwrite existing files (d2/b)
265 265
266 266 $ hg rename d1/* d2
267 267 d2/b: not overwriting - file exists
268 268 moving d1/d11/a1 to d2/d11/a1 (glob)
269 269 $ hg status -C
270 270 A d2/a
271 271 d1/a
272 272 A d2/ba
273 273 d1/ba
274 274 A d2/d11/a1
275 275 d1/d11/a1
276 276 R d1/a
277 277 R d1/ba
278 278 R d1/d11/a1
279 279 $ diff -u d1/b d2/b
280 280 --- d1/b * (glob)
281 281 +++ d2/b * (glob)
282 282 @@ * (glob)
283 283 -d1/b
284 284 +d2/b
285 285 [1]
286 286 $ hg update -C
287 287 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
288 288 $ rm d2/a d2/ba d2/d11/a1
289 289
290 290 attempt to move one file into a non-existent directory
291 291
292 292 $ hg rename d1/a dx/
293 293 abort: destination dx/ is not a directory
294 294 [255]
295 295 $ hg status -C
296 296 $ hg update -C
297 297 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
298 298
299 299 attempt to move potentially more than one file into a non-existent directory
300 300
301 301 $ hg rename 'glob:d1/**' dx
302 302 abort: with multiple sources, destination must be an existing directory
303 303 [255]
304 304
305 305 move every file under d1 to d2/d21 (glob)
306 306
307 307 $ mkdir d2/d21
308 308 $ hg rename 'glob:d1/**' d2/d21
309 309 moving d1/a to d2/d21/a (glob)
310 310 moving d1/b to d2/d21/b (glob)
311 311 moving d1/ba to d2/d21/ba (glob)
312 312 moving d1/d11/a1 to d2/d21/a1 (glob)
313 313 $ hg status -C
314 314 A d2/d21/a
315 315 d1/a
316 316 A d2/d21/a1
317 317 d1/d11/a1
318 318 A d2/d21/b
319 319 d1/b
320 320 A d2/d21/ba
321 321 d1/ba
322 322 R d1/a
323 323 R d1/b
324 324 R d1/ba
325 325 R d1/d11/a1
326 326 $ hg update -C
327 327 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
328 328 $ rm -rf d2/d21
329 329
330 330 move --after some files under d1 to d2/d21 (glob)
331 331
332 332 $ mkdir d2/d21
333 333 $ mv d1/a d1/d11/a1 d2/d21
334 334 $ hg rename --after 'glob:d1/**' d2/d21
335 335 moving d1/a to d2/d21/a (glob)
336 336 d1/b: not recording move - d2/d21/b does not exist (glob)
337 337 d1/ba: not recording move - d2/d21/ba does not exist (glob)
338 338 moving d1/d11/a1 to d2/d21/a1 (glob)
339 339 $ hg status -C
340 340 A d2/d21/a
341 341 d1/a
342 342 A d2/d21/a1
343 343 d1/d11/a1
344 344 R d1/a
345 345 R d1/d11/a1
346 346 $ hg update -C
347 347 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
348 348 $ rm -rf d2/d21
349 349
350 350 move every file under d1 starting with an 'a' to d2/d21 (regexp)
351 351
352 352 $ mkdir d2/d21
353 353 $ hg rename 're:d1/([^a][^/]*/)*a.*' d2/d21
354 354 moving d1/a to d2/d21/a (glob)
355 355 moving d1/d11/a1 to d2/d21/a1 (glob)
356 356 $ hg status -C
357 357 A d2/d21/a
358 358 d1/a
359 359 A d2/d21/a1
360 360 d1/d11/a1
361 361 R d1/a
362 362 R d1/d11/a1
363 363 $ hg update -C
364 364 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
365 365 $ rm -rf d2/d21
366 366
367 367 attempt to overwrite an existing file
368 368
369 369 $ echo "ca" > d1/ca
370 370 $ hg rename d1/ba d1/ca
371 371 d1/ca: not overwriting - file exists
372 372 $ hg status -C
373 373 ? d1/ca
374 374 $ hg update -C
375 375 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
376 376
377 377 forced overwrite of an existing file
378 378
379 379 $ echo "ca" > d1/ca
380 380 $ hg rename --force d1/ba d1/ca
381 381 $ hg status -C
382 382 A d1/ca
383 383 d1/ba
384 384 R d1/ba
385 385 $ hg update -C
386 386 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
387 387 $ rm d1/ca
388 388
389 389 attempt to overwrite an existing broken symlink
390 390
391 391 #if symlink
392 392 $ ln -s ba d1/ca
393 393 $ hg rename --traceback d1/ba d1/ca
394 394 d1/ca: not overwriting - file exists
395 395 $ hg status -C
396 396 ? d1/ca
397 397 $ hg update -C
398 398 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
399 399 $ rm d1/ca
400 400
401 401 replace a symlink with a file
402 402
403 403 $ ln -s ba d1/ca
404 404 $ hg rename --force d1/ba d1/ca
405 405 $ hg status -C
406 406 A d1/ca
407 407 d1/ba
408 408 R d1/ba
409 409 $ hg update -C
410 410 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
411 411 $ rm d1/ca
412 412 #endif
413 413
414 414 do not copy more than one source file to the same destination file
415 415
416 416 $ mkdir d3
417 417 $ hg rename d1/* d2/* d3
418 418 moving d1/d11/a1 to d3/d11/a1 (glob)
419 419 d3/b: not overwriting - d2/b collides with d1/b
420 420 $ hg status -C
421 421 A d3/a
422 422 d1/a
423 423 A d3/b
424 424 d1/b
425 425 A d3/ba
426 426 d1/ba
427 427 A d3/d11/a1
428 428 d1/d11/a1
429 429 R d1/a
430 430 R d1/b
431 431 R d1/ba
432 432 R d1/d11/a1
433 433 $ hg update -C
434 434 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
435 435 $ rm -rf d3
436 436
437 437 move a whole subtree with "hg rename ."
438 438
439 439 $ mkdir d3
440 440 $ (cd d1; hg rename . ../d3)
441 441 moving a to ../d3/d1/a
442 442 moving b to ../d3/d1/b
443 443 moving ba to ../d3/d1/ba
444 444 moving d11/a1 to ../d3/d1/d11/a1 (glob)
445 445 $ hg status -C
446 446 A d3/d1/a
447 447 d1/a
448 448 A d3/d1/b
449 449 d1/b
450 450 A d3/d1/ba
451 451 d1/ba
452 452 A d3/d1/d11/a1
453 453 d1/d11/a1
454 454 R d1/a
455 455 R d1/b
456 456 R d1/ba
457 457 R d1/d11/a1
458 458 $ hg update -C
459 459 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
460 460 $ rm -rf d3
461 461
462 462 move a whole subtree with "hg rename --after ."
463 463
464 464 $ mkdir d3
465 465 $ mv d1/* d3
466 466 $ (cd d1; hg rename --after . ../d3)
467 467 moving a to ../d3/a
468 468 moving b to ../d3/b
469 469 moving ba to ../d3/ba
470 470 moving d11/a1 to ../d3/d11/a1 (glob)
471 471 $ hg status -C
472 472 A d3/a
473 473 d1/a
474 474 A d3/b
475 475 d1/b
476 476 A d3/ba
477 477 d1/ba
478 478 A d3/d11/a1
479 479 d1/d11/a1
480 480 R d1/a
481 481 R d1/b
482 482 R d1/ba
483 483 R d1/d11/a1
484 484 $ hg update -C
485 485 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
486 486 $ rm -rf d3
487 487
488 488 move the parent tree with "hg rename .."
489 489
490 490 $ (cd d1/d11; hg rename .. ../../d3)
491 491 moving ../a to ../../d3/a (glob)
492 492 moving ../b to ../../d3/b (glob)
493 493 moving ../ba to ../../d3/ba (glob)
494 494 moving a1 to ../../d3/d11/a1
495 495 $ hg status -C
496 496 A d3/a
497 497 d1/a
498 498 A d3/b
499 499 d1/b
500 500 A d3/ba
501 501 d1/ba
502 502 A d3/d11/a1
503 503 d1/d11/a1
504 504 R d1/a
505 505 R d1/b
506 506 R d1/ba
507 507 R d1/d11/a1
508 508 $ hg update -C
509 509 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
510 510 $ rm -rf d3
511 511
512 512 skip removed files
513 513
514 514 $ hg remove d1/b
515 515 $ hg rename d1 d3
516 516 moving d1/a to d3/a (glob)
517 517 moving d1/ba to d3/ba (glob)
518 518 moving d1/d11/a1 to d3/d11/a1 (glob)
519 519 $ hg status -C
520 520 A d3/a
521 521 d1/a
522 522 A d3/ba
523 523 d1/ba
524 524 A d3/d11/a1
525 525 d1/d11/a1
526 526 R d1/a
527 527 R d1/b
528 528 R d1/ba
529 529 R d1/d11/a1
530 530 $ hg update -C
531 531 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
532 532 $ rm -rf d3
533 533
534 534 transitive rename
535 535
536 536 $ hg rename d1/b d1/bb
537 537 $ hg rename d1/bb d1/bc
538 538 $ hg status -C
539 539 A d1/bc
540 540 d1/b
541 541 R d1/b
542 542 $ hg update -C
543 543 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
544 544 $ rm d1/bc
545 545
546 546 transitive rename --after
547 547
548 548 $ hg rename d1/b d1/bb
549 549 $ mv d1/bb d1/bc
550 550 $ hg rename --after d1/bb d1/bc
551 551 $ hg status -C
552 552 A d1/bc
553 553 d1/b
554 554 R d1/b
555 555 $ hg update -C
556 556 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
557 557 $ rm d1/bc
558 558
559 559 $ echo "# idempotent renames (d1/b -> d1/bb followed by d1/bb -> d1/b)"
560 560 # idempotent renames (d1/b -> d1/bb followed by d1/bb -> d1/b)
561 561 $ hg rename d1/b d1/bb
562 562 $ echo "some stuff added to d1/bb" >> d1/bb
563 563 $ hg rename d1/bb d1/b
564 564 $ hg status -C
565 565 M d1/b
566 566 $ hg update -C
567 567 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
568 568
569 569 overwriting with renames (issue1959)
570 570
571 571 $ hg rename d1/a d1/c
572 572 $ hg rename d1/b d1/a
573 573 $ hg status -C
574 A d1/a
574 M d1/a
575 575 d1/b
576 576 A d1/c
577 577 d1/a
578 578 R d1/b
579 579 $ hg diff --git
580 diff --git a/d1/b b/d1/a
581 rename from d1/b
582 rename to d1/a
580 diff --git a/d1/a b/d1/a
581 --- a/d1/a
582 +++ b/d1/a
583 @@ -1,1 +1,1 @@
584 -d1/a
585 +d1/b
586 diff --git a/d1/b b/d1/b
587 deleted file mode 100644
588 --- a/d1/b
589 +++ /dev/null
590 @@ -1,1 +0,0 @@
591 -d1/b
583 592 diff --git a/d1/a b/d1/c
584 593 copy from d1/a
585 594 copy to d1/c
586 595 $ hg update -C
587 596 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
588 597 $ rm d1/c # The file was marked as added, so 'hg update' action was 'forget'
589 598
590 599 check illegal path components
591 600
592 601 $ hg rename d1/d11/a1 .hg/foo
593 602 abort: path contains illegal component: .hg/foo (glob)
594 603 [255]
595 604 $ hg status -C
596 605 $ hg rename d1/d11/a1 ../foo
597 606 abort: ../foo not under root '$TESTTMP'
598 607 [255]
599 608 $ hg status -C
600 609
601 610 $ mv d1/d11/a1 .hg/foo
602 611 $ hg rename --after d1/d11/a1 .hg/foo
603 612 abort: path contains illegal component: .hg/foo (glob)
604 613 [255]
605 614 $ hg status -C
606 615 ! d1/d11/a1
607 616 $ hg update -C
608 617 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
609 618 $ rm .hg/foo
610 619
611 620 $ hg rename d1/d11/a1 .hg
612 621 abort: path contains illegal component: .hg/a1 (glob)
613 622 [255]
614 623 $ hg status -C
615 624 $ hg rename d1/d11/a1 ..
616 625 abort: ../a1 not under root '$TESTTMP' (glob)
617 626 [255]
618 627 $ hg status -C
619 628
620 629 $ mv d1/d11/a1 .hg
621 630 $ hg rename --after d1/d11/a1 .hg
622 631 abort: path contains illegal component: .hg/a1 (glob)
623 632 [255]
624 633 $ hg status -C
625 634 ! d1/d11/a1
626 635 $ hg update -C
627 636 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
628 637 $ rm .hg/a1
629 638
630 639 $ (cd d1/d11; hg rename ../../d2/b ../../.hg/foo)
631 640 abort: path contains illegal component: .hg/foo (glob)
632 641 [255]
633 642 $ hg status -C
634 643 $ (cd d1/d11; hg rename ../../d2/b ../../../foo)
635 644 abort: ../../../foo not under root '$TESTTMP'
636 645 [255]
637 646 $ hg status -C
638 647
@@ -1,392 +1,438 b''
1 1 $ hg init repo1
2 2 $ cd repo1
3 3 $ mkdir a b a/1 b/1 b/2
4 4 $ touch in_root a/in_a b/in_b a/1/in_a_1 b/1/in_b_1 b/2/in_b_2
5 5
6 6 hg status in repo root:
7 7
8 8 $ hg status
9 9 ? a/1/in_a_1
10 10 ? a/in_a
11 11 ? b/1/in_b_1
12 12 ? b/2/in_b_2
13 13 ? b/in_b
14 14 ? in_root
15 15
16 16 hg status . in repo root:
17 17
18 18 $ hg status .
19 19 ? a/1/in_a_1
20 20 ? a/in_a
21 21 ? b/1/in_b_1
22 22 ? b/2/in_b_2
23 23 ? b/in_b
24 24 ? in_root
25 25
26 26 $ hg status --cwd a
27 27 ? a/1/in_a_1
28 28 ? a/in_a
29 29 ? b/1/in_b_1
30 30 ? b/2/in_b_2
31 31 ? b/in_b
32 32 ? in_root
33 33 $ hg status --cwd a .
34 34 ? 1/in_a_1
35 35 ? in_a
36 36 $ hg status --cwd a ..
37 37 ? 1/in_a_1
38 38 ? in_a
39 39 ? ../b/1/in_b_1
40 40 ? ../b/2/in_b_2
41 41 ? ../b/in_b
42 42 ? ../in_root
43 43
44 44 $ hg status --cwd b
45 45 ? a/1/in_a_1
46 46 ? a/in_a
47 47 ? b/1/in_b_1
48 48 ? b/2/in_b_2
49 49 ? b/in_b
50 50 ? in_root
51 51 $ hg status --cwd b .
52 52 ? 1/in_b_1
53 53 ? 2/in_b_2
54 54 ? in_b
55 55 $ hg status --cwd b ..
56 56 ? ../a/1/in_a_1
57 57 ? ../a/in_a
58 58 ? 1/in_b_1
59 59 ? 2/in_b_2
60 60 ? in_b
61 61 ? ../in_root
62 62
63 63 $ hg status --cwd a/1
64 64 ? a/1/in_a_1
65 65 ? a/in_a
66 66 ? b/1/in_b_1
67 67 ? b/2/in_b_2
68 68 ? b/in_b
69 69 ? in_root
70 70 $ hg status --cwd a/1 .
71 71 ? in_a_1
72 72 $ hg status --cwd a/1 ..
73 73 ? in_a_1
74 74 ? ../in_a
75 75
76 76 $ hg status --cwd b/1
77 77 ? a/1/in_a_1
78 78 ? a/in_a
79 79 ? b/1/in_b_1
80 80 ? b/2/in_b_2
81 81 ? b/in_b
82 82 ? in_root
83 83 $ hg status --cwd b/1 .
84 84 ? in_b_1
85 85 $ hg status --cwd b/1 ..
86 86 ? in_b_1
87 87 ? ../2/in_b_2
88 88 ? ../in_b
89 89
90 90 $ hg status --cwd b/2
91 91 ? a/1/in_a_1
92 92 ? a/in_a
93 93 ? b/1/in_b_1
94 94 ? b/2/in_b_2
95 95 ? b/in_b
96 96 ? in_root
97 97 $ hg status --cwd b/2 .
98 98 ? in_b_2
99 99 $ hg status --cwd b/2 ..
100 100 ? ../1/in_b_1
101 101 ? in_b_2
102 102 ? ../in_b
103 103
104 104 combining patterns with root and patterns without a root works
105 105
106 106 $ hg st a/in_a re:.*b$
107 107 ? a/in_a
108 108 ? b/in_b
109 109
110 110 $ cd ..
111 111
112 112 $ hg init repo2
113 113 $ cd repo2
114 114 $ touch modified removed deleted ignored
115 115 $ echo "^ignored$" > .hgignore
116 116 $ hg ci -A -m 'initial checkin'
117 117 adding .hgignore
118 118 adding deleted
119 119 adding modified
120 120 adding removed
121 121 $ touch modified added unknown ignored
122 122 $ hg add added
123 123 $ hg remove removed
124 124 $ rm deleted
125 125
126 126 hg status:
127 127
128 128 $ hg status
129 129 A added
130 130 R removed
131 131 ! deleted
132 132 ? unknown
133 133
134 134 hg status modified added removed deleted unknown never-existed ignored:
135 135
136 136 $ hg status modified added removed deleted unknown never-existed ignored
137 137 never-existed: * (glob)
138 138 A added
139 139 R removed
140 140 ! deleted
141 141 ? unknown
142 142
143 143 $ hg copy modified copied
144 144
145 145 hg status -C:
146 146
147 147 $ hg status -C
148 148 A added
149 149 A copied
150 150 modified
151 151 R removed
152 152 ! deleted
153 153 ? unknown
154 154
155 155 hg status -A:
156 156
157 157 $ hg status -A
158 158 A added
159 159 A copied
160 160 modified
161 161 R removed
162 162 ! deleted
163 163 ? unknown
164 164 I ignored
165 165 C .hgignore
166 166 C modified
167 167
168 168 $ hg status -A -Tjson
169 169 [
170 170 {
171 171 "path": "added",
172 172 "status": "A"
173 173 },
174 174 {
175 175 "copy": "modified",
176 176 "path": "copied",
177 177 "status": "A"
178 178 },
179 179 {
180 180 "path": "removed",
181 181 "status": "R"
182 182 },
183 183 {
184 184 "path": "deleted",
185 185 "status": "!"
186 186 },
187 187 {
188 188 "path": "unknown",
189 189 "status": "?"
190 190 },
191 191 {
192 192 "path": "ignored",
193 193 "status": "I"
194 194 },
195 195 {
196 196 "path": ".hgignore",
197 197 "status": "C"
198 198 },
199 199 {
200 200 "path": "modified",
201 201 "status": "C"
202 202 }
203 203 ]
204 204
205 205 $ hg status -A -Tpickle > pickle
206 206 >>> import pickle
207 207 >>> print sorted((x['status'], x['path']) for x in pickle.load(open("pickle")))
208 208 [('!', 'deleted'), ('?', 'pickle'), ('?', 'unknown'), ('A', 'added'), ('A', 'copied'), ('C', '.hgignore'), ('C', 'modified'), ('I', 'ignored'), ('R', 'removed')]
209 209 $ rm pickle
210 210
211 211 $ echo "^ignoreddir$" > .hgignore
212 212 $ mkdir ignoreddir
213 213 $ touch ignoreddir/file
214 214
215 215 hg status ignoreddir/file:
216 216
217 217 $ hg status ignoreddir/file
218 218
219 219 hg status -i ignoreddir/file:
220 220
221 221 $ hg status -i ignoreddir/file
222 222 I ignoreddir/file
223 223 $ cd ..
224 224
225 225 Check 'status -q' and some combinations
226 226
227 227 $ hg init repo3
228 228 $ cd repo3
229 229 $ touch modified removed deleted ignored
230 230 $ echo "^ignored$" > .hgignore
231 231 $ hg commit -A -m 'initial checkin'
232 232 adding .hgignore
233 233 adding deleted
234 234 adding modified
235 235 adding removed
236 236 $ touch added unknown ignored
237 237 $ hg add added
238 238 $ echo "test" >> modified
239 239 $ hg remove removed
240 240 $ rm deleted
241 241 $ hg copy modified copied
242 242
243 243 Run status with 2 different flags.
244 244 Check if result is the same or different.
245 245 If result is not as expected, raise error
246 246
247 247 $ assert() {
248 248 > hg status $1 > ../a
249 249 > hg status $2 > ../b
250 250 > if diff ../a ../b > /dev/null; then
251 251 > out=0
252 252 > else
253 253 > out=1
254 254 > fi
255 255 > if [ $3 -eq 0 ]; then
256 256 > df="same"
257 257 > else
258 258 > df="different"
259 259 > fi
260 260 > if [ $out -ne $3 ]; then
261 261 > echo "Error on $1 and $2, should be $df."
262 262 > fi
263 263 > }
264 264
265 265 Assert flag1 flag2 [0-same | 1-different]
266 266
267 267 $ assert "-q" "-mard" 0
268 268 $ assert "-A" "-marduicC" 0
269 269 $ assert "-qA" "-mardcC" 0
270 270 $ assert "-qAui" "-A" 0
271 271 $ assert "-qAu" "-marducC" 0
272 272 $ assert "-qAi" "-mardicC" 0
273 273 $ assert "-qu" "-u" 0
274 274 $ assert "-q" "-u" 1
275 275 $ assert "-m" "-a" 1
276 276 $ assert "-r" "-d" 1
277 277 $ cd ..
278 278
279 279 $ hg init repo4
280 280 $ cd repo4
281 281 $ touch modified removed deleted
282 282 $ hg ci -q -A -m 'initial checkin'
283 283 $ touch added unknown
284 284 $ hg add added
285 285 $ hg remove removed
286 286 $ rm deleted
287 287 $ echo x > modified
288 288 $ hg copy modified copied
289 289 $ hg ci -m 'test checkin' -d "1000001 0"
290 290 $ rm *
291 291 $ touch unrelated
292 292 $ hg ci -q -A -m 'unrelated checkin' -d "1000002 0"
293 293
294 294 hg status --change 1:
295 295
296 296 $ hg status --change 1
297 297 M modified
298 298 A added
299 299 A copied
300 300 R removed
301 301
302 302 hg status --change 1 unrelated:
303 303
304 304 $ hg status --change 1 unrelated
305 305
306 306 hg status -C --change 1 added modified copied removed deleted:
307 307
308 308 $ hg status -C --change 1 added modified copied removed deleted
309 309 M modified
310 310 A added
311 311 A copied
312 312 modified
313 313 R removed
314 314
315 315 hg status -A --change 1 and revset:
316 316
317 317 $ hg status -A --change '1|1'
318 318 M modified
319 319 A added
320 320 A copied
321 321 modified
322 322 R removed
323 323 C deleted
324 324
325 325 $ cd ..
326 326
327 327 hg status of binary file starting with '\1\n', a separator for metadata:
328 328
329 329 $ hg init repo5
330 330 $ cd repo5
331 331 >>> open("010a", "wb").write("\1\nfoo")
332 332 $ hg ci -q -A -m 'initial checkin'
333 333 $ hg status -A
334 334 C 010a
335 335
336 336 >>> open("010a", "wb").write("\1\nbar")
337 337 $ hg status -A
338 338 M 010a
339 339 $ hg ci -q -m 'modify 010a'
340 340 $ hg status -A --rev 0:1
341 341 M 010a
342 342
343 343 $ touch empty
344 344 $ hg ci -q -A -m 'add another file'
345 345 $ hg status -A --rev 1:2 010a
346 346 C 010a
347 347
348 348 $ cd ..
349 349
350 350 test "hg status" with "directory pattern" which matches against files
351 351 only known on target revision.
352 352
353 353 $ hg init repo6
354 354 $ cd repo6
355 355
356 356 $ echo a > a.txt
357 357 $ hg add a.txt
358 358 $ hg commit -m '#0'
359 359 $ mkdir -p 1/2/3/4/5
360 360 $ echo b > 1/2/3/4/5/b.txt
361 361 $ hg add 1/2/3/4/5/b.txt
362 362 $ hg commit -m '#1'
363 363
364 364 $ hg update -C 0 > /dev/null
365 365 $ hg status -A
366 366 C a.txt
367 367
368 368 the directory matching against specified pattern should be removed,
369 369 because directory existence prevents 'dirstate.walk()' from showing
370 370 warning message about such pattern.
371 371
372 372 $ test ! -d 1
373 373 $ hg status -A --rev 1 1/2/3/4/5/b.txt
374 374 R 1/2/3/4/5/b.txt
375 375 $ hg status -A --rev 1 1/2/3/4/5
376 376 R 1/2/3/4/5/b.txt
377 377 $ hg status -A --rev 1 1/2/3
378 378 R 1/2/3/4/5/b.txt
379 379 $ hg status -A --rev 1 1
380 380 R 1/2/3/4/5/b.txt
381 381
382 382 $ hg status --config ui.formatdebug=True --rev 1 1
383 383 status = [
384 384 {*'path': '1/2/3/4/5/b.txt'*}, (glob)
385 385 ]
386 386
387 387 #if windows
388 388 $ hg --config ui.slash=false status -A --rev 1 1
389 389 R 1\2\3\4\5\b.txt
390 390 #endif
391 391
392 392 $ cd ..
393
394 Status after move overwriting a file (issue4458)
395 =================================================
396
397
398 $ hg init issue4458
399 $ cd issue4458
400 $ echo a > a
401 $ echo b > b
402 $ hg commit -Am base
403 adding a
404 adding b
405
406
407 with --force
408
409 $ hg mv b --force a
410 $ hg st --copies
411 M a
412 b
413 R b
414 $ hg revert --all
415 reverting a
416 undeleting b
417 $ rm *.orig
418
419 without force
420
421 $ hg rm a
422 $ hg st --copies
423 R a
424 $ hg mv b a
425 $ hg st --copies
426 M a
427 b
428 R b
429
430 Other "bug" highlight, the revision status does not report the copy information.
431 This is buggy behavior.
432
433 $ hg commit -m 'blah'
434 $ hg st --copies --change .
435 M a
436 R b
437
438 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now