##// END OF EJS Templates
filecontext: use 'is not None' to check for filelog existence...
Durham Goode -
r19149:921b64e1 default
parent child Browse files
Show More
@@ -1,1379 +1,1381 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 is not None:
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 '_changeid' in self.__dict__:
441 return self._changeid
442 elif '_changectx' in self.__dict__:
441 return self._changectx.rev()
443 return self._changectx.rev()
442 else:
444 else:
443 return self._filelog.linkrev(self._filerev)
445 return self._filelog.linkrev(self._filerev)
444
446
445 @propertycache
447 @propertycache
446 def _filenode(self):
448 def _filenode(self):
447 if '_fileid' in self.__dict__:
449 if '_fileid' in self.__dict__:
448 return self._filelog.lookup(self._fileid)
450 return self._filelog.lookup(self._fileid)
449 else:
451 else:
450 return self._changectx.filenode(self._path)
452 return self._changectx.filenode(self._path)
451
453
452 @propertycache
454 @propertycache
453 def _filerev(self):
455 def _filerev(self):
454 return self._filelog.rev(self._filenode)
456 return self._filelog.rev(self._filenode)
455
457
456 @propertycache
458 @propertycache
457 def _repopath(self):
459 def _repopath(self):
458 return self._path
460 return self._path
459
461
460 def __nonzero__(self):
462 def __nonzero__(self):
461 try:
463 try:
462 self._filenode
464 self._filenode
463 return True
465 return True
464 except error.LookupError:
466 except error.LookupError:
465 # file is missing
467 # file is missing
466 return False
468 return False
467
469
468 def __str__(self):
470 def __str__(self):
469 return "%s@%s" % (self.path(), short(self.node()))
471 return "%s@%s" % (self.path(), short(self.node()))
470
472
471 def __repr__(self):
473 def __repr__(self):
472 return "<filectx %s>" % str(self)
474 return "<filectx %s>" % str(self)
473
475
474 def __hash__(self):
476 def __hash__(self):
475 try:
477 try:
476 return hash((self._path, self._filenode))
478 return hash((self._path, self._filenode))
477 except AttributeError:
479 except AttributeError:
478 return id(self)
480 return id(self)
479
481
480 def __eq__(self, other):
482 def __eq__(self, other):
481 try:
483 try:
482 return (self._path == other._path
484 return (self._path == other._path
483 and self._filenode == other._filenode)
485 and self._filenode == other._filenode)
484 except AttributeError:
486 except AttributeError:
485 return False
487 return False
486
488
487 def __ne__(self, other):
489 def __ne__(self, other):
488 return not (self == other)
490 return not (self == other)
489
491
490 def filectx(self, fileid):
492 def filectx(self, fileid):
491 '''opens an arbitrary revision of the file without
493 '''opens an arbitrary revision of the file without
492 opening a new filelog'''
494 opening a new filelog'''
493 return filectx(self._repo, self._path, fileid=fileid,
495 return filectx(self._repo, self._path, fileid=fileid,
494 filelog=self._filelog)
496 filelog=self._filelog)
495
497
496 def filerev(self):
498 def filerev(self):
497 return self._filerev
499 return self._filerev
498 def filenode(self):
500 def filenode(self):
499 return self._filenode
501 return self._filenode
500 def flags(self):
502 def flags(self):
501 return self._changectx.flags(self._path)
503 return self._changectx.flags(self._path)
502 def filelog(self):
504 def filelog(self):
503 return self._filelog
505 return self._filelog
504
506
505 def rev(self):
507 def rev(self):
506 if '_changectx' in self.__dict__:
508 if '_changectx' in self.__dict__:
507 return self._changectx.rev()
509 return self._changectx.rev()
508 if '_changeid' in self.__dict__:
510 if '_changeid' in self.__dict__:
509 return self._changectx.rev()
511 return self._changectx.rev()
510 return self._filelog.linkrev(self._filerev)
512 return self._filelog.linkrev(self._filerev)
511
513
512 def linkrev(self):
514 def linkrev(self):
513 return self._filelog.linkrev(self._filerev)
515 return self._filelog.linkrev(self._filerev)
514 def node(self):
516 def node(self):
515 return self._changectx.node()
517 return self._changectx.node()
516 def hex(self):
518 def hex(self):
517 return hex(self.node())
519 return hex(self.node())
518 def user(self):
520 def user(self):
519 return self._changectx.user()
521 return self._changectx.user()
520 def date(self):
522 def date(self):
521 return self._changectx.date()
523 return self._changectx.date()
522 def files(self):
524 def files(self):
523 return self._changectx.files()
525 return self._changectx.files()
524 def description(self):
526 def description(self):
525 return self._changectx.description()
527 return self._changectx.description()
526 def branch(self):
528 def branch(self):
527 return self._changectx.branch()
529 return self._changectx.branch()
528 def extra(self):
530 def extra(self):
529 return self._changectx.extra()
531 return self._changectx.extra()
530 def phase(self):
532 def phase(self):
531 return self._changectx.phase()
533 return self._changectx.phase()
532 def phasestr(self):
534 def phasestr(self):
533 return self._changectx.phasestr()
535 return self._changectx.phasestr()
534 def manifest(self):
536 def manifest(self):
535 return self._changectx.manifest()
537 return self._changectx.manifest()
536 def changectx(self):
538 def changectx(self):
537 return self._changectx
539 return self._changectx
538
540
539 def data(self):
541 def data(self):
540 return self._filelog.read(self._filenode)
542 return self._filelog.read(self._filenode)
541 def path(self):
543 def path(self):
542 return self._path
544 return self._path
543 def size(self):
545 def size(self):
544 return self._filelog.size(self._filerev)
546 return self._filelog.size(self._filerev)
545
547
546 def isbinary(self):
548 def isbinary(self):
547 try:
549 try:
548 return util.binary(self.data())
550 return util.binary(self.data())
549 except IOError:
551 except IOError:
550 return False
552 return False
551
553
552 def cmp(self, fctx):
554 def cmp(self, fctx):
553 """compare with other file context
555 """compare with other file context
554
556
555 returns True if different than fctx.
557 returns True if different than fctx.
556 """
558 """
557 if (fctx._filerev is None
559 if (fctx._filerev is None
558 and (self._repo._encodefilterpats
560 and (self._repo._encodefilterpats
559 # if file data starts with '\1\n', empty metadata block is
561 # if file data starts with '\1\n', empty metadata block is
560 # prepended, which adds 4 bytes to filelog.size().
562 # prepended, which adds 4 bytes to filelog.size().
561 or self.size() - 4 == fctx.size())
563 or self.size() - 4 == fctx.size())
562 or self.size() == fctx.size()):
564 or self.size() == fctx.size()):
563 return self._filelog.cmp(self._filenode, fctx.data())
565 return self._filelog.cmp(self._filenode, fctx.data())
564
566
565 return True
567 return True
566
568
567 def renamed(self):
569 def renamed(self):
568 """check if file was actually renamed in this changeset revision
570 """check if file was actually renamed in this changeset revision
569
571
570 If rename logged in file revision, we report copy for changeset only
572 If rename logged in file revision, we report copy for changeset only
571 if file revisions linkrev points back to the changeset in question
573 if file revisions linkrev points back to the changeset in question
572 or both changeset parents contain different file revisions.
574 or both changeset parents contain different file revisions.
573 """
575 """
574
576
575 renamed = self._filelog.renamed(self._filenode)
577 renamed = self._filelog.renamed(self._filenode)
576 if not renamed:
578 if not renamed:
577 return renamed
579 return renamed
578
580
579 if self.rev() == self.linkrev():
581 if self.rev() == self.linkrev():
580 return renamed
582 return renamed
581
583
582 name = self.path()
584 name = self.path()
583 fnode = self._filenode
585 fnode = self._filenode
584 for p in self._changectx.parents():
586 for p in self._changectx.parents():
585 try:
587 try:
586 if fnode == p.filenode(name):
588 if fnode == p.filenode(name):
587 return None
589 return None
588 except error.LookupError:
590 except error.LookupError:
589 pass
591 pass
590 return renamed
592 return renamed
591
593
592 def parents(self):
594 def parents(self):
593 p = self._path
595 p = self._path
594 fl = self._filelog
596 fl = self._filelog
595 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
597 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
596
598
597 r = self._filelog.renamed(self._filenode)
599 r = self._filelog.renamed(self._filenode)
598 if r:
600 if r:
599 pl[0] = (r[0], r[1], None)
601 pl[0] = (r[0], r[1], None)
600
602
601 return [filectx(self._repo, p, fileid=n, filelog=l)
603 return [filectx(self._repo, p, fileid=n, filelog=l)
602 for p, n, l in pl if n != nullid]
604 for p, n, l in pl if n != nullid]
603
605
604 def p1(self):
606 def p1(self):
605 return self.parents()[0]
607 return self.parents()[0]
606
608
607 def p2(self):
609 def p2(self):
608 p = self.parents()
610 p = self.parents()
609 if len(p) == 2:
611 if len(p) == 2:
610 return p[1]
612 return p[1]
611 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
613 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
612
614
613 def children(self):
615 def children(self):
614 # hard for renames
616 # hard for renames
615 c = self._filelog.children(self._filenode)
617 c = self._filelog.children(self._filenode)
616 return [filectx(self._repo, self._path, fileid=x,
618 return [filectx(self._repo, self._path, fileid=x,
617 filelog=self._filelog) for x in c]
619 filelog=self._filelog) for x in c]
618
620
619 def annotate(self, follow=False, linenumber=None, diffopts=None):
621 def annotate(self, follow=False, linenumber=None, diffopts=None):
620 '''returns a list of tuples of (ctx, line) for each line
622 '''returns a list of tuples of (ctx, line) for each line
621 in the file, where ctx is the filectx of the node where
623 in the file, where ctx is the filectx of the node where
622 that line was last changed.
624 that line was last changed.
623 This returns tuples of ((ctx, linenumber), line) for each line,
625 This returns tuples of ((ctx, linenumber), line) for each line,
624 if "linenumber" parameter is NOT "None".
626 if "linenumber" parameter is NOT "None".
625 In such tuples, linenumber means one at the first appearance
627 In such tuples, linenumber means one at the first appearance
626 in the managed file.
628 in the managed file.
627 To reduce annotation cost,
629 To reduce annotation cost,
628 this returns fixed value(False is used) as linenumber,
630 this returns fixed value(False is used) as linenumber,
629 if "linenumber" parameter is "False".'''
631 if "linenumber" parameter is "False".'''
630
632
631 def decorate_compat(text, rev):
633 def decorate_compat(text, rev):
632 return ([rev] * len(text.splitlines()), text)
634 return ([rev] * len(text.splitlines()), text)
633
635
634 def without_linenumber(text, rev):
636 def without_linenumber(text, rev):
635 return ([(rev, False)] * len(text.splitlines()), text)
637 return ([(rev, False)] * len(text.splitlines()), text)
636
638
637 def with_linenumber(text, rev):
639 def with_linenumber(text, rev):
638 size = len(text.splitlines())
640 size = len(text.splitlines())
639 return ([(rev, i) for i in xrange(1, size + 1)], text)
641 return ([(rev, i) for i in xrange(1, size + 1)], text)
640
642
641 decorate = (((linenumber is None) and decorate_compat) or
643 decorate = (((linenumber is None) and decorate_compat) or
642 (linenumber and with_linenumber) or
644 (linenumber and with_linenumber) or
643 without_linenumber)
645 without_linenumber)
644
646
645 def pair(parent, child):
647 def pair(parent, child):
646 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
648 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
647 refine=True)
649 refine=True)
648 for (a1, a2, b1, b2), t in blocks:
650 for (a1, a2, b1, b2), t in blocks:
649 # Changed blocks ('!') or blocks made only of blank lines ('~')
651 # Changed blocks ('!') or blocks made only of blank lines ('~')
650 # belong to the child.
652 # belong to the child.
651 if t == '=':
653 if t == '=':
652 child[0][b1:b2] = parent[0][a1:a2]
654 child[0][b1:b2] = parent[0][a1:a2]
653 return child
655 return child
654
656
655 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
657 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
656 def getctx(path, fileid):
658 def getctx(path, fileid):
657 log = path == self._path and self._filelog or getlog(path)
659 log = path == self._path and self._filelog or getlog(path)
658 return filectx(self._repo, path, fileid=fileid, filelog=log)
660 return filectx(self._repo, path, fileid=fileid, filelog=log)
659 getctx = util.lrucachefunc(getctx)
661 getctx = util.lrucachefunc(getctx)
660
662
661 def parents(f):
663 def parents(f):
662 # we want to reuse filectx objects as much as possible
664 # we want to reuse filectx objects as much as possible
663 p = f._path
665 p = f._path
664 if f._filerev is None: # working dir
666 if f._filerev is None: # working dir
665 pl = [(n.path(), n.filerev()) for n in f.parents()]
667 pl = [(n.path(), n.filerev()) for n in f.parents()]
666 else:
668 else:
667 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
669 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
668
670
669 if follow:
671 if follow:
670 r = f.renamed()
672 r = f.renamed()
671 if r:
673 if r:
672 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
674 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
673
675
674 return [getctx(p, n) for p, n in pl if n != nullrev]
676 return [getctx(p, n) for p, n in pl if n != nullrev]
675
677
676 # use linkrev to find the first changeset where self appeared
678 # use linkrev to find the first changeset where self appeared
677 if self.rev() != self.linkrev():
679 if self.rev() != self.linkrev():
678 base = self.filectx(self.filerev())
680 base = self.filectx(self.filerev())
679 else:
681 else:
680 base = self
682 base = self
681
683
682 # This algorithm would prefer to be recursive, but Python is a
684 # This algorithm would prefer to be recursive, but Python is a
683 # bit recursion-hostile. Instead we do an iterative
685 # bit recursion-hostile. Instead we do an iterative
684 # depth-first search.
686 # depth-first search.
685
687
686 visit = [base]
688 visit = [base]
687 hist = {}
689 hist = {}
688 pcache = {}
690 pcache = {}
689 needed = {base: 1}
691 needed = {base: 1}
690 while visit:
692 while visit:
691 f = visit[-1]
693 f = visit[-1]
692 pcached = f in pcache
694 pcached = f in pcache
693 if not pcached:
695 if not pcached:
694 pcache[f] = parents(f)
696 pcache[f] = parents(f)
695
697
696 ready = True
698 ready = True
697 pl = pcache[f]
699 pl = pcache[f]
698 for p in pl:
700 for p in pl:
699 if p not in hist:
701 if p not in hist:
700 ready = False
702 ready = False
701 visit.append(p)
703 visit.append(p)
702 if not pcached:
704 if not pcached:
703 needed[p] = needed.get(p, 0) + 1
705 needed[p] = needed.get(p, 0) + 1
704 if ready:
706 if ready:
705 visit.pop()
707 visit.pop()
706 reusable = f in hist
708 reusable = f in hist
707 if reusable:
709 if reusable:
708 curr = hist[f]
710 curr = hist[f]
709 else:
711 else:
710 curr = decorate(f.data(), f)
712 curr = decorate(f.data(), f)
711 for p in pl:
713 for p in pl:
712 if not reusable:
714 if not reusable:
713 curr = pair(hist[p], curr)
715 curr = pair(hist[p], curr)
714 if needed[p] == 1:
716 if needed[p] == 1:
715 del hist[p]
717 del hist[p]
716 del needed[p]
718 del needed[p]
717 else:
719 else:
718 needed[p] -= 1
720 needed[p] -= 1
719
721
720 hist[f] = curr
722 hist[f] = curr
721 pcache[f] = []
723 pcache[f] = []
722
724
723 return zip(hist[base][0], hist[base][1].splitlines(True))
725 return zip(hist[base][0], hist[base][1].splitlines(True))
724
726
725 def ancestor(self, fc2, actx):
727 def ancestor(self, fc2, actx):
726 """
728 """
727 find the common ancestor file context, if any, of self, and fc2
729 find the common ancestor file context, if any, of self, and fc2
728
730
729 actx must be the changectx of the common ancestor
731 actx must be the changectx of the common ancestor
730 of self's and fc2's respective changesets.
732 of self's and fc2's respective changesets.
731 """
733 """
732
734
733 # the easy case: no (relevant) renames
735 # the easy case: no (relevant) renames
734 if fc2.path() == self.path() and self.path() in actx:
736 if fc2.path() == self.path() and self.path() in actx:
735 return actx[self.path()]
737 return actx[self.path()]
736
738
737 # the next easiest cases: unambiguous predecessor (name trumps
739 # the next easiest cases: unambiguous predecessor (name trumps
738 # history)
740 # history)
739 if self.path() in actx and fc2.path() not in actx:
741 if self.path() in actx and fc2.path() not in actx:
740 return actx[self.path()]
742 return actx[self.path()]
741 if fc2.path() in actx and self.path() not in actx:
743 if fc2.path() in actx and self.path() not in actx:
742 return actx[fc2.path()]
744 return actx[fc2.path()]
743
745
744 # prime the ancestor cache for the working directory
746 # prime the ancestor cache for the working directory
745 acache = {}
747 acache = {}
746 for c in (self, fc2):
748 for c in (self, fc2):
747 if c._filerev is None:
749 if c._filerev is None:
748 pl = [(n.path(), n.filenode()) for n in c.parents()]
750 pl = [(n.path(), n.filenode()) for n in c.parents()]
749 acache[(c._path, None)] = pl
751 acache[(c._path, None)] = pl
750
752
751 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
753 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
752 def parents(vertex):
754 def parents(vertex):
753 if vertex in acache:
755 if vertex in acache:
754 return acache[vertex]
756 return acache[vertex]
755 f, n = vertex
757 f, n = vertex
756 if f not in flcache:
758 if f not in flcache:
757 flcache[f] = self._repo.file(f)
759 flcache[f] = self._repo.file(f)
758 fl = flcache[f]
760 fl = flcache[f]
759 pl = [(f, p) for p in fl.parents(n) if p != nullid]
761 pl = [(f, p) for p in fl.parents(n) if p != nullid]
760 re = fl.renamed(n)
762 re = fl.renamed(n)
761 if re:
763 if re:
762 pl.append(re)
764 pl.append(re)
763 acache[vertex] = pl
765 acache[vertex] = pl
764 return pl
766 return pl
765
767
766 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
768 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
767 v = ancestor.genericancestor(a, b, parents)
769 v = ancestor.genericancestor(a, b, parents)
768 if v:
770 if v:
769 f, n = v
771 f, n = v
770 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
772 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
771
773
772 return None
774 return None
773
775
774 def ancestors(self, followfirst=False):
776 def ancestors(self, followfirst=False):
775 visit = {}
777 visit = {}
776 c = self
778 c = self
777 cut = followfirst and 1 or None
779 cut = followfirst and 1 or None
778 while True:
780 while True:
779 for parent in c.parents()[:cut]:
781 for parent in c.parents()[:cut]:
780 visit[(parent.rev(), parent.node())] = parent
782 visit[(parent.rev(), parent.node())] = parent
781 if not visit:
783 if not visit:
782 break
784 break
783 c = visit.pop(max(visit))
785 c = visit.pop(max(visit))
784 yield c
786 yield c
785
787
786 def copies(self, c2):
788 def copies(self, c2):
787 if not util.safehasattr(self, "_copycache"):
789 if not util.safehasattr(self, "_copycache"):
788 self._copycache = {}
790 self._copycache = {}
789 sc2 = str(c2)
791 sc2 = str(c2)
790 if sc2 not in self._copycache:
792 if sc2 not in self._copycache:
791 self._copycache[sc2] = copies.pathcopies(c2)
793 self._copycache[sc2] = copies.pathcopies(c2)
792 return self._copycache[sc2]
794 return self._copycache[sc2]
793
795
794 class workingctx(changectx):
796 class workingctx(changectx):
795 """A workingctx object makes access to data related to
797 """A workingctx object makes access to data related to
796 the current working directory convenient.
798 the current working directory convenient.
797 date - any valid date string or (unixtime, offset), or None.
799 date - any valid date string or (unixtime, offset), or None.
798 user - username string, or None.
800 user - username string, or None.
799 extra - a dictionary of extra values, or None.
801 extra - a dictionary of extra values, or None.
800 changes - a list of file lists as returned by localrepo.status()
802 changes - a list of file lists as returned by localrepo.status()
801 or None to use the repository status.
803 or None to use the repository status.
802 """
804 """
803 def __init__(self, repo, text="", user=None, date=None, extra=None,
805 def __init__(self, repo, text="", user=None, date=None, extra=None,
804 changes=None):
806 changes=None):
805 self._repo = repo
807 self._repo = repo
806 self._rev = None
808 self._rev = None
807 self._node = None
809 self._node = None
808 self._text = text
810 self._text = text
809 if date:
811 if date:
810 self._date = util.parsedate(date)
812 self._date = util.parsedate(date)
811 if user:
813 if user:
812 self._user = user
814 self._user = user
813 if changes:
815 if changes:
814 self._status = list(changes[:4])
816 self._status = list(changes[:4])
815 self._unknown = changes[4]
817 self._unknown = changes[4]
816 self._ignored = changes[5]
818 self._ignored = changes[5]
817 self._clean = changes[6]
819 self._clean = changes[6]
818 else:
820 else:
819 self._unknown = None
821 self._unknown = None
820 self._ignored = None
822 self._ignored = None
821 self._clean = None
823 self._clean = None
822
824
823 self._extra = {}
825 self._extra = {}
824 if extra:
826 if extra:
825 self._extra = extra.copy()
827 self._extra = extra.copy()
826 if 'branch' not in self._extra:
828 if 'branch' not in self._extra:
827 try:
829 try:
828 branch = encoding.fromlocal(self._repo.dirstate.branch())
830 branch = encoding.fromlocal(self._repo.dirstate.branch())
829 except UnicodeDecodeError:
831 except UnicodeDecodeError:
830 raise util.Abort(_('branch name not in UTF-8!'))
832 raise util.Abort(_('branch name not in UTF-8!'))
831 self._extra['branch'] = branch
833 self._extra['branch'] = branch
832 if self._extra['branch'] == '':
834 if self._extra['branch'] == '':
833 self._extra['branch'] = 'default'
835 self._extra['branch'] = 'default'
834
836
835 def __str__(self):
837 def __str__(self):
836 return str(self._parents[0]) + "+"
838 return str(self._parents[0]) + "+"
837
839
838 def __repr__(self):
840 def __repr__(self):
839 return "<workingctx %s>" % str(self)
841 return "<workingctx %s>" % str(self)
840
842
841 def __nonzero__(self):
843 def __nonzero__(self):
842 return True
844 return True
843
845
844 def __contains__(self, key):
846 def __contains__(self, key):
845 return self._repo.dirstate[key] not in "?r"
847 return self._repo.dirstate[key] not in "?r"
846
848
847 def _buildflagfunc(self):
849 def _buildflagfunc(self):
848 # Create a fallback function for getting file flags when the
850 # Create a fallback function for getting file flags when the
849 # filesystem doesn't support them
851 # filesystem doesn't support them
850
852
851 copiesget = self._repo.dirstate.copies().get
853 copiesget = self._repo.dirstate.copies().get
852
854
853 if len(self._parents) < 2:
855 if len(self._parents) < 2:
854 # when we have one parent, it's easy: copy from parent
856 # when we have one parent, it's easy: copy from parent
855 man = self._parents[0].manifest()
857 man = self._parents[0].manifest()
856 def func(f):
858 def func(f):
857 f = copiesget(f, f)
859 f = copiesget(f, f)
858 return man.flags(f)
860 return man.flags(f)
859 else:
861 else:
860 # merges are tricky: we try to reconstruct the unstored
862 # merges are tricky: we try to reconstruct the unstored
861 # result from the merge (issue1802)
863 # result from the merge (issue1802)
862 p1, p2 = self._parents
864 p1, p2 = self._parents
863 pa = p1.ancestor(p2)
865 pa = p1.ancestor(p2)
864 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
866 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
865
867
866 def func(f):
868 def func(f):
867 f = copiesget(f, f) # may be wrong for merges with copies
869 f = copiesget(f, f) # may be wrong for merges with copies
868 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
870 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
869 if fl1 == fl2:
871 if fl1 == fl2:
870 return fl1
872 return fl1
871 if fl1 == fla:
873 if fl1 == fla:
872 return fl2
874 return fl2
873 if fl2 == fla:
875 if fl2 == fla:
874 return fl1
876 return fl1
875 return '' # punt for conflicts
877 return '' # punt for conflicts
876
878
877 return func
879 return func
878
880
879 @propertycache
881 @propertycache
880 def _flagfunc(self):
882 def _flagfunc(self):
881 return self._repo.dirstate.flagfunc(self._buildflagfunc)
883 return self._repo.dirstate.flagfunc(self._buildflagfunc)
882
884
883 @propertycache
885 @propertycache
884 def _manifest(self):
886 def _manifest(self):
885 """generate a manifest corresponding to the working directory"""
887 """generate a manifest corresponding to the working directory"""
886
888
887 man = self._parents[0].manifest().copy()
889 man = self._parents[0].manifest().copy()
888 if len(self._parents) > 1:
890 if len(self._parents) > 1:
889 man2 = self.p2().manifest()
891 man2 = self.p2().manifest()
890 def getman(f):
892 def getman(f):
891 if f in man:
893 if f in man:
892 return man
894 return man
893 return man2
895 return man2
894 else:
896 else:
895 getman = lambda f: man
897 getman = lambda f: man
896
898
897 copied = self._repo.dirstate.copies()
899 copied = self._repo.dirstate.copies()
898 ff = self._flagfunc
900 ff = self._flagfunc
899 modified, added, removed, deleted = self._status
901 modified, added, removed, deleted = self._status
900 for i, l in (("a", added), ("m", modified)):
902 for i, l in (("a", added), ("m", modified)):
901 for f in l:
903 for f in l:
902 orig = copied.get(f, f)
904 orig = copied.get(f, f)
903 man[f] = getman(orig).get(orig, nullid) + i
905 man[f] = getman(orig).get(orig, nullid) + i
904 try:
906 try:
905 man.set(f, ff(f))
907 man.set(f, ff(f))
906 except OSError:
908 except OSError:
907 pass
909 pass
908
910
909 for f in deleted + removed:
911 for f in deleted + removed:
910 if f in man:
912 if f in man:
911 del man[f]
913 del man[f]
912
914
913 return man
915 return man
914
916
915 def __iter__(self):
917 def __iter__(self):
916 d = self._repo.dirstate
918 d = self._repo.dirstate
917 for f in d:
919 for f in d:
918 if d[f] != 'r':
920 if d[f] != 'r':
919 yield f
921 yield f
920
922
921 @propertycache
923 @propertycache
922 def _status(self):
924 def _status(self):
923 return self._repo.status()[:4]
925 return self._repo.status()[:4]
924
926
925 @propertycache
927 @propertycache
926 def _user(self):
928 def _user(self):
927 return self._repo.ui.username()
929 return self._repo.ui.username()
928
930
929 @propertycache
931 @propertycache
930 def _date(self):
932 def _date(self):
931 return util.makedate()
933 return util.makedate()
932
934
933 @propertycache
935 @propertycache
934 def _parents(self):
936 def _parents(self):
935 p = self._repo.dirstate.parents()
937 p = self._repo.dirstate.parents()
936 if p[1] == nullid:
938 if p[1] == nullid:
937 p = p[:-1]
939 p = p[:-1]
938 return [changectx(self._repo, x) for x in p]
940 return [changectx(self._repo, x) for x in p]
939
941
940 def status(self, ignored=False, clean=False, unknown=False):
942 def status(self, ignored=False, clean=False, unknown=False):
941 """Explicit status query
943 """Explicit status query
942 Unless this method is used to query the working copy status, the
944 Unless this method is used to query the working copy status, the
943 _status property will implicitly read the status using its default
945 _status property will implicitly read the status using its default
944 arguments."""
946 arguments."""
945 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
947 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
946 self._unknown = self._ignored = self._clean = None
948 self._unknown = self._ignored = self._clean = None
947 if unknown:
949 if unknown:
948 self._unknown = stat[4]
950 self._unknown = stat[4]
949 if ignored:
951 if ignored:
950 self._ignored = stat[5]
952 self._ignored = stat[5]
951 if clean:
953 if clean:
952 self._clean = stat[6]
954 self._clean = stat[6]
953 self._status = stat[:4]
955 self._status = stat[:4]
954 return stat
956 return stat
955
957
956 def manifest(self):
958 def manifest(self):
957 return self._manifest
959 return self._manifest
958 def user(self):
960 def user(self):
959 return self._user or self._repo.ui.username()
961 return self._user or self._repo.ui.username()
960 def date(self):
962 def date(self):
961 return self._date
963 return self._date
962 def description(self):
964 def description(self):
963 return self._text
965 return self._text
964 def files(self):
966 def files(self):
965 return sorted(self._status[0] + self._status[1] + self._status[2])
967 return sorted(self._status[0] + self._status[1] + self._status[2])
966
968
967 def modified(self):
969 def modified(self):
968 return self._status[0]
970 return self._status[0]
969 def added(self):
971 def added(self):
970 return self._status[1]
972 return self._status[1]
971 def removed(self):
973 def removed(self):
972 return self._status[2]
974 return self._status[2]
973 def deleted(self):
975 def deleted(self):
974 return self._status[3]
976 return self._status[3]
975 def unknown(self):
977 def unknown(self):
976 assert self._unknown is not None # must call status first
978 assert self._unknown is not None # must call status first
977 return self._unknown
979 return self._unknown
978 def ignored(self):
980 def ignored(self):
979 assert self._ignored is not None # must call status first
981 assert self._ignored is not None # must call status first
980 return self._ignored
982 return self._ignored
981 def clean(self):
983 def clean(self):
982 assert self._clean is not None # must call status first
984 assert self._clean is not None # must call status first
983 return self._clean
985 return self._clean
984 def branch(self):
986 def branch(self):
985 return encoding.tolocal(self._extra['branch'])
987 return encoding.tolocal(self._extra['branch'])
986 def closesbranch(self):
988 def closesbranch(self):
987 return 'close' in self._extra
989 return 'close' in self._extra
988 def extra(self):
990 def extra(self):
989 return self._extra
991 return self._extra
990
992
991 def tags(self):
993 def tags(self):
992 t = []
994 t = []
993 for p in self.parents():
995 for p in self.parents():
994 t.extend(p.tags())
996 t.extend(p.tags())
995 return t
997 return t
996
998
997 def bookmarks(self):
999 def bookmarks(self):
998 b = []
1000 b = []
999 for p in self.parents():
1001 for p in self.parents():
1000 b.extend(p.bookmarks())
1002 b.extend(p.bookmarks())
1001 return b
1003 return b
1002
1004
1003 def phase(self):
1005 def phase(self):
1004 phase = phases.draft # default phase to draft
1006 phase = phases.draft # default phase to draft
1005 for p in self.parents():
1007 for p in self.parents():
1006 phase = max(phase, p.phase())
1008 phase = max(phase, p.phase())
1007 return phase
1009 return phase
1008
1010
1009 def hidden(self):
1011 def hidden(self):
1010 return False
1012 return False
1011
1013
1012 def children(self):
1014 def children(self):
1013 return []
1015 return []
1014
1016
1015 def flags(self, path):
1017 def flags(self, path):
1016 if '_manifest' in self.__dict__:
1018 if '_manifest' in self.__dict__:
1017 try:
1019 try:
1018 return self._manifest.flags(path)
1020 return self._manifest.flags(path)
1019 except KeyError:
1021 except KeyError:
1020 return ''
1022 return ''
1021
1023
1022 try:
1024 try:
1023 return self._flagfunc(path)
1025 return self._flagfunc(path)
1024 except OSError:
1026 except OSError:
1025 return ''
1027 return ''
1026
1028
1027 def filectx(self, path, filelog=None):
1029 def filectx(self, path, filelog=None):
1028 """get a file context from the working directory"""
1030 """get a file context from the working directory"""
1029 return workingfilectx(self._repo, path, workingctx=self,
1031 return workingfilectx(self._repo, path, workingctx=self,
1030 filelog=filelog)
1032 filelog=filelog)
1031
1033
1032 def ancestor(self, c2):
1034 def ancestor(self, c2):
1033 """return the ancestor context of self and c2"""
1035 """return the ancestor context of self and c2"""
1034 return self._parents[0].ancestor(c2) # punt on two parents for now
1036 return self._parents[0].ancestor(c2) # punt on two parents for now
1035
1037
1036 def walk(self, match):
1038 def walk(self, match):
1037 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1039 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1038 True, False))
1040 True, False))
1039
1041
1040 def dirty(self, missing=False, merge=True, branch=True):
1042 def dirty(self, missing=False, merge=True, branch=True):
1041 "check whether a working directory is modified"
1043 "check whether a working directory is modified"
1042 # check subrepos first
1044 # check subrepos first
1043 for s in sorted(self.substate):
1045 for s in sorted(self.substate):
1044 if self.sub(s).dirty():
1046 if self.sub(s).dirty():
1045 return True
1047 return True
1046 # check current working dir
1048 # check current working dir
1047 return ((merge and self.p2()) or
1049 return ((merge and self.p2()) or
1048 (branch and self.branch() != self.p1().branch()) or
1050 (branch and self.branch() != self.p1().branch()) or
1049 self.modified() or self.added() or self.removed() or
1051 self.modified() or self.added() or self.removed() or
1050 (missing and self.deleted()))
1052 (missing and self.deleted()))
1051
1053
1052 def add(self, list, prefix=""):
1054 def add(self, list, prefix=""):
1053 join = lambda f: os.path.join(prefix, f)
1055 join = lambda f: os.path.join(prefix, f)
1054 wlock = self._repo.wlock()
1056 wlock = self._repo.wlock()
1055 ui, ds = self._repo.ui, self._repo.dirstate
1057 ui, ds = self._repo.ui, self._repo.dirstate
1056 try:
1058 try:
1057 rejected = []
1059 rejected = []
1058 for f in list:
1060 for f in list:
1059 scmutil.checkportable(ui, join(f))
1061 scmutil.checkportable(ui, join(f))
1060 p = self._repo.wjoin(f)
1062 p = self._repo.wjoin(f)
1061 try:
1063 try:
1062 st = os.lstat(p)
1064 st = os.lstat(p)
1063 except OSError:
1065 except OSError:
1064 ui.warn(_("%s does not exist!\n") % join(f))
1066 ui.warn(_("%s does not exist!\n") % join(f))
1065 rejected.append(f)
1067 rejected.append(f)
1066 continue
1068 continue
1067 if st.st_size > 10000000:
1069 if st.st_size > 10000000:
1068 ui.warn(_("%s: up to %d MB of RAM may be required "
1070 ui.warn(_("%s: up to %d MB of RAM may be required "
1069 "to manage this file\n"
1071 "to manage this file\n"
1070 "(use 'hg revert %s' to cancel the "
1072 "(use 'hg revert %s' to cancel the "
1071 "pending addition)\n")
1073 "pending addition)\n")
1072 % (f, 3 * st.st_size // 1000000, join(f)))
1074 % (f, 3 * st.st_size // 1000000, join(f)))
1073 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1075 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1074 ui.warn(_("%s not added: only files and symlinks "
1076 ui.warn(_("%s not added: only files and symlinks "
1075 "supported currently\n") % join(f))
1077 "supported currently\n") % join(f))
1076 rejected.append(p)
1078 rejected.append(p)
1077 elif ds[f] in 'amn':
1079 elif ds[f] in 'amn':
1078 ui.warn(_("%s already tracked!\n") % join(f))
1080 ui.warn(_("%s already tracked!\n") % join(f))
1079 elif ds[f] == 'r':
1081 elif ds[f] == 'r':
1080 ds.normallookup(f)
1082 ds.normallookup(f)
1081 else:
1083 else:
1082 ds.add(f)
1084 ds.add(f)
1083 return rejected
1085 return rejected
1084 finally:
1086 finally:
1085 wlock.release()
1087 wlock.release()
1086
1088
1087 def forget(self, files, prefix=""):
1089 def forget(self, files, prefix=""):
1088 join = lambda f: os.path.join(prefix, f)
1090 join = lambda f: os.path.join(prefix, f)
1089 wlock = self._repo.wlock()
1091 wlock = self._repo.wlock()
1090 try:
1092 try:
1091 rejected = []
1093 rejected = []
1092 for f in files:
1094 for f in files:
1093 if f not in self._repo.dirstate:
1095 if f not in self._repo.dirstate:
1094 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1096 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1095 rejected.append(f)
1097 rejected.append(f)
1096 elif self._repo.dirstate[f] != 'a':
1098 elif self._repo.dirstate[f] != 'a':
1097 self._repo.dirstate.remove(f)
1099 self._repo.dirstate.remove(f)
1098 else:
1100 else:
1099 self._repo.dirstate.drop(f)
1101 self._repo.dirstate.drop(f)
1100 return rejected
1102 return rejected
1101 finally:
1103 finally:
1102 wlock.release()
1104 wlock.release()
1103
1105
1104 def ancestors(self):
1106 def ancestors(self):
1105 for a in self._repo.changelog.ancestors(
1107 for a in self._repo.changelog.ancestors(
1106 [p.rev() for p in self._parents]):
1108 [p.rev() for p in self._parents]):
1107 yield changectx(self._repo, a)
1109 yield changectx(self._repo, a)
1108
1110
1109 def undelete(self, list):
1111 def undelete(self, list):
1110 pctxs = self.parents()
1112 pctxs = self.parents()
1111 wlock = self._repo.wlock()
1113 wlock = self._repo.wlock()
1112 try:
1114 try:
1113 for f in list:
1115 for f in list:
1114 if self._repo.dirstate[f] != 'r':
1116 if self._repo.dirstate[f] != 'r':
1115 self._repo.ui.warn(_("%s not removed!\n") % f)
1117 self._repo.ui.warn(_("%s not removed!\n") % f)
1116 else:
1118 else:
1117 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1119 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1118 t = fctx.data()
1120 t = fctx.data()
1119 self._repo.wwrite(f, t, fctx.flags())
1121 self._repo.wwrite(f, t, fctx.flags())
1120 self._repo.dirstate.normal(f)
1122 self._repo.dirstate.normal(f)
1121 finally:
1123 finally:
1122 wlock.release()
1124 wlock.release()
1123
1125
1124 def copy(self, source, dest):
1126 def copy(self, source, dest):
1125 p = self._repo.wjoin(dest)
1127 p = self._repo.wjoin(dest)
1126 if not os.path.lexists(p):
1128 if not os.path.lexists(p):
1127 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1129 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1128 elif not (os.path.isfile(p) or os.path.islink(p)):
1130 elif not (os.path.isfile(p) or os.path.islink(p)):
1129 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1131 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1130 "symbolic link\n") % dest)
1132 "symbolic link\n") % dest)
1131 else:
1133 else:
1132 wlock = self._repo.wlock()
1134 wlock = self._repo.wlock()
1133 try:
1135 try:
1134 if self._repo.dirstate[dest] in '?r':
1136 if self._repo.dirstate[dest] in '?r':
1135 self._repo.dirstate.add(dest)
1137 self._repo.dirstate.add(dest)
1136 self._repo.dirstate.copy(source, dest)
1138 self._repo.dirstate.copy(source, dest)
1137 finally:
1139 finally:
1138 wlock.release()
1140 wlock.release()
1139
1141
1140 def markcommitted(self, node):
1142 def markcommitted(self, node):
1141 """Perform post-commit cleanup necessary after committing this ctx
1143 """Perform post-commit cleanup necessary after committing this ctx
1142
1144
1143 Specifically, this updates backing stores this working context
1145 Specifically, this updates backing stores this working context
1144 wraps to reflect the fact that the changes reflected by this
1146 wraps to reflect the fact that the changes reflected by this
1145 workingctx have been committed. For example, it marks
1147 workingctx have been committed. For example, it marks
1146 modified and added files as normal in the dirstate.
1148 modified and added files as normal in the dirstate.
1147
1149
1148 """
1150 """
1149
1151
1150 for f in self.modified() + self.added():
1152 for f in self.modified() + self.added():
1151 self._repo.dirstate.normal(f)
1153 self._repo.dirstate.normal(f)
1152 for f in self.removed():
1154 for f in self.removed():
1153 self._repo.dirstate.drop(f)
1155 self._repo.dirstate.drop(f)
1154 self._repo.dirstate.setparents(node)
1156 self._repo.dirstate.setparents(node)
1155
1157
1156 def dirs(self):
1158 def dirs(self):
1157 return self._repo.dirstate.dirs()
1159 return self._repo.dirstate.dirs()
1158
1160
1159 class workingfilectx(filectx):
1161 class workingfilectx(filectx):
1160 """A workingfilectx object makes access to data related to a particular
1162 """A workingfilectx object makes access to data related to a particular
1161 file in the working directory convenient."""
1163 file in the working directory convenient."""
1162 def __init__(self, repo, path, filelog=None, workingctx=None):
1164 def __init__(self, repo, path, filelog=None, workingctx=None):
1163 """changeid can be a changeset revision, node, or tag.
1165 """changeid can be a changeset revision, node, or tag.
1164 fileid can be a file revision or node."""
1166 fileid can be a file revision or node."""
1165 self._repo = repo
1167 self._repo = repo
1166 self._path = path
1168 self._path = path
1167 self._changeid = None
1169 self._changeid = None
1168 self._filerev = self._filenode = None
1170 self._filerev = self._filenode = None
1169
1171
1170 if filelog:
1172 if filelog is not None:
1171 self._filelog = filelog
1173 self._filelog = filelog
1172 if workingctx:
1174 if workingctx:
1173 self._changectx = workingctx
1175 self._changectx = workingctx
1174
1176
1175 @propertycache
1177 @propertycache
1176 def _changectx(self):
1178 def _changectx(self):
1177 return workingctx(self._repo)
1179 return workingctx(self._repo)
1178
1180
1179 def __nonzero__(self):
1181 def __nonzero__(self):
1180 return True
1182 return True
1181
1183
1182 def __str__(self):
1184 def __str__(self):
1183 return "%s@%s" % (self.path(), self._changectx)
1185 return "%s@%s" % (self.path(), self._changectx)
1184
1186
1185 def __repr__(self):
1187 def __repr__(self):
1186 return "<workingfilectx %s>" % str(self)
1188 return "<workingfilectx %s>" % str(self)
1187
1189
1188 def data(self):
1190 def data(self):
1189 return self._repo.wread(self._path)
1191 return self._repo.wread(self._path)
1190 def renamed(self):
1192 def renamed(self):
1191 rp = self._repo.dirstate.copied(self._path)
1193 rp = self._repo.dirstate.copied(self._path)
1192 if not rp:
1194 if not rp:
1193 return None
1195 return None
1194 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1196 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1195
1197
1196 def parents(self):
1198 def parents(self):
1197 '''return parent filectxs, following copies if necessary'''
1199 '''return parent filectxs, following copies if necessary'''
1198 def filenode(ctx, path):
1200 def filenode(ctx, path):
1199 return ctx._manifest.get(path, nullid)
1201 return ctx._manifest.get(path, nullid)
1200
1202
1201 path = self._path
1203 path = self._path
1202 fl = self._filelog
1204 fl = self._filelog
1203 pcl = self._changectx._parents
1205 pcl = self._changectx._parents
1204 renamed = self.renamed()
1206 renamed = self.renamed()
1205
1207
1206 if renamed:
1208 if renamed:
1207 pl = [renamed + (None,)]
1209 pl = [renamed + (None,)]
1208 else:
1210 else:
1209 pl = [(path, filenode(pcl[0], path), fl)]
1211 pl = [(path, filenode(pcl[0], path), fl)]
1210
1212
1211 for pc in pcl[1:]:
1213 for pc in pcl[1:]:
1212 pl.append((path, filenode(pc, path), fl))
1214 pl.append((path, filenode(pc, path), fl))
1213
1215
1214 return [filectx(self._repo, p, fileid=n, filelog=l)
1216 return [filectx(self._repo, p, fileid=n, filelog=l)
1215 for p, n, l in pl if n != nullid]
1217 for p, n, l in pl if n != nullid]
1216
1218
1217 def children(self):
1219 def children(self):
1218 return []
1220 return []
1219
1221
1220 def size(self):
1222 def size(self):
1221 return os.lstat(self._repo.wjoin(self._path)).st_size
1223 return os.lstat(self._repo.wjoin(self._path)).st_size
1222 def date(self):
1224 def date(self):
1223 t, tz = self._changectx.date()
1225 t, tz = self._changectx.date()
1224 try:
1226 try:
1225 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
1227 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
1226 except OSError, err:
1228 except OSError, err:
1227 if err.errno != errno.ENOENT:
1229 if err.errno != errno.ENOENT:
1228 raise
1230 raise
1229 return (t, tz)
1231 return (t, tz)
1230
1232
1231 def cmp(self, fctx):
1233 def cmp(self, fctx):
1232 """compare with other file context
1234 """compare with other file context
1233
1235
1234 returns True if different than fctx.
1236 returns True if different than fctx.
1235 """
1237 """
1236 # fctx should be a filectx (not a workingfilectx)
1238 # fctx should be a filectx (not a workingfilectx)
1237 # invert comparison to reuse the same code path
1239 # invert comparison to reuse the same code path
1238 return fctx.cmp(self)
1240 return fctx.cmp(self)
1239
1241
1240 class memctx(object):
1242 class memctx(object):
1241 """Use memctx to perform in-memory commits via localrepo.commitctx().
1243 """Use memctx to perform in-memory commits via localrepo.commitctx().
1242
1244
1243 Revision information is supplied at initialization time while
1245 Revision information is supplied at initialization time while
1244 related files data and is made available through a callback
1246 related files data and is made available through a callback
1245 mechanism. 'repo' is the current localrepo, 'parents' is a
1247 mechanism. 'repo' is the current localrepo, 'parents' is a
1246 sequence of two parent revisions identifiers (pass None for every
1248 sequence of two parent revisions identifiers (pass None for every
1247 missing parent), 'text' is the commit message and 'files' lists
1249 missing parent), 'text' is the commit message and 'files' lists
1248 names of files touched by the revision (normalized and relative to
1250 names of files touched by the revision (normalized and relative to
1249 repository root).
1251 repository root).
1250
1252
1251 filectxfn(repo, memctx, path) is a callable receiving the
1253 filectxfn(repo, memctx, path) is a callable receiving the
1252 repository, the current memctx object and the normalized path of
1254 repository, the current memctx object and the normalized path of
1253 requested file, relative to repository root. It is fired by the
1255 requested file, relative to repository root. It is fired by the
1254 commit function for every file in 'files', but calls order is
1256 commit function for every file in 'files', but calls order is
1255 undefined. If the file is available in the revision being
1257 undefined. If the file is available in the revision being
1256 committed (updated or added), filectxfn returns a memfilectx
1258 committed (updated or added), filectxfn returns a memfilectx
1257 object. If the file was removed, filectxfn raises an
1259 object. If the file was removed, filectxfn raises an
1258 IOError. Moved files are represented by marking the source file
1260 IOError. Moved files are represented by marking the source file
1259 removed and the new file added with copy information (see
1261 removed and the new file added with copy information (see
1260 memfilectx).
1262 memfilectx).
1261
1263
1262 user receives the committer name and defaults to current
1264 user receives the committer name and defaults to current
1263 repository username, date is the commit date in any format
1265 repository username, date is the commit date in any format
1264 supported by util.parsedate() and defaults to current date, extra
1266 supported by util.parsedate() and defaults to current date, extra
1265 is a dictionary of metadata or is left empty.
1267 is a dictionary of metadata or is left empty.
1266 """
1268 """
1267 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1269 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1268 date=None, extra=None):
1270 date=None, extra=None):
1269 self._repo = repo
1271 self._repo = repo
1270 self._rev = None
1272 self._rev = None
1271 self._node = None
1273 self._node = None
1272 self._text = text
1274 self._text = text
1273 self._date = date and util.parsedate(date) or util.makedate()
1275 self._date = date and util.parsedate(date) or util.makedate()
1274 self._user = user
1276 self._user = user
1275 parents = [(p or nullid) for p in parents]
1277 parents = [(p or nullid) for p in parents]
1276 p1, p2 = parents
1278 p1, p2 = parents
1277 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1279 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1278 files = sorted(set(files))
1280 files = sorted(set(files))
1279 self._status = [files, [], [], [], []]
1281 self._status = [files, [], [], [], []]
1280 self._filectxfn = filectxfn
1282 self._filectxfn = filectxfn
1281
1283
1282 self._extra = extra and extra.copy() or {}
1284 self._extra = extra and extra.copy() or {}
1283 if self._extra.get('branch', '') == '':
1285 if self._extra.get('branch', '') == '':
1284 self._extra['branch'] = 'default'
1286 self._extra['branch'] = 'default'
1285
1287
1286 def __str__(self):
1288 def __str__(self):
1287 return str(self._parents[0]) + "+"
1289 return str(self._parents[0]) + "+"
1288
1290
1289 def __int__(self):
1291 def __int__(self):
1290 return self._rev
1292 return self._rev
1291
1293
1292 def __nonzero__(self):
1294 def __nonzero__(self):
1293 return True
1295 return True
1294
1296
1295 def __getitem__(self, key):
1297 def __getitem__(self, key):
1296 return self.filectx(key)
1298 return self.filectx(key)
1297
1299
1298 def p1(self):
1300 def p1(self):
1299 return self._parents[0]
1301 return self._parents[0]
1300 def p2(self):
1302 def p2(self):
1301 return self._parents[1]
1303 return self._parents[1]
1302
1304
1303 def user(self):
1305 def user(self):
1304 return self._user or self._repo.ui.username()
1306 return self._user or self._repo.ui.username()
1305 def date(self):
1307 def date(self):
1306 return self._date
1308 return self._date
1307 def description(self):
1309 def description(self):
1308 return self._text
1310 return self._text
1309 def files(self):
1311 def files(self):
1310 return self.modified()
1312 return self.modified()
1311 def modified(self):
1313 def modified(self):
1312 return self._status[0]
1314 return self._status[0]
1313 def added(self):
1315 def added(self):
1314 return self._status[1]
1316 return self._status[1]
1315 def removed(self):
1317 def removed(self):
1316 return self._status[2]
1318 return self._status[2]
1317 def deleted(self):
1319 def deleted(self):
1318 return self._status[3]
1320 return self._status[3]
1319 def unknown(self):
1321 def unknown(self):
1320 return self._status[4]
1322 return self._status[4]
1321 def ignored(self):
1323 def ignored(self):
1322 return self._status[5]
1324 return self._status[5]
1323 def clean(self):
1325 def clean(self):
1324 return self._status[6]
1326 return self._status[6]
1325 def branch(self):
1327 def branch(self):
1326 return encoding.tolocal(self._extra['branch'])
1328 return encoding.tolocal(self._extra['branch'])
1327 def extra(self):
1329 def extra(self):
1328 return self._extra
1330 return self._extra
1329 def flags(self, f):
1331 def flags(self, f):
1330 return self[f].flags()
1332 return self[f].flags()
1331
1333
1332 def parents(self):
1334 def parents(self):
1333 """return contexts for each parent changeset"""
1335 """return contexts for each parent changeset"""
1334 return self._parents
1336 return self._parents
1335
1337
1336 def filectx(self, path, filelog=None):
1338 def filectx(self, path, filelog=None):
1337 """get a file context from the working directory"""
1339 """get a file context from the working directory"""
1338 return self._filectxfn(self._repo, self, path)
1340 return self._filectxfn(self._repo, self, path)
1339
1341
1340 def commit(self):
1342 def commit(self):
1341 """commit context to the repo"""
1343 """commit context to the repo"""
1342 return self._repo.commitctx(self)
1344 return self._repo.commitctx(self)
1343
1345
1344 class memfilectx(object):
1346 class memfilectx(object):
1345 """memfilectx represents an in-memory file to commit.
1347 """memfilectx represents an in-memory file to commit.
1346
1348
1347 See memctx for more details.
1349 See memctx for more details.
1348 """
1350 """
1349 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1351 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1350 """
1352 """
1351 path is the normalized file path relative to repository root.
1353 path is the normalized file path relative to repository root.
1352 data is the file content as a string.
1354 data is the file content as a string.
1353 islink is True if the file is a symbolic link.
1355 islink is True if the file is a symbolic link.
1354 isexec is True if the file is executable.
1356 isexec is True if the file is executable.
1355 copied is the source file path if current file was copied in the
1357 copied is the source file path if current file was copied in the
1356 revision being committed, or None."""
1358 revision being committed, or None."""
1357 self._path = path
1359 self._path = path
1358 self._data = data
1360 self._data = data
1359 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1361 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1360 self._copied = None
1362 self._copied = None
1361 if copied:
1363 if copied:
1362 self._copied = (copied, nullid)
1364 self._copied = (copied, nullid)
1363
1365
1364 def __nonzero__(self):
1366 def __nonzero__(self):
1365 return True
1367 return True
1366 def __str__(self):
1368 def __str__(self):
1367 return "%s@%s" % (self.path(), self._changectx)
1369 return "%s@%s" % (self.path(), self._changectx)
1368 def path(self):
1370 def path(self):
1369 return self._path
1371 return self._path
1370 def data(self):
1372 def data(self):
1371 return self._data
1373 return self._data
1372 def flags(self):
1374 def flags(self):
1373 return self._flags
1375 return self._flags
1374 def isexec(self):
1376 def isexec(self):
1375 return 'x' in self._flags
1377 return 'x' in self._flags
1376 def islink(self):
1378 def islink(self):
1377 return 'l' in self._flags
1379 return 'l' in self._flags
1378 def renamed(self):
1380 def renamed(self):
1379 return self._copied
1381 return self._copied
General Comments 0
You need to be logged in to leave comments. Login now