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