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