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