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