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