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