##// END OF EJS Templates
context: use wdirhex constant instead of calculating it...
Martin von Zweigbergk -
r42133:21cc92fe default
parent child Browse files
Show More
@@ -1,2550 +1,2550 b''
1 # context.py - changeset and file context objects for mercurial
1 # context.py - changeset and file context objects for mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __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 wdirhex,
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 def modified(self):
1186 def modified(self):
1187 return self._status.modified
1187 return self._status.modified
1188 def added(self):
1188 def added(self):
1189 return self._status.added
1189 return self._status.added
1190 def removed(self):
1190 def removed(self):
1191 return self._status.removed
1191 return self._status.removed
1192 def deleted(self):
1192 def deleted(self):
1193 return self._status.deleted
1193 return self._status.deleted
1194 @propertycache
1194 @propertycache
1195 def _copies(self):
1195 def _copies(self):
1196 p1copies = {}
1196 p1copies = {}
1197 p2copies = {}
1197 p2copies = {}
1198 parents = self._repo.dirstate.parents()
1198 parents = self._repo.dirstate.parents()
1199 p1manifest = self._repo[parents[0]].manifest()
1199 p1manifest = self._repo[parents[0]].manifest()
1200 p2manifest = self._repo[parents[1]].manifest()
1200 p2manifest = self._repo[parents[1]].manifest()
1201 narrowmatch = self._repo.narrowmatch()
1201 narrowmatch = self._repo.narrowmatch()
1202 for dst, src in self._repo.dirstate.copies().items():
1202 for dst, src in self._repo.dirstate.copies().items():
1203 if not narrowmatch(dst):
1203 if not narrowmatch(dst):
1204 continue
1204 continue
1205 if src in p1manifest:
1205 if src in p1manifest:
1206 p1copies[dst] = src
1206 p1copies[dst] = src
1207 elif src in p2manifest:
1207 elif src in p2manifest:
1208 p2copies[dst] = src
1208 p2copies[dst] = src
1209 return p1copies, p2copies
1209 return p1copies, p2copies
1210 def p1copies(self):
1210 def p1copies(self):
1211 return self._copies[0]
1211 return self._copies[0]
1212 def p2copies(self):
1212 def p2copies(self):
1213 return self._copies[1]
1213 return self._copies[1]
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 wdirhex
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):
1667 def renamed(self):
1668 path = self.copysource()
1668 path = self.copysource()
1669 if not path:
1669 if not path:
1670 return None
1670 return None
1671 return path, self._changectx._parents[0]._manifest.get(path, nullid)
1671 return path, self._changectx._parents[0]._manifest.get(path, nullid)
1672
1672
1673 def parents(self):
1673 def parents(self):
1674 '''return parent filectxs, following copies if necessary'''
1674 '''return parent filectxs, following copies if necessary'''
1675 def filenode(ctx, path):
1675 def filenode(ctx, path):
1676 return ctx._manifest.get(path, nullid)
1676 return ctx._manifest.get(path, nullid)
1677
1677
1678 path = self._path
1678 path = self._path
1679 fl = self._filelog
1679 fl = self._filelog
1680 pcl = self._changectx._parents
1680 pcl = self._changectx._parents
1681 renamed = self.renamed()
1681 renamed = self.renamed()
1682
1682
1683 if renamed:
1683 if renamed:
1684 pl = [renamed + (None,)]
1684 pl = [renamed + (None,)]
1685 else:
1685 else:
1686 pl = [(path, filenode(pcl[0], path), fl)]
1686 pl = [(path, filenode(pcl[0], path), fl)]
1687
1687
1688 for pc in pcl[1:]:
1688 for pc in pcl[1:]:
1689 pl.append((path, filenode(pc, path), fl))
1689 pl.append((path, filenode(pc, path), fl))
1690
1690
1691 return [self._parentfilectx(p, fileid=n, filelog=l)
1691 return [self._parentfilectx(p, fileid=n, filelog=l)
1692 for p, n, l in pl if n != nullid]
1692 for p, n, l in pl if n != nullid]
1693
1693
1694 def children(self):
1694 def children(self):
1695 return []
1695 return []
1696
1696
1697 class workingfilectx(committablefilectx):
1697 class workingfilectx(committablefilectx):
1698 """A workingfilectx object makes access to data related to a particular
1698 """A workingfilectx object makes access to data related to a particular
1699 file in the working directory convenient."""
1699 file in the working directory convenient."""
1700 def __init__(self, repo, path, filelog=None, workingctx=None):
1700 def __init__(self, repo, path, filelog=None, workingctx=None):
1701 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1701 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1702
1702
1703 @propertycache
1703 @propertycache
1704 def _changectx(self):
1704 def _changectx(self):
1705 return workingctx(self._repo)
1705 return workingctx(self._repo)
1706
1706
1707 def data(self):
1707 def data(self):
1708 return self._repo.wread(self._path)
1708 return self._repo.wread(self._path)
1709 def copysource(self):
1709 def copysource(self):
1710 return self._repo.dirstate.copied(self._path)
1710 return self._repo.dirstate.copied(self._path)
1711
1711
1712 def size(self):
1712 def size(self):
1713 return self._repo.wvfs.lstat(self._path).st_size
1713 return self._repo.wvfs.lstat(self._path).st_size
1714 def date(self):
1714 def date(self):
1715 t, tz = self._changectx.date()
1715 t, tz = self._changectx.date()
1716 try:
1716 try:
1717 return (self._repo.wvfs.lstat(self._path)[stat.ST_MTIME], tz)
1717 return (self._repo.wvfs.lstat(self._path)[stat.ST_MTIME], tz)
1718 except OSError as err:
1718 except OSError as err:
1719 if err.errno != errno.ENOENT:
1719 if err.errno != errno.ENOENT:
1720 raise
1720 raise
1721 return (t, tz)
1721 return (t, tz)
1722
1722
1723 def exists(self):
1723 def exists(self):
1724 return self._repo.wvfs.exists(self._path)
1724 return self._repo.wvfs.exists(self._path)
1725
1725
1726 def lexists(self):
1726 def lexists(self):
1727 return self._repo.wvfs.lexists(self._path)
1727 return self._repo.wvfs.lexists(self._path)
1728
1728
1729 def audit(self):
1729 def audit(self):
1730 return self._repo.wvfs.audit(self._path)
1730 return self._repo.wvfs.audit(self._path)
1731
1731
1732 def cmp(self, fctx):
1732 def cmp(self, fctx):
1733 """compare with other file context
1733 """compare with other file context
1734
1734
1735 returns True if different than fctx.
1735 returns True if different than fctx.
1736 """
1736 """
1737 # fctx should be a filectx (not a workingfilectx)
1737 # fctx should be a filectx (not a workingfilectx)
1738 # invert comparison to reuse the same code path
1738 # invert comparison to reuse the same code path
1739 return fctx.cmp(self)
1739 return fctx.cmp(self)
1740
1740
1741 def remove(self, ignoremissing=False):
1741 def remove(self, ignoremissing=False):
1742 """wraps unlink for a repo's working directory"""
1742 """wraps unlink for a repo's working directory"""
1743 rmdir = self._repo.ui.configbool('experimental', 'removeemptydirs')
1743 rmdir = self._repo.ui.configbool('experimental', 'removeemptydirs')
1744 self._repo.wvfs.unlinkpath(self._path, ignoremissing=ignoremissing,
1744 self._repo.wvfs.unlinkpath(self._path, ignoremissing=ignoremissing,
1745 rmdir=rmdir)
1745 rmdir=rmdir)
1746
1746
1747 def write(self, data, flags, backgroundclose=False, **kwargs):
1747 def write(self, data, flags, backgroundclose=False, **kwargs):
1748 """wraps repo.wwrite"""
1748 """wraps repo.wwrite"""
1749 self._repo.wwrite(self._path, data, flags,
1749 self._repo.wwrite(self._path, data, flags,
1750 backgroundclose=backgroundclose,
1750 backgroundclose=backgroundclose,
1751 **kwargs)
1751 **kwargs)
1752
1752
1753 def markcopied(self, src):
1753 def markcopied(self, src):
1754 """marks this file a copy of `src`"""
1754 """marks this file a copy of `src`"""
1755 if self._repo.dirstate[self._path] in "nma":
1755 if self._repo.dirstate[self._path] in "nma":
1756 self._repo.dirstate.copy(src, self._path)
1756 self._repo.dirstate.copy(src, self._path)
1757
1757
1758 def clearunknown(self):
1758 def clearunknown(self):
1759 """Removes conflicting items in the working directory so that
1759 """Removes conflicting items in the working directory so that
1760 ``write()`` can be called successfully.
1760 ``write()`` can be called successfully.
1761 """
1761 """
1762 wvfs = self._repo.wvfs
1762 wvfs = self._repo.wvfs
1763 f = self._path
1763 f = self._path
1764 wvfs.audit(f)
1764 wvfs.audit(f)
1765 if self._repo.ui.configbool('experimental', 'merge.checkpathconflicts'):
1765 if self._repo.ui.configbool('experimental', 'merge.checkpathconflicts'):
1766 # remove files under the directory as they should already be
1766 # remove files under the directory as they should already be
1767 # warned and backed up
1767 # warned and backed up
1768 if wvfs.isdir(f) and not wvfs.islink(f):
1768 if wvfs.isdir(f) and not wvfs.islink(f):
1769 wvfs.rmtree(f, forcibly=True)
1769 wvfs.rmtree(f, forcibly=True)
1770 for p in reversed(list(util.finddirs(f))):
1770 for p in reversed(list(util.finddirs(f))):
1771 if wvfs.isfileorlink(p):
1771 if wvfs.isfileorlink(p):
1772 wvfs.unlink(p)
1772 wvfs.unlink(p)
1773 break
1773 break
1774 else:
1774 else:
1775 # don't remove files if path conflicts are not processed
1775 # don't remove files if path conflicts are not processed
1776 if wvfs.isdir(f) and not wvfs.islink(f):
1776 if wvfs.isdir(f) and not wvfs.islink(f):
1777 wvfs.removedirs(f)
1777 wvfs.removedirs(f)
1778
1778
1779 def setflags(self, l, x):
1779 def setflags(self, l, x):
1780 self._repo.wvfs.setflags(self._path, l, x)
1780 self._repo.wvfs.setflags(self._path, l, x)
1781
1781
1782 class overlayworkingctx(committablectx):
1782 class overlayworkingctx(committablectx):
1783 """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
1784 converted into a commit context.
1784 converted into a commit context.
1785
1785
1786 self._cache[path] maps to a dict with keys: {
1786 self._cache[path] maps to a dict with keys: {
1787 'exists': bool?
1787 'exists': bool?
1788 'date': date?
1788 'date': date?
1789 'data': str?
1789 'data': str?
1790 'flags': str?
1790 'flags': str?
1791 'copied': str? (path or None)
1791 'copied': str? (path or None)
1792 }
1792 }
1793 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
1794 is `False`, the file was deleted.
1794 is `False`, the file was deleted.
1795 """
1795 """
1796
1796
1797 def __init__(self, repo):
1797 def __init__(self, repo):
1798 super(overlayworkingctx, self).__init__(repo)
1798 super(overlayworkingctx, self).__init__(repo)
1799 self.clean()
1799 self.clean()
1800
1800
1801 def setbase(self, wrappedctx):
1801 def setbase(self, wrappedctx):
1802 self._wrappedctx = wrappedctx
1802 self._wrappedctx = wrappedctx
1803 self._parents = [wrappedctx]
1803 self._parents = [wrappedctx]
1804 # Drop old manifest cache as it is now out of date.
1804 # Drop old manifest cache as it is now out of date.
1805 # This is necessary when, e.g., rebasing several nodes with one
1805 # This is necessary when, e.g., rebasing several nodes with one
1806 # ``overlayworkingctx`` (e.g. with --collapse).
1806 # ``overlayworkingctx`` (e.g. with --collapse).
1807 util.clearcachedproperty(self, '_manifest')
1807 util.clearcachedproperty(self, '_manifest')
1808
1808
1809 def data(self, path):
1809 def data(self, path):
1810 if self.isdirty(path):
1810 if self.isdirty(path):
1811 if self._cache[path]['exists']:
1811 if self._cache[path]['exists']:
1812 if self._cache[path]['data']:
1812 if self._cache[path]['data']:
1813 return self._cache[path]['data']
1813 return self._cache[path]['data']
1814 else:
1814 else:
1815 # Must fallback here, too, because we only set flags.
1815 # Must fallback here, too, because we only set flags.
1816 return self._wrappedctx[path].data()
1816 return self._wrappedctx[path].data()
1817 else:
1817 else:
1818 raise error.ProgrammingError("No such file or directory: %s" %
1818 raise error.ProgrammingError("No such file or directory: %s" %
1819 path)
1819 path)
1820 else:
1820 else:
1821 return self._wrappedctx[path].data()
1821 return self._wrappedctx[path].data()
1822
1822
1823 @propertycache
1823 @propertycache
1824 def _manifest(self):
1824 def _manifest(self):
1825 parents = self.parents()
1825 parents = self.parents()
1826 man = parents[0].manifest().copy()
1826 man = parents[0].manifest().copy()
1827
1827
1828 flag = self._flagfunc
1828 flag = self._flagfunc
1829 for path in self.added():
1829 for path in self.added():
1830 man[path] = addednodeid
1830 man[path] = addednodeid
1831 man.setflag(path, flag(path))
1831 man.setflag(path, flag(path))
1832 for path in self.modified():
1832 for path in self.modified():
1833 man[path] = modifiednodeid
1833 man[path] = modifiednodeid
1834 man.setflag(path, flag(path))
1834 man.setflag(path, flag(path))
1835 for path in self.removed():
1835 for path in self.removed():
1836 del man[path]
1836 del man[path]
1837 return man
1837 return man
1838
1838
1839 @propertycache
1839 @propertycache
1840 def _flagfunc(self):
1840 def _flagfunc(self):
1841 def f(path):
1841 def f(path):
1842 return self._cache[path]['flags']
1842 return self._cache[path]['flags']
1843 return f
1843 return f
1844
1844
1845 def files(self):
1845 def files(self):
1846 return sorted(self.added() + self.modified() + self.removed())
1846 return sorted(self.added() + self.modified() + self.removed())
1847
1847
1848 def modified(self):
1848 def modified(self):
1849 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
1850 self._existsinparent(f)]
1850 self._existsinparent(f)]
1851
1851
1852 def added(self):
1852 def added(self):
1853 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
1854 not self._existsinparent(f)]
1854 not self._existsinparent(f)]
1855
1855
1856 def removed(self):
1856 def removed(self):
1857 return [f for f in self._cache.keys() if
1857 return [f for f in self._cache.keys() if
1858 not self._cache[f]['exists'] and self._existsinparent(f)]
1858 not self._cache[f]['exists'] and self._existsinparent(f)]
1859
1859
1860 def p1copies(self):
1860 def p1copies(self):
1861 copies = self._repo._wrappedctx.p1copies().copy()
1861 copies = self._repo._wrappedctx.p1copies().copy()
1862 narrowmatch = self._repo.narrowmatch()
1862 narrowmatch = self._repo.narrowmatch()
1863 for f in self._cache.keys():
1863 for f in self._cache.keys():
1864 if not narrowmatch(f):
1864 if not narrowmatch(f):
1865 continue
1865 continue
1866 copies.pop(f, None) # delete if it exists
1866 copies.pop(f, None) # delete if it exists
1867 source = self._cache[f]['copied']
1867 source = self._cache[f]['copied']
1868 if source:
1868 if source:
1869 copies[f] = source
1869 copies[f] = source
1870 return copies
1870 return copies
1871
1871
1872 def p2copies(self):
1872 def p2copies(self):
1873 copies = self._repo._wrappedctx.p2copies().copy()
1873 copies = self._repo._wrappedctx.p2copies().copy()
1874 narrowmatch = self._repo.narrowmatch()
1874 narrowmatch = self._repo.narrowmatch()
1875 for f in self._cache.keys():
1875 for f in self._cache.keys():
1876 if not narrowmatch(f):
1876 if not narrowmatch(f):
1877 continue
1877 continue
1878 copies.pop(f, None) # delete if it exists
1878 copies.pop(f, None) # delete if it exists
1879 source = self._cache[f]['copied']
1879 source = self._cache[f]['copied']
1880 if source:
1880 if source:
1881 copies[f] = source
1881 copies[f] = source
1882 return copies
1882 return copies
1883
1883
1884 def isinmemory(self):
1884 def isinmemory(self):
1885 return True
1885 return True
1886
1886
1887 def filedate(self, path):
1887 def filedate(self, path):
1888 if self.isdirty(path):
1888 if self.isdirty(path):
1889 return self._cache[path]['date']
1889 return self._cache[path]['date']
1890 else:
1890 else:
1891 return self._wrappedctx[path].date()
1891 return self._wrappedctx[path].date()
1892
1892
1893 def markcopied(self, path, origin):
1893 def markcopied(self, path, origin):
1894 self._markdirty(path, exists=True, date=self.filedate(path),
1894 self._markdirty(path, exists=True, date=self.filedate(path),
1895 flags=self.flags(path), copied=origin)
1895 flags=self.flags(path), copied=origin)
1896
1896
1897 def copydata(self, path):
1897 def copydata(self, path):
1898 if self.isdirty(path):
1898 if self.isdirty(path):
1899 return self._cache[path]['copied']
1899 return self._cache[path]['copied']
1900 else:
1900 else:
1901 raise error.ProgrammingError('copydata() called on clean context')
1901 raise error.ProgrammingError('copydata() called on clean context')
1902
1902
1903 def flags(self, path):
1903 def flags(self, path):
1904 if self.isdirty(path):
1904 if self.isdirty(path):
1905 if self._cache[path]['exists']:
1905 if self._cache[path]['exists']:
1906 return self._cache[path]['flags']
1906 return self._cache[path]['flags']
1907 else:
1907 else:
1908 raise error.ProgrammingError("No such file or directory: %s" %
1908 raise error.ProgrammingError("No such file or directory: %s" %
1909 self._path)
1909 self._path)
1910 else:
1910 else:
1911 return self._wrappedctx[path].flags()
1911 return self._wrappedctx[path].flags()
1912
1912
1913 def __contains__(self, key):
1913 def __contains__(self, key):
1914 if key in self._cache:
1914 if key in self._cache:
1915 return self._cache[key]['exists']
1915 return self._cache[key]['exists']
1916 return key in self.p1()
1916 return key in self.p1()
1917
1917
1918 def _existsinparent(self, path):
1918 def _existsinparent(self, path):
1919 try:
1919 try:
1920 # ``commitctx` raises a ``ManifestLookupError`` if a path does not
1920 # ``commitctx` raises a ``ManifestLookupError`` if a path does not
1921 # exist, unlike ``workingctx``, which returns a ``workingfilectx``
1921 # exist, unlike ``workingctx``, which returns a ``workingfilectx``
1922 # with an ``exists()`` function.
1922 # with an ``exists()`` function.
1923 self._wrappedctx[path]
1923 self._wrappedctx[path]
1924 return True
1924 return True
1925 except error.ManifestLookupError:
1925 except error.ManifestLookupError:
1926 return False
1926 return False
1927
1927
1928 def _auditconflicts(self, path):
1928 def _auditconflicts(self, path):
1929 """Replicates conflict checks done by wvfs.write().
1929 """Replicates conflict checks done by wvfs.write().
1930
1930
1931 Since we never write to the filesystem and never call `applyupdates` in
1931 Since we never write to the filesystem and never call `applyupdates` in
1932 IMM, we'll never check that a path is actually writable -- e.g., because
1932 IMM, we'll never check that a path is actually writable -- e.g., because
1933 it adds `a/foo`, but `a` is actually a file in the other commit.
1933 it adds `a/foo`, but `a` is actually a file in the other commit.
1934 """
1934 """
1935 def fail(path, component):
1935 def fail(path, component):
1936 # p1() is the base and we're receiving "writes" for p2()'s
1936 # p1() is the base and we're receiving "writes" for p2()'s
1937 # files.
1937 # files.
1938 if 'l' in self.p1()[component].flags():
1938 if 'l' in self.p1()[component].flags():
1939 raise error.Abort("error: %s conflicts with symlink %s "
1939 raise error.Abort("error: %s conflicts with symlink %s "
1940 "in %d." % (path, component,
1940 "in %d." % (path, component,
1941 self.p1().rev()))
1941 self.p1().rev()))
1942 else:
1942 else:
1943 raise error.Abort("error: '%s' conflicts with file '%s' in "
1943 raise error.Abort("error: '%s' conflicts with file '%s' in "
1944 "%d." % (path, component,
1944 "%d." % (path, component,
1945 self.p1().rev()))
1945 self.p1().rev()))
1946
1946
1947 # Test that each new directory to be created to write this path from p2
1947 # Test that each new directory to be created to write this path from p2
1948 # is not a file in p1.
1948 # is not a file in p1.
1949 components = path.split('/')
1949 components = path.split('/')
1950 for i in pycompat.xrange(len(components)):
1950 for i in pycompat.xrange(len(components)):
1951 component = "/".join(components[0:i])
1951 component = "/".join(components[0:i])
1952 if component in self:
1952 if component in self:
1953 fail(path, component)
1953 fail(path, component)
1954
1954
1955 # Test the other direction -- that this path from p2 isn't a directory
1955 # Test the other direction -- that this path from p2 isn't a directory
1956 # in p1 (test that p1 doesn't have any paths matching `path/*`).
1956 # in p1 (test that p1 doesn't have any paths matching `path/*`).
1957 match = self.match(include=[path + '/'], default=b'path')
1957 match = self.match(include=[path + '/'], default=b'path')
1958 matches = self.p1().manifest().matches(match)
1958 matches = self.p1().manifest().matches(match)
1959 mfiles = matches.keys()
1959 mfiles = matches.keys()
1960 if len(mfiles) > 0:
1960 if len(mfiles) > 0:
1961 if len(mfiles) == 1 and mfiles[0] == path:
1961 if len(mfiles) == 1 and mfiles[0] == path:
1962 return
1962 return
1963 # omit the files which are deleted in current IMM wctx
1963 # omit the files which are deleted in current IMM wctx
1964 mfiles = [m for m in mfiles if m in self]
1964 mfiles = [m for m in mfiles if m in self]
1965 if not mfiles:
1965 if not mfiles:
1966 return
1966 return
1967 raise error.Abort("error: file '%s' cannot be written because "
1967 raise error.Abort("error: file '%s' cannot be written because "
1968 " '%s/' is a folder in %s (containing %d "
1968 " '%s/' is a folder in %s (containing %d "
1969 "entries: %s)"
1969 "entries: %s)"
1970 % (path, path, self.p1(), len(mfiles),
1970 % (path, path, self.p1(), len(mfiles),
1971 ', '.join(mfiles)))
1971 ', '.join(mfiles)))
1972
1972
1973 def write(self, path, data, flags='', **kwargs):
1973 def write(self, path, data, flags='', **kwargs):
1974 if data is None:
1974 if data is None:
1975 raise error.ProgrammingError("data must be non-None")
1975 raise error.ProgrammingError("data must be non-None")
1976 self._auditconflicts(path)
1976 self._auditconflicts(path)
1977 self._markdirty(path, exists=True, data=data, date=dateutil.makedate(),
1977 self._markdirty(path, exists=True, data=data, date=dateutil.makedate(),
1978 flags=flags)
1978 flags=flags)
1979
1979
1980 def setflags(self, path, l, x):
1980 def setflags(self, path, l, x):
1981 flag = ''
1981 flag = ''
1982 if l:
1982 if l:
1983 flag = 'l'
1983 flag = 'l'
1984 elif x:
1984 elif x:
1985 flag = 'x'
1985 flag = 'x'
1986 self._markdirty(path, exists=True, date=dateutil.makedate(),
1986 self._markdirty(path, exists=True, date=dateutil.makedate(),
1987 flags=flag)
1987 flags=flag)
1988
1988
1989 def remove(self, path):
1989 def remove(self, path):
1990 self._markdirty(path, exists=False)
1990 self._markdirty(path, exists=False)
1991
1991
1992 def exists(self, path):
1992 def exists(self, path):
1993 """exists behaves like `lexists`, but needs to follow symlinks and
1993 """exists behaves like `lexists`, but needs to follow symlinks and
1994 return False if they are broken.
1994 return False if they are broken.
1995 """
1995 """
1996 if self.isdirty(path):
1996 if self.isdirty(path):
1997 # If this path exists and is a symlink, "follow" it by calling
1997 # If this path exists and is a symlink, "follow" it by calling
1998 # exists on the destination path.
1998 # exists on the destination path.
1999 if (self._cache[path]['exists'] and
1999 if (self._cache[path]['exists'] and
2000 'l' in self._cache[path]['flags']):
2000 'l' in self._cache[path]['flags']):
2001 return self.exists(self._cache[path]['data'].strip())
2001 return self.exists(self._cache[path]['data'].strip())
2002 else:
2002 else:
2003 return self._cache[path]['exists']
2003 return self._cache[path]['exists']
2004
2004
2005 return self._existsinparent(path)
2005 return self._existsinparent(path)
2006
2006
2007 def lexists(self, path):
2007 def lexists(self, path):
2008 """lexists returns True if the path exists"""
2008 """lexists returns True if the path exists"""
2009 if self.isdirty(path):
2009 if self.isdirty(path):
2010 return self._cache[path]['exists']
2010 return self._cache[path]['exists']
2011
2011
2012 return self._existsinparent(path)
2012 return self._existsinparent(path)
2013
2013
2014 def size(self, path):
2014 def size(self, path):
2015 if self.isdirty(path):
2015 if self.isdirty(path):
2016 if self._cache[path]['exists']:
2016 if self._cache[path]['exists']:
2017 return len(self._cache[path]['data'])
2017 return len(self._cache[path]['data'])
2018 else:
2018 else:
2019 raise error.ProgrammingError("No such file or directory: %s" %
2019 raise error.ProgrammingError("No such file or directory: %s" %
2020 self._path)
2020 self._path)
2021 return self._wrappedctx[path].size()
2021 return self._wrappedctx[path].size()
2022
2022
2023 def tomemctx(self, text, branch=None, extra=None, date=None, parents=None,
2023 def tomemctx(self, text, branch=None, extra=None, date=None, parents=None,
2024 user=None, editor=None):
2024 user=None, editor=None):
2025 """Converts this ``overlayworkingctx`` into a ``memctx`` ready to be
2025 """Converts this ``overlayworkingctx`` into a ``memctx`` ready to be
2026 committed.
2026 committed.
2027
2027
2028 ``text`` is the commit message.
2028 ``text`` is the commit message.
2029 ``parents`` (optional) are rev numbers.
2029 ``parents`` (optional) are rev numbers.
2030 """
2030 """
2031 # Default parents to the wrapped contexts' if not passed.
2031 # Default parents to the wrapped contexts' if not passed.
2032 if parents is None:
2032 if parents is None:
2033 parents = self._wrappedctx.parents()
2033 parents = self._wrappedctx.parents()
2034 if len(parents) == 1:
2034 if len(parents) == 1:
2035 parents = (parents[0], None)
2035 parents = (parents[0], None)
2036
2036
2037 # ``parents`` is passed as rev numbers; convert to ``commitctxs``.
2037 # ``parents`` is passed as rev numbers; convert to ``commitctxs``.
2038 if parents[1] is None:
2038 if parents[1] is None:
2039 parents = (self._repo[parents[0]], None)
2039 parents = (self._repo[parents[0]], None)
2040 else:
2040 else:
2041 parents = (self._repo[parents[0]], self._repo[parents[1]])
2041 parents = (self._repo[parents[0]], self._repo[parents[1]])
2042
2042
2043 files = self._cache.keys()
2043 files = self._cache.keys()
2044 def getfile(repo, memctx, path):
2044 def getfile(repo, memctx, path):
2045 if self._cache[path]['exists']:
2045 if self._cache[path]['exists']:
2046 return memfilectx(repo, memctx, path,
2046 return memfilectx(repo, memctx, path,
2047 self._cache[path]['data'],
2047 self._cache[path]['data'],
2048 'l' in self._cache[path]['flags'],
2048 'l' in self._cache[path]['flags'],
2049 'x' in self._cache[path]['flags'],
2049 'x' in self._cache[path]['flags'],
2050 self._cache[path]['copied'])
2050 self._cache[path]['copied'])
2051 else:
2051 else:
2052 # Returning None, but including the path in `files`, is
2052 # Returning None, but including the path in `files`, is
2053 # necessary for memctx to register a deletion.
2053 # necessary for memctx to register a deletion.
2054 return None
2054 return None
2055 return memctx(self._repo, parents, text, files, getfile, date=date,
2055 return memctx(self._repo, parents, text, files, getfile, date=date,
2056 extra=extra, user=user, branch=branch, editor=editor)
2056 extra=extra, user=user, branch=branch, editor=editor)
2057
2057
2058 def isdirty(self, path):
2058 def isdirty(self, path):
2059 return path in self._cache
2059 return path in self._cache
2060
2060
2061 def isempty(self):
2061 def isempty(self):
2062 # We need to discard any keys that are actually clean before the empty
2062 # We need to discard any keys that are actually clean before the empty
2063 # commit check.
2063 # commit check.
2064 self._compact()
2064 self._compact()
2065 return len(self._cache) == 0
2065 return len(self._cache) == 0
2066
2066
2067 def clean(self):
2067 def clean(self):
2068 self._cache = {}
2068 self._cache = {}
2069
2069
2070 def _compact(self):
2070 def _compact(self):
2071 """Removes keys from the cache that are actually clean, by comparing
2071 """Removes keys from the cache that are actually clean, by comparing
2072 them with the underlying context.
2072 them with the underlying context.
2073
2073
2074 This can occur during the merge process, e.g. by passing --tool :local
2074 This can occur during the merge process, e.g. by passing --tool :local
2075 to resolve a conflict.
2075 to resolve a conflict.
2076 """
2076 """
2077 keys = []
2077 keys = []
2078 # This won't be perfect, but can help performance significantly when
2078 # This won't be perfect, but can help performance significantly when
2079 # using things like remotefilelog.
2079 # using things like remotefilelog.
2080 scmutil.prefetchfiles(
2080 scmutil.prefetchfiles(
2081 self.repo(), [self.p1().rev()],
2081 self.repo(), [self.p1().rev()],
2082 scmutil.matchfiles(self.repo(), self._cache.keys()))
2082 scmutil.matchfiles(self.repo(), self._cache.keys()))
2083
2083
2084 for path in self._cache.keys():
2084 for path in self._cache.keys():
2085 cache = self._cache[path]
2085 cache = self._cache[path]
2086 try:
2086 try:
2087 underlying = self._wrappedctx[path]
2087 underlying = self._wrappedctx[path]
2088 if (underlying.data() == cache['data'] and
2088 if (underlying.data() == cache['data'] and
2089 underlying.flags() == cache['flags']):
2089 underlying.flags() == cache['flags']):
2090 keys.append(path)
2090 keys.append(path)
2091 except error.ManifestLookupError:
2091 except error.ManifestLookupError:
2092 # Path not in the underlying manifest (created).
2092 # Path not in the underlying manifest (created).
2093 continue
2093 continue
2094
2094
2095 for path in keys:
2095 for path in keys:
2096 del self._cache[path]
2096 del self._cache[path]
2097 return keys
2097 return keys
2098
2098
2099 def _markdirty(self, path, exists, data=None, date=None, flags='',
2099 def _markdirty(self, path, exists, data=None, date=None, flags='',
2100 copied=None):
2100 copied=None):
2101 # data not provided, let's see if we already have some; if not, let's
2101 # 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
2102 # grab it from our underlying context, so that we always have data if
2103 # the file is marked as existing.
2103 # the file is marked as existing.
2104 if exists and data is None:
2104 if exists and data is None:
2105 oldentry = self._cache.get(path) or {}
2105 oldentry = self._cache.get(path) or {}
2106 data = oldentry.get('data') or self._wrappedctx[path].data()
2106 data = oldentry.get('data') or self._wrappedctx[path].data()
2107
2107
2108 self._cache[path] = {
2108 self._cache[path] = {
2109 'exists': exists,
2109 'exists': exists,
2110 'data': data,
2110 'data': data,
2111 'date': date,
2111 'date': date,
2112 'flags': flags,
2112 'flags': flags,
2113 'copied': copied,
2113 'copied': copied,
2114 }
2114 }
2115
2115
2116 def filectx(self, path, filelog=None):
2116 def filectx(self, path, filelog=None):
2117 return overlayworkingfilectx(self._repo, path, parent=self,
2117 return overlayworkingfilectx(self._repo, path, parent=self,
2118 filelog=filelog)
2118 filelog=filelog)
2119
2119
2120 class overlayworkingfilectx(committablefilectx):
2120 class overlayworkingfilectx(committablefilectx):
2121 """Wrap a ``workingfilectx`` but intercepts all writes into an in-memory
2121 """Wrap a ``workingfilectx`` but intercepts all writes into an in-memory
2122 cache, which can be flushed through later by calling ``flush()``."""
2122 cache, which can be flushed through later by calling ``flush()``."""
2123
2123
2124 def __init__(self, repo, path, filelog=None, parent=None):
2124 def __init__(self, repo, path, filelog=None, parent=None):
2125 super(overlayworkingfilectx, self).__init__(repo, path, filelog,
2125 super(overlayworkingfilectx, self).__init__(repo, path, filelog,
2126 parent)
2126 parent)
2127 self._repo = repo
2127 self._repo = repo
2128 self._parent = parent
2128 self._parent = parent
2129 self._path = path
2129 self._path = path
2130
2130
2131 def cmp(self, fctx):
2131 def cmp(self, fctx):
2132 return self.data() != fctx.data()
2132 return self.data() != fctx.data()
2133
2133
2134 def changectx(self):
2134 def changectx(self):
2135 return self._parent
2135 return self._parent
2136
2136
2137 def data(self):
2137 def data(self):
2138 return self._parent.data(self._path)
2138 return self._parent.data(self._path)
2139
2139
2140 def date(self):
2140 def date(self):
2141 return self._parent.filedate(self._path)
2141 return self._parent.filedate(self._path)
2142
2142
2143 def exists(self):
2143 def exists(self):
2144 return self.lexists()
2144 return self.lexists()
2145
2145
2146 def lexists(self):
2146 def lexists(self):
2147 return self._parent.exists(self._path)
2147 return self._parent.exists(self._path)
2148
2148
2149 def copysource(self):
2149 def copysource(self):
2150 return self._parent.copydata(self._path)
2150 return self._parent.copydata(self._path)
2151
2151
2152 def size(self):
2152 def size(self):
2153 return self._parent.size(self._path)
2153 return self._parent.size(self._path)
2154
2154
2155 def markcopied(self, origin):
2155 def markcopied(self, origin):
2156 self._parent.markcopied(self._path, origin)
2156 self._parent.markcopied(self._path, origin)
2157
2157
2158 def audit(self):
2158 def audit(self):
2159 pass
2159 pass
2160
2160
2161 def flags(self):
2161 def flags(self):
2162 return self._parent.flags(self._path)
2162 return self._parent.flags(self._path)
2163
2163
2164 def setflags(self, islink, isexec):
2164 def setflags(self, islink, isexec):
2165 return self._parent.setflags(self._path, islink, isexec)
2165 return self._parent.setflags(self._path, islink, isexec)
2166
2166
2167 def write(self, data, flags, backgroundclose=False, **kwargs):
2167 def write(self, data, flags, backgroundclose=False, **kwargs):
2168 return self._parent.write(self._path, data, flags, **kwargs)
2168 return self._parent.write(self._path, data, flags, **kwargs)
2169
2169
2170 def remove(self, ignoremissing=False):
2170 def remove(self, ignoremissing=False):
2171 return self._parent.remove(self._path)
2171 return self._parent.remove(self._path)
2172
2172
2173 def clearunknown(self):
2173 def clearunknown(self):
2174 pass
2174 pass
2175
2175
2176 class workingcommitctx(workingctx):
2176 class workingcommitctx(workingctx):
2177 """A workingcommitctx object makes access to data related to
2177 """A workingcommitctx object makes access to data related to
2178 the revision being committed convenient.
2178 the revision being committed convenient.
2179
2179
2180 This hides changes in the working directory, if they aren't
2180 This hides changes in the working directory, if they aren't
2181 committed in this context.
2181 committed in this context.
2182 """
2182 """
2183 def __init__(self, repo, changes,
2183 def __init__(self, repo, changes,
2184 text="", user=None, date=None, extra=None):
2184 text="", user=None, date=None, extra=None):
2185 super(workingcommitctx, self).__init__(repo, text, user, date, extra,
2185 super(workingcommitctx, self).__init__(repo, text, user, date, extra,
2186 changes)
2186 changes)
2187
2187
2188 def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
2188 def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
2189 """Return matched files only in ``self._status``
2189 """Return matched files only in ``self._status``
2190
2190
2191 Uncommitted files appear "clean" via this context, even if
2191 Uncommitted files appear "clean" via this context, even if
2192 they aren't actually so in the working directory.
2192 they aren't actually so in the working directory.
2193 """
2193 """
2194 if clean:
2194 if clean:
2195 clean = [f for f in self._manifest if f not in self._changedset]
2195 clean = [f for f in self._manifest if f not in self._changedset]
2196 else:
2196 else:
2197 clean = []
2197 clean = []
2198 return scmutil.status([f for f in self._status.modified if match(f)],
2198 return scmutil.status([f for f in self._status.modified if match(f)],
2199 [f for f in self._status.added if match(f)],
2199 [f for f in self._status.added if match(f)],
2200 [f for f in self._status.removed if match(f)],
2200 [f for f in self._status.removed if match(f)],
2201 [], [], [], clean)
2201 [], [], [], clean)
2202
2202
2203 @propertycache
2203 @propertycache
2204 def _changedset(self):
2204 def _changedset(self):
2205 """Return the set of files changed in this context
2205 """Return the set of files changed in this context
2206 """
2206 """
2207 changed = set(self._status.modified)
2207 changed = set(self._status.modified)
2208 changed.update(self._status.added)
2208 changed.update(self._status.added)
2209 changed.update(self._status.removed)
2209 changed.update(self._status.removed)
2210 return changed
2210 return changed
2211
2211
2212 def makecachingfilectxfn(func):
2212 def makecachingfilectxfn(func):
2213 """Create a filectxfn that caches based on the path.
2213 """Create a filectxfn that caches based on the path.
2214
2214
2215 We can't use util.cachefunc because it uses all arguments as the cache
2215 We can't use util.cachefunc because it uses all arguments as the cache
2216 key and this creates a cycle since the arguments include the repo and
2216 key and this creates a cycle since the arguments include the repo and
2217 memctx.
2217 memctx.
2218 """
2218 """
2219 cache = {}
2219 cache = {}
2220
2220
2221 def getfilectx(repo, memctx, path):
2221 def getfilectx(repo, memctx, path):
2222 if path not in cache:
2222 if path not in cache:
2223 cache[path] = func(repo, memctx, path)
2223 cache[path] = func(repo, memctx, path)
2224 return cache[path]
2224 return cache[path]
2225
2225
2226 return getfilectx
2226 return getfilectx
2227
2227
2228 def memfilefromctx(ctx):
2228 def memfilefromctx(ctx):
2229 """Given a context return a memfilectx for ctx[path]
2229 """Given a context return a memfilectx for ctx[path]
2230
2230
2231 This is a convenience method for building a memctx based on another
2231 This is a convenience method for building a memctx based on another
2232 context.
2232 context.
2233 """
2233 """
2234 def getfilectx(repo, memctx, path):
2234 def getfilectx(repo, memctx, path):
2235 fctx = ctx[path]
2235 fctx = ctx[path]
2236 copied = fctx.copysource()
2236 copied = fctx.copysource()
2237 return memfilectx(repo, memctx, path, fctx.data(),
2237 return memfilectx(repo, memctx, path, fctx.data(),
2238 islink=fctx.islink(), isexec=fctx.isexec(),
2238 islink=fctx.islink(), isexec=fctx.isexec(),
2239 copied=copied)
2239 copied=copied)
2240
2240
2241 return getfilectx
2241 return getfilectx
2242
2242
2243 def memfilefrompatch(patchstore):
2243 def memfilefrompatch(patchstore):
2244 """Given a patch (e.g. patchstore object) return a memfilectx
2244 """Given a patch (e.g. patchstore object) return a memfilectx
2245
2245
2246 This is a convenience method for building a memctx based on a patchstore.
2246 This is a convenience method for building a memctx based on a patchstore.
2247 """
2247 """
2248 def getfilectx(repo, memctx, path):
2248 def getfilectx(repo, memctx, path):
2249 data, mode, copied = patchstore.getfile(path)
2249 data, mode, copied = patchstore.getfile(path)
2250 if data is None:
2250 if data is None:
2251 return None
2251 return None
2252 islink, isexec = mode
2252 islink, isexec = mode
2253 return memfilectx(repo, memctx, path, data, islink=islink,
2253 return memfilectx(repo, memctx, path, data, islink=islink,
2254 isexec=isexec, copied=copied)
2254 isexec=isexec, copied=copied)
2255
2255
2256 return getfilectx
2256 return getfilectx
2257
2257
2258 class memctx(committablectx):
2258 class memctx(committablectx):
2259 """Use memctx to perform in-memory commits via localrepo.commitctx().
2259 """Use memctx to perform in-memory commits via localrepo.commitctx().
2260
2260
2261 Revision information is supplied at initialization time while
2261 Revision information is supplied at initialization time while
2262 related files data and is made available through a callback
2262 related files data and is made available through a callback
2263 mechanism. 'repo' is the current localrepo, 'parents' is a
2263 mechanism. 'repo' is the current localrepo, 'parents' is a
2264 sequence of two parent revisions identifiers (pass None for every
2264 sequence of two parent revisions identifiers (pass None for every
2265 missing parent), 'text' is the commit message and 'files' lists
2265 missing parent), 'text' is the commit message and 'files' lists
2266 names of files touched by the revision (normalized and relative to
2266 names of files touched by the revision (normalized and relative to
2267 repository root).
2267 repository root).
2268
2268
2269 filectxfn(repo, memctx, path) is a callable receiving the
2269 filectxfn(repo, memctx, path) is a callable receiving the
2270 repository, the current memctx object and the normalized path of
2270 repository, the current memctx object and the normalized path of
2271 requested file, relative to repository root. It is fired by the
2271 requested file, relative to repository root. It is fired by the
2272 commit function for every file in 'files', but calls order is
2272 commit function for every file in 'files', but calls order is
2273 undefined. If the file is available in the revision being
2273 undefined. If the file is available in the revision being
2274 committed (updated or added), filectxfn returns a memfilectx
2274 committed (updated or added), filectxfn returns a memfilectx
2275 object. If the file was removed, filectxfn return None for recent
2275 object. If the file was removed, filectxfn return None for recent
2276 Mercurial. Moved files are represented by marking the source file
2276 Mercurial. Moved files are represented by marking the source file
2277 removed and the new file added with copy information (see
2277 removed and the new file added with copy information (see
2278 memfilectx).
2278 memfilectx).
2279
2279
2280 user receives the committer name and defaults to current
2280 user receives the committer name and defaults to current
2281 repository username, date is the commit date in any format
2281 repository username, date is the commit date in any format
2282 supported by dateutil.parsedate() and defaults to current date, extra
2282 supported by dateutil.parsedate() and defaults to current date, extra
2283 is a dictionary of metadata or is left empty.
2283 is a dictionary of metadata or is left empty.
2284 """
2284 """
2285
2285
2286 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
2286 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
2287 # Extensions that need to retain compatibility across Mercurial 3.1 can use
2287 # Extensions that need to retain compatibility across Mercurial 3.1 can use
2288 # this field to determine what to do in filectxfn.
2288 # this field to determine what to do in filectxfn.
2289 _returnnoneformissingfiles = True
2289 _returnnoneformissingfiles = True
2290
2290
2291 def __init__(self, repo, parents, text, files, filectxfn, user=None,
2291 def __init__(self, repo, parents, text, files, filectxfn, user=None,
2292 date=None, extra=None, branch=None, editor=False):
2292 date=None, extra=None, branch=None, editor=False):
2293 super(memctx, self).__init__(repo, text, user, date, extra)
2293 super(memctx, self).__init__(repo, text, user, date, extra)
2294 self._rev = None
2294 self._rev = None
2295 self._node = None
2295 self._node = None
2296 parents = [(p or nullid) for p in parents]
2296 parents = [(p or nullid) for p in parents]
2297 p1, p2 = parents
2297 p1, p2 = parents
2298 self._parents = [self._repo[p] for p in (p1, p2)]
2298 self._parents = [self._repo[p] for p in (p1, p2)]
2299 files = sorted(set(files))
2299 files = sorted(set(files))
2300 self._files = files
2300 self._files = files
2301 if branch is not None:
2301 if branch is not None:
2302 self._extra['branch'] = encoding.fromlocal(branch)
2302 self._extra['branch'] = encoding.fromlocal(branch)
2303 self.substate = {}
2303 self.substate = {}
2304
2304
2305 if isinstance(filectxfn, patch.filestore):
2305 if isinstance(filectxfn, patch.filestore):
2306 filectxfn = memfilefrompatch(filectxfn)
2306 filectxfn = memfilefrompatch(filectxfn)
2307 elif not callable(filectxfn):
2307 elif not callable(filectxfn):
2308 # if store is not callable, wrap it in a function
2308 # if store is not callable, wrap it in a function
2309 filectxfn = memfilefromctx(filectxfn)
2309 filectxfn = memfilefromctx(filectxfn)
2310
2310
2311 # memoizing increases performance for e.g. vcs convert scenarios.
2311 # memoizing increases performance for e.g. vcs convert scenarios.
2312 self._filectxfn = makecachingfilectxfn(filectxfn)
2312 self._filectxfn = makecachingfilectxfn(filectxfn)
2313
2313
2314 if editor:
2314 if editor:
2315 self._text = editor(self._repo, self, [])
2315 self._text = editor(self._repo, self, [])
2316 self._repo.savecommitmessage(self._text)
2316 self._repo.savecommitmessage(self._text)
2317
2317
2318 def filectx(self, path, filelog=None):
2318 def filectx(self, path, filelog=None):
2319 """get a file context from the working directory
2319 """get a file context from the working directory
2320
2320
2321 Returns None if file doesn't exist and should be removed."""
2321 Returns None if file doesn't exist and should be removed."""
2322 return self._filectxfn(self._repo, self, path)
2322 return self._filectxfn(self._repo, self, path)
2323
2323
2324 def commit(self):
2324 def commit(self):
2325 """commit context to the repo"""
2325 """commit context to the repo"""
2326 return self._repo.commitctx(self)
2326 return self._repo.commitctx(self)
2327
2327
2328 @propertycache
2328 @propertycache
2329 def _manifest(self):
2329 def _manifest(self):
2330 """generate a manifest based on the return values of filectxfn"""
2330 """generate a manifest based on the return values of filectxfn"""
2331
2331
2332 # keep this simple for now; just worry about p1
2332 # keep this simple for now; just worry about p1
2333 pctx = self._parents[0]
2333 pctx = self._parents[0]
2334 man = pctx.manifest().copy()
2334 man = pctx.manifest().copy()
2335
2335
2336 for f in self._status.modified:
2336 for f in self._status.modified:
2337 man[f] = modifiednodeid
2337 man[f] = modifiednodeid
2338
2338
2339 for f in self._status.added:
2339 for f in self._status.added:
2340 man[f] = addednodeid
2340 man[f] = addednodeid
2341
2341
2342 for f in self._status.removed:
2342 for f in self._status.removed:
2343 if f in man:
2343 if f in man:
2344 del man[f]
2344 del man[f]
2345
2345
2346 return man
2346 return man
2347
2347
2348 @propertycache
2348 @propertycache
2349 def _status(self):
2349 def _status(self):
2350 """Calculate exact status from ``files`` specified at construction
2350 """Calculate exact status from ``files`` specified at construction
2351 """
2351 """
2352 man1 = self.p1().manifest()
2352 man1 = self.p1().manifest()
2353 p2 = self._parents[1]
2353 p2 = self._parents[1]
2354 # "1 < len(self._parents)" can't be used for checking
2354 # "1 < len(self._parents)" can't be used for checking
2355 # existence of the 2nd parent, because "memctx._parents" is
2355 # existence of the 2nd parent, because "memctx._parents" is
2356 # explicitly initialized by the list, of which length is 2.
2356 # explicitly initialized by the list, of which length is 2.
2357 if p2.node() != nullid:
2357 if p2.node() != nullid:
2358 man2 = p2.manifest()
2358 man2 = p2.manifest()
2359 managing = lambda f: f in man1 or f in man2
2359 managing = lambda f: f in man1 or f in man2
2360 else:
2360 else:
2361 managing = lambda f: f in man1
2361 managing = lambda f: f in man1
2362
2362
2363 modified, added, removed = [], [], []
2363 modified, added, removed = [], [], []
2364 for f in self._files:
2364 for f in self._files:
2365 if not managing(f):
2365 if not managing(f):
2366 added.append(f)
2366 added.append(f)
2367 elif self[f]:
2367 elif self[f]:
2368 modified.append(f)
2368 modified.append(f)
2369 else:
2369 else:
2370 removed.append(f)
2370 removed.append(f)
2371
2371
2372 return scmutil.status(modified, added, removed, [], [], [], [])
2372 return scmutil.status(modified, added, removed, [], [], [], [])
2373
2373
2374 class memfilectx(committablefilectx):
2374 class memfilectx(committablefilectx):
2375 """memfilectx represents an in-memory file to commit.
2375 """memfilectx represents an in-memory file to commit.
2376
2376
2377 See memctx and committablefilectx for more details.
2377 See memctx and committablefilectx for more details.
2378 """
2378 """
2379 def __init__(self, repo, changectx, path, data, islink=False,
2379 def __init__(self, repo, changectx, path, data, islink=False,
2380 isexec=False, copied=None):
2380 isexec=False, copied=None):
2381 """
2381 """
2382 path is the normalized file path relative to repository root.
2382 path is the normalized file path relative to repository root.
2383 data is the file content as a string.
2383 data is the file content as a string.
2384 islink is True if the file is a symbolic link.
2384 islink is True if the file is a symbolic link.
2385 isexec is True if the file is executable.
2385 isexec is True if the file is executable.
2386 copied is the source file path if current file was copied in the
2386 copied is the source file path if current file was copied in the
2387 revision being committed, or None."""
2387 revision being committed, or None."""
2388 super(memfilectx, self).__init__(repo, path, None, changectx)
2388 super(memfilectx, self).__init__(repo, path, None, changectx)
2389 self._data = data
2389 self._data = data
2390 if islink:
2390 if islink:
2391 self._flags = 'l'
2391 self._flags = 'l'
2392 elif isexec:
2392 elif isexec:
2393 self._flags = 'x'
2393 self._flags = 'x'
2394 else:
2394 else:
2395 self._flags = ''
2395 self._flags = ''
2396 self._copied = None
2396 self._copied = None
2397 if copied:
2397 if copied:
2398 self._copied = (copied, nullid)
2398 self._copied = (copied, nullid)
2399
2399
2400 def cmp(self, fctx):
2400 def cmp(self, fctx):
2401 return self.data() != fctx.data()
2401 return self.data() != fctx.data()
2402
2402
2403 def data(self):
2403 def data(self):
2404 return self._data
2404 return self._data
2405
2405
2406 def remove(self, ignoremissing=False):
2406 def remove(self, ignoremissing=False):
2407 """wraps unlink for a repo's working directory"""
2407 """wraps unlink for a repo's working directory"""
2408 # need to figure out what to do here
2408 # need to figure out what to do here
2409 del self._changectx[self._path]
2409 del self._changectx[self._path]
2410
2410
2411 def write(self, data, flags, **kwargs):
2411 def write(self, data, flags, **kwargs):
2412 """wraps repo.wwrite"""
2412 """wraps repo.wwrite"""
2413 self._data = data
2413 self._data = data
2414
2414
2415
2415
2416 class metadataonlyctx(committablectx):
2416 class metadataonlyctx(committablectx):
2417 """Like memctx but it's reusing the manifest of different commit.
2417 """Like memctx but it's reusing the manifest of different commit.
2418 Intended to be used by lightweight operations that are creating
2418 Intended to be used by lightweight operations that are creating
2419 metadata-only changes.
2419 metadata-only changes.
2420
2420
2421 Revision information is supplied at initialization time. 'repo' is the
2421 Revision information is supplied at initialization time. 'repo' is the
2422 current localrepo, 'ctx' is original revision which manifest we're reuisng
2422 current localrepo, 'ctx' is original revision which manifest we're reuisng
2423 'parents' is a sequence of two parent revisions identifiers (pass None for
2423 'parents' is a sequence of two parent revisions identifiers (pass None for
2424 every missing parent), 'text' is the commit.
2424 every missing parent), 'text' is the commit.
2425
2425
2426 user receives the committer name and defaults to current repository
2426 user receives the committer name and defaults to current repository
2427 username, date is the commit date in any format supported by
2427 username, date is the commit date in any format supported by
2428 dateutil.parsedate() and defaults to current date, extra is a dictionary of
2428 dateutil.parsedate() and defaults to current date, extra is a dictionary of
2429 metadata or is left empty.
2429 metadata or is left empty.
2430 """
2430 """
2431 def __init__(self, repo, originalctx, parents=None, text=None, user=None,
2431 def __init__(self, repo, originalctx, parents=None, text=None, user=None,
2432 date=None, extra=None, editor=False):
2432 date=None, extra=None, editor=False):
2433 if text is None:
2433 if text is None:
2434 text = originalctx.description()
2434 text = originalctx.description()
2435 super(metadataonlyctx, self).__init__(repo, text, user, date, extra)
2435 super(metadataonlyctx, self).__init__(repo, text, user, date, extra)
2436 self._rev = None
2436 self._rev = None
2437 self._node = None
2437 self._node = None
2438 self._originalctx = originalctx
2438 self._originalctx = originalctx
2439 self._manifestnode = originalctx.manifestnode()
2439 self._manifestnode = originalctx.manifestnode()
2440 if parents is None:
2440 if parents is None:
2441 parents = originalctx.parents()
2441 parents = originalctx.parents()
2442 else:
2442 else:
2443 parents = [repo[p] for p in parents if p is not None]
2443 parents = [repo[p] for p in parents if p is not None]
2444 parents = parents[:]
2444 parents = parents[:]
2445 while len(parents) < 2:
2445 while len(parents) < 2:
2446 parents.append(repo[nullid])
2446 parents.append(repo[nullid])
2447 p1, p2 = self._parents = parents
2447 p1, p2 = self._parents = parents
2448
2448
2449 # sanity check to ensure that the reused manifest parents are
2449 # sanity check to ensure that the reused manifest parents are
2450 # manifests of our commit parents
2450 # manifests of our commit parents
2451 mp1, mp2 = self.manifestctx().parents
2451 mp1, mp2 = self.manifestctx().parents
2452 if p1 != nullid and p1.manifestnode() != mp1:
2452 if p1 != nullid and p1.manifestnode() != mp1:
2453 raise RuntimeError(r"can't reuse the manifest: its p1 "
2453 raise RuntimeError(r"can't reuse the manifest: its p1 "
2454 r"doesn't match the new ctx p1")
2454 r"doesn't match the new ctx p1")
2455 if p2 != nullid and p2.manifestnode() != mp2:
2455 if p2 != nullid and p2.manifestnode() != mp2:
2456 raise RuntimeError(r"can't reuse the manifest: "
2456 raise RuntimeError(r"can't reuse the manifest: "
2457 r"its p2 doesn't match the new ctx p2")
2457 r"its p2 doesn't match the new ctx p2")
2458
2458
2459 self._files = originalctx.files()
2459 self._files = originalctx.files()
2460 self.substate = {}
2460 self.substate = {}
2461
2461
2462 if editor:
2462 if editor:
2463 self._text = editor(self._repo, self, [])
2463 self._text = editor(self._repo, self, [])
2464 self._repo.savecommitmessage(self._text)
2464 self._repo.savecommitmessage(self._text)
2465
2465
2466 def manifestnode(self):
2466 def manifestnode(self):
2467 return self._manifestnode
2467 return self._manifestnode
2468
2468
2469 @property
2469 @property
2470 def _manifestctx(self):
2470 def _manifestctx(self):
2471 return self._repo.manifestlog[self._manifestnode]
2471 return self._repo.manifestlog[self._manifestnode]
2472
2472
2473 def filectx(self, path, filelog=None):
2473 def filectx(self, path, filelog=None):
2474 return self._originalctx.filectx(path, filelog=filelog)
2474 return self._originalctx.filectx(path, filelog=filelog)
2475
2475
2476 def commit(self):
2476 def commit(self):
2477 """commit context to the repo"""
2477 """commit context to the repo"""
2478 return self._repo.commitctx(self)
2478 return self._repo.commitctx(self)
2479
2479
2480 @property
2480 @property
2481 def _manifest(self):
2481 def _manifest(self):
2482 return self._originalctx.manifest()
2482 return self._originalctx.manifest()
2483
2483
2484 @propertycache
2484 @propertycache
2485 def _status(self):
2485 def _status(self):
2486 """Calculate exact status from ``files`` specified in the ``origctx``
2486 """Calculate exact status from ``files`` specified in the ``origctx``
2487 and parents manifests.
2487 and parents manifests.
2488 """
2488 """
2489 man1 = self.p1().manifest()
2489 man1 = self.p1().manifest()
2490 p2 = self._parents[1]
2490 p2 = self._parents[1]
2491 # "1 < len(self._parents)" can't be used for checking
2491 # "1 < len(self._parents)" can't be used for checking
2492 # existence of the 2nd parent, because "metadataonlyctx._parents" is
2492 # existence of the 2nd parent, because "metadataonlyctx._parents" is
2493 # explicitly initialized by the list, of which length is 2.
2493 # explicitly initialized by the list, of which length is 2.
2494 if p2.node() != nullid:
2494 if p2.node() != nullid:
2495 man2 = p2.manifest()
2495 man2 = p2.manifest()
2496 managing = lambda f: f in man1 or f in man2
2496 managing = lambda f: f in man1 or f in man2
2497 else:
2497 else:
2498 managing = lambda f: f in man1
2498 managing = lambda f: f in man1
2499
2499
2500 modified, added, removed = [], [], []
2500 modified, added, removed = [], [], []
2501 for f in self._files:
2501 for f in self._files:
2502 if not managing(f):
2502 if not managing(f):
2503 added.append(f)
2503 added.append(f)
2504 elif f in self:
2504 elif f in self:
2505 modified.append(f)
2505 modified.append(f)
2506 else:
2506 else:
2507 removed.append(f)
2507 removed.append(f)
2508
2508
2509 return scmutil.status(modified, added, removed, [], [], [], [])
2509 return scmutil.status(modified, added, removed, [], [], [], [])
2510
2510
2511 class arbitraryfilectx(object):
2511 class arbitraryfilectx(object):
2512 """Allows you to use filectx-like functions on a file in an arbitrary
2512 """Allows you to use filectx-like functions on a file in an arbitrary
2513 location on disk, possibly not in the working directory.
2513 location on disk, possibly not in the working directory.
2514 """
2514 """
2515 def __init__(self, path, repo=None):
2515 def __init__(self, path, repo=None):
2516 # Repo is optional because contrib/simplemerge uses this class.
2516 # Repo is optional because contrib/simplemerge uses this class.
2517 self._repo = repo
2517 self._repo = repo
2518 self._path = path
2518 self._path = path
2519
2519
2520 def cmp(self, fctx):
2520 def cmp(self, fctx):
2521 # filecmp follows symlinks whereas `cmp` should not, so skip the fast
2521 # filecmp follows symlinks whereas `cmp` should not, so skip the fast
2522 # path if either side is a symlink.
2522 # path if either side is a symlink.
2523 symlinks = ('l' in self.flags() or 'l' in fctx.flags())
2523 symlinks = ('l' in self.flags() or 'l' in fctx.flags())
2524 if not symlinks and isinstance(fctx, workingfilectx) and self._repo:
2524 if not symlinks and isinstance(fctx, workingfilectx) and self._repo:
2525 # Add a fast-path for merge if both sides are disk-backed.
2525 # Add a fast-path for merge if both sides are disk-backed.
2526 # Note that filecmp uses the opposite return values (True if same)
2526 # Note that filecmp uses the opposite return values (True if same)
2527 # from our cmp functions (True if different).
2527 # from our cmp functions (True if different).
2528 return not filecmp.cmp(self.path(), self._repo.wjoin(fctx.path()))
2528 return not filecmp.cmp(self.path(), self._repo.wjoin(fctx.path()))
2529 return self.data() != fctx.data()
2529 return self.data() != fctx.data()
2530
2530
2531 def path(self):
2531 def path(self):
2532 return self._path
2532 return self._path
2533
2533
2534 def flags(self):
2534 def flags(self):
2535 return ''
2535 return ''
2536
2536
2537 def data(self):
2537 def data(self):
2538 return util.readfile(self._path)
2538 return util.readfile(self._path)
2539
2539
2540 def decodeddata(self):
2540 def decodeddata(self):
2541 with open(self._path, "rb") as f:
2541 with open(self._path, "rb") as f:
2542 return f.read()
2542 return f.read()
2543
2543
2544 def remove(self):
2544 def remove(self):
2545 util.unlink(self._path)
2545 util.unlink(self._path)
2546
2546
2547 def write(self, data, flags, **kwargs):
2547 def write(self, data, flags, **kwargs):
2548 assert not flags
2548 assert not flags
2549 with open(self._path, "wb") as f:
2549 with open(self._path, "wb") as f:
2550 f.write(data)
2550 f.write(data)
General Comments 0
You need to be logged in to leave comments. Login now