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