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