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