17
17
propertycache = util.propertycache
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
24
memctx: a context that represents changes in-memory and can also
26
def __new__(cls, repo, changeid='', *args, **kwargs):
27
if isinstance(changeid, basectx):
30
o = super(basectx, cls).__new__(cls)
39
return short(self.node())
45
return "<%s %s>" % (type(self).__name__, str(self))
47
def __eq__(self, other):
49
return type(self) == type(other) and self._rev == other._rev
50
except AttributeError:
53
def __ne__(self, other):
54
return not (self == other)
56
def __contains__(self, key):
57
return key in self._manifest
59
def __getitem__(self, key):
60
return self.filectx(key)
63
for f in sorted(self._manifest):
68
return subrepo.state(self, self._repo.ui)
75
return hex(self.node())
79
return phases.phasenames[self.phase()]
81
return self.phase() > phases.public
84
"""True if the changeset is obsolete"""
85
return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
88
"""True if the changeset is extinct"""
89
return self.rev() in obsmod.getrevs(self._repo, 'extinct')
92
"""True if the changeset is not obsolete but it's ancestor are"""
93
return self.rev() in obsmod.getrevs(self._repo, 'unstable')
96
"""True if the changeset try to be a successor of a public changeset
98
Only non-public and non-obsolete changesets may be bumped.
100
return self.rev() in obsmod.getrevs(self._repo, 'bumped')
103
"""Is a successors of a changeset with multiple possible successors set
105
Only non-public and non-obsolete changesets may be divergent.
107
return self.rev() in obsmod.getrevs(self._repo, 'divergent')
110
"""True if the changeset is either unstable, bumped or divergent"""
111
return self.unstable() or self.bumped() or self.divergent()
114
"""return the list of troubles affecting this changesets.
116
Troubles are returned as strings. possible values are:
123
troubles.append('unstable')
125
troubles.append('bumped')
127
troubles.append('divergent')
131
"""return contexts for each parent changeset"""
135
return self._parents[0]
138
if len(self._parents) == 2:
139
return self._parents[1]
140
return changectx(self._repo, -1)
142
def _fileinfo(self, path):
143
if '_manifest' in self.__dict__:
145
return self._manifest[path], self._manifest.flags(path)
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)
155
raise error.ManifestLookupError(self._node, path,
156
_('not found in manifest'))
160
def filenode(self, path):
161
return self._fileinfo(path)[0]
163
def flags(self, path):
165
return self._fileinfo(path)[1]
166
except error.LookupError:
170
return subrepo.subrepo(self, path)
172
def match(self, pats=[], include=None, exclude=None, default='glob'):
174
return matchmod.match(r.root, r.getcwd(), pats,
175
include, exclude, default,
176
auditor=r.auditor, ctx=self)
178
def diff(self, ctx2=None, match=None, **opts):
179
"""Returns a diff generator for the given contexts and matcher"""
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)
190
return scmutil.dirs(self._manifest)
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
22
202
def __init__(self, repo, changeid=''):
23
203
"""changeid is a revision number, node, or tag"""
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):
24
210
if changeid == '':
239
369
for d in self._repo.changelog.descendants([self._rev]):
240
370
yield changectx(self._repo, d)
243
"""True if the changeset is obsolete"""
244
return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
247
"""True if the changeset is extinct"""
248
return self.rev() in obsmod.getrevs(self._repo, 'extinct')
251
"""True if the changeset is not obsolete but it's ancestor are"""
252
return self.rev() in obsmod.getrevs(self._repo, 'unstable')
255
"""True if the changeset try to be a successor of a public changeset
257
Only non-public and non-obsolete changesets may be bumped.
259
return self.rev() in obsmod.getrevs(self._repo, 'bumped')
262
"""Is a successors of a changeset with multiple possible successors set
264
Only non-public and non-obsolete changesets may be divergent.
266
return self.rev() in obsmod.getrevs(self._repo, 'divergent')
269
"""True if the changeset is either unstable, bumped or divergent"""
270
return self.unstable() or self.bumped() or self.divergent()
273
"""return the list of troubles affecting this changesets.
275
Troubles are returned as strings. possible values are:
282
troubles.append('unstable')
284
troubles.append('bumped')
286
troubles.append('divergent')
289
def _fileinfo(self, path):
290
if '_manifest' in self.__dict__:
292
return self._manifest[path], self._manifest.flags(path)
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)
302
raise error.ManifestLookupError(self._node, path,
303
_('not found in manifest'))
307
def filenode(self, path):
308
return self._fileinfo(path)[0]
310
def flags(self, path):
312
return self._fileinfo(path)[1]
313
except error.LookupError:
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):
357
return subrepo.subrepo(self, path)
359
def match(self, pats=[], include=None, exclude=None, default='glob'):
361
return matchmod.match(r.root, r.getcwd(), pats,
362
include, exclude, default,
363
auditor=r.auditor, ctx=self)
365
def diff(self, ctx2=None, match=None, **opts):
366
"""Returns a diff generator for the given contexts and matcher"""
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)
377
return scmutil.dirs(self._manifest)
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."""
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))
401
if filelog is not None:
402
self._filelog = filelog
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
412
def _changectx(self):
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
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"
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
416
workingfilectx: a filecontext that represents files from the working
418
memfilectx: a filecontext that represents files in-memory."""
419
def __new__(cls, repo, path, *args, **kwargs):
420
return super(basefilectx, cls).__new__(cls)
435
423
def _filelog(self):
783
730
self._copycache[sc2] = copies.pathcopies(c2)
784
731
return self._copycache[sc2]
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.
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."""
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))
749
if filelog is not None:
750
self._filelog = filelog
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
760
def _changectx(self):
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
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"
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)
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)
789
return self._filelog.read(self._filenode)
791
return self._filelog.size(self._filerev)
794
"""check if file was actually renamed in this changeset revision
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.
801
renamed = self._filelog.renamed(self._filenode)
805
if self.rev() == self.linkrev():
809
fnode = self._filenode
810
for p in self._changectx.parents():
812
if fnode == p.filenode(name):
814
except error.LookupError:
820
c = self._filelog.children(self._filenode)
821
return [filectx(self._repo, self._path, fileid=x,
822
filelog=self._filelog) for x in c]
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,
797
829
self._repo = repo
1029
1038
return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
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)
1046
def markcommitted(self, node):
1047
"""Perform post-commit cleanup necessary after committing this ctx
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.
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)
1063
return self._repo.dirstate.dirs()
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.
1074
def __init__(self, repo, text="", user=None, date=None, extra=None,
1076
super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1079
d = self._repo.dirstate
1086
p = self._repo.dirstate.parents()
1089
return [changectx(self._repo, x) for x in p]
1091
def filectx(self, path, filelog=None):
1092
"""get a file context from the working directory"""
1093
return workingfilectx(self._repo, path, workingctx=self,
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
1130
1193
wlock.release()
1132
def markcommitted(self, node):
1133
"""Perform post-commit cleanup necessary after committing this ctx
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.
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)
1149
return self._repo.dirstate.dirs()
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
1209
1233
def children(self):
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)
1243
def _changectx(self):
1244
return workingctx(self._repo)
1247
return self._repo.wread(self._path)
1249
rp = self._repo.dirstate.copied(self._path)
1252
return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
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()
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: