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