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