~ubuntu-branches/ubuntu/wily/mercurial/wily

« back to all changes in this revision

Viewing changes to mercurial/context.py

  • Committer: Package Import Robot
  • Author(s): Javi Merino
  • Date: 2013-11-01 23:19:57 UTC
  • mfrom: (1.2.38) (9.1.16 experimental)
  • Revision ID: package-import@ubuntu.com-20131101231957-hs70pwpinavlz3t6
Tags: 2.8-1
* New upstream release
* Fix mercurial-git and hgsubversion autopkgtest by loading the
  appropriate extension
* Bump standards-version to 3.9.5 (no change needed)

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
propertycache = util.propertycache
18
18
 
19
 
class changectx(object):
 
19
class basectx(object):
 
20
    """A basectx object represents the common logic for its children:
 
21
    changectx: read-only context that is already present in the repo,
 
22
    workingctx: a context that represents the working directory and can
 
23
                be committed,
 
24
    memctx: a context that represents changes in-memory and can also
 
25
            be committed."""
 
26
    def __new__(cls, repo, changeid='', *args, **kwargs):
 
27
        if isinstance(changeid, basectx):
 
28
            return changeid
 
29
 
 
30
        o = super(basectx, cls).__new__(cls)
 
31
 
 
32
        o._repo = repo
 
33
        o._rev = nullrev
 
34
        o._node = nullid
 
35
 
 
36
        return o
 
37
 
 
38
    def __str__(self):
 
39
        return short(self.node())
 
40
 
 
41
    def __int__(self):
 
42
        return self.rev()
 
43
 
 
44
    def __repr__(self):
 
45
        return "<%s %s>" % (type(self).__name__, str(self))
 
46
 
 
47
    def __eq__(self, other):
 
48
        try:
 
49
            return type(self) == type(other) and self._rev == other._rev
 
50
        except AttributeError:
 
51
            return False
 
52
 
 
53
    def __ne__(self, other):
 
54
        return not (self == other)
 
55
 
 
56
    def __contains__(self, key):
 
57
        return key in self._manifest
 
58
 
 
59
    def __getitem__(self, key):
 
60
        return self.filectx(key)
 
61
 
 
62
    def __iter__(self):
 
63
        for f in sorted(self._manifest):
 
64
            yield f
 
65
 
 
66
    @propertycache
 
67
    def substate(self):
 
68
        return subrepo.state(self, self._repo.ui)
 
69
 
 
70
    def rev(self):
 
71
        return self._rev
 
72
    def node(self):
 
73
        return self._node
 
74
    def hex(self):
 
75
        return hex(self.node())
 
76
    def manifest(self):
 
77
        return self._manifest
 
78
    def phasestr(self):
 
79
        return phases.phasenames[self.phase()]
 
80
    def mutable(self):
 
81
        return self.phase() > phases.public
 
82
 
 
83
    def obsolete(self):
 
84
        """True if the changeset is obsolete"""
 
85
        return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
 
86
 
 
87
    def extinct(self):
 
88
        """True if the changeset is extinct"""
 
89
        return self.rev() in obsmod.getrevs(self._repo, 'extinct')
 
90
 
 
91
    def unstable(self):
 
92
        """True if the changeset is not obsolete but it's ancestor are"""
 
93
        return self.rev() in obsmod.getrevs(self._repo, 'unstable')
 
94
 
 
95
    def bumped(self):
 
96
        """True if the changeset try to be a successor of a public changeset
 
97
 
 
98
        Only non-public and non-obsolete changesets may be bumped.
 
99
        """
 
100
        return self.rev() in obsmod.getrevs(self._repo, 'bumped')
 
101
 
 
102
    def divergent(self):
 
103
        """Is a successors of a changeset with multiple possible successors set
 
104
 
 
105
        Only non-public and non-obsolete changesets may be divergent.
 
106
        """
 
107
        return self.rev() in obsmod.getrevs(self._repo, 'divergent')
 
108
 
 
109
    def troubled(self):
 
110
        """True if the changeset is either unstable, bumped or divergent"""
 
111
        return self.unstable() or self.bumped() or self.divergent()
 
112
 
 
113
    def troubles(self):
 
114
        """return the list of troubles affecting this changesets.
 
115
 
 
116
        Troubles are returned as strings. possible values are:
 
117
        - unstable,
 
118
        - bumped,
 
119
        - divergent.
 
120
        """
 
121
        troubles = []
 
122
        if self.unstable():
 
123
            troubles.append('unstable')
 
124
        if self.bumped():
 
125
            troubles.append('bumped')
 
126
        if self.divergent():
 
127
            troubles.append('divergent')
 
128
        return troubles
 
129
 
 
130
    def parents(self):
 
131
        """return contexts for each parent changeset"""
 
132
        return self._parents
 
133
 
 
134
    def p1(self):
 
135
        return self._parents[0]
 
136
 
 
137
    def p2(self):
 
138
        if len(self._parents) == 2:
 
139
            return self._parents[1]
 
140
        return changectx(self._repo, -1)
 
141
 
 
142
    def _fileinfo(self, path):
 
143
        if '_manifest' in self.__dict__:
 
144
            try:
 
145
                return self._manifest[path], self._manifest.flags(path)
 
146
            except KeyError:
 
147
                raise error.ManifestLookupError(self._node, path,
 
148
                                                _('not found in manifest'))
 
149
        if '_manifestdelta' in self.__dict__ or path in self.files():
 
150
            if path in self._manifestdelta:
 
151
                return (self._manifestdelta[path],
 
152
                        self._manifestdelta.flags(path))
 
153
        node, flag = self._repo.manifest.find(self._changeset[0], path)
 
154
        if not node:
 
155
            raise error.ManifestLookupError(self._node, path,
 
156
                                            _('not found in manifest'))
 
157
 
 
158
        return node, flag
 
159
 
 
160
    def filenode(self, path):
 
161
        return self._fileinfo(path)[0]
 
162
 
 
163
    def flags(self, path):
 
164
        try:
 
165
            return self._fileinfo(path)[1]
 
166
        except error.LookupError:
 
167
            return ''
 
168
 
 
169
    def sub(self, path):
 
170
        return subrepo.subrepo(self, path)
 
171
 
 
172
    def match(self, pats=[], include=None, exclude=None, default='glob'):
 
173
        r = self._repo
 
174
        return matchmod.match(r.root, r.getcwd(), pats,
 
175
                              include, exclude, default,
 
176
                              auditor=r.auditor, ctx=self)
 
177
 
 
178
    def diff(self, ctx2=None, match=None, **opts):
 
179
        """Returns a diff generator for the given contexts and matcher"""
 
180
        if ctx2 is None:
 
181
            ctx2 = self.p1()
 
182
        if ctx2 is not None:
 
183
            ctx2 = self._repo[ctx2]
 
184
        diffopts = patch.diffopts(self._repo.ui, opts)
 
185
        return patch.diff(self._repo, ctx2.node(), self.node(),
 
186
                          match=match, opts=diffopts)
 
187
 
 
188
    @propertycache
 
189
    def _dirs(self):
 
190
        return scmutil.dirs(self._manifest)
 
191
 
 
192
    def dirs(self):
 
193
        return self._dirs
 
194
 
 
195
    def dirty(self):
 
196
        return False
 
197
 
 
198
class changectx(basectx):
20
199
    """A changecontext object makes access to data related to a particular
21
 
    changeset convenient."""
 
200
    changeset convenient. It represents a read-only context already present in
 
201
    the repo."""
22
202
    def __init__(self, repo, changeid=''):
23
203
        """changeid is a revision number, node, or tag"""
 
204
 
 
205
        # since basectx.__new__ already took care of copying the object, we
 
206
        # don't need to do anything in __init__, so we just exit here
 
207
        if isinstance(changeid, basectx):
 
208
            return
 
209
 
24
210
        if changeid == '':
25
211
            changeid = '.'
26
212
        self._repo = repo
114
300
        raise error.RepoLookupError(
115
301
            _("unknown revision '%s'") % changeid)
116
302
 
117
 
    def __str__(self):
118
 
        return short(self.node())
119
 
 
120
 
    def __int__(self):
121
 
        return self.rev()
122
 
 
123
 
    def __repr__(self):
124
 
        return "<changectx %s>" % str(self)
125
 
 
126
303
    def __hash__(self):
127
304
        try:
128
305
            return hash(self._rev)
129
306
        except AttributeError:
130
307
            return id(self)
131
308
 
132
 
    def __eq__(self, other):
133
 
        try:
134
 
            return self._rev == other._rev
135
 
        except AttributeError:
136
 
            return False
137
 
 
138
 
    def __ne__(self, other):
139
 
        return not (self == other)
140
 
 
141
309
    def __nonzero__(self):
142
310
        return self._rev != nullrev
143
311
 
160
328
            p = p[:-1]
161
329
        return [changectx(self._repo, x) for x in p]
162
330
 
163
 
    @propertycache
164
 
    def substate(self):
165
 
        return subrepo.state(self, self._repo.ui)
166
 
 
167
 
    def __contains__(self, key):
168
 
        return key in self._manifest
169
 
 
170
 
    def __getitem__(self, key):
171
 
        return self.filectx(key)
172
 
 
173
 
    def __iter__(self):
174
 
        for f in sorted(self._manifest):
175
 
            yield f
176
 
 
177
331
    def changeset(self):
178
332
        return self._changeset
179
 
    def manifest(self):
180
 
        return self._manifest
181
333
    def manifestnode(self):
182
334
        return self._changeset[0]
183
335
 
184
 
    def rev(self):
185
 
        return self._rev
186
 
    def node(self):
187
 
        return self._node
188
 
    def hex(self):
189
 
        return hex(self._node)
190
336
    def user(self):
191
337
        return self._changeset[1]
192
338
    def date(self):
207
353
        return self._repo.nodebookmarks(self._node)
208
354
    def phase(self):
209
355
        return self._repo._phasecache.phase(self._repo, self._rev)
210
 
    def phasestr(self):
211
 
        return phases.phasenames[self.phase()]
212
 
    def mutable(self):
213
 
        return self.phase() > phases.public
214
356
    def hidden(self):
215
357
        return self._rev in repoview.filterrevs(self._repo, 'visible')
216
358
 
217
 
    def parents(self):
218
 
        """return contexts for each parent changeset"""
219
 
        return self._parents
220
 
 
221
 
    def p1(self):
222
 
        return self._parents[0]
223
 
 
224
 
    def p2(self):
225
 
        if len(self._parents) == 2:
226
 
            return self._parents[1]
227
 
        return changectx(self._repo, -1)
228
 
 
229
359
    def children(self):
230
360
        """return contexts for each child changeset"""
231
361
        c = self._repo.changelog.children(self._node)
239
369
        for d in self._repo.changelog.descendants([self._rev]):
240
370
            yield changectx(self._repo, d)
241
371
 
242
 
    def obsolete(self):
243
 
        """True if the changeset is obsolete"""
244
 
        return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
245
 
 
246
 
    def extinct(self):
247
 
        """True if the changeset is extinct"""
248
 
        return self.rev() in obsmod.getrevs(self._repo, 'extinct')
249
 
 
250
 
    def unstable(self):
251
 
        """True if the changeset is not obsolete but it's ancestor are"""
252
 
        return self.rev() in obsmod.getrevs(self._repo, 'unstable')
253
 
 
254
 
    def bumped(self):
255
 
        """True if the changeset try to be a successor of a public changeset
256
 
 
257
 
        Only non-public and non-obsolete changesets may be bumped.
258
 
        """
259
 
        return self.rev() in obsmod.getrevs(self._repo, 'bumped')
260
 
 
261
 
    def divergent(self):
262
 
        """Is a successors of a changeset with multiple possible successors set
263
 
 
264
 
        Only non-public and non-obsolete changesets may be divergent.
265
 
        """
266
 
        return self.rev() in obsmod.getrevs(self._repo, 'divergent')
267
 
 
268
 
    def troubled(self):
269
 
        """True if the changeset is either unstable, bumped or divergent"""
270
 
        return self.unstable() or self.bumped() or self.divergent()
271
 
 
272
 
    def troubles(self):
273
 
        """return the list of troubles affecting this changesets.
274
 
 
275
 
        Troubles are returned as strings. possible values are:
276
 
        - unstable,
277
 
        - bumped,
278
 
        - divergent.
279
 
        """
280
 
        troubles = []
281
 
        if self.unstable():
282
 
            troubles.append('unstable')
283
 
        if self.bumped():
284
 
            troubles.append('bumped')
285
 
        if self.divergent():
286
 
            troubles.append('divergent')
287
 
        return troubles
288
 
 
289
 
    def _fileinfo(self, path):
290
 
        if '_manifest' in self.__dict__:
291
 
            try:
292
 
                return self._manifest[path], self._manifest.flags(path)
293
 
            except KeyError:
294
 
                raise error.ManifestLookupError(self._node, path,
295
 
                                                _('not found in manifest'))
296
 
        if '_manifestdelta' in self.__dict__ or path in self.files():
297
 
            if path in self._manifestdelta:
298
 
                return (self._manifestdelta[path],
299
 
                        self._manifestdelta.flags(path))
300
 
        node, flag = self._repo.manifest.find(self._changeset[0], path)
301
 
        if not node:
302
 
            raise error.ManifestLookupError(self._node, path,
303
 
                                            _('not found in manifest'))
304
 
 
305
 
        return node, flag
306
 
 
307
 
    def filenode(self, path):
308
 
        return self._fileinfo(path)[0]
309
 
 
310
 
    def flags(self, path):
311
 
        try:
312
 
            return self._fileinfo(path)[1]
313
 
        except error.LookupError:
314
 
            return ''
315
 
 
316
372
    def filectx(self, path, fileid=None, filelog=None):
317
373
        """get a file context from this changeset"""
318
374
        if fileid is None:
353
409
            if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
354
410
                yield fn
355
411
 
356
 
    def sub(self, path):
357
 
        return subrepo.subrepo(self, path)
358
 
 
359
 
    def match(self, pats=[], include=None, exclude=None, default='glob'):
360
 
        r = self._repo
361
 
        return matchmod.match(r.root, r.getcwd(), pats,
362
 
                              include, exclude, default,
363
 
                              auditor=r.auditor, ctx=self)
364
 
 
365
 
    def diff(self, ctx2=None, match=None, **opts):
366
 
        """Returns a diff generator for the given contexts and matcher"""
367
 
        if ctx2 is None:
368
 
            ctx2 = self.p1()
369
 
        if ctx2 is not None and not isinstance(ctx2, changectx):
370
 
            ctx2 = self._repo[ctx2]
371
 
        diffopts = patch.diffopts(self._repo.ui, opts)
372
 
        return patch.diff(self._repo, ctx2.node(), self.node(),
373
 
                          match=match, opts=diffopts)
374
 
 
375
 
    @propertycache
376
 
    def _dirs(self):
377
 
        return scmutil.dirs(self._manifest)
378
 
 
379
 
    def dirs(self):
380
 
        return self._dirs
381
 
 
382
 
    def dirty(self):
383
 
        return False
384
 
 
385
 
class filectx(object):
386
 
    """A filecontext object makes access to data related to a particular
387
 
       filerevision convenient."""
388
 
    def __init__(self, repo, path, changeid=None, fileid=None,
389
 
                 filelog=None, changectx=None):
390
 
        """changeid can be a changeset revision, node, or tag.
391
 
           fileid can be a file revision or node."""
392
 
        self._repo = repo
393
 
        self._path = path
394
 
 
395
 
        assert (changeid is not None
396
 
                or fileid is not None
397
 
                or changectx is not None), \
398
 
                ("bad args: changeid=%r, fileid=%r, changectx=%r"
399
 
                 % (changeid, fileid, changectx))
400
 
 
401
 
        if filelog is not None:
402
 
            self._filelog = filelog
403
 
 
404
 
        if changeid is not None:
405
 
            self._changeid = changeid
406
 
        if changectx is not None:
407
 
            self._changectx = changectx
408
 
        if fileid is not None:
409
 
            self._fileid = fileid
410
 
 
411
 
    @propertycache
412
 
    def _changectx(self):
413
 
        try:
414
 
            return changectx(self._repo, self._changeid)
415
 
        except error.RepoLookupError:
416
 
            # Linkrev may point to any revision in the repository.  When the
417
 
            # repository is filtered this may lead to `filectx` trying to build
418
 
            # `changectx` for filtered revision. In such case we fallback to
419
 
            # creating `changectx` on the unfiltered version of the reposition.
420
 
            # This fallback should not be an issue because `changectx` from
421
 
            # `filectx` are not used in complex operations that care about
422
 
            # filtering.
423
 
            #
424
 
            # This fallback is a cheap and dirty fix that prevent several
425
 
            # crashes. It does not ensure the behavior is correct. However the
426
 
            # behavior was not correct before filtering either and "incorrect
427
 
            # behavior" is seen as better as "crash"
428
 
            #
429
 
            # Linkrevs have several serious troubles with filtering that are
430
 
            # complicated to solve. Proper handling of the issue here should be
431
 
            # considered when solving linkrev issue are on the table.
432
 
            return changectx(self._repo.unfiltered(), self._changeid)
 
412
class basefilectx(object):
 
413
    """A filecontext object represents the common logic for its children:
 
414
    filectx: read-only access to a filerevision that is already present
 
415
             in the repo,
 
416
    workingfilectx: a filecontext that represents files from the working
 
417
                    directory,
 
418
    memfilectx: a filecontext that represents files in-memory."""
 
419
    def __new__(cls, repo, path, *args, **kwargs):
 
420
        return super(basefilectx, cls).__new__(cls)
433
421
 
434
422
    @propertycache
435
423
    def _filelog(self):
468
456
            return False
469
457
 
470
458
    def __str__(self):
471
 
        return "%s@%s" % (self.path(), short(self.node()))
 
459
        return "%s@%s" % (self.path(), self._changectx)
472
460
 
473
461
    def __repr__(self):
474
 
        return "<filectx %s>" % str(self)
 
462
        return "<%s %s>" % (type(self).__name__, str(self))
475
463
 
476
464
    def __hash__(self):
477
465
        try:
481
469
 
482
470
    def __eq__(self, other):
483
471
        try:
484
 
            return (self._path == other._path
 
472
            return (type(self) == type(other) and self._path == other._path
485
473
                    and self._filenode == other._filenode)
486
474
        except AttributeError:
487
475
            return False
489
477
    def __ne__(self, other):
490
478
        return not (self == other)
491
479
 
492
 
    def filectx(self, fileid):
493
 
        '''opens an arbitrary revision of the file without
494
 
        opening a new filelog'''
495
 
        return filectx(self._repo, self._path, fileid=fileid,
496
 
                       filelog=self._filelog)
497
 
 
498
480
    def filerev(self):
499
481
        return self._filerev
500
482
    def filenode(self):
510
492
    def node(self):
511
493
        return self._changectx.node()
512
494
    def hex(self):
513
 
        return hex(self.node())
 
495
        return self._changectx.hex()
514
496
    def user(self):
515
497
        return self._changectx.user()
516
498
    def date(self):
532
514
    def changectx(self):
533
515
        return self._changectx
534
516
 
535
 
    def data(self):
536
 
        return self._filelog.read(self._filenode)
537
517
    def path(self):
538
518
        return self._path
539
 
    def size(self):
540
 
        return self._filelog.size(self._filerev)
541
519
 
542
520
    def isbinary(self):
543
521
        try:
560
538
 
561
539
        return True
562
540
 
563
 
    def renamed(self):
564
 
        """check if file was actually renamed in this changeset revision
565
 
 
566
 
        If rename logged in file revision, we report copy for changeset only
567
 
        if file revisions linkrev points back to the changeset in question
568
 
        or both changeset parents contain different file revisions.
569
 
        """
570
 
 
571
 
        renamed = self._filelog.renamed(self._filenode)
572
 
        if not renamed:
573
 
            return renamed
574
 
 
575
 
        if self.rev() == self.linkrev():
576
 
            return renamed
577
 
 
578
 
        name = self.path()
579
 
        fnode = self._filenode
580
 
        for p in self._changectx.parents():
581
 
            try:
582
 
                if fnode == p.filenode(name):
583
 
                    return None
584
 
            except error.LookupError:
585
 
                pass
586
 
        return renamed
587
 
 
588
541
    def parents(self):
589
542
        p = self._path
590
543
        fl = self._filelog
606
559
            return p[1]
607
560
        return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
608
561
 
609
 
    def children(self):
610
 
        # hard for renames
611
 
        c = self._filelog.children(self._filenode)
612
 
        return [filectx(self._repo, self._path, fileid=x,
613
 
                        filelog=self._filelog) for x in c]
614
 
 
615
562
    def annotate(self, follow=False, linenumber=None, diffopts=None):
616
563
        '''returns a list of tuples of (ctx, line) for each line
617
564
        in the file, where ctx is the filectx of the node where
783
730
            self._copycache[sc2] = copies.pathcopies(c2)
784
731
        return self._copycache[sc2]
785
732
 
786
 
class workingctx(changectx):
787
 
    """A workingctx object makes access to data related to
788
 
    the current working directory convenient.
789
 
    date - any valid date string or (unixtime, offset), or None.
790
 
    user - username string, or None.
791
 
    extra - a dictionary of extra values, or None.
792
 
    changes - a list of file lists as returned by localrepo.status()
793
 
               or None to use the repository status.
794
 
    """
 
733
class filectx(basefilectx):
 
734
    """A filecontext object makes access to data related to a particular
 
735
       filerevision convenient."""
 
736
    def __init__(self, repo, path, changeid=None, fileid=None,
 
737
                 filelog=None, changectx=None):
 
738
        """changeid can be a changeset revision, node, or tag.
 
739
           fileid can be a file revision or node."""
 
740
        self._repo = repo
 
741
        self._path = path
 
742
 
 
743
        assert (changeid is not None
 
744
                or fileid is not None
 
745
                or changectx is not None), \
 
746
                ("bad args: changeid=%r, fileid=%r, changectx=%r"
 
747
                 % (changeid, fileid, changectx))
 
748
 
 
749
        if filelog is not None:
 
750
            self._filelog = filelog
 
751
 
 
752
        if changeid is not None:
 
753
            self._changeid = changeid
 
754
        if changectx is not None:
 
755
            self._changectx = changectx
 
756
        if fileid is not None:
 
757
            self._fileid = fileid
 
758
 
 
759
    @propertycache
 
760
    def _changectx(self):
 
761
        try:
 
762
            return changectx(self._repo, self._changeid)
 
763
        except error.RepoLookupError:
 
764
            # Linkrev may point to any revision in the repository.  When the
 
765
            # repository is filtered this may lead to `filectx` trying to build
 
766
            # `changectx` for filtered revision. In such case we fallback to
 
767
            # creating `changectx` on the unfiltered version of the reposition.
 
768
            # This fallback should not be an issue because `changectx` from
 
769
            # `filectx` are not used in complex operations that care about
 
770
            # filtering.
 
771
            #
 
772
            # This fallback is a cheap and dirty fix that prevent several
 
773
            # crashes. It does not ensure the behavior is correct. However the
 
774
            # behavior was not correct before filtering either and "incorrect
 
775
            # behavior" is seen as better as "crash"
 
776
            #
 
777
            # Linkrevs have several serious troubles with filtering that are
 
778
            # complicated to solve. Proper handling of the issue here should be
 
779
            # considered when solving linkrev issue are on the table.
 
780
            return changectx(self._repo.unfiltered(), self._changeid)
 
781
 
 
782
    def filectx(self, fileid):
 
783
        '''opens an arbitrary revision of the file without
 
784
        opening a new filelog'''
 
785
        return filectx(self._repo, self._path, fileid=fileid,
 
786
                       filelog=self._filelog)
 
787
 
 
788
    def data(self):
 
789
        return self._filelog.read(self._filenode)
 
790
    def size(self):
 
791
        return self._filelog.size(self._filerev)
 
792
 
 
793
    def renamed(self):
 
794
        """check if file was actually renamed in this changeset revision
 
795
 
 
796
        If rename logged in file revision, we report copy for changeset only
 
797
        if file revisions linkrev points back to the changeset in question
 
798
        or both changeset parents contain different file revisions.
 
799
        """
 
800
 
 
801
        renamed = self._filelog.renamed(self._filenode)
 
802
        if not renamed:
 
803
            return renamed
 
804
 
 
805
        if self.rev() == self.linkrev():
 
806
            return renamed
 
807
 
 
808
        name = self.path()
 
809
        fnode = self._filenode
 
810
        for p in self._changectx.parents():
 
811
            try:
 
812
                if fnode == p.filenode(name):
 
813
                    return None
 
814
            except error.LookupError:
 
815
                pass
 
816
        return renamed
 
817
 
 
818
    def children(self):
 
819
        # hard for renames
 
820
        c = self._filelog.children(self._filenode)
 
821
        return [filectx(self._repo, self._path, fileid=x,
 
822
                        filelog=self._filelog) for x in c]
 
823
 
 
824
class committablectx(basectx):
 
825
    """A committablectx object provides common functionality for a context that
 
826
    wants the ability to commit, e.g. workingctx or memctx."""
795
827
    def __init__(self, repo, text="", user=None, date=None, extra=None,
796
828
                 changes=None):
797
829
        self._repo = repo
827
859
    def __str__(self):
828
860
        return str(self._parents[0]) + "+"
829
861
 
830
 
    def __repr__(self):
831
 
        return "<workingctx %s>" % str(self)
832
 
 
833
862
    def __nonzero__(self):
834
863
        return True
835
864
 
904
933
 
905
934
        return man
906
935
 
907
 
    def __iter__(self):
908
 
        d = self._repo.dirstate
909
 
        for f in d:
910
 
            if d[f] != 'r':
911
 
                yield f
912
 
 
913
936
    @propertycache
914
937
    def _status(self):
915
938
        return self._repo.status()[:4]
922
945
    def _date(self):
923
946
        return util.makedate()
924
947
 
925
 
    @propertycache
926
 
    def _parents(self):
927
 
        p = self._repo.dirstate.parents()
928
 
        if p[1] == nullid:
929
 
            p = p[:-1]
930
 
        return [changectx(self._repo, x) for x in p]
931
 
 
932
948
    def status(self, ignored=False, clean=False, unknown=False):
933
949
        """Explicit status query
934
950
        Unless this method is used to query the working copy status, the
945
961
        self._status = stat[:4]
946
962
        return stat
947
963
 
948
 
    def manifest(self):
949
 
        return self._manifest
950
964
    def user(self):
951
965
        return self._user or self._repo.ui.username()
952
966
    def date(self):
1016
1030
        except OSError:
1017
1031
            return ''
1018
1032
 
1019
 
    def filectx(self, path, filelog=None):
1020
 
        """get a file context from the working directory"""
1021
 
        return workingfilectx(self._repo, path, workingctx=self,
1022
 
                              filelog=filelog)
1023
 
 
1024
1033
    def ancestor(self, c2):
1025
1034
        """return the ancestor context of self and c2"""
1026
1035
        return self._parents[0].ancestor(c2) # punt on two parents for now
1029
1038
        return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1030
1039
                                               True, False))
1031
1040
 
 
1041
    def ancestors(self):
 
1042
        for a in self._repo.changelog.ancestors(
 
1043
            [p.rev() for p in self._parents]):
 
1044
            yield changectx(self._repo, a)
 
1045
 
 
1046
    def markcommitted(self, node):
 
1047
        """Perform post-commit cleanup necessary after committing this ctx
 
1048
 
 
1049
        Specifically, this updates backing stores this working context
 
1050
        wraps to reflect the fact that the changes reflected by this
 
1051
        workingctx have been committed.  For example, it marks
 
1052
        modified and added files as normal in the dirstate.
 
1053
 
 
1054
        """
 
1055
 
 
1056
        for f in self.modified() + self.added():
 
1057
            self._repo.dirstate.normal(f)
 
1058
        for f in self.removed():
 
1059
            self._repo.dirstate.drop(f)
 
1060
        self._repo.dirstate.setparents(node)
 
1061
 
 
1062
    def dirs(self):
 
1063
        return self._repo.dirstate.dirs()
 
1064
 
 
1065
class workingctx(committablectx):
 
1066
    """A workingctx object makes access to data related to
 
1067
    the current working directory convenient.
 
1068
    date - any valid date string or (unixtime, offset), or None.
 
1069
    user - username string, or None.
 
1070
    extra - a dictionary of extra values, or None.
 
1071
    changes - a list of file lists as returned by localrepo.status()
 
1072
               or None to use the repository status.
 
1073
    """
 
1074
    def __init__(self, repo, text="", user=None, date=None, extra=None,
 
1075
                 changes=None):
 
1076
        super(workingctx, self).__init__(repo, text, user, date, extra, changes)
 
1077
 
 
1078
    def __iter__(self):
 
1079
        d = self._repo.dirstate
 
1080
        for f in d:
 
1081
            if d[f] != 'r':
 
1082
                yield f
 
1083
 
 
1084
    @propertycache
 
1085
    def _parents(self):
 
1086
        p = self._repo.dirstate.parents()
 
1087
        if p[1] == nullid:
 
1088
            p = p[:-1]
 
1089
        return [changectx(self._repo, x) for x in p]
 
1090
 
 
1091
    def filectx(self, path, filelog=None):
 
1092
        """get a file context from the working directory"""
 
1093
        return workingfilectx(self._repo, path, workingctx=self,
 
1094
                              filelog=filelog)
 
1095
 
1032
1096
    def dirty(self, missing=False, merge=True, branch=True):
1033
1097
        "check whether a working directory is modified"
1034
1098
        # check subrepos first
1047
1111
        ui, ds = self._repo.ui, self._repo.dirstate
1048
1112
        try:
1049
1113
            rejected = []
 
1114
            lstat = self._repo.wvfs.lstat
1050
1115
            for f in list:
1051
1116
                scmutil.checkportable(ui, join(f))
1052
 
                p = self._repo.wjoin(f)
1053
1117
                try:
1054
 
                    st = os.lstat(p)
 
1118
                    st = lstat(f)
1055
1119
                except OSError:
1056
1120
                    ui.warn(_("%s does not exist!\n") % join(f))
1057
1121
                    rejected.append(f)
1065
1129
                if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1066
1130
                    ui.warn(_("%s not added: only files and symlinks "
1067
1131
                              "supported currently\n") % join(f))
1068
 
                    rejected.append(p)
 
1132
                    rejected.append(f)
1069
1133
                elif ds[f] in 'amn':
1070
1134
                    ui.warn(_("%s already tracked!\n") % join(f))
1071
1135
                elif ds[f] == 'r':
1093
1157
        finally:
1094
1158
            wlock.release()
1095
1159
 
1096
 
    def ancestors(self):
1097
 
        for a in self._repo.changelog.ancestors(
1098
 
            [p.rev() for p in self._parents]):
1099
 
            yield changectx(self._repo, a)
1100
 
 
1101
1160
    def undelete(self, list):
1102
1161
        pctxs = self.parents()
1103
1162
        wlock = self._repo.wlock()
1114
1173
            wlock.release()
1115
1174
 
1116
1175
    def copy(self, source, dest):
1117
 
        p = self._repo.wjoin(dest)
1118
 
        if not os.path.lexists(p):
 
1176
        try:
 
1177
            st = self._repo.wvfs.lstat(dest)
 
1178
        except OSError, err:
 
1179
            if err.errno != errno.ENOENT:
 
1180
                raise
1119
1181
            self._repo.ui.warn(_("%s does not exist!\n") % dest)
1120
 
        elif not (os.path.isfile(p) or os.path.islink(p)):
 
1182
            return
 
1183
        if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1121
1184
            self._repo.ui.warn(_("copy failed: %s is not a file or a "
1122
1185
                                 "symbolic link\n") % dest)
1123
1186
        else:
1129
1192
            finally:
1130
1193
                wlock.release()
1131
1194
 
1132
 
    def markcommitted(self, node):
1133
 
        """Perform post-commit cleanup necessary after committing this ctx
1134
 
 
1135
 
        Specifically, this updates backing stores this working context
1136
 
        wraps to reflect the fact that the changes reflected by this
1137
 
        workingctx have been committed.  For example, it marks
1138
 
        modified and added files as normal in the dirstate.
1139
 
 
1140
 
        """
1141
 
 
1142
 
        for f in self.modified() + self.added():
1143
 
            self._repo.dirstate.normal(f)
1144
 
        for f in self.removed():
1145
 
            self._repo.dirstate.drop(f)
1146
 
        self._repo.dirstate.setparents(node)
1147
 
 
1148
 
    def dirs(self):
1149
 
        return self._repo.dirstate.dirs()
1150
 
 
1151
 
class workingfilectx(filectx):
1152
 
    """A workingfilectx object makes access to data related to a particular
1153
 
       file in the working directory convenient."""
1154
 
    def __init__(self, repo, path, filelog=None, workingctx=None):
1155
 
        """changeid can be a changeset revision, node, or tag.
1156
 
           fileid can be a file revision or node."""
 
1195
class committablefilectx(basefilectx):
 
1196
    """A committablefilectx provides common functionality for a file context
 
1197
    that wants the ability to commit, e.g. workingfilectx or memfilectx."""
 
1198
    def __init__(self, repo, path, filelog=None, ctx=None):
1157
1199
        self._repo = repo
1158
1200
        self._path = path
1159
1201
        self._changeid = None
1161
1203
 
1162
1204
        if filelog is not None:
1163
1205
            self._filelog = filelog
1164
 
        if workingctx:
1165
 
            self._changectx = workingctx
1166
 
 
1167
 
    @propertycache
1168
 
    def _changectx(self):
1169
 
        return workingctx(self._repo)
 
1206
        if ctx:
 
1207
            self._changectx = ctx
1170
1208
 
1171
1209
    def __nonzero__(self):
1172
1210
        return True
1173
1211
 
1174
 
    def __str__(self):
1175
 
        return "%s@%s" % (self.path(), self._changectx)
1176
 
 
1177
 
    def __repr__(self):
1178
 
        return "<workingfilectx %s>" % str(self)
1179
 
 
1180
 
    def data(self):
1181
 
        return self._repo.wread(self._path)
1182
 
    def renamed(self):
1183
 
        rp = self._repo.dirstate.copied(self._path)
1184
 
        if not rp:
1185
 
            return None
1186
 
        return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1187
 
 
1188
1212
    def parents(self):
1189
1213
        '''return parent filectxs, following copies if necessary'''
1190
1214
        def filenode(ctx, path):
1209
1233
    def children(self):
1210
1234
        return []
1211
1235
 
 
1236
class workingfilectx(committablefilectx):
 
1237
    """A workingfilectx object makes access to data related to a particular
 
1238
       file in the working directory convenient."""
 
1239
    def __init__(self, repo, path, filelog=None, workingctx=None):
 
1240
        super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
 
1241
 
 
1242
    @propertycache
 
1243
    def _changectx(self):
 
1244
        return workingctx(self._repo)
 
1245
 
 
1246
    def data(self):
 
1247
        return self._repo.wread(self._path)
 
1248
    def renamed(self):
 
1249
        rp = self._repo.dirstate.copied(self._path)
 
1250
        if not rp:
 
1251
            return None
 
1252
        return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
 
1253
 
1212
1254
    def size(self):
1213
 
        return os.lstat(self._repo.wjoin(self._path)).st_size
 
1255
        return self._repo.wvfs.lstat(self._path).st_size
1214
1256
    def date(self):
1215
1257
        t, tz = self._changectx.date()
1216
1258
        try:
1217
 
            return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
 
1259
            return (int(self._repo.wvfs.lstat(self._path).st_mtime), tz)
1218
1260
        except OSError, err:
1219
1261
            if err.errno != errno.ENOENT:
1220
1262
                raise