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