##// END OF EJS Templates
context: move dirty() to committablectx...
Sean Farley -
r32610:bf728e72 default
parent child Browse files
Show More
@@ -1,2341 +1,2341
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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import os
11 import os
12 import re
12 import re
13 import stat
13 import stat
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 addednodeid,
17 addednodeid,
18 bin,
18 bin,
19 hex,
19 hex,
20 modifiednodeid,
20 modifiednodeid,
21 nullid,
21 nullid,
22 nullrev,
22 nullrev,
23 short,
23 short,
24 wdirid,
24 wdirid,
25 wdirnodes,
25 wdirnodes,
26 )
26 )
27 from . import (
27 from . import (
28 encoding,
28 encoding,
29 error,
29 error,
30 fileset,
30 fileset,
31 match as matchmod,
31 match as matchmod,
32 mdiff,
32 mdiff,
33 obsolete as obsmod,
33 obsolete as obsmod,
34 patch,
34 patch,
35 phases,
35 phases,
36 pycompat,
36 pycompat,
37 repoview,
37 repoview,
38 revlog,
38 revlog,
39 scmutil,
39 scmutil,
40 subrepo,
40 subrepo,
41 util,
41 util,
42 )
42 )
43
43
44 propertycache = util.propertycache
44 propertycache = util.propertycache
45
45
46 nonascii = re.compile(r'[^\x21-\x7f]').search
46 nonascii = re.compile(r'[^\x21-\x7f]').search
47
47
48 class basectx(object):
48 class basectx(object):
49 """A basectx object represents the common logic for its children:
49 """A basectx object represents the common logic for its children:
50 changectx: read-only context that is already present in the repo,
50 changectx: read-only context that is already present in the repo,
51 workingctx: a context that represents the working directory and can
51 workingctx: a context that represents the working directory and can
52 be committed,
52 be committed,
53 memctx: a context that represents changes in-memory and can also
53 memctx: a context that represents changes in-memory and can also
54 be committed."""
54 be committed."""
55 def __new__(cls, repo, changeid='', *args, **kwargs):
55 def __new__(cls, repo, changeid='', *args, **kwargs):
56 if isinstance(changeid, basectx):
56 if isinstance(changeid, basectx):
57 return changeid
57 return changeid
58
58
59 o = super(basectx, cls).__new__(cls)
59 o = super(basectx, cls).__new__(cls)
60
60
61 o._repo = repo
61 o._repo = repo
62 o._rev = nullrev
62 o._rev = nullrev
63 o._node = nullid
63 o._node = nullid
64
64
65 return o
65 return o
66
66
67 def __str__(self):
67 def __str__(self):
68 r = short(self.node())
68 r = short(self.node())
69 if pycompat.ispy3:
69 if pycompat.ispy3:
70 return r.decode('ascii')
70 return r.decode('ascii')
71 return r
71 return r
72
72
73 def __bytes__(self):
73 def __bytes__(self):
74 return short(self.node())
74 return short(self.node())
75
75
76 def __int__(self):
76 def __int__(self):
77 return self.rev()
77 return self.rev()
78
78
79 def __repr__(self):
79 def __repr__(self):
80 return "<%s %s>" % (type(self).__name__, str(self))
80 return "<%s %s>" % (type(self).__name__, str(self))
81
81
82 def __eq__(self, other):
82 def __eq__(self, other):
83 try:
83 try:
84 return type(self) == type(other) and self._rev == other._rev
84 return type(self) == type(other) and self._rev == other._rev
85 except AttributeError:
85 except AttributeError:
86 return False
86 return False
87
87
88 def __ne__(self, other):
88 def __ne__(self, other):
89 return not (self == other)
89 return not (self == other)
90
90
91 def __contains__(self, key):
91 def __contains__(self, key):
92 return key in self._manifest
92 return key in self._manifest
93
93
94 def __getitem__(self, key):
94 def __getitem__(self, key):
95 return self.filectx(key)
95 return self.filectx(key)
96
96
97 def __iter__(self):
97 def __iter__(self):
98 return iter(self._manifest)
98 return iter(self._manifest)
99
99
100 def _buildstatusmanifest(self, status):
100 def _buildstatusmanifest(self, status):
101 """Builds a manifest that includes the given status results, if this is
101 """Builds a manifest that includes the given status results, if this is
102 a working copy context. For non-working copy contexts, it just returns
102 a working copy context. For non-working copy contexts, it just returns
103 the normal manifest."""
103 the normal manifest."""
104 return self.manifest()
104 return self.manifest()
105
105
106 def _matchstatus(self, other, match):
106 def _matchstatus(self, other, match):
107 """return match.always if match is none
107 """return match.always if match is none
108
108
109 This internal method provides a way for child objects to override the
109 This internal method provides a way for child objects to override the
110 match operator.
110 match operator.
111 """
111 """
112 return match or matchmod.always(self._repo.root, self._repo.getcwd())
112 return match or matchmod.always(self._repo.root, self._repo.getcwd())
113
113
114 def _buildstatus(self, other, s, match, listignored, listclean,
114 def _buildstatus(self, other, s, match, listignored, listclean,
115 listunknown):
115 listunknown):
116 """build a status with respect to another context"""
116 """build a status with respect to another context"""
117 # Load earliest manifest first for caching reasons. More specifically,
117 # Load earliest manifest first for caching reasons. More specifically,
118 # if you have revisions 1000 and 1001, 1001 is probably stored as a
118 # if you have revisions 1000 and 1001, 1001 is probably stored as a
119 # delta against 1000. Thus, if you read 1000 first, we'll reconstruct
119 # delta against 1000. Thus, if you read 1000 first, we'll reconstruct
120 # 1000 and cache it so that when you read 1001, we just need to apply a
120 # 1000 and cache it so that when you read 1001, we just need to apply a
121 # delta to what's in the cache. So that's one full reconstruction + one
121 # delta to what's in the cache. So that's one full reconstruction + one
122 # delta application.
122 # delta application.
123 mf2 = None
123 mf2 = None
124 if self.rev() is not None and self.rev() < other.rev():
124 if self.rev() is not None and self.rev() < other.rev():
125 mf2 = self._buildstatusmanifest(s)
125 mf2 = self._buildstatusmanifest(s)
126 mf1 = other._buildstatusmanifest(s)
126 mf1 = other._buildstatusmanifest(s)
127 if mf2 is None:
127 if mf2 is None:
128 mf2 = self._buildstatusmanifest(s)
128 mf2 = self._buildstatusmanifest(s)
129
129
130 modified, added = [], []
130 modified, added = [], []
131 removed = []
131 removed = []
132 clean = []
132 clean = []
133 deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
133 deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
134 deletedset = set(deleted)
134 deletedset = set(deleted)
135 d = mf1.diff(mf2, match=match, clean=listclean)
135 d = mf1.diff(mf2, match=match, clean=listclean)
136 for fn, value in d.iteritems():
136 for fn, value in d.iteritems():
137 if fn in deletedset:
137 if fn in deletedset:
138 continue
138 continue
139 if value is None:
139 if value is None:
140 clean.append(fn)
140 clean.append(fn)
141 continue
141 continue
142 (node1, flag1), (node2, flag2) = value
142 (node1, flag1), (node2, flag2) = value
143 if node1 is None:
143 if node1 is None:
144 added.append(fn)
144 added.append(fn)
145 elif node2 is None:
145 elif node2 is None:
146 removed.append(fn)
146 removed.append(fn)
147 elif flag1 != flag2:
147 elif flag1 != flag2:
148 modified.append(fn)
148 modified.append(fn)
149 elif node2 not in wdirnodes:
149 elif node2 not in wdirnodes:
150 # When comparing files between two commits, we save time by
150 # When comparing files between two commits, we save time by
151 # not comparing the file contents when the nodeids differ.
151 # not comparing the file contents when the nodeids differ.
152 # Note that this means we incorrectly report a reverted change
152 # Note that this means we incorrectly report a reverted change
153 # to a file as a modification.
153 # to a file as a modification.
154 modified.append(fn)
154 modified.append(fn)
155 elif self[fn].cmp(other[fn]):
155 elif self[fn].cmp(other[fn]):
156 modified.append(fn)
156 modified.append(fn)
157 else:
157 else:
158 clean.append(fn)
158 clean.append(fn)
159
159
160 if removed:
160 if removed:
161 # need to filter files if they are already reported as removed
161 # need to filter files if they are already reported as removed
162 unknown = [fn for fn in unknown if fn not in mf1 and
162 unknown = [fn for fn in unknown if fn not in mf1 and
163 (not match or match(fn))]
163 (not match or match(fn))]
164 ignored = [fn for fn in ignored if fn not in mf1 and
164 ignored = [fn for fn in ignored if fn not in mf1 and
165 (not match or match(fn))]
165 (not match or match(fn))]
166 # if they're deleted, don't report them as removed
166 # if they're deleted, don't report them as removed
167 removed = [fn for fn in removed if fn not in deletedset]
167 removed = [fn for fn in removed if fn not in deletedset]
168
168
169 return scmutil.status(modified, added, removed, deleted, unknown,
169 return scmutil.status(modified, added, removed, deleted, unknown,
170 ignored, clean)
170 ignored, clean)
171
171
172 @propertycache
172 @propertycache
173 def substate(self):
173 def substate(self):
174 return subrepo.state(self, self._repo.ui)
174 return subrepo.state(self, self._repo.ui)
175
175
176 def subrev(self, subpath):
176 def subrev(self, subpath):
177 return self.substate[subpath][1]
177 return self.substate[subpath][1]
178
178
179 def rev(self):
179 def rev(self):
180 return self._rev
180 return self._rev
181 def node(self):
181 def node(self):
182 return self._node
182 return self._node
183 def hex(self):
183 def hex(self):
184 return hex(self.node())
184 return hex(self.node())
185 def manifest(self):
185 def manifest(self):
186 return self._manifest
186 return self._manifest
187 def manifestctx(self):
187 def manifestctx(self):
188 return self._manifestctx
188 return self._manifestctx
189 def repo(self):
189 def repo(self):
190 return self._repo
190 return self._repo
191 def phasestr(self):
191 def phasestr(self):
192 return phases.phasenames[self.phase()]
192 return phases.phasenames[self.phase()]
193 def mutable(self):
193 def mutable(self):
194 return self.phase() > phases.public
194 return self.phase() > phases.public
195
195
196 def getfileset(self, expr):
196 def getfileset(self, expr):
197 return fileset.getfileset(self, expr)
197 return fileset.getfileset(self, expr)
198
198
199 def obsolete(self):
199 def obsolete(self):
200 """True if the changeset is obsolete"""
200 """True if the changeset is obsolete"""
201 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
201 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
202
202
203 def extinct(self):
203 def extinct(self):
204 """True if the changeset is extinct"""
204 """True if the changeset is extinct"""
205 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
205 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
206
206
207 def unstable(self):
207 def unstable(self):
208 """True if the changeset is not obsolete but it's ancestor are"""
208 """True if the changeset is not obsolete but it's ancestor are"""
209 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
209 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
210
210
211 def bumped(self):
211 def bumped(self):
212 """True if the changeset try to be a successor of a public changeset
212 """True if the changeset try to be a successor of a public changeset
213
213
214 Only non-public and non-obsolete changesets may be bumped.
214 Only non-public and non-obsolete changesets may be bumped.
215 """
215 """
216 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
216 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
217
217
218 def divergent(self):
218 def divergent(self):
219 """Is a successors of a changeset with multiple possible successors set
219 """Is a successors of a changeset with multiple possible successors set
220
220
221 Only non-public and non-obsolete changesets may be divergent.
221 Only non-public and non-obsolete changesets may be divergent.
222 """
222 """
223 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
223 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
224
224
225 def troubled(self):
225 def troubled(self):
226 """True if the changeset is either unstable, bumped or divergent"""
226 """True if the changeset is either unstable, bumped or divergent"""
227 return self.unstable() or self.bumped() or self.divergent()
227 return self.unstable() or self.bumped() or self.divergent()
228
228
229 def troubles(self):
229 def troubles(self):
230 """return the list of troubles affecting this changesets.
230 """return the list of troubles affecting this changesets.
231
231
232 Troubles are returned as strings. possible values are:
232 Troubles are returned as strings. possible values are:
233 - unstable,
233 - unstable,
234 - bumped,
234 - bumped,
235 - divergent.
235 - divergent.
236 """
236 """
237 troubles = []
237 troubles = []
238 if self.unstable():
238 if self.unstable():
239 troubles.append('unstable')
239 troubles.append('unstable')
240 if self.bumped():
240 if self.bumped():
241 troubles.append('bumped')
241 troubles.append('bumped')
242 if self.divergent():
242 if self.divergent():
243 troubles.append('divergent')
243 troubles.append('divergent')
244 return troubles
244 return troubles
245
245
246 def parents(self):
246 def parents(self):
247 """return contexts for each parent changeset"""
247 """return contexts for each parent changeset"""
248 return self._parents
248 return self._parents
249
249
250 def p1(self):
250 def p1(self):
251 return self._parents[0]
251 return self._parents[0]
252
252
253 def p2(self):
253 def p2(self):
254 parents = self._parents
254 parents = self._parents
255 if len(parents) == 2:
255 if len(parents) == 2:
256 return parents[1]
256 return parents[1]
257 return changectx(self._repo, nullrev)
257 return changectx(self._repo, nullrev)
258
258
259 def _fileinfo(self, path):
259 def _fileinfo(self, path):
260 if r'_manifest' in self.__dict__:
260 if r'_manifest' in self.__dict__:
261 try:
261 try:
262 return self._manifest[path], self._manifest.flags(path)
262 return self._manifest[path], self._manifest.flags(path)
263 except KeyError:
263 except KeyError:
264 raise error.ManifestLookupError(self._node, path,
264 raise error.ManifestLookupError(self._node, path,
265 _('not found in manifest'))
265 _('not found in manifest'))
266 if r'_manifestdelta' in self.__dict__ or path in self.files():
266 if r'_manifestdelta' in self.__dict__ or path in self.files():
267 if path in self._manifestdelta:
267 if path in self._manifestdelta:
268 return (self._manifestdelta[path],
268 return (self._manifestdelta[path],
269 self._manifestdelta.flags(path))
269 self._manifestdelta.flags(path))
270 mfl = self._repo.manifestlog
270 mfl = self._repo.manifestlog
271 try:
271 try:
272 node, flag = mfl[self._changeset.manifest].find(path)
272 node, flag = mfl[self._changeset.manifest].find(path)
273 except KeyError:
273 except KeyError:
274 raise error.ManifestLookupError(self._node, path,
274 raise error.ManifestLookupError(self._node, path,
275 _('not found in manifest'))
275 _('not found in manifest'))
276
276
277 return node, flag
277 return node, flag
278
278
279 def filenode(self, path):
279 def filenode(self, path):
280 return self._fileinfo(path)[0]
280 return self._fileinfo(path)[0]
281
281
282 def flags(self, path):
282 def flags(self, path):
283 try:
283 try:
284 return self._fileinfo(path)[1]
284 return self._fileinfo(path)[1]
285 except error.LookupError:
285 except error.LookupError:
286 return ''
286 return ''
287
287
288 def sub(self, path, allowcreate=True):
288 def sub(self, path, allowcreate=True):
289 '''return a subrepo for the stored revision of path, never wdir()'''
289 '''return a subrepo for the stored revision of path, never wdir()'''
290 return subrepo.subrepo(self, path, allowcreate=allowcreate)
290 return subrepo.subrepo(self, path, allowcreate=allowcreate)
291
291
292 def nullsub(self, path, pctx):
292 def nullsub(self, path, pctx):
293 return subrepo.nullsubrepo(self, path, pctx)
293 return subrepo.nullsubrepo(self, path, pctx)
294
294
295 def workingsub(self, path):
295 def workingsub(self, path):
296 '''return a subrepo for the stored revision, or wdir if this is a wdir
296 '''return a subrepo for the stored revision, or wdir if this is a wdir
297 context.
297 context.
298 '''
298 '''
299 return subrepo.subrepo(self, path, allowwdir=True)
299 return subrepo.subrepo(self, path, allowwdir=True)
300
300
301 def match(self, pats=None, include=None, exclude=None, default='glob',
301 def match(self, pats=None, include=None, exclude=None, default='glob',
302 listsubrepos=False, badfn=None):
302 listsubrepos=False, badfn=None):
303 if pats is None:
303 if pats is None:
304 pats = []
304 pats = []
305 r = self._repo
305 r = self._repo
306 return matchmod.match(r.root, r.getcwd(), pats,
306 return matchmod.match(r.root, r.getcwd(), pats,
307 include, exclude, default,
307 include, exclude, default,
308 auditor=r.nofsauditor, ctx=self,
308 auditor=r.nofsauditor, ctx=self,
309 listsubrepos=listsubrepos, badfn=badfn)
309 listsubrepos=listsubrepos, badfn=badfn)
310
310
311 def diff(self, ctx2=None, match=None, **opts):
311 def diff(self, ctx2=None, match=None, **opts):
312 """Returns a diff generator for the given contexts and matcher"""
312 """Returns a diff generator for the given contexts and matcher"""
313 if ctx2 is None:
313 if ctx2 is None:
314 ctx2 = self.p1()
314 ctx2 = self.p1()
315 if ctx2 is not None:
315 if ctx2 is not None:
316 ctx2 = self._repo[ctx2]
316 ctx2 = self._repo[ctx2]
317 diffopts = patch.diffopts(self._repo.ui, opts)
317 diffopts = patch.diffopts(self._repo.ui, opts)
318 return patch.diff(self._repo, ctx2, self, match=match, opts=diffopts)
318 return patch.diff(self._repo, ctx2, self, match=match, opts=diffopts)
319
319
320 def dirs(self):
320 def dirs(self):
321 return self._manifest.dirs()
321 return self._manifest.dirs()
322
322
323 def hasdir(self, dir):
323 def hasdir(self, dir):
324 return self._manifest.hasdir(dir)
324 return self._manifest.hasdir(dir)
325
325
326 def dirty(self, missing=False, merge=True, branch=True):
327 return False
328
329 def status(self, other=None, match=None, listignored=False,
326 def status(self, other=None, match=None, listignored=False,
330 listclean=False, listunknown=False, listsubrepos=False):
327 listclean=False, listunknown=False, listsubrepos=False):
331 """return status of files between two nodes or node and working
328 """return status of files between two nodes or node and working
332 directory.
329 directory.
333
330
334 If other is None, compare this node with working directory.
331 If other is None, compare this node with working directory.
335
332
336 returns (modified, added, removed, deleted, unknown, ignored, clean)
333 returns (modified, added, removed, deleted, unknown, ignored, clean)
337 """
334 """
338
335
339 ctx1 = self
336 ctx1 = self
340 ctx2 = self._repo[other]
337 ctx2 = self._repo[other]
341
338
342 # This next code block is, admittedly, fragile logic that tests for
339 # This next code block is, admittedly, fragile logic that tests for
343 # reversing the contexts and wouldn't need to exist if it weren't for
340 # reversing the contexts and wouldn't need to exist if it weren't for
344 # the fast (and common) code path of comparing the working directory
341 # the fast (and common) code path of comparing the working directory
345 # with its first parent.
342 # with its first parent.
346 #
343 #
347 # What we're aiming for here is the ability to call:
344 # What we're aiming for here is the ability to call:
348 #
345 #
349 # workingctx.status(parentctx)
346 # workingctx.status(parentctx)
350 #
347 #
351 # If we always built the manifest for each context and compared those,
348 # If we always built the manifest for each context and compared those,
352 # then we'd be done. But the special case of the above call means we
349 # then we'd be done. But the special case of the above call means we
353 # just copy the manifest of the parent.
350 # just copy the manifest of the parent.
354 reversed = False
351 reversed = False
355 if (not isinstance(ctx1, changectx)
352 if (not isinstance(ctx1, changectx)
356 and isinstance(ctx2, changectx)):
353 and isinstance(ctx2, changectx)):
357 reversed = True
354 reversed = True
358 ctx1, ctx2 = ctx2, ctx1
355 ctx1, ctx2 = ctx2, ctx1
359
356
360 match = ctx2._matchstatus(ctx1, match)
357 match = ctx2._matchstatus(ctx1, match)
361 r = scmutil.status([], [], [], [], [], [], [])
358 r = scmutil.status([], [], [], [], [], [], [])
362 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
359 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
363 listunknown)
360 listunknown)
364
361
365 if reversed:
362 if reversed:
366 # Reverse added and removed. Clear deleted, unknown and ignored as
363 # Reverse added and removed. Clear deleted, unknown and ignored as
367 # these make no sense to reverse.
364 # these make no sense to reverse.
368 r = scmutil.status(r.modified, r.removed, r.added, [], [], [],
365 r = scmutil.status(r.modified, r.removed, r.added, [], [], [],
369 r.clean)
366 r.clean)
370
367
371 if listsubrepos:
368 if listsubrepos:
372 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
369 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
373 try:
370 try:
374 rev2 = ctx2.subrev(subpath)
371 rev2 = ctx2.subrev(subpath)
375 except KeyError:
372 except KeyError:
376 # A subrepo that existed in node1 was deleted between
373 # A subrepo that existed in node1 was deleted between
377 # node1 and node2 (inclusive). Thus, ctx2's substate
374 # node1 and node2 (inclusive). Thus, ctx2's substate
378 # won't contain that subpath. The best we can do ignore it.
375 # won't contain that subpath. The best we can do ignore it.
379 rev2 = None
376 rev2 = None
380 submatch = matchmod.subdirmatcher(subpath, match)
377 submatch = matchmod.subdirmatcher(subpath, match)
381 s = sub.status(rev2, match=submatch, ignored=listignored,
378 s = sub.status(rev2, match=submatch, ignored=listignored,
382 clean=listclean, unknown=listunknown,
379 clean=listclean, unknown=listunknown,
383 listsubrepos=True)
380 listsubrepos=True)
384 for rfiles, sfiles in zip(r, s):
381 for rfiles, sfiles in zip(r, s):
385 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
382 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
386
383
387 for l in r:
384 for l in r:
388 l.sort()
385 l.sort()
389
386
390 return r
387 return r
391
388
392
389
393 def makememctx(repo, parents, text, user, date, branch, files, store,
390 def makememctx(repo, parents, text, user, date, branch, files, store,
394 editor=None, extra=None):
391 editor=None, extra=None):
395 def getfilectx(repo, memctx, path):
392 def getfilectx(repo, memctx, path):
396 data, mode, copied = store.getfile(path)
393 data, mode, copied = store.getfile(path)
397 if data is None:
394 if data is None:
398 return None
395 return None
399 islink, isexec = mode
396 islink, isexec = mode
400 return memfilectx(repo, path, data, islink=islink, isexec=isexec,
397 return memfilectx(repo, path, data, islink=islink, isexec=isexec,
401 copied=copied, memctx=memctx)
398 copied=copied, memctx=memctx)
402 if extra is None:
399 if extra is None:
403 extra = {}
400 extra = {}
404 if branch:
401 if branch:
405 extra['branch'] = encoding.fromlocal(branch)
402 extra['branch'] = encoding.fromlocal(branch)
406 ctx = memctx(repo, parents, text, files, getfilectx, user,
403 ctx = memctx(repo, parents, text, files, getfilectx, user,
407 date, extra, editor)
404 date, extra, editor)
408 return ctx
405 return ctx
409
406
410 def _filterederror(repo, changeid):
407 def _filterederror(repo, changeid):
411 """build an exception to be raised about a filtered changeid
408 """build an exception to be raised about a filtered changeid
412
409
413 This is extracted in a function to help extensions (eg: evolve) to
410 This is extracted in a function to help extensions (eg: evolve) to
414 experiment with various message variants."""
411 experiment with various message variants."""
415 if repo.filtername.startswith('visible'):
412 if repo.filtername.startswith('visible'):
416 msg = _("hidden revision '%s'") % changeid
413 msg = _("hidden revision '%s'") % changeid
417 hint = _('use --hidden to access hidden revisions')
414 hint = _('use --hidden to access hidden revisions')
418 return error.FilteredRepoLookupError(msg, hint=hint)
415 return error.FilteredRepoLookupError(msg, hint=hint)
419 msg = _("filtered revision '%s' (not in '%s' subset)")
416 msg = _("filtered revision '%s' (not in '%s' subset)")
420 msg %= (changeid, repo.filtername)
417 msg %= (changeid, repo.filtername)
421 return error.FilteredRepoLookupError(msg)
418 return error.FilteredRepoLookupError(msg)
422
419
423 class changectx(basectx):
420 class changectx(basectx):
424 """A changecontext object makes access to data related to a particular
421 """A changecontext object makes access to data related to a particular
425 changeset convenient. It represents a read-only context already present in
422 changeset convenient. It represents a read-only context already present in
426 the repo."""
423 the repo."""
427 def __init__(self, repo, changeid=''):
424 def __init__(self, repo, changeid=''):
428 """changeid is a revision number, node, or tag"""
425 """changeid is a revision number, node, or tag"""
429
426
430 # since basectx.__new__ already took care of copying the object, we
427 # since basectx.__new__ already took care of copying the object, we
431 # don't need to do anything in __init__, so we just exit here
428 # don't need to do anything in __init__, so we just exit here
432 if isinstance(changeid, basectx):
429 if isinstance(changeid, basectx):
433 return
430 return
434
431
435 if changeid == '':
432 if changeid == '':
436 changeid = '.'
433 changeid = '.'
437 self._repo = repo
434 self._repo = repo
438
435
439 try:
436 try:
440 if isinstance(changeid, int):
437 if isinstance(changeid, int):
441 self._node = repo.changelog.node(changeid)
438 self._node = repo.changelog.node(changeid)
442 self._rev = changeid
439 self._rev = changeid
443 return
440 return
444 if not pycompat.ispy3 and isinstance(changeid, long):
441 if not pycompat.ispy3 and isinstance(changeid, long):
445 changeid = str(changeid)
442 changeid = str(changeid)
446 if changeid == 'null':
443 if changeid == 'null':
447 self._node = nullid
444 self._node = nullid
448 self._rev = nullrev
445 self._rev = nullrev
449 return
446 return
450 if changeid == 'tip':
447 if changeid == 'tip':
451 self._node = repo.changelog.tip()
448 self._node = repo.changelog.tip()
452 self._rev = repo.changelog.rev(self._node)
449 self._rev = repo.changelog.rev(self._node)
453 return
450 return
454 if changeid == '.' or changeid == repo.dirstate.p1():
451 if changeid == '.' or changeid == repo.dirstate.p1():
455 # this is a hack to delay/avoid loading obsmarkers
452 # this is a hack to delay/avoid loading obsmarkers
456 # when we know that '.' won't be hidden
453 # when we know that '.' won't be hidden
457 self._node = repo.dirstate.p1()
454 self._node = repo.dirstate.p1()
458 self._rev = repo.unfiltered().changelog.rev(self._node)
455 self._rev = repo.unfiltered().changelog.rev(self._node)
459 return
456 return
460 if len(changeid) == 20:
457 if len(changeid) == 20:
461 try:
458 try:
462 self._node = changeid
459 self._node = changeid
463 self._rev = repo.changelog.rev(changeid)
460 self._rev = repo.changelog.rev(changeid)
464 return
461 return
465 except error.FilteredRepoLookupError:
462 except error.FilteredRepoLookupError:
466 raise
463 raise
467 except LookupError:
464 except LookupError:
468 pass
465 pass
469
466
470 try:
467 try:
471 r = int(changeid)
468 r = int(changeid)
472 if '%d' % r != changeid:
469 if '%d' % r != changeid:
473 raise ValueError
470 raise ValueError
474 l = len(repo.changelog)
471 l = len(repo.changelog)
475 if r < 0:
472 if r < 0:
476 r += l
473 r += l
477 if r < 0 or r >= l:
474 if r < 0 or r >= l:
478 raise ValueError
475 raise ValueError
479 self._rev = r
476 self._rev = r
480 self._node = repo.changelog.node(r)
477 self._node = repo.changelog.node(r)
481 return
478 return
482 except error.FilteredIndexError:
479 except error.FilteredIndexError:
483 raise
480 raise
484 except (ValueError, OverflowError, IndexError):
481 except (ValueError, OverflowError, IndexError):
485 pass
482 pass
486
483
487 if len(changeid) == 40:
484 if len(changeid) == 40:
488 try:
485 try:
489 self._node = bin(changeid)
486 self._node = bin(changeid)
490 self._rev = repo.changelog.rev(self._node)
487 self._rev = repo.changelog.rev(self._node)
491 return
488 return
492 except error.FilteredLookupError:
489 except error.FilteredLookupError:
493 raise
490 raise
494 except (TypeError, LookupError):
491 except (TypeError, LookupError):
495 pass
492 pass
496
493
497 # lookup bookmarks through the name interface
494 # lookup bookmarks through the name interface
498 try:
495 try:
499 self._node = repo.names.singlenode(repo, changeid)
496 self._node = repo.names.singlenode(repo, changeid)
500 self._rev = repo.changelog.rev(self._node)
497 self._rev = repo.changelog.rev(self._node)
501 return
498 return
502 except KeyError:
499 except KeyError:
503 pass
500 pass
504 except error.FilteredRepoLookupError:
501 except error.FilteredRepoLookupError:
505 raise
502 raise
506 except error.RepoLookupError:
503 except error.RepoLookupError:
507 pass
504 pass
508
505
509 self._node = repo.unfiltered().changelog._partialmatch(changeid)
506 self._node = repo.unfiltered().changelog._partialmatch(changeid)
510 if self._node is not None:
507 if self._node is not None:
511 self._rev = repo.changelog.rev(self._node)
508 self._rev = repo.changelog.rev(self._node)
512 return
509 return
513
510
514 # lookup failed
511 # lookup failed
515 # check if it might have come from damaged dirstate
512 # check if it might have come from damaged dirstate
516 #
513 #
517 # XXX we could avoid the unfiltered if we had a recognizable
514 # XXX we could avoid the unfiltered if we had a recognizable
518 # exception for filtered changeset access
515 # exception for filtered changeset access
519 if changeid in repo.unfiltered().dirstate.parents():
516 if changeid in repo.unfiltered().dirstate.parents():
520 msg = _("working directory has unknown parent '%s'!")
517 msg = _("working directory has unknown parent '%s'!")
521 raise error.Abort(msg % short(changeid))
518 raise error.Abort(msg % short(changeid))
522 try:
519 try:
523 if len(changeid) == 20 and nonascii(changeid):
520 if len(changeid) == 20 and nonascii(changeid):
524 changeid = hex(changeid)
521 changeid = hex(changeid)
525 except TypeError:
522 except TypeError:
526 pass
523 pass
527 except (error.FilteredIndexError, error.FilteredLookupError,
524 except (error.FilteredIndexError, error.FilteredLookupError,
528 error.FilteredRepoLookupError):
525 error.FilteredRepoLookupError):
529 raise _filterederror(repo, changeid)
526 raise _filterederror(repo, changeid)
530 except IndexError:
527 except IndexError:
531 pass
528 pass
532 raise error.RepoLookupError(
529 raise error.RepoLookupError(
533 _("unknown revision '%s'") % changeid)
530 _("unknown revision '%s'") % changeid)
534
531
535 def __hash__(self):
532 def __hash__(self):
536 try:
533 try:
537 return hash(self._rev)
534 return hash(self._rev)
538 except AttributeError:
535 except AttributeError:
539 return id(self)
536 return id(self)
540
537
541 def __nonzero__(self):
538 def __nonzero__(self):
542 return self._rev != nullrev
539 return self._rev != nullrev
543
540
544 __bool__ = __nonzero__
541 __bool__ = __nonzero__
545
542
546 @propertycache
543 @propertycache
547 def _changeset(self):
544 def _changeset(self):
548 return self._repo.changelog.changelogrevision(self.rev())
545 return self._repo.changelog.changelogrevision(self.rev())
549
546
550 @propertycache
547 @propertycache
551 def _manifest(self):
548 def _manifest(self):
552 return self._manifestctx.read()
549 return self._manifestctx.read()
553
550
554 @property
551 @property
555 def _manifestctx(self):
552 def _manifestctx(self):
556 return self._repo.manifestlog[self._changeset.manifest]
553 return self._repo.manifestlog[self._changeset.manifest]
557
554
558 @propertycache
555 @propertycache
559 def _manifestdelta(self):
556 def _manifestdelta(self):
560 return self._manifestctx.readdelta()
557 return self._manifestctx.readdelta()
561
558
562 @propertycache
559 @propertycache
563 def _parents(self):
560 def _parents(self):
564 repo = self._repo
561 repo = self._repo
565 p1, p2 = repo.changelog.parentrevs(self._rev)
562 p1, p2 = repo.changelog.parentrevs(self._rev)
566 if p2 == nullrev:
563 if p2 == nullrev:
567 return [changectx(repo, p1)]
564 return [changectx(repo, p1)]
568 return [changectx(repo, p1), changectx(repo, p2)]
565 return [changectx(repo, p1), changectx(repo, p2)]
569
566
570 def changeset(self):
567 def changeset(self):
571 c = self._changeset
568 c = self._changeset
572 return (
569 return (
573 c.manifest,
570 c.manifest,
574 c.user,
571 c.user,
575 c.date,
572 c.date,
576 c.files,
573 c.files,
577 c.description,
574 c.description,
578 c.extra,
575 c.extra,
579 )
576 )
580 def manifestnode(self):
577 def manifestnode(self):
581 return self._changeset.manifest
578 return self._changeset.manifest
582
579
583 def user(self):
580 def user(self):
584 return self._changeset.user
581 return self._changeset.user
585 def date(self):
582 def date(self):
586 return self._changeset.date
583 return self._changeset.date
587 def files(self):
584 def files(self):
588 return self._changeset.files
585 return self._changeset.files
589 def description(self):
586 def description(self):
590 return self._changeset.description
587 return self._changeset.description
591 def branch(self):
588 def branch(self):
592 return encoding.tolocal(self._changeset.extra.get("branch"))
589 return encoding.tolocal(self._changeset.extra.get("branch"))
593 def closesbranch(self):
590 def closesbranch(self):
594 return 'close' in self._changeset.extra
591 return 'close' in self._changeset.extra
595 def extra(self):
592 def extra(self):
596 return self._changeset.extra
593 return self._changeset.extra
597 def tags(self):
594 def tags(self):
598 return self._repo.nodetags(self._node)
595 return self._repo.nodetags(self._node)
599 def bookmarks(self):
596 def bookmarks(self):
600 return self._repo.nodebookmarks(self._node)
597 return self._repo.nodebookmarks(self._node)
601 def phase(self):
598 def phase(self):
602 return self._repo._phasecache.phase(self._repo, self._rev)
599 return self._repo._phasecache.phase(self._repo, self._rev)
603 def hidden(self):
600 def hidden(self):
604 return self._rev in repoview.filterrevs(self._repo, 'visible')
601 return self._rev in repoview.filterrevs(self._repo, 'visible')
605
602
606 def children(self):
603 def children(self):
607 """return contexts for each child changeset"""
604 """return contexts for each child changeset"""
608 c = self._repo.changelog.children(self._node)
605 c = self._repo.changelog.children(self._node)
609 return [changectx(self._repo, x) for x in c]
606 return [changectx(self._repo, x) for x in c]
610
607
611 def ancestors(self):
608 def ancestors(self):
612 for a in self._repo.changelog.ancestors([self._rev]):
609 for a in self._repo.changelog.ancestors([self._rev]):
613 yield changectx(self._repo, a)
610 yield changectx(self._repo, a)
614
611
615 def descendants(self):
612 def descendants(self):
616 for d in self._repo.changelog.descendants([self._rev]):
613 for d in self._repo.changelog.descendants([self._rev]):
617 yield changectx(self._repo, d)
614 yield changectx(self._repo, d)
618
615
619 def filectx(self, path, fileid=None, filelog=None):
616 def filectx(self, path, fileid=None, filelog=None):
620 """get a file context from this changeset"""
617 """get a file context from this changeset"""
621 if fileid is None:
618 if fileid is None:
622 fileid = self.filenode(path)
619 fileid = self.filenode(path)
623 return filectx(self._repo, path, fileid=fileid,
620 return filectx(self._repo, path, fileid=fileid,
624 changectx=self, filelog=filelog)
621 changectx=self, filelog=filelog)
625
622
626 def ancestor(self, c2, warn=False):
623 def ancestor(self, c2, warn=False):
627 """return the "best" ancestor context of self and c2
624 """return the "best" ancestor context of self and c2
628
625
629 If there are multiple candidates, it will show a message and check
626 If there are multiple candidates, it will show a message and check
630 merge.preferancestor configuration before falling back to the
627 merge.preferancestor configuration before falling back to the
631 revlog ancestor."""
628 revlog ancestor."""
632 # deal with workingctxs
629 # deal with workingctxs
633 n2 = c2._node
630 n2 = c2._node
634 if n2 is None:
631 if n2 is None:
635 n2 = c2._parents[0]._node
632 n2 = c2._parents[0]._node
636 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
633 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
637 if not cahs:
634 if not cahs:
638 anc = nullid
635 anc = nullid
639 elif len(cahs) == 1:
636 elif len(cahs) == 1:
640 anc = cahs[0]
637 anc = cahs[0]
641 else:
638 else:
642 # experimental config: merge.preferancestor
639 # experimental config: merge.preferancestor
643 for r in self._repo.ui.configlist('merge', 'preferancestor', ['*']):
640 for r in self._repo.ui.configlist('merge', 'preferancestor', ['*']):
644 try:
641 try:
645 ctx = changectx(self._repo, r)
642 ctx = changectx(self._repo, r)
646 except error.RepoLookupError:
643 except error.RepoLookupError:
647 continue
644 continue
648 anc = ctx.node()
645 anc = ctx.node()
649 if anc in cahs:
646 if anc in cahs:
650 break
647 break
651 else:
648 else:
652 anc = self._repo.changelog.ancestor(self._node, n2)
649 anc = self._repo.changelog.ancestor(self._node, n2)
653 if warn:
650 if warn:
654 self._repo.ui.status(
651 self._repo.ui.status(
655 (_("note: using %s as ancestor of %s and %s\n") %
652 (_("note: using %s as ancestor of %s and %s\n") %
656 (short(anc), short(self._node), short(n2))) +
653 (short(anc), short(self._node), short(n2))) +
657 ''.join(_(" alternatively, use --config "
654 ''.join(_(" alternatively, use --config "
658 "merge.preferancestor=%s\n") %
655 "merge.preferancestor=%s\n") %
659 short(n) for n in sorted(cahs) if n != anc))
656 short(n) for n in sorted(cahs) if n != anc))
660 return changectx(self._repo, anc)
657 return changectx(self._repo, anc)
661
658
662 def descendant(self, other):
659 def descendant(self, other):
663 """True if other is descendant of this changeset"""
660 """True if other is descendant of this changeset"""
664 return self._repo.changelog.descendant(self._rev, other._rev)
661 return self._repo.changelog.descendant(self._rev, other._rev)
665
662
666 def walk(self, match):
663 def walk(self, match):
667 '''Generates matching file names.'''
664 '''Generates matching file names.'''
668
665
669 # Wrap match.bad method to have message with nodeid
666 # Wrap match.bad method to have message with nodeid
670 def bad(fn, msg):
667 def bad(fn, msg):
671 # The manifest doesn't know about subrepos, so don't complain about
668 # The manifest doesn't know about subrepos, so don't complain about
672 # paths into valid subrepos.
669 # paths into valid subrepos.
673 if any(fn == s or fn.startswith(s + '/')
670 if any(fn == s or fn.startswith(s + '/')
674 for s in self.substate):
671 for s in self.substate):
675 return
672 return
676 match.bad(fn, _('no such file in rev %s') % self)
673 match.bad(fn, _('no such file in rev %s') % self)
677
674
678 m = matchmod.badmatch(match, bad)
675 m = matchmod.badmatch(match, bad)
679 return self._manifest.walk(m)
676 return self._manifest.walk(m)
680
677
681 def matches(self, match):
678 def matches(self, match):
682 return self.walk(match)
679 return self.walk(match)
683
680
684 class basefilectx(object):
681 class basefilectx(object):
685 """A filecontext object represents the common logic for its children:
682 """A filecontext object represents the common logic for its children:
686 filectx: read-only access to a filerevision that is already present
683 filectx: read-only access to a filerevision that is already present
687 in the repo,
684 in the repo,
688 workingfilectx: a filecontext that represents files from the working
685 workingfilectx: a filecontext that represents files from the working
689 directory,
686 directory,
690 memfilectx: a filecontext that represents files in-memory,
687 memfilectx: a filecontext that represents files in-memory,
691 overlayfilectx: duplicate another filecontext with some fields overridden.
688 overlayfilectx: duplicate another filecontext with some fields overridden.
692 """
689 """
693 @propertycache
690 @propertycache
694 def _filelog(self):
691 def _filelog(self):
695 return self._repo.file(self._path)
692 return self._repo.file(self._path)
696
693
697 @propertycache
694 @propertycache
698 def _changeid(self):
695 def _changeid(self):
699 if r'_changeid' in self.__dict__:
696 if r'_changeid' in self.__dict__:
700 return self._changeid
697 return self._changeid
701 elif r'_changectx' in self.__dict__:
698 elif r'_changectx' in self.__dict__:
702 return self._changectx.rev()
699 return self._changectx.rev()
703 elif r'_descendantrev' in self.__dict__:
700 elif r'_descendantrev' in self.__dict__:
704 # this file context was created from a revision with a known
701 # this file context was created from a revision with a known
705 # descendant, we can (lazily) correct for linkrev aliases
702 # descendant, we can (lazily) correct for linkrev aliases
706 return self._adjustlinkrev(self._descendantrev)
703 return self._adjustlinkrev(self._descendantrev)
707 else:
704 else:
708 return self._filelog.linkrev(self._filerev)
705 return self._filelog.linkrev(self._filerev)
709
706
710 @propertycache
707 @propertycache
711 def _filenode(self):
708 def _filenode(self):
712 if r'_fileid' in self.__dict__:
709 if r'_fileid' in self.__dict__:
713 return self._filelog.lookup(self._fileid)
710 return self._filelog.lookup(self._fileid)
714 else:
711 else:
715 return self._changectx.filenode(self._path)
712 return self._changectx.filenode(self._path)
716
713
717 @propertycache
714 @propertycache
718 def _filerev(self):
715 def _filerev(self):
719 return self._filelog.rev(self._filenode)
716 return self._filelog.rev(self._filenode)
720
717
721 @propertycache
718 @propertycache
722 def _repopath(self):
719 def _repopath(self):
723 return self._path
720 return self._path
724
721
725 def __nonzero__(self):
722 def __nonzero__(self):
726 try:
723 try:
727 self._filenode
724 self._filenode
728 return True
725 return True
729 except error.LookupError:
726 except error.LookupError:
730 # file is missing
727 # file is missing
731 return False
728 return False
732
729
733 __bool__ = __nonzero__
730 __bool__ = __nonzero__
734
731
735 def __str__(self):
732 def __str__(self):
736 try:
733 try:
737 return "%s@%s" % (self.path(), self._changectx)
734 return "%s@%s" % (self.path(), self._changectx)
738 except error.LookupError:
735 except error.LookupError:
739 return "%s@???" % self.path()
736 return "%s@???" % self.path()
740
737
741 def __repr__(self):
738 def __repr__(self):
742 return "<%s %s>" % (type(self).__name__, str(self))
739 return "<%s %s>" % (type(self).__name__, str(self))
743
740
744 def __hash__(self):
741 def __hash__(self):
745 try:
742 try:
746 return hash((self._path, self._filenode))
743 return hash((self._path, self._filenode))
747 except AttributeError:
744 except AttributeError:
748 return id(self)
745 return id(self)
749
746
750 def __eq__(self, other):
747 def __eq__(self, other):
751 try:
748 try:
752 return (type(self) == type(other) and self._path == other._path
749 return (type(self) == type(other) and self._path == other._path
753 and self._filenode == other._filenode)
750 and self._filenode == other._filenode)
754 except AttributeError:
751 except AttributeError:
755 return False
752 return False
756
753
757 def __ne__(self, other):
754 def __ne__(self, other):
758 return not (self == other)
755 return not (self == other)
759
756
760 def filerev(self):
757 def filerev(self):
761 return self._filerev
758 return self._filerev
762 def filenode(self):
759 def filenode(self):
763 return self._filenode
760 return self._filenode
764 @propertycache
761 @propertycache
765 def _flags(self):
762 def _flags(self):
766 return self._changectx.flags(self._path)
763 return self._changectx.flags(self._path)
767 def flags(self):
764 def flags(self):
768 return self._flags
765 return self._flags
769 def filelog(self):
766 def filelog(self):
770 return self._filelog
767 return self._filelog
771 def rev(self):
768 def rev(self):
772 return self._changeid
769 return self._changeid
773 def linkrev(self):
770 def linkrev(self):
774 return self._filelog.linkrev(self._filerev)
771 return self._filelog.linkrev(self._filerev)
775 def node(self):
772 def node(self):
776 return self._changectx.node()
773 return self._changectx.node()
777 def hex(self):
774 def hex(self):
778 return self._changectx.hex()
775 return self._changectx.hex()
779 def user(self):
776 def user(self):
780 return self._changectx.user()
777 return self._changectx.user()
781 def date(self):
778 def date(self):
782 return self._changectx.date()
779 return self._changectx.date()
783 def files(self):
780 def files(self):
784 return self._changectx.files()
781 return self._changectx.files()
785 def description(self):
782 def description(self):
786 return self._changectx.description()
783 return self._changectx.description()
787 def branch(self):
784 def branch(self):
788 return self._changectx.branch()
785 return self._changectx.branch()
789 def extra(self):
786 def extra(self):
790 return self._changectx.extra()
787 return self._changectx.extra()
791 def phase(self):
788 def phase(self):
792 return self._changectx.phase()
789 return self._changectx.phase()
793 def phasestr(self):
790 def phasestr(self):
794 return self._changectx.phasestr()
791 return self._changectx.phasestr()
795 def manifest(self):
792 def manifest(self):
796 return self._changectx.manifest()
793 return self._changectx.manifest()
797 def changectx(self):
794 def changectx(self):
798 return self._changectx
795 return self._changectx
799 def renamed(self):
796 def renamed(self):
800 return self._copied
797 return self._copied
801 def repo(self):
798 def repo(self):
802 return self._repo
799 return self._repo
803 def size(self):
800 def size(self):
804 return len(self.data())
801 return len(self.data())
805
802
806 def path(self):
803 def path(self):
807 return self._path
804 return self._path
808
805
809 def isbinary(self):
806 def isbinary(self):
810 try:
807 try:
811 return util.binary(self.data())
808 return util.binary(self.data())
812 except IOError:
809 except IOError:
813 return False
810 return False
814 def isexec(self):
811 def isexec(self):
815 return 'x' in self.flags()
812 return 'x' in self.flags()
816 def islink(self):
813 def islink(self):
817 return 'l' in self.flags()
814 return 'l' in self.flags()
818
815
819 def isabsent(self):
816 def isabsent(self):
820 """whether this filectx represents a file not in self._changectx
817 """whether this filectx represents a file not in self._changectx
821
818
822 This is mainly for merge code to detect change/delete conflicts. This is
819 This is mainly for merge code to detect change/delete conflicts. This is
823 expected to be True for all subclasses of basectx."""
820 expected to be True for all subclasses of basectx."""
824 return False
821 return False
825
822
826 _customcmp = False
823 _customcmp = False
827 def cmp(self, fctx):
824 def cmp(self, fctx):
828 """compare with other file context
825 """compare with other file context
829
826
830 returns True if different than fctx.
827 returns True if different than fctx.
831 """
828 """
832 if fctx._customcmp:
829 if fctx._customcmp:
833 return fctx.cmp(self)
830 return fctx.cmp(self)
834
831
835 if (fctx._filenode is None
832 if (fctx._filenode is None
836 and (self._repo._encodefilterpats
833 and (self._repo._encodefilterpats
837 # if file data starts with '\1\n', empty metadata block is
834 # if file data starts with '\1\n', empty metadata block is
838 # prepended, which adds 4 bytes to filelog.size().
835 # prepended, which adds 4 bytes to filelog.size().
839 or self.size() - 4 == fctx.size())
836 or self.size() - 4 == fctx.size())
840 or self.size() == fctx.size()):
837 or self.size() == fctx.size()):
841 return self._filelog.cmp(self._filenode, fctx.data())
838 return self._filelog.cmp(self._filenode, fctx.data())
842
839
843 return True
840 return True
844
841
845 def _adjustlinkrev(self, srcrev, inclusive=False):
842 def _adjustlinkrev(self, srcrev, inclusive=False):
846 """return the first ancestor of <srcrev> introducing <fnode>
843 """return the first ancestor of <srcrev> introducing <fnode>
847
844
848 If the linkrev of the file revision does not point to an ancestor of
845 If the linkrev of the file revision does not point to an ancestor of
849 srcrev, we'll walk down the ancestors until we find one introducing
846 srcrev, we'll walk down the ancestors until we find one introducing
850 this file revision.
847 this file revision.
851
848
852 :srcrev: the changeset revision we search ancestors from
849 :srcrev: the changeset revision we search ancestors from
853 :inclusive: if true, the src revision will also be checked
850 :inclusive: if true, the src revision will also be checked
854 """
851 """
855 repo = self._repo
852 repo = self._repo
856 cl = repo.unfiltered().changelog
853 cl = repo.unfiltered().changelog
857 mfl = repo.manifestlog
854 mfl = repo.manifestlog
858 # fetch the linkrev
855 # fetch the linkrev
859 lkr = self.linkrev()
856 lkr = self.linkrev()
860 # hack to reuse ancestor computation when searching for renames
857 # hack to reuse ancestor computation when searching for renames
861 memberanc = getattr(self, '_ancestrycontext', None)
858 memberanc = getattr(self, '_ancestrycontext', None)
862 iteranc = None
859 iteranc = None
863 if srcrev is None:
860 if srcrev is None:
864 # wctx case, used by workingfilectx during mergecopy
861 # wctx case, used by workingfilectx during mergecopy
865 revs = [p.rev() for p in self._repo[None].parents()]
862 revs = [p.rev() for p in self._repo[None].parents()]
866 inclusive = True # we skipped the real (revless) source
863 inclusive = True # we skipped the real (revless) source
867 else:
864 else:
868 revs = [srcrev]
865 revs = [srcrev]
869 if memberanc is None:
866 if memberanc is None:
870 memberanc = iteranc = cl.ancestors(revs, lkr,
867 memberanc = iteranc = cl.ancestors(revs, lkr,
871 inclusive=inclusive)
868 inclusive=inclusive)
872 # check if this linkrev is an ancestor of srcrev
869 # check if this linkrev is an ancestor of srcrev
873 if lkr not in memberanc:
870 if lkr not in memberanc:
874 if iteranc is None:
871 if iteranc is None:
875 iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
872 iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
876 fnode = self._filenode
873 fnode = self._filenode
877 path = self._path
874 path = self._path
878 for a in iteranc:
875 for a in iteranc:
879 ac = cl.read(a) # get changeset data (we avoid object creation)
876 ac = cl.read(a) # get changeset data (we avoid object creation)
880 if path in ac[3]: # checking the 'files' field.
877 if path in ac[3]: # checking the 'files' field.
881 # The file has been touched, check if the content is
878 # The file has been touched, check if the content is
882 # similar to the one we search for.
879 # similar to the one we search for.
883 if fnode == mfl[ac[0]].readfast().get(path):
880 if fnode == mfl[ac[0]].readfast().get(path):
884 return a
881 return a
885 # In theory, we should never get out of that loop without a result.
882 # In theory, we should never get out of that loop without a result.
886 # But if manifest uses a buggy file revision (not children of the
883 # But if manifest uses a buggy file revision (not children of the
887 # one it replaces) we could. Such a buggy situation will likely
884 # one it replaces) we could. Such a buggy situation will likely
888 # result is crash somewhere else at to some point.
885 # result is crash somewhere else at to some point.
889 return lkr
886 return lkr
890
887
891 def introrev(self):
888 def introrev(self):
892 """return the rev of the changeset which introduced this file revision
889 """return the rev of the changeset which introduced this file revision
893
890
894 This method is different from linkrev because it take into account the
891 This method is different from linkrev because it take into account the
895 changeset the filectx was created from. It ensures the returned
892 changeset the filectx was created from. It ensures the returned
896 revision is one of its ancestors. This prevents bugs from
893 revision is one of its ancestors. This prevents bugs from
897 'linkrev-shadowing' when a file revision is used by multiple
894 'linkrev-shadowing' when a file revision is used by multiple
898 changesets.
895 changesets.
899 """
896 """
900 lkr = self.linkrev()
897 lkr = self.linkrev()
901 attrs = vars(self)
898 attrs = vars(self)
902 noctx = not ('_changeid' in attrs or '_changectx' in attrs)
899 noctx = not ('_changeid' in attrs or '_changectx' in attrs)
903 if noctx or self.rev() == lkr:
900 if noctx or self.rev() == lkr:
904 return self.linkrev()
901 return self.linkrev()
905 return self._adjustlinkrev(self.rev(), inclusive=True)
902 return self._adjustlinkrev(self.rev(), inclusive=True)
906
903
907 def _parentfilectx(self, path, fileid, filelog):
904 def _parentfilectx(self, path, fileid, filelog):
908 """create parent filectx keeping ancestry info for _adjustlinkrev()"""
905 """create parent filectx keeping ancestry info for _adjustlinkrev()"""
909 fctx = filectx(self._repo, path, fileid=fileid, filelog=filelog)
906 fctx = filectx(self._repo, path, fileid=fileid, filelog=filelog)
910 if '_changeid' in vars(self) or '_changectx' in vars(self):
907 if '_changeid' in vars(self) or '_changectx' in vars(self):
911 # If self is associated with a changeset (probably explicitly
908 # If self is associated with a changeset (probably explicitly
912 # fed), ensure the created filectx is associated with a
909 # fed), ensure the created filectx is associated with a
913 # changeset that is an ancestor of self.changectx.
910 # changeset that is an ancestor of self.changectx.
914 # This lets us later use _adjustlinkrev to get a correct link.
911 # This lets us later use _adjustlinkrev to get a correct link.
915 fctx._descendantrev = self.rev()
912 fctx._descendantrev = self.rev()
916 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
913 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
917 elif '_descendantrev' in vars(self):
914 elif '_descendantrev' in vars(self):
918 # Otherwise propagate _descendantrev if we have one associated.
915 # Otherwise propagate _descendantrev if we have one associated.
919 fctx._descendantrev = self._descendantrev
916 fctx._descendantrev = self._descendantrev
920 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
917 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
921 return fctx
918 return fctx
922
919
923 def parents(self):
920 def parents(self):
924 _path = self._path
921 _path = self._path
925 fl = self._filelog
922 fl = self._filelog
926 parents = self._filelog.parents(self._filenode)
923 parents = self._filelog.parents(self._filenode)
927 pl = [(_path, node, fl) for node in parents if node != nullid]
924 pl = [(_path, node, fl) for node in parents if node != nullid]
928
925
929 r = fl.renamed(self._filenode)
926 r = fl.renamed(self._filenode)
930 if r:
927 if r:
931 # - In the simple rename case, both parent are nullid, pl is empty.
928 # - In the simple rename case, both parent are nullid, pl is empty.
932 # - In case of merge, only one of the parent is null id and should
929 # - In case of merge, only one of the parent is null id and should
933 # be replaced with the rename information. This parent is -always-
930 # be replaced with the rename information. This parent is -always-
934 # the first one.
931 # the first one.
935 #
932 #
936 # As null id have always been filtered out in the previous list
933 # As null id have always been filtered out in the previous list
937 # comprehension, inserting to 0 will always result in "replacing
934 # comprehension, inserting to 0 will always result in "replacing
938 # first nullid parent with rename information.
935 # first nullid parent with rename information.
939 pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
936 pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
940
937
941 return [self._parentfilectx(path, fnode, l) for path, fnode, l in pl]
938 return [self._parentfilectx(path, fnode, l) for path, fnode, l in pl]
942
939
943 def p1(self):
940 def p1(self):
944 return self.parents()[0]
941 return self.parents()[0]
945
942
946 def p2(self):
943 def p2(self):
947 p = self.parents()
944 p = self.parents()
948 if len(p) == 2:
945 if len(p) == 2:
949 return p[1]
946 return p[1]
950 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
947 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
951
948
952 def annotate(self, follow=False, linenumber=False, skiprevs=None,
949 def annotate(self, follow=False, linenumber=False, skiprevs=None,
953 diffopts=None):
950 diffopts=None):
954 '''returns a list of tuples of ((ctx, number), line) for each line
951 '''returns a list of tuples of ((ctx, number), line) for each line
955 in the file, where ctx is the filectx of the node where
952 in the file, where ctx is the filectx of the node where
956 that line was last changed; if linenumber parameter is true, number is
953 that line was last changed; if linenumber parameter is true, number is
957 the line number at the first appearance in the managed file, otherwise,
954 the line number at the first appearance in the managed file, otherwise,
958 number has a fixed value of False.
955 number has a fixed value of False.
959 '''
956 '''
960
957
961 def lines(text):
958 def lines(text):
962 if text.endswith("\n"):
959 if text.endswith("\n"):
963 return text.count("\n")
960 return text.count("\n")
964 return text.count("\n") + int(bool(text))
961 return text.count("\n") + int(bool(text))
965
962
966 if linenumber:
963 if linenumber:
967 def decorate(text, rev):
964 def decorate(text, rev):
968 return ([(rev, i) for i in xrange(1, lines(text) + 1)], text)
965 return ([(rev, i) for i in xrange(1, lines(text) + 1)], text)
969 else:
966 else:
970 def decorate(text, rev):
967 def decorate(text, rev):
971 return ([(rev, False)] * lines(text), text)
968 return ([(rev, False)] * lines(text), text)
972
969
973 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
970 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
974
971
975 def parents(f):
972 def parents(f):
976 # Cut _descendantrev here to mitigate the penalty of lazy linkrev
973 # Cut _descendantrev here to mitigate the penalty of lazy linkrev
977 # adjustment. Otherwise, p._adjustlinkrev() would walk changelog
974 # adjustment. Otherwise, p._adjustlinkrev() would walk changelog
978 # from the topmost introrev (= srcrev) down to p.linkrev() if it
975 # from the topmost introrev (= srcrev) down to p.linkrev() if it
979 # isn't an ancestor of the srcrev.
976 # isn't an ancestor of the srcrev.
980 f._changeid
977 f._changeid
981 pl = f.parents()
978 pl = f.parents()
982
979
983 # Don't return renamed parents if we aren't following.
980 # Don't return renamed parents if we aren't following.
984 if not follow:
981 if not follow:
985 pl = [p for p in pl if p.path() == f.path()]
982 pl = [p for p in pl if p.path() == f.path()]
986
983
987 # renamed filectx won't have a filelog yet, so set it
984 # renamed filectx won't have a filelog yet, so set it
988 # from the cache to save time
985 # from the cache to save time
989 for p in pl:
986 for p in pl:
990 if not '_filelog' in p.__dict__:
987 if not '_filelog' in p.__dict__:
991 p._filelog = getlog(p.path())
988 p._filelog = getlog(p.path())
992
989
993 return pl
990 return pl
994
991
995 # use linkrev to find the first changeset where self appeared
992 # use linkrev to find the first changeset where self appeared
996 base = self
993 base = self
997 introrev = self.introrev()
994 introrev = self.introrev()
998 if self.rev() != introrev:
995 if self.rev() != introrev:
999 base = self.filectx(self.filenode(), changeid=introrev)
996 base = self.filectx(self.filenode(), changeid=introrev)
1000 if getattr(base, '_ancestrycontext', None) is None:
997 if getattr(base, '_ancestrycontext', None) is None:
1001 cl = self._repo.changelog
998 cl = self._repo.changelog
1002 if introrev is None:
999 if introrev is None:
1003 # wctx is not inclusive, but works because _ancestrycontext
1000 # wctx is not inclusive, but works because _ancestrycontext
1004 # is used to test filelog revisions
1001 # is used to test filelog revisions
1005 ac = cl.ancestors([p.rev() for p in base.parents()],
1002 ac = cl.ancestors([p.rev() for p in base.parents()],
1006 inclusive=True)
1003 inclusive=True)
1007 else:
1004 else:
1008 ac = cl.ancestors([introrev], inclusive=True)
1005 ac = cl.ancestors([introrev], inclusive=True)
1009 base._ancestrycontext = ac
1006 base._ancestrycontext = ac
1010
1007
1011 # This algorithm would prefer to be recursive, but Python is a
1008 # This algorithm would prefer to be recursive, but Python is a
1012 # bit recursion-hostile. Instead we do an iterative
1009 # bit recursion-hostile. Instead we do an iterative
1013 # depth-first search.
1010 # depth-first search.
1014
1011
1015 # 1st DFS pre-calculates pcache and needed
1012 # 1st DFS pre-calculates pcache and needed
1016 visit = [base]
1013 visit = [base]
1017 pcache = {}
1014 pcache = {}
1018 needed = {base: 1}
1015 needed = {base: 1}
1019 while visit:
1016 while visit:
1020 f = visit.pop()
1017 f = visit.pop()
1021 if f in pcache:
1018 if f in pcache:
1022 continue
1019 continue
1023 pl = parents(f)
1020 pl = parents(f)
1024 pcache[f] = pl
1021 pcache[f] = pl
1025 for p in pl:
1022 for p in pl:
1026 needed[p] = needed.get(p, 0) + 1
1023 needed[p] = needed.get(p, 0) + 1
1027 if p not in pcache:
1024 if p not in pcache:
1028 visit.append(p)
1025 visit.append(p)
1029
1026
1030 # 2nd DFS does the actual annotate
1027 # 2nd DFS does the actual annotate
1031 visit[:] = [base]
1028 visit[:] = [base]
1032 hist = {}
1029 hist = {}
1033 while visit:
1030 while visit:
1034 f = visit[-1]
1031 f = visit[-1]
1035 if f in hist:
1032 if f in hist:
1036 visit.pop()
1033 visit.pop()
1037 continue
1034 continue
1038
1035
1039 ready = True
1036 ready = True
1040 pl = pcache[f]
1037 pl = pcache[f]
1041 for p in pl:
1038 for p in pl:
1042 if p not in hist:
1039 if p not in hist:
1043 ready = False
1040 ready = False
1044 visit.append(p)
1041 visit.append(p)
1045 if ready:
1042 if ready:
1046 visit.pop()
1043 visit.pop()
1047 curr = decorate(f.data(), f)
1044 curr = decorate(f.data(), f)
1048 skipchild = False
1045 skipchild = False
1049 if skiprevs is not None:
1046 if skiprevs is not None:
1050 skipchild = f._changeid in skiprevs
1047 skipchild = f._changeid in skiprevs
1051 curr = _annotatepair([hist[p] for p in pl], f, curr, skipchild,
1048 curr = _annotatepair([hist[p] for p in pl], f, curr, skipchild,
1052 diffopts)
1049 diffopts)
1053 for p in pl:
1050 for p in pl:
1054 if needed[p] == 1:
1051 if needed[p] == 1:
1055 del hist[p]
1052 del hist[p]
1056 del needed[p]
1053 del needed[p]
1057 else:
1054 else:
1058 needed[p] -= 1
1055 needed[p] -= 1
1059
1056
1060 hist[f] = curr
1057 hist[f] = curr
1061 del pcache[f]
1058 del pcache[f]
1062
1059
1063 return zip(hist[base][0], hist[base][1].splitlines(True))
1060 return zip(hist[base][0], hist[base][1].splitlines(True))
1064
1061
1065 def ancestors(self, followfirst=False):
1062 def ancestors(self, followfirst=False):
1066 visit = {}
1063 visit = {}
1067 c = self
1064 c = self
1068 if followfirst:
1065 if followfirst:
1069 cut = 1
1066 cut = 1
1070 else:
1067 else:
1071 cut = None
1068 cut = None
1072
1069
1073 while True:
1070 while True:
1074 for parent in c.parents()[:cut]:
1071 for parent in c.parents()[:cut]:
1075 visit[(parent.linkrev(), parent.filenode())] = parent
1072 visit[(parent.linkrev(), parent.filenode())] = parent
1076 if not visit:
1073 if not visit:
1077 break
1074 break
1078 c = visit.pop(max(visit))
1075 c = visit.pop(max(visit))
1079 yield c
1076 yield c
1080
1077
1081 def _annotatepair(parents, childfctx, child, skipchild, diffopts):
1078 def _annotatepair(parents, childfctx, child, skipchild, diffopts):
1082 r'''
1079 r'''
1083 Given parent and child fctxes and annotate data for parents, for all lines
1080 Given parent and child fctxes and annotate data for parents, for all lines
1084 in either parent that match the child, annotate the child with the parent's
1081 in either parent that match the child, annotate the child with the parent's
1085 data.
1082 data.
1086
1083
1087 Additionally, if `skipchild` is True, replace all other lines with parent
1084 Additionally, if `skipchild` is True, replace all other lines with parent
1088 annotate data as well such that child is never blamed for any lines.
1085 annotate data as well such that child is never blamed for any lines.
1089
1086
1090 >>> oldfctx = 'old'
1087 >>> oldfctx = 'old'
1091 >>> p1fctx, p2fctx, childfctx = 'p1', 'p2', 'c'
1088 >>> p1fctx, p2fctx, childfctx = 'p1', 'p2', 'c'
1092 >>> olddata = 'a\nb\n'
1089 >>> olddata = 'a\nb\n'
1093 >>> p1data = 'a\nb\nc\n'
1090 >>> p1data = 'a\nb\nc\n'
1094 >>> p2data = 'a\nc\nd\n'
1091 >>> p2data = 'a\nc\nd\n'
1095 >>> childdata = 'a\nb2\nc\nc2\nd\n'
1092 >>> childdata = 'a\nb2\nc\nc2\nd\n'
1096 >>> diffopts = mdiff.diffopts()
1093 >>> diffopts = mdiff.diffopts()
1097
1094
1098 >>> def decorate(text, rev):
1095 >>> def decorate(text, rev):
1099 ... return ([(rev, i) for i in xrange(1, text.count('\n') + 1)], text)
1096 ... return ([(rev, i) for i in xrange(1, text.count('\n') + 1)], text)
1100
1097
1101 Basic usage:
1098 Basic usage:
1102
1099
1103 >>> oldann = decorate(olddata, oldfctx)
1100 >>> oldann = decorate(olddata, oldfctx)
1104 >>> p1ann = decorate(p1data, p1fctx)
1101 >>> p1ann = decorate(p1data, p1fctx)
1105 >>> p1ann = _annotatepair([oldann], p1fctx, p1ann, False, diffopts)
1102 >>> p1ann = _annotatepair([oldann], p1fctx, p1ann, False, diffopts)
1106 >>> p1ann[0]
1103 >>> p1ann[0]
1107 [('old', 1), ('old', 2), ('p1', 3)]
1104 [('old', 1), ('old', 2), ('p1', 3)]
1108 >>> p2ann = decorate(p2data, p2fctx)
1105 >>> p2ann = decorate(p2data, p2fctx)
1109 >>> p2ann = _annotatepair([oldann], p2fctx, p2ann, False, diffopts)
1106 >>> p2ann = _annotatepair([oldann], p2fctx, p2ann, False, diffopts)
1110 >>> p2ann[0]
1107 >>> p2ann[0]
1111 [('old', 1), ('p2', 2), ('p2', 3)]
1108 [('old', 1), ('p2', 2), ('p2', 3)]
1112
1109
1113 Test with multiple parents (note the difference caused by ordering):
1110 Test with multiple parents (note the difference caused by ordering):
1114
1111
1115 >>> childann = decorate(childdata, childfctx)
1112 >>> childann = decorate(childdata, childfctx)
1116 >>> childann = _annotatepair([p1ann, p2ann], childfctx, childann, False,
1113 >>> childann = _annotatepair([p1ann, p2ann], childfctx, childann, False,
1117 ... diffopts)
1114 ... diffopts)
1118 >>> childann[0]
1115 >>> childann[0]
1119 [('old', 1), ('c', 2), ('p2', 2), ('c', 4), ('p2', 3)]
1116 [('old', 1), ('c', 2), ('p2', 2), ('c', 4), ('p2', 3)]
1120
1117
1121 >>> childann = decorate(childdata, childfctx)
1118 >>> childann = decorate(childdata, childfctx)
1122 >>> childann = _annotatepair([p2ann, p1ann], childfctx, childann, False,
1119 >>> childann = _annotatepair([p2ann, p1ann], childfctx, childann, False,
1123 ... diffopts)
1120 ... diffopts)
1124 >>> childann[0]
1121 >>> childann[0]
1125 [('old', 1), ('c', 2), ('p1', 3), ('c', 4), ('p2', 3)]
1122 [('old', 1), ('c', 2), ('p1', 3), ('c', 4), ('p2', 3)]
1126
1123
1127 Test with skipchild (note the difference caused by ordering):
1124 Test with skipchild (note the difference caused by ordering):
1128
1125
1129 >>> childann = decorate(childdata, childfctx)
1126 >>> childann = decorate(childdata, childfctx)
1130 >>> childann = _annotatepair([p1ann, p2ann], childfctx, childann, True,
1127 >>> childann = _annotatepair([p1ann, p2ann], childfctx, childann, True,
1131 ... diffopts)
1128 ... diffopts)
1132 >>> childann[0]
1129 >>> childann[0]
1133 [('old', 1), ('old', 2), ('p2', 2), ('p2', 2), ('p2', 3)]
1130 [('old', 1), ('old', 2), ('p2', 2), ('p2', 2), ('p2', 3)]
1134
1131
1135 >>> childann = decorate(childdata, childfctx)
1132 >>> childann = decorate(childdata, childfctx)
1136 >>> childann = _annotatepair([p2ann, p1ann], childfctx, childann, True,
1133 >>> childann = _annotatepair([p2ann, p1ann], childfctx, childann, True,
1137 ... diffopts)
1134 ... diffopts)
1138 >>> childann[0]
1135 >>> childann[0]
1139 [('old', 1), ('old', 2), ('p1', 3), ('p1', 3), ('p2', 3)]
1136 [('old', 1), ('old', 2), ('p1', 3), ('p1', 3), ('p2', 3)]
1140 '''
1137 '''
1141 pblocks = [(parent, mdiff.allblocks(parent[1], child[1], opts=diffopts))
1138 pblocks = [(parent, mdiff.allblocks(parent[1], child[1], opts=diffopts))
1142 for parent in parents]
1139 for parent in parents]
1143
1140
1144 if skipchild:
1141 if skipchild:
1145 # Need to iterate over the blocks twice -- make it a list
1142 # Need to iterate over the blocks twice -- make it a list
1146 pblocks = [(p, list(blocks)) for (p, blocks) in pblocks]
1143 pblocks = [(p, list(blocks)) for (p, blocks) in pblocks]
1147 # Mercurial currently prefers p2 over p1 for annotate.
1144 # Mercurial currently prefers p2 over p1 for annotate.
1148 # TODO: change this?
1145 # TODO: change this?
1149 for parent, blocks in pblocks:
1146 for parent, blocks in pblocks:
1150 for (a1, a2, b1, b2), t in blocks:
1147 for (a1, a2, b1, b2), t in blocks:
1151 # Changed blocks ('!') or blocks made only of blank lines ('~')
1148 # Changed blocks ('!') or blocks made only of blank lines ('~')
1152 # belong to the child.
1149 # belong to the child.
1153 if t == '=':
1150 if t == '=':
1154 child[0][b1:b2] = parent[0][a1:a2]
1151 child[0][b1:b2] = parent[0][a1:a2]
1155
1152
1156 if skipchild:
1153 if skipchild:
1157 # Now try and match up anything that couldn't be matched,
1154 # Now try and match up anything that couldn't be matched,
1158 # Reversing pblocks maintains bias towards p2, matching above
1155 # Reversing pblocks maintains bias towards p2, matching above
1159 # behavior.
1156 # behavior.
1160 pblocks.reverse()
1157 pblocks.reverse()
1161
1158
1162 # The heuristics are:
1159 # The heuristics are:
1163 # * Work on blocks of changed lines (effectively diff hunks with -U0).
1160 # * Work on blocks of changed lines (effectively diff hunks with -U0).
1164 # This could potentially be smarter but works well enough.
1161 # This could potentially be smarter but works well enough.
1165 # * For a non-matching section, do a best-effort fit. Match lines in
1162 # * For a non-matching section, do a best-effort fit. Match lines in
1166 # diff hunks 1:1, dropping lines as necessary.
1163 # diff hunks 1:1, dropping lines as necessary.
1167 # * Repeat the last line as a last resort.
1164 # * Repeat the last line as a last resort.
1168
1165
1169 # First, replace as much as possible without repeating the last line.
1166 # First, replace as much as possible without repeating the last line.
1170 remaining = [(parent, []) for parent, _blocks in pblocks]
1167 remaining = [(parent, []) for parent, _blocks in pblocks]
1171 for idx, (parent, blocks) in enumerate(pblocks):
1168 for idx, (parent, blocks) in enumerate(pblocks):
1172 for (a1, a2, b1, b2), _t in blocks:
1169 for (a1, a2, b1, b2), _t in blocks:
1173 if a2 - a1 >= b2 - b1:
1170 if a2 - a1 >= b2 - b1:
1174 for bk in xrange(b1, b2):
1171 for bk in xrange(b1, b2):
1175 if child[0][bk][0] == childfctx:
1172 if child[0][bk][0] == childfctx:
1176 ak = min(a1 + (bk - b1), a2 - 1)
1173 ak = min(a1 + (bk - b1), a2 - 1)
1177 child[0][bk] = parent[0][ak]
1174 child[0][bk] = parent[0][ak]
1178 else:
1175 else:
1179 remaining[idx][1].append((a1, a2, b1, b2))
1176 remaining[idx][1].append((a1, a2, b1, b2))
1180
1177
1181 # Then, look at anything left, which might involve repeating the last
1178 # Then, look at anything left, which might involve repeating the last
1182 # line.
1179 # line.
1183 for parent, blocks in remaining:
1180 for parent, blocks in remaining:
1184 for a1, a2, b1, b2 in blocks:
1181 for a1, a2, b1, b2 in blocks:
1185 for bk in xrange(b1, b2):
1182 for bk in xrange(b1, b2):
1186 if child[0][bk][0] == childfctx:
1183 if child[0][bk][0] == childfctx:
1187 ak = min(a1 + (bk - b1), a2 - 1)
1184 ak = min(a1 + (bk - b1), a2 - 1)
1188 child[0][bk] = parent[0][ak]
1185 child[0][bk] = parent[0][ak]
1189 return child
1186 return child
1190
1187
1191 class filectx(basefilectx):
1188 class filectx(basefilectx):
1192 """A filecontext object makes access to data related to a particular
1189 """A filecontext object makes access to data related to a particular
1193 filerevision convenient."""
1190 filerevision convenient."""
1194 def __init__(self, repo, path, changeid=None, fileid=None,
1191 def __init__(self, repo, path, changeid=None, fileid=None,
1195 filelog=None, changectx=None):
1192 filelog=None, changectx=None):
1196 """changeid can be a changeset revision, node, or tag.
1193 """changeid can be a changeset revision, node, or tag.
1197 fileid can be a file revision or node."""
1194 fileid can be a file revision or node."""
1198 self._repo = repo
1195 self._repo = repo
1199 self._path = path
1196 self._path = path
1200
1197
1201 assert (changeid is not None
1198 assert (changeid is not None
1202 or fileid is not None
1199 or fileid is not None
1203 or changectx is not None), \
1200 or changectx is not None), \
1204 ("bad args: changeid=%r, fileid=%r, changectx=%r"
1201 ("bad args: changeid=%r, fileid=%r, changectx=%r"
1205 % (changeid, fileid, changectx))
1202 % (changeid, fileid, changectx))
1206
1203
1207 if filelog is not None:
1204 if filelog is not None:
1208 self._filelog = filelog
1205 self._filelog = filelog
1209
1206
1210 if changeid is not None:
1207 if changeid is not None:
1211 self._changeid = changeid
1208 self._changeid = changeid
1212 if changectx is not None:
1209 if changectx is not None:
1213 self._changectx = changectx
1210 self._changectx = changectx
1214 if fileid is not None:
1211 if fileid is not None:
1215 self._fileid = fileid
1212 self._fileid = fileid
1216
1213
1217 @propertycache
1214 @propertycache
1218 def _changectx(self):
1215 def _changectx(self):
1219 try:
1216 try:
1220 return changectx(self._repo, self._changeid)
1217 return changectx(self._repo, self._changeid)
1221 except error.FilteredRepoLookupError:
1218 except error.FilteredRepoLookupError:
1222 # Linkrev may point to any revision in the repository. When the
1219 # Linkrev may point to any revision in the repository. When the
1223 # repository is filtered this may lead to `filectx` trying to build
1220 # repository is filtered this may lead to `filectx` trying to build
1224 # `changectx` for filtered revision. In such case we fallback to
1221 # `changectx` for filtered revision. In such case we fallback to
1225 # creating `changectx` on the unfiltered version of the reposition.
1222 # creating `changectx` on the unfiltered version of the reposition.
1226 # This fallback should not be an issue because `changectx` from
1223 # This fallback should not be an issue because `changectx` from
1227 # `filectx` are not used in complex operations that care about
1224 # `filectx` are not used in complex operations that care about
1228 # filtering.
1225 # filtering.
1229 #
1226 #
1230 # This fallback is a cheap and dirty fix that prevent several
1227 # This fallback is a cheap and dirty fix that prevent several
1231 # crashes. It does not ensure the behavior is correct. However the
1228 # crashes. It does not ensure the behavior is correct. However the
1232 # behavior was not correct before filtering either and "incorrect
1229 # behavior was not correct before filtering either and "incorrect
1233 # behavior" is seen as better as "crash"
1230 # behavior" is seen as better as "crash"
1234 #
1231 #
1235 # Linkrevs have several serious troubles with filtering that are
1232 # Linkrevs have several serious troubles with filtering that are
1236 # complicated to solve. Proper handling of the issue here should be
1233 # complicated to solve. Proper handling of the issue here should be
1237 # considered when solving linkrev issue are on the table.
1234 # considered when solving linkrev issue are on the table.
1238 return changectx(self._repo.unfiltered(), self._changeid)
1235 return changectx(self._repo.unfiltered(), self._changeid)
1239
1236
1240 def filectx(self, fileid, changeid=None):
1237 def filectx(self, fileid, changeid=None):
1241 '''opens an arbitrary revision of the file without
1238 '''opens an arbitrary revision of the file without
1242 opening a new filelog'''
1239 opening a new filelog'''
1243 return filectx(self._repo, self._path, fileid=fileid,
1240 return filectx(self._repo, self._path, fileid=fileid,
1244 filelog=self._filelog, changeid=changeid)
1241 filelog=self._filelog, changeid=changeid)
1245
1242
1246 def rawdata(self):
1243 def rawdata(self):
1247 return self._filelog.revision(self._filenode, raw=True)
1244 return self._filelog.revision(self._filenode, raw=True)
1248
1245
1249 def rawflags(self):
1246 def rawflags(self):
1250 """low-level revlog flags"""
1247 """low-level revlog flags"""
1251 return self._filelog.flags(self._filerev)
1248 return self._filelog.flags(self._filerev)
1252
1249
1253 def data(self):
1250 def data(self):
1254 try:
1251 try:
1255 return self._filelog.read(self._filenode)
1252 return self._filelog.read(self._filenode)
1256 except error.CensoredNodeError:
1253 except error.CensoredNodeError:
1257 if self._repo.ui.config("censor", "policy", "abort") == "ignore":
1254 if self._repo.ui.config("censor", "policy", "abort") == "ignore":
1258 return ""
1255 return ""
1259 raise error.Abort(_("censored node: %s") % short(self._filenode),
1256 raise error.Abort(_("censored node: %s") % short(self._filenode),
1260 hint=_("set censor.policy to ignore errors"))
1257 hint=_("set censor.policy to ignore errors"))
1261
1258
1262 def size(self):
1259 def size(self):
1263 return self._filelog.size(self._filerev)
1260 return self._filelog.size(self._filerev)
1264
1261
1265 @propertycache
1262 @propertycache
1266 def _copied(self):
1263 def _copied(self):
1267 """check if file was actually renamed in this changeset revision
1264 """check if file was actually renamed in this changeset revision
1268
1265
1269 If rename logged in file revision, we report copy for changeset only
1266 If rename logged in file revision, we report copy for changeset only
1270 if file revisions linkrev points back to the changeset in question
1267 if file revisions linkrev points back to the changeset in question
1271 or both changeset parents contain different file revisions.
1268 or both changeset parents contain different file revisions.
1272 """
1269 """
1273
1270
1274 renamed = self._filelog.renamed(self._filenode)
1271 renamed = self._filelog.renamed(self._filenode)
1275 if not renamed:
1272 if not renamed:
1276 return renamed
1273 return renamed
1277
1274
1278 if self.rev() == self.linkrev():
1275 if self.rev() == self.linkrev():
1279 return renamed
1276 return renamed
1280
1277
1281 name = self.path()
1278 name = self.path()
1282 fnode = self._filenode
1279 fnode = self._filenode
1283 for p in self._changectx.parents():
1280 for p in self._changectx.parents():
1284 try:
1281 try:
1285 if fnode == p.filenode(name):
1282 if fnode == p.filenode(name):
1286 return None
1283 return None
1287 except error.LookupError:
1284 except error.LookupError:
1288 pass
1285 pass
1289 return renamed
1286 return renamed
1290
1287
1291 def children(self):
1288 def children(self):
1292 # hard for renames
1289 # hard for renames
1293 c = self._filelog.children(self._filenode)
1290 c = self._filelog.children(self._filenode)
1294 return [filectx(self._repo, self._path, fileid=x,
1291 return [filectx(self._repo, self._path, fileid=x,
1295 filelog=self._filelog) for x in c]
1292 filelog=self._filelog) for x in c]
1296
1293
1297 def _changesrange(fctx1, fctx2, linerange2, diffopts):
1294 def _changesrange(fctx1, fctx2, linerange2, diffopts):
1298 """Return `(diffinrange, linerange1)` where `diffinrange` is True
1295 """Return `(diffinrange, linerange1)` where `diffinrange` is True
1299 if diff from fctx2 to fctx1 has changes in linerange2 and
1296 if diff from fctx2 to fctx1 has changes in linerange2 and
1300 `linerange1` is the new line range for fctx1.
1297 `linerange1` is the new line range for fctx1.
1301 """
1298 """
1302 blocks = mdiff.allblocks(fctx1.data(), fctx2.data(), diffopts)
1299 blocks = mdiff.allblocks(fctx1.data(), fctx2.data(), diffopts)
1303 filteredblocks, linerange1 = mdiff.blocksinrange(blocks, linerange2)
1300 filteredblocks, linerange1 = mdiff.blocksinrange(blocks, linerange2)
1304 diffinrange = any(stype == '!' for _, stype in filteredblocks)
1301 diffinrange = any(stype == '!' for _, stype in filteredblocks)
1305 return diffinrange, linerange1
1302 return diffinrange, linerange1
1306
1303
1307 def blockancestors(fctx, fromline, toline, followfirst=False):
1304 def blockancestors(fctx, fromline, toline, followfirst=False):
1308 """Yield ancestors of `fctx` with respect to the block of lines within
1305 """Yield ancestors of `fctx` with respect to the block of lines within
1309 `fromline`-`toline` range.
1306 `fromline`-`toline` range.
1310 """
1307 """
1311 diffopts = patch.diffopts(fctx._repo.ui)
1308 diffopts = patch.diffopts(fctx._repo.ui)
1312 introrev = fctx.introrev()
1309 introrev = fctx.introrev()
1313 if fctx.rev() != introrev:
1310 if fctx.rev() != introrev:
1314 fctx = fctx.filectx(fctx.filenode(), changeid=introrev)
1311 fctx = fctx.filectx(fctx.filenode(), changeid=introrev)
1315 visit = {(fctx.linkrev(), fctx.filenode()): (fctx, (fromline, toline))}
1312 visit = {(fctx.linkrev(), fctx.filenode()): (fctx, (fromline, toline))}
1316 while visit:
1313 while visit:
1317 c, linerange2 = visit.pop(max(visit))
1314 c, linerange2 = visit.pop(max(visit))
1318 pl = c.parents()
1315 pl = c.parents()
1319 if followfirst:
1316 if followfirst:
1320 pl = pl[:1]
1317 pl = pl[:1]
1321 if not pl:
1318 if not pl:
1322 # The block originates from the initial revision.
1319 # The block originates from the initial revision.
1323 yield c, linerange2
1320 yield c, linerange2
1324 continue
1321 continue
1325 inrange = False
1322 inrange = False
1326 for p in pl:
1323 for p in pl:
1327 inrangep, linerange1 = _changesrange(p, c, linerange2, diffopts)
1324 inrangep, linerange1 = _changesrange(p, c, linerange2, diffopts)
1328 inrange = inrange or inrangep
1325 inrange = inrange or inrangep
1329 if linerange1[0] == linerange1[1]:
1326 if linerange1[0] == linerange1[1]:
1330 # Parent's linerange is empty, meaning that the block got
1327 # Parent's linerange is empty, meaning that the block got
1331 # introduced in this revision; no need to go futher in this
1328 # introduced in this revision; no need to go futher in this
1332 # branch.
1329 # branch.
1333 continue
1330 continue
1334 # Set _descendantrev with 'c' (a known descendant) so that, when
1331 # Set _descendantrev with 'c' (a known descendant) so that, when
1335 # _adjustlinkrev is called for 'p', it receives this descendant
1332 # _adjustlinkrev is called for 'p', it receives this descendant
1336 # (as srcrev) instead possibly topmost introrev.
1333 # (as srcrev) instead possibly topmost introrev.
1337 p._descendantrev = c.rev()
1334 p._descendantrev = c.rev()
1338 visit[p.linkrev(), p.filenode()] = p, linerange1
1335 visit[p.linkrev(), p.filenode()] = p, linerange1
1339 if inrange:
1336 if inrange:
1340 yield c, linerange2
1337 yield c, linerange2
1341
1338
1342 def blockdescendants(fctx, fromline, toline):
1339 def blockdescendants(fctx, fromline, toline):
1343 """Yield descendants of `fctx` with respect to the block of lines within
1340 """Yield descendants of `fctx` with respect to the block of lines within
1344 `fromline`-`toline` range.
1341 `fromline`-`toline` range.
1345 """
1342 """
1346 # First possibly yield 'fctx' if it has changes in range with respect to
1343 # First possibly yield 'fctx' if it has changes in range with respect to
1347 # its parents.
1344 # its parents.
1348 try:
1345 try:
1349 c, linerange1 = next(blockancestors(fctx, fromline, toline))
1346 c, linerange1 = next(blockancestors(fctx, fromline, toline))
1350 except StopIteration:
1347 except StopIteration:
1351 pass
1348 pass
1352 else:
1349 else:
1353 if c == fctx:
1350 if c == fctx:
1354 yield c, linerange1
1351 yield c, linerange1
1355
1352
1356 diffopts = patch.diffopts(fctx._repo.ui)
1353 diffopts = patch.diffopts(fctx._repo.ui)
1357 fl = fctx.filelog()
1354 fl = fctx.filelog()
1358 seen = {fctx.filerev(): (fctx, (fromline, toline))}
1355 seen = {fctx.filerev(): (fctx, (fromline, toline))}
1359 for i in fl.descendants([fctx.filerev()]):
1356 for i in fl.descendants([fctx.filerev()]):
1360 c = fctx.filectx(i)
1357 c = fctx.filectx(i)
1361 inrange = False
1358 inrange = False
1362 for x in fl.parentrevs(i):
1359 for x in fl.parentrevs(i):
1363 try:
1360 try:
1364 p, linerange2 = seen[x]
1361 p, linerange2 = seen[x]
1365 except KeyError:
1362 except KeyError:
1366 # nullrev or other branch
1363 # nullrev or other branch
1367 continue
1364 continue
1368 inrangep, linerange1 = _changesrange(c, p, linerange2, diffopts)
1365 inrangep, linerange1 = _changesrange(c, p, linerange2, diffopts)
1369 inrange = inrange or inrangep
1366 inrange = inrange or inrangep
1370 # If revision 'i' has been seen (it's a merge), we assume that its
1367 # If revision 'i' has been seen (it's a merge), we assume that its
1371 # line range is the same independently of which parents was used
1368 # line range is the same independently of which parents was used
1372 # to compute it.
1369 # to compute it.
1373 assert i not in seen or seen[i][1] == linerange1, (
1370 assert i not in seen or seen[i][1] == linerange1, (
1374 'computed line range for %s is not consistent between '
1371 'computed line range for %s is not consistent between '
1375 'ancestor branches' % c)
1372 'ancestor branches' % c)
1376 seen[i] = c, linerange1
1373 seen[i] = c, linerange1
1377 if inrange:
1374 if inrange:
1378 yield c, linerange1
1375 yield c, linerange1
1379
1376
1380 class committablectx(basectx):
1377 class committablectx(basectx):
1381 """A committablectx object provides common functionality for a context that
1378 """A committablectx object provides common functionality for a context that
1382 wants the ability to commit, e.g. workingctx or memctx."""
1379 wants the ability to commit, e.g. workingctx or memctx."""
1383 def __init__(self, repo, text="", user=None, date=None, extra=None,
1380 def __init__(self, repo, text="", user=None, date=None, extra=None,
1384 changes=None):
1381 changes=None):
1385 self._repo = repo
1382 self._repo = repo
1386 self._rev = None
1383 self._rev = None
1387 self._node = None
1384 self._node = None
1388 self._text = text
1385 self._text = text
1389 if date:
1386 if date:
1390 self._date = util.parsedate(date)
1387 self._date = util.parsedate(date)
1391 if user:
1388 if user:
1392 self._user = user
1389 self._user = user
1393 if changes:
1390 if changes:
1394 self._status = changes
1391 self._status = changes
1395
1392
1396 self._extra = {}
1393 self._extra = {}
1397 if extra:
1394 if extra:
1398 self._extra = extra.copy()
1395 self._extra = extra.copy()
1399 if 'branch' not in self._extra:
1396 if 'branch' not in self._extra:
1400 try:
1397 try:
1401 branch = encoding.fromlocal(self._repo.dirstate.branch())
1398 branch = encoding.fromlocal(self._repo.dirstate.branch())
1402 except UnicodeDecodeError:
1399 except UnicodeDecodeError:
1403 raise error.Abort(_('branch name not in UTF-8!'))
1400 raise error.Abort(_('branch name not in UTF-8!'))
1404 self._extra['branch'] = branch
1401 self._extra['branch'] = branch
1405 if self._extra['branch'] == '':
1402 if self._extra['branch'] == '':
1406 self._extra['branch'] = 'default'
1403 self._extra['branch'] = 'default'
1407
1404
1408 def __str__(self):
1405 def __str__(self):
1409 return str(self._parents[0]) + "+"
1406 return str(self._parents[0]) + "+"
1410
1407
1411 def __nonzero__(self):
1408 def __nonzero__(self):
1412 return True
1409 return True
1413
1410
1414 __bool__ = __nonzero__
1411 __bool__ = __nonzero__
1415
1412
1416 def _buildflagfunc(self):
1413 def _buildflagfunc(self):
1417 # Create a fallback function for getting file flags when the
1414 # Create a fallback function for getting file flags when the
1418 # filesystem doesn't support them
1415 # filesystem doesn't support them
1419
1416
1420 copiesget = self._repo.dirstate.copies().get
1417 copiesget = self._repo.dirstate.copies().get
1421 parents = self.parents()
1418 parents = self.parents()
1422 if len(parents) < 2:
1419 if len(parents) < 2:
1423 # when we have one parent, it's easy: copy from parent
1420 # when we have one parent, it's easy: copy from parent
1424 man = parents[0].manifest()
1421 man = parents[0].manifest()
1425 def func(f):
1422 def func(f):
1426 f = copiesget(f, f)
1423 f = copiesget(f, f)
1427 return man.flags(f)
1424 return man.flags(f)
1428 else:
1425 else:
1429 # merges are tricky: we try to reconstruct the unstored
1426 # merges are tricky: we try to reconstruct the unstored
1430 # result from the merge (issue1802)
1427 # result from the merge (issue1802)
1431 p1, p2 = parents
1428 p1, p2 = parents
1432 pa = p1.ancestor(p2)
1429 pa = p1.ancestor(p2)
1433 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1430 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1434
1431
1435 def func(f):
1432 def func(f):
1436 f = copiesget(f, f) # may be wrong for merges with copies
1433 f = copiesget(f, f) # may be wrong for merges with copies
1437 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1434 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1438 if fl1 == fl2:
1435 if fl1 == fl2:
1439 return fl1
1436 return fl1
1440 if fl1 == fla:
1437 if fl1 == fla:
1441 return fl2
1438 return fl2
1442 if fl2 == fla:
1439 if fl2 == fla:
1443 return fl1
1440 return fl1
1444 return '' # punt for conflicts
1441 return '' # punt for conflicts
1445
1442
1446 return func
1443 return func
1447
1444
1448 @propertycache
1445 @propertycache
1449 def _flagfunc(self):
1446 def _flagfunc(self):
1450 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1447 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1451
1448
1452 @propertycache
1449 @propertycache
1453 def _status(self):
1450 def _status(self):
1454 return self._repo.status()
1451 return self._repo.status()
1455
1452
1456 @propertycache
1453 @propertycache
1457 def _user(self):
1454 def _user(self):
1458 return self._repo.ui.username()
1455 return self._repo.ui.username()
1459
1456
1460 @propertycache
1457 @propertycache
1461 def _date(self):
1458 def _date(self):
1462 ui = self._repo.ui
1459 ui = self._repo.ui
1463 date = ui.configdate('devel', 'default-date')
1460 date = ui.configdate('devel', 'default-date')
1464 if date is None:
1461 if date is None:
1465 date = util.makedate()
1462 date = util.makedate()
1466 return date
1463 return date
1467
1464
1468 def subrev(self, subpath):
1465 def subrev(self, subpath):
1469 return None
1466 return None
1470
1467
1471 def manifestnode(self):
1468 def manifestnode(self):
1472 return None
1469 return None
1473 def user(self):
1470 def user(self):
1474 return self._user or self._repo.ui.username()
1471 return self._user or self._repo.ui.username()
1475 def date(self):
1472 def date(self):
1476 return self._date
1473 return self._date
1477 def description(self):
1474 def description(self):
1478 return self._text
1475 return self._text
1479 def files(self):
1476 def files(self):
1480 return sorted(self._status.modified + self._status.added +
1477 return sorted(self._status.modified + self._status.added +
1481 self._status.removed)
1478 self._status.removed)
1482
1479
1483 def modified(self):
1480 def modified(self):
1484 return self._status.modified
1481 return self._status.modified
1485 def added(self):
1482 def added(self):
1486 return self._status.added
1483 return self._status.added
1487 def removed(self):
1484 def removed(self):
1488 return self._status.removed
1485 return self._status.removed
1489 def deleted(self):
1486 def deleted(self):
1490 return self._status.deleted
1487 return self._status.deleted
1491 def branch(self):
1488 def branch(self):
1492 return encoding.tolocal(self._extra['branch'])
1489 return encoding.tolocal(self._extra['branch'])
1493 def closesbranch(self):
1490 def closesbranch(self):
1494 return 'close' in self._extra
1491 return 'close' in self._extra
1495 def extra(self):
1492 def extra(self):
1496 return self._extra
1493 return self._extra
1497
1494
1498 def tags(self):
1495 def tags(self):
1499 return []
1496 return []
1500
1497
1501 def bookmarks(self):
1498 def bookmarks(self):
1502 b = []
1499 b = []
1503 for p in self.parents():
1500 for p in self.parents():
1504 b.extend(p.bookmarks())
1501 b.extend(p.bookmarks())
1505 return b
1502 return b
1506
1503
1507 def phase(self):
1504 def phase(self):
1508 phase = phases.draft # default phase to draft
1505 phase = phases.draft # default phase to draft
1509 for p in self.parents():
1506 for p in self.parents():
1510 phase = max(phase, p.phase())
1507 phase = max(phase, p.phase())
1511 return phase
1508 return phase
1512
1509
1513 def hidden(self):
1510 def hidden(self):
1514 return False
1511 return False
1515
1512
1516 def children(self):
1513 def children(self):
1517 return []
1514 return []
1518
1515
1519 def flags(self, path):
1516 def flags(self, path):
1520 if r'_manifest' in self.__dict__:
1517 if r'_manifest' in self.__dict__:
1521 try:
1518 try:
1522 return self._manifest.flags(path)
1519 return self._manifest.flags(path)
1523 except KeyError:
1520 except KeyError:
1524 return ''
1521 return ''
1525
1522
1526 try:
1523 try:
1527 return self._flagfunc(path)
1524 return self._flagfunc(path)
1528 except OSError:
1525 except OSError:
1529 return ''
1526 return ''
1530
1527
1531 def ancestor(self, c2):
1528 def ancestor(self, c2):
1532 """return the "best" ancestor context of self and c2"""
1529 """return the "best" ancestor context of self and c2"""
1533 return self._parents[0].ancestor(c2) # punt on two parents for now
1530 return self._parents[0].ancestor(c2) # punt on two parents for now
1534
1531
1535 def walk(self, match):
1532 def walk(self, match):
1536 '''Generates matching file names.'''
1533 '''Generates matching file names.'''
1537 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1534 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1538 True, False))
1535 True, False))
1539
1536
1540 def matches(self, match):
1537 def matches(self, match):
1541 return sorted(self._repo.dirstate.matches(match))
1538 return sorted(self._repo.dirstate.matches(match))
1542
1539
1543 def ancestors(self):
1540 def ancestors(self):
1544 for p in self._parents:
1541 for p in self._parents:
1545 yield p
1542 yield p
1546 for a in self._repo.changelog.ancestors(
1543 for a in self._repo.changelog.ancestors(
1547 [p.rev() for p in self._parents]):
1544 [p.rev() for p in self._parents]):
1548 yield changectx(self._repo, a)
1545 yield changectx(self._repo, a)
1549
1546
1550 def markcommitted(self, node):
1547 def markcommitted(self, node):
1551 """Perform post-commit cleanup necessary after committing this ctx
1548 """Perform post-commit cleanup necessary after committing this ctx
1552
1549
1553 Specifically, this updates backing stores this working context
1550 Specifically, this updates backing stores this working context
1554 wraps to reflect the fact that the changes reflected by this
1551 wraps to reflect the fact that the changes reflected by this
1555 workingctx have been committed. For example, it marks
1552 workingctx have been committed. For example, it marks
1556 modified and added files as normal in the dirstate.
1553 modified and added files as normal in the dirstate.
1557
1554
1558 """
1555 """
1559
1556
1560 with self._repo.dirstate.parentchange():
1557 with self._repo.dirstate.parentchange():
1561 for f in self.modified() + self.added():
1558 for f in self.modified() + self.added():
1562 self._repo.dirstate.normal(f)
1559 self._repo.dirstate.normal(f)
1563 for f in self.removed():
1560 for f in self.removed():
1564 self._repo.dirstate.drop(f)
1561 self._repo.dirstate.drop(f)
1565 self._repo.dirstate.setparents(node)
1562 self._repo.dirstate.setparents(node)
1566
1563
1567 # write changes out explicitly, because nesting wlock at
1564 # write changes out explicitly, because nesting wlock at
1568 # runtime may prevent 'wlock.release()' in 'repo.commit()'
1565 # runtime may prevent 'wlock.release()' in 'repo.commit()'
1569 # from immediately doing so for subsequent changing files
1566 # from immediately doing so for subsequent changing files
1570 self._repo.dirstate.write(self._repo.currenttransaction())
1567 self._repo.dirstate.write(self._repo.currenttransaction())
1571
1568
1569 def dirty(self, missing=False, merge=True, branch=True):
1570 return False
1571
1572 class workingctx(committablectx):
1572 class workingctx(committablectx):
1573 """A workingctx object makes access to data related to
1573 """A workingctx object makes access to data related to
1574 the current working directory convenient.
1574 the current working directory convenient.
1575 date - any valid date string or (unixtime, offset), or None.
1575 date - any valid date string or (unixtime, offset), or None.
1576 user - username string, or None.
1576 user - username string, or None.
1577 extra - a dictionary of extra values, or None.
1577 extra - a dictionary of extra values, or None.
1578 changes - a list of file lists as returned by localrepo.status()
1578 changes - a list of file lists as returned by localrepo.status()
1579 or None to use the repository status.
1579 or None to use the repository status.
1580 """
1580 """
1581 def __init__(self, repo, text="", user=None, date=None, extra=None,
1581 def __init__(self, repo, text="", user=None, date=None, extra=None,
1582 changes=None):
1582 changes=None):
1583 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1583 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1584
1584
1585 def __iter__(self):
1585 def __iter__(self):
1586 d = self._repo.dirstate
1586 d = self._repo.dirstate
1587 for f in d:
1587 for f in d:
1588 if d[f] != 'r':
1588 if d[f] != 'r':
1589 yield f
1589 yield f
1590
1590
1591 def __contains__(self, key):
1591 def __contains__(self, key):
1592 return self._repo.dirstate[key] not in "?r"
1592 return self._repo.dirstate[key] not in "?r"
1593
1593
1594 def hex(self):
1594 def hex(self):
1595 return hex(wdirid)
1595 return hex(wdirid)
1596
1596
1597 @propertycache
1597 @propertycache
1598 def _parents(self):
1598 def _parents(self):
1599 p = self._repo.dirstate.parents()
1599 p = self._repo.dirstate.parents()
1600 if p[1] == nullid:
1600 if p[1] == nullid:
1601 p = p[:-1]
1601 p = p[:-1]
1602 return [changectx(self._repo, x) for x in p]
1602 return [changectx(self._repo, x) for x in p]
1603
1603
1604 def filectx(self, path, filelog=None):
1604 def filectx(self, path, filelog=None):
1605 """get a file context from the working directory"""
1605 """get a file context from the working directory"""
1606 return workingfilectx(self._repo, path, workingctx=self,
1606 return workingfilectx(self._repo, path, workingctx=self,
1607 filelog=filelog)
1607 filelog=filelog)
1608
1608
1609 def dirty(self, missing=False, merge=True, branch=True):
1609 def dirty(self, missing=False, merge=True, branch=True):
1610 "check whether a working directory is modified"
1610 "check whether a working directory is modified"
1611 # check subrepos first
1611 # check subrepos first
1612 for s in sorted(self.substate):
1612 for s in sorted(self.substate):
1613 if self.sub(s).dirty():
1613 if self.sub(s).dirty():
1614 return True
1614 return True
1615 # check current working dir
1615 # check current working dir
1616 return ((merge and self.p2()) or
1616 return ((merge and self.p2()) or
1617 (branch and self.branch() != self.p1().branch()) or
1617 (branch and self.branch() != self.p1().branch()) or
1618 self.modified() or self.added() or self.removed() or
1618 self.modified() or self.added() or self.removed() or
1619 (missing and self.deleted()))
1619 (missing and self.deleted()))
1620
1620
1621 def add(self, list, prefix=""):
1621 def add(self, list, prefix=""):
1622 join = lambda f: os.path.join(prefix, f)
1622 join = lambda f: os.path.join(prefix, f)
1623 with self._repo.wlock():
1623 with self._repo.wlock():
1624 ui, ds = self._repo.ui, self._repo.dirstate
1624 ui, ds = self._repo.ui, self._repo.dirstate
1625 rejected = []
1625 rejected = []
1626 lstat = self._repo.wvfs.lstat
1626 lstat = self._repo.wvfs.lstat
1627 for f in list:
1627 for f in list:
1628 scmutil.checkportable(ui, join(f))
1628 scmutil.checkportable(ui, join(f))
1629 try:
1629 try:
1630 st = lstat(f)
1630 st = lstat(f)
1631 except OSError:
1631 except OSError:
1632 ui.warn(_("%s does not exist!\n") % join(f))
1632 ui.warn(_("%s does not exist!\n") % join(f))
1633 rejected.append(f)
1633 rejected.append(f)
1634 continue
1634 continue
1635 if st.st_size > 10000000:
1635 if st.st_size > 10000000:
1636 ui.warn(_("%s: up to %d MB of RAM may be required "
1636 ui.warn(_("%s: up to %d MB of RAM may be required "
1637 "to manage this file\n"
1637 "to manage this file\n"
1638 "(use 'hg revert %s' to cancel the "
1638 "(use 'hg revert %s' to cancel the "
1639 "pending addition)\n")
1639 "pending addition)\n")
1640 % (f, 3 * st.st_size // 1000000, join(f)))
1640 % (f, 3 * st.st_size // 1000000, join(f)))
1641 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1641 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1642 ui.warn(_("%s not added: only files and symlinks "
1642 ui.warn(_("%s not added: only files and symlinks "
1643 "supported currently\n") % join(f))
1643 "supported currently\n") % join(f))
1644 rejected.append(f)
1644 rejected.append(f)
1645 elif ds[f] in 'amn':
1645 elif ds[f] in 'amn':
1646 ui.warn(_("%s already tracked!\n") % join(f))
1646 ui.warn(_("%s already tracked!\n") % join(f))
1647 elif ds[f] == 'r':
1647 elif ds[f] == 'r':
1648 ds.normallookup(f)
1648 ds.normallookup(f)
1649 else:
1649 else:
1650 ds.add(f)
1650 ds.add(f)
1651 return rejected
1651 return rejected
1652
1652
1653 def forget(self, files, prefix=""):
1653 def forget(self, files, prefix=""):
1654 join = lambda f: os.path.join(prefix, f)
1654 join = lambda f: os.path.join(prefix, f)
1655 with self._repo.wlock():
1655 with self._repo.wlock():
1656 rejected = []
1656 rejected = []
1657 for f in files:
1657 for f in files:
1658 if f not in self._repo.dirstate:
1658 if f not in self._repo.dirstate:
1659 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1659 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1660 rejected.append(f)
1660 rejected.append(f)
1661 elif self._repo.dirstate[f] != 'a':
1661 elif self._repo.dirstate[f] != 'a':
1662 self._repo.dirstate.remove(f)
1662 self._repo.dirstate.remove(f)
1663 else:
1663 else:
1664 self._repo.dirstate.drop(f)
1664 self._repo.dirstate.drop(f)
1665 return rejected
1665 return rejected
1666
1666
1667 def undelete(self, list):
1667 def undelete(self, list):
1668 pctxs = self.parents()
1668 pctxs = self.parents()
1669 with self._repo.wlock():
1669 with self._repo.wlock():
1670 for f in list:
1670 for f in list:
1671 if self._repo.dirstate[f] != 'r':
1671 if self._repo.dirstate[f] != 'r':
1672 self._repo.ui.warn(_("%s not removed!\n") % f)
1672 self._repo.ui.warn(_("%s not removed!\n") % f)
1673 else:
1673 else:
1674 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1674 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1675 t = fctx.data()
1675 t = fctx.data()
1676 self._repo.wwrite(f, t, fctx.flags())
1676 self._repo.wwrite(f, t, fctx.flags())
1677 self._repo.dirstate.normal(f)
1677 self._repo.dirstate.normal(f)
1678
1678
1679 def copy(self, source, dest):
1679 def copy(self, source, dest):
1680 try:
1680 try:
1681 st = self._repo.wvfs.lstat(dest)
1681 st = self._repo.wvfs.lstat(dest)
1682 except OSError as err:
1682 except OSError as err:
1683 if err.errno != errno.ENOENT:
1683 if err.errno != errno.ENOENT:
1684 raise
1684 raise
1685 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1685 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1686 return
1686 return
1687 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1687 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1688 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1688 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1689 "symbolic link\n") % dest)
1689 "symbolic link\n") % dest)
1690 else:
1690 else:
1691 with self._repo.wlock():
1691 with self._repo.wlock():
1692 if self._repo.dirstate[dest] in '?':
1692 if self._repo.dirstate[dest] in '?':
1693 self._repo.dirstate.add(dest)
1693 self._repo.dirstate.add(dest)
1694 elif self._repo.dirstate[dest] in 'r':
1694 elif self._repo.dirstate[dest] in 'r':
1695 self._repo.dirstate.normallookup(dest)
1695 self._repo.dirstate.normallookup(dest)
1696 self._repo.dirstate.copy(source, dest)
1696 self._repo.dirstate.copy(source, dest)
1697
1697
1698 def match(self, pats=None, include=None, exclude=None, default='glob',
1698 def match(self, pats=None, include=None, exclude=None, default='glob',
1699 listsubrepos=False, badfn=None):
1699 listsubrepos=False, badfn=None):
1700 if pats is None:
1700 if pats is None:
1701 pats = []
1701 pats = []
1702 r = self._repo
1702 r = self._repo
1703
1703
1704 # Only a case insensitive filesystem needs magic to translate user input
1704 # Only a case insensitive filesystem needs magic to translate user input
1705 # to actual case in the filesystem.
1705 # to actual case in the filesystem.
1706 icasefs = not util.fscasesensitive(r.root)
1706 icasefs = not util.fscasesensitive(r.root)
1707 return matchmod.match(r.root, r.getcwd(), pats, include, exclude,
1707 return matchmod.match(r.root, r.getcwd(), pats, include, exclude,
1708 default, auditor=r.auditor, ctx=self,
1708 default, auditor=r.auditor, ctx=self,
1709 listsubrepos=listsubrepos, badfn=badfn,
1709 listsubrepos=listsubrepos, badfn=badfn,
1710 icasefs=icasefs)
1710 icasefs=icasefs)
1711
1711
1712 def _filtersuspectsymlink(self, files):
1712 def _filtersuspectsymlink(self, files):
1713 if not files or self._repo.dirstate._checklink:
1713 if not files or self._repo.dirstate._checklink:
1714 return files
1714 return files
1715
1715
1716 # Symlink placeholders may get non-symlink-like contents
1716 # Symlink placeholders may get non-symlink-like contents
1717 # via user error or dereferencing by NFS or Samba servers,
1717 # via user error or dereferencing by NFS or Samba servers,
1718 # so we filter out any placeholders that don't look like a
1718 # so we filter out any placeholders that don't look like a
1719 # symlink
1719 # symlink
1720 sane = []
1720 sane = []
1721 for f in files:
1721 for f in files:
1722 if self.flags(f) == 'l':
1722 if self.flags(f) == 'l':
1723 d = self[f].data()
1723 d = self[f].data()
1724 if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d):
1724 if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d):
1725 self._repo.ui.debug('ignoring suspect symlink placeholder'
1725 self._repo.ui.debug('ignoring suspect symlink placeholder'
1726 ' "%s"\n' % f)
1726 ' "%s"\n' % f)
1727 continue
1727 continue
1728 sane.append(f)
1728 sane.append(f)
1729 return sane
1729 return sane
1730
1730
1731 def _checklookup(self, files):
1731 def _checklookup(self, files):
1732 # check for any possibly clean files
1732 # check for any possibly clean files
1733 if not files:
1733 if not files:
1734 return [], []
1734 return [], []
1735
1735
1736 modified = []
1736 modified = []
1737 fixup = []
1737 fixup = []
1738 pctx = self._parents[0]
1738 pctx = self._parents[0]
1739 # do a full compare of any files that might have changed
1739 # do a full compare of any files that might have changed
1740 for f in sorted(files):
1740 for f in sorted(files):
1741 if (f not in pctx or self.flags(f) != pctx.flags(f)
1741 if (f not in pctx or self.flags(f) != pctx.flags(f)
1742 or pctx[f].cmp(self[f])):
1742 or pctx[f].cmp(self[f])):
1743 modified.append(f)
1743 modified.append(f)
1744 else:
1744 else:
1745 fixup.append(f)
1745 fixup.append(f)
1746
1746
1747 # update dirstate for files that are actually clean
1747 # update dirstate for files that are actually clean
1748 if fixup:
1748 if fixup:
1749 try:
1749 try:
1750 # updating the dirstate is optional
1750 # updating the dirstate is optional
1751 # so we don't wait on the lock
1751 # so we don't wait on the lock
1752 # wlock can invalidate the dirstate, so cache normal _after_
1752 # wlock can invalidate the dirstate, so cache normal _after_
1753 # taking the lock
1753 # taking the lock
1754 with self._repo.wlock(False):
1754 with self._repo.wlock(False):
1755 normal = self._repo.dirstate.normal
1755 normal = self._repo.dirstate.normal
1756 for f in fixup:
1756 for f in fixup:
1757 normal(f)
1757 normal(f)
1758 # write changes out explicitly, because nesting
1758 # write changes out explicitly, because nesting
1759 # wlock at runtime may prevent 'wlock.release()'
1759 # wlock at runtime may prevent 'wlock.release()'
1760 # after this block from doing so for subsequent
1760 # after this block from doing so for subsequent
1761 # changing files
1761 # changing files
1762 self._repo.dirstate.write(self._repo.currenttransaction())
1762 self._repo.dirstate.write(self._repo.currenttransaction())
1763 except error.LockError:
1763 except error.LockError:
1764 pass
1764 pass
1765 return modified, fixup
1765 return modified, fixup
1766
1766
1767 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1767 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1768 unknown=False):
1768 unknown=False):
1769 '''Gets the status from the dirstate -- internal use only.'''
1769 '''Gets the status from the dirstate -- internal use only.'''
1770 listignored, listclean, listunknown = ignored, clean, unknown
1770 listignored, listclean, listunknown = ignored, clean, unknown
1771 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1771 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1772 subrepos = []
1772 subrepos = []
1773 if '.hgsub' in self:
1773 if '.hgsub' in self:
1774 subrepos = sorted(self.substate)
1774 subrepos = sorted(self.substate)
1775 cmp, s = self._repo.dirstate.status(match, subrepos, listignored,
1775 cmp, s = self._repo.dirstate.status(match, subrepos, listignored,
1776 listclean, listunknown)
1776 listclean, listunknown)
1777
1777
1778 # check for any possibly clean files
1778 # check for any possibly clean files
1779 if cmp:
1779 if cmp:
1780 modified2, fixup = self._checklookup(cmp)
1780 modified2, fixup = self._checklookup(cmp)
1781 s.modified.extend(modified2)
1781 s.modified.extend(modified2)
1782
1782
1783 # update dirstate for files that are actually clean
1783 # update dirstate for files that are actually clean
1784 if fixup and listclean:
1784 if fixup and listclean:
1785 s.clean.extend(fixup)
1785 s.clean.extend(fixup)
1786
1786
1787 if match.always():
1787 if match.always():
1788 # cache for performance
1788 # cache for performance
1789 if s.unknown or s.ignored or s.clean:
1789 if s.unknown or s.ignored or s.clean:
1790 # "_status" is cached with list*=False in the normal route
1790 # "_status" is cached with list*=False in the normal route
1791 self._status = scmutil.status(s.modified, s.added, s.removed,
1791 self._status = scmutil.status(s.modified, s.added, s.removed,
1792 s.deleted, [], [], [])
1792 s.deleted, [], [], [])
1793 else:
1793 else:
1794 self._status = s
1794 self._status = s
1795
1795
1796 return s
1796 return s
1797
1797
1798 @propertycache
1798 @propertycache
1799 def _manifest(self):
1799 def _manifest(self):
1800 """generate a manifest corresponding to the values in self._status
1800 """generate a manifest corresponding to the values in self._status
1801
1801
1802 This reuse the file nodeid from parent, but we use special node
1802 This reuse the file nodeid from parent, but we use special node
1803 identifiers for added and modified files. This is used by manifests
1803 identifiers for added and modified files. This is used by manifests
1804 merge to see that files are different and by update logic to avoid
1804 merge to see that files are different and by update logic to avoid
1805 deleting newly added files.
1805 deleting newly added files.
1806 """
1806 """
1807 return self._buildstatusmanifest(self._status)
1807 return self._buildstatusmanifest(self._status)
1808
1808
1809 def _buildstatusmanifest(self, status):
1809 def _buildstatusmanifest(self, status):
1810 """Builds a manifest that includes the given status results."""
1810 """Builds a manifest that includes the given status results."""
1811 parents = self.parents()
1811 parents = self.parents()
1812
1812
1813 man = parents[0].manifest().copy()
1813 man = parents[0].manifest().copy()
1814
1814
1815 ff = self._flagfunc
1815 ff = self._flagfunc
1816 for i, l in ((addednodeid, status.added),
1816 for i, l in ((addednodeid, status.added),
1817 (modifiednodeid, status.modified)):
1817 (modifiednodeid, status.modified)):
1818 for f in l:
1818 for f in l:
1819 man[f] = i
1819 man[f] = i
1820 try:
1820 try:
1821 man.setflag(f, ff(f))
1821 man.setflag(f, ff(f))
1822 except OSError:
1822 except OSError:
1823 pass
1823 pass
1824
1824
1825 for f in status.deleted + status.removed:
1825 for f in status.deleted + status.removed:
1826 if f in man:
1826 if f in man:
1827 del man[f]
1827 del man[f]
1828
1828
1829 return man
1829 return man
1830
1830
1831 def _buildstatus(self, other, s, match, listignored, listclean,
1831 def _buildstatus(self, other, s, match, listignored, listclean,
1832 listunknown):
1832 listunknown):
1833 """build a status with respect to another context
1833 """build a status with respect to another context
1834
1834
1835 This includes logic for maintaining the fast path of status when
1835 This includes logic for maintaining the fast path of status when
1836 comparing the working directory against its parent, which is to skip
1836 comparing the working directory against its parent, which is to skip
1837 building a new manifest if self (working directory) is not comparing
1837 building a new manifest if self (working directory) is not comparing
1838 against its parent (repo['.']).
1838 against its parent (repo['.']).
1839 """
1839 """
1840 s = self._dirstatestatus(match, listignored, listclean, listunknown)
1840 s = self._dirstatestatus(match, listignored, listclean, listunknown)
1841 # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
1841 # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
1842 # might have accidentally ended up with the entire contents of the file
1842 # might have accidentally ended up with the entire contents of the file
1843 # they are supposed to be linking to.
1843 # they are supposed to be linking to.
1844 s.modified[:] = self._filtersuspectsymlink(s.modified)
1844 s.modified[:] = self._filtersuspectsymlink(s.modified)
1845 if other != self._repo['.']:
1845 if other != self._repo['.']:
1846 s = super(workingctx, self)._buildstatus(other, s, match,
1846 s = super(workingctx, self)._buildstatus(other, s, match,
1847 listignored, listclean,
1847 listignored, listclean,
1848 listunknown)
1848 listunknown)
1849 return s
1849 return s
1850
1850
1851 def _matchstatus(self, other, match):
1851 def _matchstatus(self, other, match):
1852 """override the match method with a filter for directory patterns
1852 """override the match method with a filter for directory patterns
1853
1853
1854 We use inheritance to customize the match.bad method only in cases of
1854 We use inheritance to customize the match.bad method only in cases of
1855 workingctx since it belongs only to the working directory when
1855 workingctx since it belongs only to the working directory when
1856 comparing against the parent changeset.
1856 comparing against the parent changeset.
1857
1857
1858 If we aren't comparing against the working directory's parent, then we
1858 If we aren't comparing against the working directory's parent, then we
1859 just use the default match object sent to us.
1859 just use the default match object sent to us.
1860 """
1860 """
1861 superself = super(workingctx, self)
1861 superself = super(workingctx, self)
1862 match = superself._matchstatus(other, match)
1862 match = superself._matchstatus(other, match)
1863 if other != self._repo['.']:
1863 if other != self._repo['.']:
1864 def bad(f, msg):
1864 def bad(f, msg):
1865 # 'f' may be a directory pattern from 'match.files()',
1865 # 'f' may be a directory pattern from 'match.files()',
1866 # so 'f not in ctx1' is not enough
1866 # so 'f not in ctx1' is not enough
1867 if f not in other and not other.hasdir(f):
1867 if f not in other and not other.hasdir(f):
1868 self._repo.ui.warn('%s: %s\n' %
1868 self._repo.ui.warn('%s: %s\n' %
1869 (self._repo.dirstate.pathto(f), msg))
1869 (self._repo.dirstate.pathto(f), msg))
1870 match.bad = bad
1870 match.bad = bad
1871 return match
1871 return match
1872
1872
1873 class committablefilectx(basefilectx):
1873 class committablefilectx(basefilectx):
1874 """A committablefilectx provides common functionality for a file context
1874 """A committablefilectx provides common functionality for a file context
1875 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1875 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1876 def __init__(self, repo, path, filelog=None, ctx=None):
1876 def __init__(self, repo, path, filelog=None, ctx=None):
1877 self._repo = repo
1877 self._repo = repo
1878 self._path = path
1878 self._path = path
1879 self._changeid = None
1879 self._changeid = None
1880 self._filerev = self._filenode = None
1880 self._filerev = self._filenode = None
1881
1881
1882 if filelog is not None:
1882 if filelog is not None:
1883 self._filelog = filelog
1883 self._filelog = filelog
1884 if ctx:
1884 if ctx:
1885 self._changectx = ctx
1885 self._changectx = ctx
1886
1886
1887 def __nonzero__(self):
1887 def __nonzero__(self):
1888 return True
1888 return True
1889
1889
1890 __bool__ = __nonzero__
1890 __bool__ = __nonzero__
1891
1891
1892 def linkrev(self):
1892 def linkrev(self):
1893 # linked to self._changectx no matter if file is modified or not
1893 # linked to self._changectx no matter if file is modified or not
1894 return self.rev()
1894 return self.rev()
1895
1895
1896 def parents(self):
1896 def parents(self):
1897 '''return parent filectxs, following copies if necessary'''
1897 '''return parent filectxs, following copies if necessary'''
1898 def filenode(ctx, path):
1898 def filenode(ctx, path):
1899 return ctx._manifest.get(path, nullid)
1899 return ctx._manifest.get(path, nullid)
1900
1900
1901 path = self._path
1901 path = self._path
1902 fl = self._filelog
1902 fl = self._filelog
1903 pcl = self._changectx._parents
1903 pcl = self._changectx._parents
1904 renamed = self.renamed()
1904 renamed = self.renamed()
1905
1905
1906 if renamed:
1906 if renamed:
1907 pl = [renamed + (None,)]
1907 pl = [renamed + (None,)]
1908 else:
1908 else:
1909 pl = [(path, filenode(pcl[0], path), fl)]
1909 pl = [(path, filenode(pcl[0], path), fl)]
1910
1910
1911 for pc in pcl[1:]:
1911 for pc in pcl[1:]:
1912 pl.append((path, filenode(pc, path), fl))
1912 pl.append((path, filenode(pc, path), fl))
1913
1913
1914 return [self._parentfilectx(p, fileid=n, filelog=l)
1914 return [self._parentfilectx(p, fileid=n, filelog=l)
1915 for p, n, l in pl if n != nullid]
1915 for p, n, l in pl if n != nullid]
1916
1916
1917 def children(self):
1917 def children(self):
1918 return []
1918 return []
1919
1919
1920 class workingfilectx(committablefilectx):
1920 class workingfilectx(committablefilectx):
1921 """A workingfilectx object makes access to data related to a particular
1921 """A workingfilectx object makes access to data related to a particular
1922 file in the working directory convenient."""
1922 file in the working directory convenient."""
1923 def __init__(self, repo, path, filelog=None, workingctx=None):
1923 def __init__(self, repo, path, filelog=None, workingctx=None):
1924 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1924 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1925
1925
1926 @propertycache
1926 @propertycache
1927 def _changectx(self):
1927 def _changectx(self):
1928 return workingctx(self._repo)
1928 return workingctx(self._repo)
1929
1929
1930 def data(self):
1930 def data(self):
1931 return self._repo.wread(self._path)
1931 return self._repo.wread(self._path)
1932 def renamed(self):
1932 def renamed(self):
1933 rp = self._repo.dirstate.copied(self._path)
1933 rp = self._repo.dirstate.copied(self._path)
1934 if not rp:
1934 if not rp:
1935 return None
1935 return None
1936 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1936 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1937
1937
1938 def size(self):
1938 def size(self):
1939 return self._repo.wvfs.lstat(self._path).st_size
1939 return self._repo.wvfs.lstat(self._path).st_size
1940 def date(self):
1940 def date(self):
1941 t, tz = self._changectx.date()
1941 t, tz = self._changectx.date()
1942 try:
1942 try:
1943 return (self._repo.wvfs.lstat(self._path).st_mtime, tz)
1943 return (self._repo.wvfs.lstat(self._path).st_mtime, tz)
1944 except OSError as err:
1944 except OSError as err:
1945 if err.errno != errno.ENOENT:
1945 if err.errno != errno.ENOENT:
1946 raise
1946 raise
1947 return (t, tz)
1947 return (t, tz)
1948
1948
1949 def cmp(self, fctx):
1949 def cmp(self, fctx):
1950 """compare with other file context
1950 """compare with other file context
1951
1951
1952 returns True if different than fctx.
1952 returns True if different than fctx.
1953 """
1953 """
1954 # fctx should be a filectx (not a workingfilectx)
1954 # fctx should be a filectx (not a workingfilectx)
1955 # invert comparison to reuse the same code path
1955 # invert comparison to reuse the same code path
1956 return fctx.cmp(self)
1956 return fctx.cmp(self)
1957
1957
1958 def remove(self, ignoremissing=False):
1958 def remove(self, ignoremissing=False):
1959 """wraps unlink for a repo's working directory"""
1959 """wraps unlink for a repo's working directory"""
1960 self._repo.wvfs.unlinkpath(self._path, ignoremissing=ignoremissing)
1960 self._repo.wvfs.unlinkpath(self._path, ignoremissing=ignoremissing)
1961
1961
1962 def write(self, data, flags):
1962 def write(self, data, flags):
1963 """wraps repo.wwrite"""
1963 """wraps repo.wwrite"""
1964 self._repo.wwrite(self._path, data, flags)
1964 self._repo.wwrite(self._path, data, flags)
1965
1965
1966 class workingcommitctx(workingctx):
1966 class workingcommitctx(workingctx):
1967 """A workingcommitctx object makes access to data related to
1967 """A workingcommitctx object makes access to data related to
1968 the revision being committed convenient.
1968 the revision being committed convenient.
1969
1969
1970 This hides changes in the working directory, if they aren't
1970 This hides changes in the working directory, if they aren't
1971 committed in this context.
1971 committed in this context.
1972 """
1972 """
1973 def __init__(self, repo, changes,
1973 def __init__(self, repo, changes,
1974 text="", user=None, date=None, extra=None):
1974 text="", user=None, date=None, extra=None):
1975 super(workingctx, self).__init__(repo, text, user, date, extra,
1975 super(workingctx, self).__init__(repo, text, user, date, extra,
1976 changes)
1976 changes)
1977
1977
1978 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1978 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1979 unknown=False):
1979 unknown=False):
1980 """Return matched files only in ``self._status``
1980 """Return matched files only in ``self._status``
1981
1981
1982 Uncommitted files appear "clean" via this context, even if
1982 Uncommitted files appear "clean" via this context, even if
1983 they aren't actually so in the working directory.
1983 they aren't actually so in the working directory.
1984 """
1984 """
1985 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1985 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1986 if clean:
1986 if clean:
1987 clean = [f for f in self._manifest if f not in self._changedset]
1987 clean = [f for f in self._manifest if f not in self._changedset]
1988 else:
1988 else:
1989 clean = []
1989 clean = []
1990 return scmutil.status([f for f in self._status.modified if match(f)],
1990 return scmutil.status([f for f in self._status.modified if match(f)],
1991 [f for f in self._status.added if match(f)],
1991 [f for f in self._status.added if match(f)],
1992 [f for f in self._status.removed if match(f)],
1992 [f for f in self._status.removed if match(f)],
1993 [], [], [], clean)
1993 [], [], [], clean)
1994
1994
1995 @propertycache
1995 @propertycache
1996 def _changedset(self):
1996 def _changedset(self):
1997 """Return the set of files changed in this context
1997 """Return the set of files changed in this context
1998 """
1998 """
1999 changed = set(self._status.modified)
1999 changed = set(self._status.modified)
2000 changed.update(self._status.added)
2000 changed.update(self._status.added)
2001 changed.update(self._status.removed)
2001 changed.update(self._status.removed)
2002 return changed
2002 return changed
2003
2003
2004 def makecachingfilectxfn(func):
2004 def makecachingfilectxfn(func):
2005 """Create a filectxfn that caches based on the path.
2005 """Create a filectxfn that caches based on the path.
2006
2006
2007 We can't use util.cachefunc because it uses all arguments as the cache
2007 We can't use util.cachefunc because it uses all arguments as the cache
2008 key and this creates a cycle since the arguments include the repo and
2008 key and this creates a cycle since the arguments include the repo and
2009 memctx.
2009 memctx.
2010 """
2010 """
2011 cache = {}
2011 cache = {}
2012
2012
2013 def getfilectx(repo, memctx, path):
2013 def getfilectx(repo, memctx, path):
2014 if path not in cache:
2014 if path not in cache:
2015 cache[path] = func(repo, memctx, path)
2015 cache[path] = func(repo, memctx, path)
2016 return cache[path]
2016 return cache[path]
2017
2017
2018 return getfilectx
2018 return getfilectx
2019
2019
2020 class memctx(committablectx):
2020 class memctx(committablectx):
2021 """Use memctx to perform in-memory commits via localrepo.commitctx().
2021 """Use memctx to perform in-memory commits via localrepo.commitctx().
2022
2022
2023 Revision information is supplied at initialization time while
2023 Revision information is supplied at initialization time while
2024 related files data and is made available through a callback
2024 related files data and is made available through a callback
2025 mechanism. 'repo' is the current localrepo, 'parents' is a
2025 mechanism. 'repo' is the current localrepo, 'parents' is a
2026 sequence of two parent revisions identifiers (pass None for every
2026 sequence of two parent revisions identifiers (pass None for every
2027 missing parent), 'text' is the commit message and 'files' lists
2027 missing parent), 'text' is the commit message and 'files' lists
2028 names of files touched by the revision (normalized and relative to
2028 names of files touched by the revision (normalized and relative to
2029 repository root).
2029 repository root).
2030
2030
2031 filectxfn(repo, memctx, path) is a callable receiving the
2031 filectxfn(repo, memctx, path) is a callable receiving the
2032 repository, the current memctx object and the normalized path of
2032 repository, the current memctx object and the normalized path of
2033 requested file, relative to repository root. It is fired by the
2033 requested file, relative to repository root. It is fired by the
2034 commit function for every file in 'files', but calls order is
2034 commit function for every file in 'files', but calls order is
2035 undefined. If the file is available in the revision being
2035 undefined. If the file is available in the revision being
2036 committed (updated or added), filectxfn returns a memfilectx
2036 committed (updated or added), filectxfn returns a memfilectx
2037 object. If the file was removed, filectxfn return None for recent
2037 object. If the file was removed, filectxfn return None for recent
2038 Mercurial. Moved files are represented by marking the source file
2038 Mercurial. Moved files are represented by marking the source file
2039 removed and the new file added with copy information (see
2039 removed and the new file added with copy information (see
2040 memfilectx).
2040 memfilectx).
2041
2041
2042 user receives the committer name and defaults to current
2042 user receives the committer name and defaults to current
2043 repository username, date is the commit date in any format
2043 repository username, date is the commit date in any format
2044 supported by util.parsedate() and defaults to current date, extra
2044 supported by util.parsedate() and defaults to current date, extra
2045 is a dictionary of metadata or is left empty.
2045 is a dictionary of metadata or is left empty.
2046 """
2046 """
2047
2047
2048 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
2048 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
2049 # Extensions that need to retain compatibility across Mercurial 3.1 can use
2049 # Extensions that need to retain compatibility across Mercurial 3.1 can use
2050 # this field to determine what to do in filectxfn.
2050 # this field to determine what to do in filectxfn.
2051 _returnnoneformissingfiles = True
2051 _returnnoneformissingfiles = True
2052
2052
2053 def __init__(self, repo, parents, text, files, filectxfn, user=None,
2053 def __init__(self, repo, parents, text, files, filectxfn, user=None,
2054 date=None, extra=None, editor=False):
2054 date=None, extra=None, editor=False):
2055 super(memctx, self).__init__(repo, text, user, date, extra)
2055 super(memctx, self).__init__(repo, text, user, date, extra)
2056 self._rev = None
2056 self._rev = None
2057 self._node = None
2057 self._node = None
2058 parents = [(p or nullid) for p in parents]
2058 parents = [(p or nullid) for p in parents]
2059 p1, p2 = parents
2059 p1, p2 = parents
2060 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
2060 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
2061 files = sorted(set(files))
2061 files = sorted(set(files))
2062 self._files = files
2062 self._files = files
2063 self.substate = {}
2063 self.substate = {}
2064
2064
2065 # if store is not callable, wrap it in a function
2065 # if store is not callable, wrap it in a function
2066 if not callable(filectxfn):
2066 if not callable(filectxfn):
2067 def getfilectx(repo, memctx, path):
2067 def getfilectx(repo, memctx, path):
2068 fctx = filectxfn[path]
2068 fctx = filectxfn[path]
2069 # this is weird but apparently we only keep track of one parent
2069 # this is weird but apparently we only keep track of one parent
2070 # (why not only store that instead of a tuple?)
2070 # (why not only store that instead of a tuple?)
2071 copied = fctx.renamed()
2071 copied = fctx.renamed()
2072 if copied:
2072 if copied:
2073 copied = copied[0]
2073 copied = copied[0]
2074 return memfilectx(repo, path, fctx.data(),
2074 return memfilectx(repo, path, fctx.data(),
2075 islink=fctx.islink(), isexec=fctx.isexec(),
2075 islink=fctx.islink(), isexec=fctx.isexec(),
2076 copied=copied, memctx=memctx)
2076 copied=copied, memctx=memctx)
2077 self._filectxfn = getfilectx
2077 self._filectxfn = getfilectx
2078 else:
2078 else:
2079 # memoizing increases performance for e.g. vcs convert scenarios.
2079 # memoizing increases performance for e.g. vcs convert scenarios.
2080 self._filectxfn = makecachingfilectxfn(filectxfn)
2080 self._filectxfn = makecachingfilectxfn(filectxfn)
2081
2081
2082 if editor:
2082 if editor:
2083 self._text = editor(self._repo, self, [])
2083 self._text = editor(self._repo, self, [])
2084 self._repo.savecommitmessage(self._text)
2084 self._repo.savecommitmessage(self._text)
2085
2085
2086 def filectx(self, path, filelog=None):
2086 def filectx(self, path, filelog=None):
2087 """get a file context from the working directory
2087 """get a file context from the working directory
2088
2088
2089 Returns None if file doesn't exist and should be removed."""
2089 Returns None if file doesn't exist and should be removed."""
2090 return self._filectxfn(self._repo, self, path)
2090 return self._filectxfn(self._repo, self, path)
2091
2091
2092 def commit(self):
2092 def commit(self):
2093 """commit context to the repo"""
2093 """commit context to the repo"""
2094 return self._repo.commitctx(self)
2094 return self._repo.commitctx(self)
2095
2095
2096 @propertycache
2096 @propertycache
2097 def _manifest(self):
2097 def _manifest(self):
2098 """generate a manifest based on the return values of filectxfn"""
2098 """generate a manifest based on the return values of filectxfn"""
2099
2099
2100 # keep this simple for now; just worry about p1
2100 # keep this simple for now; just worry about p1
2101 pctx = self._parents[0]
2101 pctx = self._parents[0]
2102 man = pctx.manifest().copy()
2102 man = pctx.manifest().copy()
2103
2103
2104 for f in self._status.modified:
2104 for f in self._status.modified:
2105 p1node = nullid
2105 p1node = nullid
2106 p2node = nullid
2106 p2node = nullid
2107 p = pctx[f].parents() # if file isn't in pctx, check p2?
2107 p = pctx[f].parents() # if file isn't in pctx, check p2?
2108 if len(p) > 0:
2108 if len(p) > 0:
2109 p1node = p[0].filenode()
2109 p1node = p[0].filenode()
2110 if len(p) > 1:
2110 if len(p) > 1:
2111 p2node = p[1].filenode()
2111 p2node = p[1].filenode()
2112 man[f] = revlog.hash(self[f].data(), p1node, p2node)
2112 man[f] = revlog.hash(self[f].data(), p1node, p2node)
2113
2113
2114 for f in self._status.added:
2114 for f in self._status.added:
2115 man[f] = revlog.hash(self[f].data(), nullid, nullid)
2115 man[f] = revlog.hash(self[f].data(), nullid, nullid)
2116
2116
2117 for f in self._status.removed:
2117 for f in self._status.removed:
2118 if f in man:
2118 if f in man:
2119 del man[f]
2119 del man[f]
2120
2120
2121 return man
2121 return man
2122
2122
2123 @propertycache
2123 @propertycache
2124 def _status(self):
2124 def _status(self):
2125 """Calculate exact status from ``files`` specified at construction
2125 """Calculate exact status from ``files`` specified at construction
2126 """
2126 """
2127 man1 = self.p1().manifest()
2127 man1 = self.p1().manifest()
2128 p2 = self._parents[1]
2128 p2 = self._parents[1]
2129 # "1 < len(self._parents)" can't be used for checking
2129 # "1 < len(self._parents)" can't be used for checking
2130 # existence of the 2nd parent, because "memctx._parents" is
2130 # existence of the 2nd parent, because "memctx._parents" is
2131 # explicitly initialized by the list, of which length is 2.
2131 # explicitly initialized by the list, of which length is 2.
2132 if p2.node() != nullid:
2132 if p2.node() != nullid:
2133 man2 = p2.manifest()
2133 man2 = p2.manifest()
2134 managing = lambda f: f in man1 or f in man2
2134 managing = lambda f: f in man1 or f in man2
2135 else:
2135 else:
2136 managing = lambda f: f in man1
2136 managing = lambda f: f in man1
2137
2137
2138 modified, added, removed = [], [], []
2138 modified, added, removed = [], [], []
2139 for f in self._files:
2139 for f in self._files:
2140 if not managing(f):
2140 if not managing(f):
2141 added.append(f)
2141 added.append(f)
2142 elif self[f]:
2142 elif self[f]:
2143 modified.append(f)
2143 modified.append(f)
2144 else:
2144 else:
2145 removed.append(f)
2145 removed.append(f)
2146
2146
2147 return scmutil.status(modified, added, removed, [], [], [], [])
2147 return scmutil.status(modified, added, removed, [], [], [], [])
2148
2148
2149 class memfilectx(committablefilectx):
2149 class memfilectx(committablefilectx):
2150 """memfilectx represents an in-memory file to commit.
2150 """memfilectx represents an in-memory file to commit.
2151
2151
2152 See memctx and committablefilectx for more details.
2152 See memctx and committablefilectx for more details.
2153 """
2153 """
2154 def __init__(self, repo, path, data, islink=False,
2154 def __init__(self, repo, path, data, islink=False,
2155 isexec=False, copied=None, memctx=None):
2155 isexec=False, copied=None, memctx=None):
2156 """
2156 """
2157 path is the normalized file path relative to repository root.
2157 path is the normalized file path relative to repository root.
2158 data is the file content as a string.
2158 data is the file content as a string.
2159 islink is True if the file is a symbolic link.
2159 islink is True if the file is a symbolic link.
2160 isexec is True if the file is executable.
2160 isexec is True if the file is executable.
2161 copied is the source file path if current file was copied in the
2161 copied is the source file path if current file was copied in the
2162 revision being committed, or None."""
2162 revision being committed, or None."""
2163 super(memfilectx, self).__init__(repo, path, None, memctx)
2163 super(memfilectx, self).__init__(repo, path, None, memctx)
2164 self._data = data
2164 self._data = data
2165 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
2165 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
2166 self._copied = None
2166 self._copied = None
2167 if copied:
2167 if copied:
2168 self._copied = (copied, nullid)
2168 self._copied = (copied, nullid)
2169
2169
2170 def data(self):
2170 def data(self):
2171 return self._data
2171 return self._data
2172
2172
2173 def remove(self, ignoremissing=False):
2173 def remove(self, ignoremissing=False):
2174 """wraps unlink for a repo's working directory"""
2174 """wraps unlink for a repo's working directory"""
2175 # need to figure out what to do here
2175 # need to figure out what to do here
2176 del self._changectx[self._path]
2176 del self._changectx[self._path]
2177
2177
2178 def write(self, data, flags):
2178 def write(self, data, flags):
2179 """wraps repo.wwrite"""
2179 """wraps repo.wwrite"""
2180 self._data = data
2180 self._data = data
2181
2181
2182 class overlayfilectx(committablefilectx):
2182 class overlayfilectx(committablefilectx):
2183 """Like memfilectx but take an original filectx and optional parameters to
2183 """Like memfilectx but take an original filectx and optional parameters to
2184 override parts of it. This is useful when fctx.data() is expensive (i.e.
2184 override parts of it. This is useful when fctx.data() is expensive (i.e.
2185 flag processor is expensive) and raw data, flags, and filenode could be
2185 flag processor is expensive) and raw data, flags, and filenode could be
2186 reused (ex. rebase or mode-only amend a REVIDX_EXTSTORED file).
2186 reused (ex. rebase or mode-only amend a REVIDX_EXTSTORED file).
2187 """
2187 """
2188
2188
2189 def __init__(self, originalfctx, datafunc=None, path=None, flags=None,
2189 def __init__(self, originalfctx, datafunc=None, path=None, flags=None,
2190 copied=None, ctx=None):
2190 copied=None, ctx=None):
2191 """originalfctx: filecontext to duplicate
2191 """originalfctx: filecontext to duplicate
2192
2192
2193 datafunc: None or a function to override data (file content). It is a
2193 datafunc: None or a function to override data (file content). It is a
2194 function to be lazy. path, flags, copied, ctx: None or overridden value
2194 function to be lazy. path, flags, copied, ctx: None or overridden value
2195
2195
2196 copied could be (path, rev), or False. copied could also be just path,
2196 copied could be (path, rev), or False. copied could also be just path,
2197 and will be converted to (path, nullid). This simplifies some callers.
2197 and will be converted to (path, nullid). This simplifies some callers.
2198 """
2198 """
2199
2199
2200 if path is None:
2200 if path is None:
2201 path = originalfctx.path()
2201 path = originalfctx.path()
2202 if ctx is None:
2202 if ctx is None:
2203 ctx = originalfctx.changectx()
2203 ctx = originalfctx.changectx()
2204 ctxmatch = lambda: True
2204 ctxmatch = lambda: True
2205 else:
2205 else:
2206 ctxmatch = lambda: ctx == originalfctx.changectx()
2206 ctxmatch = lambda: ctx == originalfctx.changectx()
2207
2207
2208 repo = originalfctx.repo()
2208 repo = originalfctx.repo()
2209 flog = originalfctx.filelog()
2209 flog = originalfctx.filelog()
2210 super(overlayfilectx, self).__init__(repo, path, flog, ctx)
2210 super(overlayfilectx, self).__init__(repo, path, flog, ctx)
2211
2211
2212 if copied is None:
2212 if copied is None:
2213 copied = originalfctx.renamed()
2213 copied = originalfctx.renamed()
2214 copiedmatch = lambda: True
2214 copiedmatch = lambda: True
2215 else:
2215 else:
2216 if copied and not isinstance(copied, tuple):
2216 if copied and not isinstance(copied, tuple):
2217 # repo._filecommit will recalculate copyrev so nullid is okay
2217 # repo._filecommit will recalculate copyrev so nullid is okay
2218 copied = (copied, nullid)
2218 copied = (copied, nullid)
2219 copiedmatch = lambda: copied == originalfctx.renamed()
2219 copiedmatch = lambda: copied == originalfctx.renamed()
2220
2220
2221 # When data, copied (could affect data), ctx (could affect filelog
2221 # When data, copied (could affect data), ctx (could affect filelog
2222 # parents) are not overridden, rawdata, rawflags, and filenode may be
2222 # parents) are not overridden, rawdata, rawflags, and filenode may be
2223 # reused (repo._filecommit should double check filelog parents).
2223 # reused (repo._filecommit should double check filelog parents).
2224 #
2224 #
2225 # path, flags are not hashed in filelog (but in manifestlog) so they do
2225 # path, flags are not hashed in filelog (but in manifestlog) so they do
2226 # not affect reusable here.
2226 # not affect reusable here.
2227 #
2227 #
2228 # If ctx or copied is overridden to a same value with originalfctx,
2228 # If ctx or copied is overridden to a same value with originalfctx,
2229 # still consider it's reusable. originalfctx.renamed() may be a bit
2229 # still consider it's reusable. originalfctx.renamed() may be a bit
2230 # expensive so it's not called unless necessary. Assuming datafunc is
2230 # expensive so it's not called unless necessary. Assuming datafunc is
2231 # always expensive, do not call it for this "reusable" test.
2231 # always expensive, do not call it for this "reusable" test.
2232 reusable = datafunc is None and ctxmatch() and copiedmatch()
2232 reusable = datafunc is None and ctxmatch() and copiedmatch()
2233
2233
2234 if datafunc is None:
2234 if datafunc is None:
2235 datafunc = originalfctx.data
2235 datafunc = originalfctx.data
2236 if flags is None:
2236 if flags is None:
2237 flags = originalfctx.flags()
2237 flags = originalfctx.flags()
2238
2238
2239 self._datafunc = datafunc
2239 self._datafunc = datafunc
2240 self._flags = flags
2240 self._flags = flags
2241 self._copied = copied
2241 self._copied = copied
2242
2242
2243 if reusable:
2243 if reusable:
2244 # copy extra fields from originalfctx
2244 # copy extra fields from originalfctx
2245 attrs = ['rawdata', 'rawflags', '_filenode', '_filerev']
2245 attrs = ['rawdata', 'rawflags', '_filenode', '_filerev']
2246 for attr in attrs:
2246 for attr in attrs:
2247 if util.safehasattr(originalfctx, attr):
2247 if util.safehasattr(originalfctx, attr):
2248 setattr(self, attr, getattr(originalfctx, attr))
2248 setattr(self, attr, getattr(originalfctx, attr))
2249
2249
2250 def data(self):
2250 def data(self):
2251 return self._datafunc()
2251 return self._datafunc()
2252
2252
2253 class metadataonlyctx(committablectx):
2253 class metadataonlyctx(committablectx):
2254 """Like memctx but it's reusing the manifest of different commit.
2254 """Like memctx but it's reusing the manifest of different commit.
2255 Intended to be used by lightweight operations that are creating
2255 Intended to be used by lightweight operations that are creating
2256 metadata-only changes.
2256 metadata-only changes.
2257
2257
2258 Revision information is supplied at initialization time. 'repo' is the
2258 Revision information is supplied at initialization time. 'repo' is the
2259 current localrepo, 'ctx' is original revision which manifest we're reuisng
2259 current localrepo, 'ctx' is original revision which manifest we're reuisng
2260 'parents' is a sequence of two parent revisions identifiers (pass None for
2260 'parents' is a sequence of two parent revisions identifiers (pass None for
2261 every missing parent), 'text' is the commit.
2261 every missing parent), 'text' is the commit.
2262
2262
2263 user receives the committer name and defaults to current repository
2263 user receives the committer name and defaults to current repository
2264 username, date is the commit date in any format supported by
2264 username, date is the commit date in any format supported by
2265 util.parsedate() and defaults to current date, extra is a dictionary of
2265 util.parsedate() and defaults to current date, extra is a dictionary of
2266 metadata or is left empty.
2266 metadata or is left empty.
2267 """
2267 """
2268 def __new__(cls, repo, originalctx, *args, **kwargs):
2268 def __new__(cls, repo, originalctx, *args, **kwargs):
2269 return super(metadataonlyctx, cls).__new__(cls, repo)
2269 return super(metadataonlyctx, cls).__new__(cls, repo)
2270
2270
2271 def __init__(self, repo, originalctx, parents, text, user=None, date=None,
2271 def __init__(self, repo, originalctx, parents, text, user=None, date=None,
2272 extra=None, editor=False):
2272 extra=None, editor=False):
2273 super(metadataonlyctx, self).__init__(repo, text, user, date, extra)
2273 super(metadataonlyctx, self).__init__(repo, text, user, date, extra)
2274 self._rev = None
2274 self._rev = None
2275 self._node = None
2275 self._node = None
2276 self._originalctx = originalctx
2276 self._originalctx = originalctx
2277 self._manifestnode = originalctx.manifestnode()
2277 self._manifestnode = originalctx.manifestnode()
2278 parents = [(p or nullid) for p in parents]
2278 parents = [(p or nullid) for p in parents]
2279 p1, p2 = self._parents = [changectx(self._repo, p) for p in parents]
2279 p1, p2 = self._parents = [changectx(self._repo, p) for p in parents]
2280
2280
2281 # sanity check to ensure that the reused manifest parents are
2281 # sanity check to ensure that the reused manifest parents are
2282 # manifests of our commit parents
2282 # manifests of our commit parents
2283 mp1, mp2 = self.manifestctx().parents
2283 mp1, mp2 = self.manifestctx().parents
2284 if p1 != nullid and p1.manifestnode() != mp1:
2284 if p1 != nullid and p1.manifestnode() != mp1:
2285 raise RuntimeError('can\'t reuse the manifest: '
2285 raise RuntimeError('can\'t reuse the manifest: '
2286 'its p1 doesn\'t match the new ctx p1')
2286 'its p1 doesn\'t match the new ctx p1')
2287 if p2 != nullid and p2.manifestnode() != mp2:
2287 if p2 != nullid and p2.manifestnode() != mp2:
2288 raise RuntimeError('can\'t reuse the manifest: '
2288 raise RuntimeError('can\'t reuse the manifest: '
2289 'its p2 doesn\'t match the new ctx p2')
2289 'its p2 doesn\'t match the new ctx p2')
2290
2290
2291 self._files = originalctx.files()
2291 self._files = originalctx.files()
2292 self.substate = {}
2292 self.substate = {}
2293
2293
2294 if editor:
2294 if editor:
2295 self._text = editor(self._repo, self, [])
2295 self._text = editor(self._repo, self, [])
2296 self._repo.savecommitmessage(self._text)
2296 self._repo.savecommitmessage(self._text)
2297
2297
2298 def manifestnode(self):
2298 def manifestnode(self):
2299 return self._manifestnode
2299 return self._manifestnode
2300
2300
2301 @property
2301 @property
2302 def _manifestctx(self):
2302 def _manifestctx(self):
2303 return self._repo.manifestlog[self._manifestnode]
2303 return self._repo.manifestlog[self._manifestnode]
2304
2304
2305 def filectx(self, path, filelog=None):
2305 def filectx(self, path, filelog=None):
2306 return self._originalctx.filectx(path, filelog=filelog)
2306 return self._originalctx.filectx(path, filelog=filelog)
2307
2307
2308 def commit(self):
2308 def commit(self):
2309 """commit context to the repo"""
2309 """commit context to the repo"""
2310 return self._repo.commitctx(self)
2310 return self._repo.commitctx(self)
2311
2311
2312 @property
2312 @property
2313 def _manifest(self):
2313 def _manifest(self):
2314 return self._originalctx.manifest()
2314 return self._originalctx.manifest()
2315
2315
2316 @propertycache
2316 @propertycache
2317 def _status(self):
2317 def _status(self):
2318 """Calculate exact status from ``files`` specified in the ``origctx``
2318 """Calculate exact status from ``files`` specified in the ``origctx``
2319 and parents manifests.
2319 and parents manifests.
2320 """
2320 """
2321 man1 = self.p1().manifest()
2321 man1 = self.p1().manifest()
2322 p2 = self._parents[1]
2322 p2 = self._parents[1]
2323 # "1 < len(self._parents)" can't be used for checking
2323 # "1 < len(self._parents)" can't be used for checking
2324 # existence of the 2nd parent, because "metadataonlyctx._parents" is
2324 # existence of the 2nd parent, because "metadataonlyctx._parents" is
2325 # explicitly initialized by the list, of which length is 2.
2325 # explicitly initialized by the list, of which length is 2.
2326 if p2.node() != nullid:
2326 if p2.node() != nullid:
2327 man2 = p2.manifest()
2327 man2 = p2.manifest()
2328 managing = lambda f: f in man1 or f in man2
2328 managing = lambda f: f in man1 or f in man2
2329 else:
2329 else:
2330 managing = lambda f: f in man1
2330 managing = lambda f: f in man1
2331
2331
2332 modified, added, removed = [], [], []
2332 modified, added, removed = [], [], []
2333 for f in self._files:
2333 for f in self._files:
2334 if not managing(f):
2334 if not managing(f):
2335 added.append(f)
2335 added.append(f)
2336 elif self[f]:
2336 elif self[f]:
2337 modified.append(f)
2337 modified.append(f)
2338 else:
2338 else:
2339 removed.append(f)
2339 removed.append(f)
2340
2340
2341 return scmutil.status(modified, added, removed, [], [], [], [])
2341 return scmutil.status(modified, added, removed, [], [], [], [])
General Comments 0
You need to be logged in to leave comments. Login now