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