##// END OF EJS Templates
basectx: remove unnecessary check of instance...
Sean Farley -
r19568:f58235d8 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):
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 and not isinstance(ctx2, changectx):
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 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