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