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