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