##// END OF EJS Templates
revlog: delete isdescendantrev() in favor of isancestorrev()...
Martin von Zweigbergk -
r38690:21846c94 default
parent child Browse files
Show More
@@ -1,2545 +1,2545 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 bin,
18 bin,
19 hex,
19 hex,
20 modifiednodeid,
20 modifiednodeid,
21 nullid,
21 nullid,
22 nullrev,
22 nullrev,
23 short,
23 short,
24 wdirfilenodeids,
24 wdirfilenodeids,
25 wdirid,
25 wdirid,
26 )
26 )
27 from . import (
27 from . import (
28 dagop,
28 dagop,
29 encoding,
29 encoding,
30 error,
30 error,
31 fileset,
31 fileset,
32 match as matchmod,
32 match as matchmod,
33 obsolete as obsmod,
33 obsolete as obsmod,
34 patch,
34 patch,
35 pathutil,
35 pathutil,
36 phases,
36 phases,
37 pycompat,
37 pycompat,
38 repoview,
38 repoview,
39 revlog,
39 revlog,
40 scmutil,
40 scmutil,
41 sparse,
41 sparse,
42 subrepo,
42 subrepo,
43 subrepoutil,
43 subrepoutil,
44 util,
44 util,
45 )
45 )
46 from .utils import (
46 from .utils import (
47 dateutil,
47 dateutil,
48 stringutil,
48 stringutil,
49 )
49 )
50
50
51 propertycache = util.propertycache
51 propertycache = util.propertycache
52
52
53 class basectx(object):
53 class basectx(object):
54 """A basectx object represents the common logic for its children:
54 """A basectx object represents the common logic for its children:
55 changectx: read-only context that is already present in the repo,
55 changectx: read-only context that is already present in the repo,
56 workingctx: a context that represents the working directory and can
56 workingctx: a context that represents the working directory and can
57 be committed,
57 be committed,
58 memctx: a context that represents changes in-memory and can also
58 memctx: a context that represents changes in-memory and can also
59 be committed."""
59 be committed."""
60
60
61 def __init__(self, repo):
61 def __init__(self, repo):
62 self._repo = repo
62 self._repo = repo
63
63
64 def __bytes__(self):
64 def __bytes__(self):
65 return short(self.node())
65 return short(self.node())
66
66
67 __str__ = encoding.strmethod(__bytes__)
67 __str__ = encoding.strmethod(__bytes__)
68
68
69 def __repr__(self):
69 def __repr__(self):
70 return r"<%s %s>" % (type(self).__name__, str(self))
70 return r"<%s %s>" % (type(self).__name__, str(self))
71
71
72 def __eq__(self, other):
72 def __eq__(self, other):
73 try:
73 try:
74 return type(self) == type(other) and self._rev == other._rev
74 return type(self) == type(other) and self._rev == other._rev
75 except AttributeError:
75 except AttributeError:
76 return False
76 return False
77
77
78 def __ne__(self, other):
78 def __ne__(self, other):
79 return not (self == other)
79 return not (self == other)
80
80
81 def __contains__(self, key):
81 def __contains__(self, key):
82 return key in self._manifest
82 return key in self._manifest
83
83
84 def __getitem__(self, key):
84 def __getitem__(self, key):
85 return self.filectx(key)
85 return self.filectx(key)
86
86
87 def __iter__(self):
87 def __iter__(self):
88 return iter(self._manifest)
88 return iter(self._manifest)
89
89
90 def _buildstatusmanifest(self, status):
90 def _buildstatusmanifest(self, status):
91 """Builds a manifest that includes the given status results, if this is
91 """Builds a manifest that includes the given status results, if this is
92 a working copy context. For non-working copy contexts, it just returns
92 a working copy context. For non-working copy contexts, it just returns
93 the normal manifest."""
93 the normal manifest."""
94 return self.manifest()
94 return self.manifest()
95
95
96 def _matchstatus(self, other, match):
96 def _matchstatus(self, other, match):
97 """This internal method provides a way for child objects to override the
97 """This internal method provides a way for child objects to override the
98 match operator.
98 match operator.
99 """
99 """
100 return match
100 return match
101
101
102 def _buildstatus(self, other, s, match, listignored, listclean,
102 def _buildstatus(self, other, s, match, listignored, listclean,
103 listunknown):
103 listunknown):
104 """build a status with respect to another context"""
104 """build a status with respect to another context"""
105 # Load earliest manifest first for caching reasons. More specifically,
105 # Load earliest manifest first for caching reasons. More specifically,
106 # if you have revisions 1000 and 1001, 1001 is probably stored as a
106 # if you have revisions 1000 and 1001, 1001 is probably stored as a
107 # delta against 1000. Thus, if you read 1000 first, we'll reconstruct
107 # delta against 1000. Thus, if you read 1000 first, we'll reconstruct
108 # 1000 and cache it so that when you read 1001, we just need to apply a
108 # 1000 and cache it so that when you read 1001, we just need to apply a
109 # delta to what's in the cache. So that's one full reconstruction + one
109 # delta to what's in the cache. So that's one full reconstruction + one
110 # delta application.
110 # delta application.
111 mf2 = None
111 mf2 = None
112 if self.rev() is not None and self.rev() < other.rev():
112 if self.rev() is not None and self.rev() < other.rev():
113 mf2 = self._buildstatusmanifest(s)
113 mf2 = self._buildstatusmanifest(s)
114 mf1 = other._buildstatusmanifest(s)
114 mf1 = other._buildstatusmanifest(s)
115 if mf2 is None:
115 if mf2 is None:
116 mf2 = self._buildstatusmanifest(s)
116 mf2 = self._buildstatusmanifest(s)
117
117
118 modified, added = [], []
118 modified, added = [], []
119 removed = []
119 removed = []
120 clean = []
120 clean = []
121 deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
121 deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
122 deletedset = set(deleted)
122 deletedset = set(deleted)
123 d = mf1.diff(mf2, match=match, clean=listclean)
123 d = mf1.diff(mf2, match=match, clean=listclean)
124 for fn, value in d.iteritems():
124 for fn, value in d.iteritems():
125 if fn in deletedset:
125 if fn in deletedset:
126 continue
126 continue
127 if value is None:
127 if value is None:
128 clean.append(fn)
128 clean.append(fn)
129 continue
129 continue
130 (node1, flag1), (node2, flag2) = value
130 (node1, flag1), (node2, flag2) = value
131 if node1 is None:
131 if node1 is None:
132 added.append(fn)
132 added.append(fn)
133 elif node2 is None:
133 elif node2 is None:
134 removed.append(fn)
134 removed.append(fn)
135 elif flag1 != flag2:
135 elif flag1 != flag2:
136 modified.append(fn)
136 modified.append(fn)
137 elif node2 not in wdirfilenodeids:
137 elif node2 not in wdirfilenodeids:
138 # When comparing files between two commits, we save time by
138 # When comparing files between two commits, we save time by
139 # not comparing the file contents when the nodeids differ.
139 # not comparing the file contents when the nodeids differ.
140 # Note that this means we incorrectly report a reverted change
140 # Note that this means we incorrectly report a reverted change
141 # to a file as a modification.
141 # to a file as a modification.
142 modified.append(fn)
142 modified.append(fn)
143 elif self[fn].cmp(other[fn]):
143 elif self[fn].cmp(other[fn]):
144 modified.append(fn)
144 modified.append(fn)
145 else:
145 else:
146 clean.append(fn)
146 clean.append(fn)
147
147
148 if removed:
148 if removed:
149 # need to filter files if they are already reported as removed
149 # need to filter files if they are already reported as removed
150 unknown = [fn for fn in unknown if fn not in mf1 and
150 unknown = [fn for fn in unknown if fn not in mf1 and
151 (not match or match(fn))]
151 (not match or match(fn))]
152 ignored = [fn for fn in ignored if fn not in mf1 and
152 ignored = [fn for fn in ignored if fn not in mf1 and
153 (not match or match(fn))]
153 (not match or match(fn))]
154 # if they're deleted, don't report them as removed
154 # if they're deleted, don't report them as removed
155 removed = [fn for fn in removed if fn not in deletedset]
155 removed = [fn for fn in removed if fn not in deletedset]
156
156
157 return scmutil.status(modified, added, removed, deleted, unknown,
157 return scmutil.status(modified, added, removed, deleted, unknown,
158 ignored, clean)
158 ignored, clean)
159
159
160 @propertycache
160 @propertycache
161 def substate(self):
161 def substate(self):
162 return subrepoutil.state(self, self._repo.ui)
162 return subrepoutil.state(self, self._repo.ui)
163
163
164 def subrev(self, subpath):
164 def subrev(self, subpath):
165 return self.substate[subpath][1]
165 return self.substate[subpath][1]
166
166
167 def rev(self):
167 def rev(self):
168 return self._rev
168 return self._rev
169 def node(self):
169 def node(self):
170 return self._node
170 return self._node
171 def hex(self):
171 def hex(self):
172 return hex(self.node())
172 return hex(self.node())
173 def manifest(self):
173 def manifest(self):
174 return self._manifest
174 return self._manifest
175 def manifestctx(self):
175 def manifestctx(self):
176 return self._manifestctx
176 return self._manifestctx
177 def repo(self):
177 def repo(self):
178 return self._repo
178 return self._repo
179 def phasestr(self):
179 def phasestr(self):
180 return phases.phasenames[self.phase()]
180 return phases.phasenames[self.phase()]
181 def mutable(self):
181 def mutable(self):
182 return self.phase() > phases.public
182 return self.phase() > phases.public
183
183
184 def matchfileset(self, expr, badfn=None):
184 def matchfileset(self, expr, badfn=None):
185 return fileset.match(self, expr, badfn=badfn)
185 return fileset.match(self, expr, badfn=badfn)
186
186
187 def obsolete(self):
187 def obsolete(self):
188 """True if the changeset is obsolete"""
188 """True if the changeset is obsolete"""
189 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
189 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
190
190
191 def extinct(self):
191 def extinct(self):
192 """True if the changeset is extinct"""
192 """True if the changeset is extinct"""
193 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
193 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
194
194
195 def orphan(self):
195 def orphan(self):
196 """True if the changeset is not obsolete but it's ancestor are"""
196 """True if the changeset is not obsolete but it's ancestor are"""
197 return self.rev() in obsmod.getrevs(self._repo, 'orphan')
197 return self.rev() in obsmod.getrevs(self._repo, 'orphan')
198
198
199 def phasedivergent(self):
199 def phasedivergent(self):
200 """True if the changeset try to be a successor of a public changeset
200 """True if the changeset try to be a successor of a public changeset
201
201
202 Only non-public and non-obsolete changesets may be bumped.
202 Only non-public and non-obsolete changesets may be bumped.
203 """
203 """
204 return self.rev() in obsmod.getrevs(self._repo, 'phasedivergent')
204 return self.rev() in obsmod.getrevs(self._repo, 'phasedivergent')
205
205
206 def contentdivergent(self):
206 def contentdivergent(self):
207 """Is a successors of a changeset with multiple possible successors set
207 """Is a successors of a changeset with multiple possible successors set
208
208
209 Only non-public and non-obsolete changesets may be divergent.
209 Only non-public and non-obsolete changesets may be divergent.
210 """
210 """
211 return self.rev() in obsmod.getrevs(self._repo, 'contentdivergent')
211 return self.rev() in obsmod.getrevs(self._repo, 'contentdivergent')
212
212
213 def isunstable(self):
213 def isunstable(self):
214 """True if the changeset is either unstable, bumped or divergent"""
214 """True if the changeset is either unstable, bumped or divergent"""
215 return self.orphan() or self.phasedivergent() or self.contentdivergent()
215 return self.orphan() or self.phasedivergent() or self.contentdivergent()
216
216
217 def instabilities(self):
217 def instabilities(self):
218 """return the list of instabilities affecting this changeset.
218 """return the list of instabilities affecting this changeset.
219
219
220 Instabilities are returned as strings. possible values are:
220 Instabilities are returned as strings. possible values are:
221 - orphan,
221 - orphan,
222 - phase-divergent,
222 - phase-divergent,
223 - content-divergent.
223 - content-divergent.
224 """
224 """
225 instabilities = []
225 instabilities = []
226 if self.orphan():
226 if self.orphan():
227 instabilities.append('orphan')
227 instabilities.append('orphan')
228 if self.phasedivergent():
228 if self.phasedivergent():
229 instabilities.append('phase-divergent')
229 instabilities.append('phase-divergent')
230 if self.contentdivergent():
230 if self.contentdivergent():
231 instabilities.append('content-divergent')
231 instabilities.append('content-divergent')
232 return instabilities
232 return instabilities
233
233
234 def parents(self):
234 def parents(self):
235 """return contexts for each parent changeset"""
235 """return contexts for each parent changeset"""
236 return self._parents
236 return self._parents
237
237
238 def p1(self):
238 def p1(self):
239 return self._parents[0]
239 return self._parents[0]
240
240
241 def p2(self):
241 def p2(self):
242 parents = self._parents
242 parents = self._parents
243 if len(parents) == 2:
243 if len(parents) == 2:
244 return parents[1]
244 return parents[1]
245 return changectx(self._repo, nullrev)
245 return changectx(self._repo, nullrev)
246
246
247 def _fileinfo(self, path):
247 def _fileinfo(self, path):
248 if r'_manifest' in self.__dict__:
248 if r'_manifest' in self.__dict__:
249 try:
249 try:
250 return self._manifest[path], self._manifest.flags(path)
250 return self._manifest[path], self._manifest.flags(path)
251 except KeyError:
251 except KeyError:
252 raise error.ManifestLookupError(self._node, path,
252 raise error.ManifestLookupError(self._node, path,
253 _('not found in manifest'))
253 _('not found in manifest'))
254 if r'_manifestdelta' in self.__dict__ or path in self.files():
254 if r'_manifestdelta' in self.__dict__ or path in self.files():
255 if path in self._manifestdelta:
255 if path in self._manifestdelta:
256 return (self._manifestdelta[path],
256 return (self._manifestdelta[path],
257 self._manifestdelta.flags(path))
257 self._manifestdelta.flags(path))
258 mfl = self._repo.manifestlog
258 mfl = self._repo.manifestlog
259 try:
259 try:
260 node, flag = mfl[self._changeset.manifest].find(path)
260 node, flag = mfl[self._changeset.manifest].find(path)
261 except KeyError:
261 except KeyError:
262 raise error.ManifestLookupError(self._node, path,
262 raise error.ManifestLookupError(self._node, path,
263 _('not found in manifest'))
263 _('not found in manifest'))
264
264
265 return node, flag
265 return node, flag
266
266
267 def filenode(self, path):
267 def filenode(self, path):
268 return self._fileinfo(path)[0]
268 return self._fileinfo(path)[0]
269
269
270 def flags(self, path):
270 def flags(self, path):
271 try:
271 try:
272 return self._fileinfo(path)[1]
272 return self._fileinfo(path)[1]
273 except error.LookupError:
273 except error.LookupError:
274 return ''
274 return ''
275
275
276 def sub(self, path, allowcreate=True):
276 def sub(self, path, allowcreate=True):
277 '''return a subrepo for the stored revision of path, never wdir()'''
277 '''return a subrepo for the stored revision of path, never wdir()'''
278 return subrepo.subrepo(self, path, allowcreate=allowcreate)
278 return subrepo.subrepo(self, path, allowcreate=allowcreate)
279
279
280 def nullsub(self, path, pctx):
280 def nullsub(self, path, pctx):
281 return subrepo.nullsubrepo(self, path, pctx)
281 return subrepo.nullsubrepo(self, path, pctx)
282
282
283 def workingsub(self, path):
283 def workingsub(self, path):
284 '''return a subrepo for the stored revision, or wdir if this is a wdir
284 '''return a subrepo for the stored revision, or wdir if this is a wdir
285 context.
285 context.
286 '''
286 '''
287 return subrepo.subrepo(self, path, allowwdir=True)
287 return subrepo.subrepo(self, path, allowwdir=True)
288
288
289 def match(self, pats=None, include=None, exclude=None, default='glob',
289 def match(self, pats=None, include=None, exclude=None, default='glob',
290 listsubrepos=False, badfn=None):
290 listsubrepos=False, badfn=None):
291 r = self._repo
291 r = self._repo
292 return matchmod.match(r.root, r.getcwd(), pats,
292 return matchmod.match(r.root, r.getcwd(), pats,
293 include, exclude, default,
293 include, exclude, default,
294 auditor=r.nofsauditor, ctx=self,
294 auditor=r.nofsauditor, ctx=self,
295 listsubrepos=listsubrepos, badfn=badfn)
295 listsubrepos=listsubrepos, badfn=badfn)
296
296
297 def diff(self, ctx2=None, match=None, changes=None, opts=None,
297 def diff(self, ctx2=None, match=None, changes=None, opts=None,
298 losedatafn=None, prefix='', relroot='', copy=None,
298 losedatafn=None, prefix='', relroot='', copy=None,
299 hunksfilterfn=None):
299 hunksfilterfn=None):
300 """Returns a diff generator for the given contexts and matcher"""
300 """Returns a diff generator for the given contexts and matcher"""
301 if ctx2 is None:
301 if ctx2 is None:
302 ctx2 = self.p1()
302 ctx2 = self.p1()
303 if ctx2 is not None:
303 if ctx2 is not None:
304 ctx2 = self._repo[ctx2]
304 ctx2 = self._repo[ctx2]
305 return patch.diff(self._repo, ctx2, self, match=match, changes=changes,
305 return patch.diff(self._repo, ctx2, self, match=match, changes=changes,
306 opts=opts, losedatafn=losedatafn, prefix=prefix,
306 opts=opts, losedatafn=losedatafn, prefix=prefix,
307 relroot=relroot, copy=copy,
307 relroot=relroot, copy=copy,
308 hunksfilterfn=hunksfilterfn)
308 hunksfilterfn=hunksfilterfn)
309
309
310 def dirs(self):
310 def dirs(self):
311 return self._manifest.dirs()
311 return self._manifest.dirs()
312
312
313 def hasdir(self, dir):
313 def hasdir(self, dir):
314 return self._manifest.hasdir(dir)
314 return self._manifest.hasdir(dir)
315
315
316 def status(self, other=None, match=None, listignored=False,
316 def status(self, other=None, match=None, listignored=False,
317 listclean=False, listunknown=False, listsubrepos=False):
317 listclean=False, listunknown=False, listsubrepos=False):
318 """return status of files between two nodes or node and working
318 """return status of files between two nodes or node and working
319 directory.
319 directory.
320
320
321 If other is None, compare this node with working directory.
321 If other is None, compare this node with working directory.
322
322
323 returns (modified, added, removed, deleted, unknown, ignored, clean)
323 returns (modified, added, removed, deleted, unknown, ignored, clean)
324 """
324 """
325
325
326 ctx1 = self
326 ctx1 = self
327 ctx2 = self._repo[other]
327 ctx2 = self._repo[other]
328
328
329 # This next code block is, admittedly, fragile logic that tests for
329 # This next code block is, admittedly, fragile logic that tests for
330 # reversing the contexts and wouldn't need to exist if it weren't for
330 # reversing the contexts and wouldn't need to exist if it weren't for
331 # the fast (and common) code path of comparing the working directory
331 # the fast (and common) code path of comparing the working directory
332 # with its first parent.
332 # with its first parent.
333 #
333 #
334 # What we're aiming for here is the ability to call:
334 # What we're aiming for here is the ability to call:
335 #
335 #
336 # workingctx.status(parentctx)
336 # workingctx.status(parentctx)
337 #
337 #
338 # If we always built the manifest for each context and compared those,
338 # If we always built the manifest for each context and compared those,
339 # then we'd be done. But the special case of the above call means we
339 # then we'd be done. But the special case of the above call means we
340 # just copy the manifest of the parent.
340 # just copy the manifest of the parent.
341 reversed = False
341 reversed = False
342 if (not isinstance(ctx1, changectx)
342 if (not isinstance(ctx1, changectx)
343 and isinstance(ctx2, changectx)):
343 and isinstance(ctx2, changectx)):
344 reversed = True
344 reversed = True
345 ctx1, ctx2 = ctx2, ctx1
345 ctx1, ctx2 = ctx2, ctx1
346
346
347 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
347 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
348 match = ctx2._matchstatus(ctx1, match)
348 match = ctx2._matchstatus(ctx1, match)
349 r = scmutil.status([], [], [], [], [], [], [])
349 r = scmutil.status([], [], [], [], [], [], [])
350 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
350 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
351 listunknown)
351 listunknown)
352
352
353 if reversed:
353 if reversed:
354 # Reverse added and removed. Clear deleted, unknown and ignored as
354 # Reverse added and removed. Clear deleted, unknown and ignored as
355 # these make no sense to reverse.
355 # these make no sense to reverse.
356 r = scmutil.status(r.modified, r.removed, r.added, [], [], [],
356 r = scmutil.status(r.modified, r.removed, r.added, [], [], [],
357 r.clean)
357 r.clean)
358
358
359 if listsubrepos:
359 if listsubrepos:
360 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
360 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
361 try:
361 try:
362 rev2 = ctx2.subrev(subpath)
362 rev2 = ctx2.subrev(subpath)
363 except KeyError:
363 except KeyError:
364 # A subrepo that existed in node1 was deleted between
364 # A subrepo that existed in node1 was deleted between
365 # node1 and node2 (inclusive). Thus, ctx2's substate
365 # node1 and node2 (inclusive). Thus, ctx2's substate
366 # won't contain that subpath. The best we can do ignore it.
366 # won't contain that subpath. The best we can do ignore it.
367 rev2 = None
367 rev2 = None
368 submatch = matchmod.subdirmatcher(subpath, match)
368 submatch = matchmod.subdirmatcher(subpath, match)
369 s = sub.status(rev2, match=submatch, ignored=listignored,
369 s = sub.status(rev2, match=submatch, ignored=listignored,
370 clean=listclean, unknown=listunknown,
370 clean=listclean, unknown=listunknown,
371 listsubrepos=True)
371 listsubrepos=True)
372 for rfiles, sfiles in zip(r, s):
372 for rfiles, sfiles in zip(r, s):
373 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
373 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
374
374
375 for l in r:
375 for l in r:
376 l.sort()
376 l.sort()
377
377
378 return r
378 return r
379
379
380 class changectx(basectx):
380 class changectx(basectx):
381 """A changecontext object makes access to data related to a particular
381 """A changecontext object makes access to data related to a particular
382 changeset convenient. It represents a read-only context already present in
382 changeset convenient. It represents a read-only context already present in
383 the repo."""
383 the repo."""
384 def __init__(self, repo, changeid='.'):
384 def __init__(self, repo, changeid='.'):
385 """changeid is a revision number, node, or tag"""
385 """changeid is a revision number, node, or tag"""
386 super(changectx, self).__init__(repo)
386 super(changectx, self).__init__(repo)
387
387
388 try:
388 try:
389 if isinstance(changeid, int):
389 if isinstance(changeid, int):
390 self._node = repo.changelog.node(changeid)
390 self._node = repo.changelog.node(changeid)
391 self._rev = changeid
391 self._rev = changeid
392 return
392 return
393 elif changeid == 'null':
393 elif changeid == 'null':
394 self._node = nullid
394 self._node = nullid
395 self._rev = nullrev
395 self._rev = nullrev
396 return
396 return
397 elif changeid == 'tip':
397 elif changeid == 'tip':
398 self._node = repo.changelog.tip()
398 self._node = repo.changelog.tip()
399 self._rev = repo.changelog.rev(self._node)
399 self._rev = repo.changelog.rev(self._node)
400 return
400 return
401 elif (changeid == '.'
401 elif (changeid == '.'
402 or repo.local() and changeid == repo.dirstate.p1()):
402 or repo.local() and changeid == repo.dirstate.p1()):
403 # this is a hack to delay/avoid loading obsmarkers
403 # this is a hack to delay/avoid loading obsmarkers
404 # when we know that '.' won't be hidden
404 # when we know that '.' won't be hidden
405 self._node = repo.dirstate.p1()
405 self._node = repo.dirstate.p1()
406 self._rev = repo.unfiltered().changelog.rev(self._node)
406 self._rev = repo.unfiltered().changelog.rev(self._node)
407 return
407 return
408 elif len(changeid) == 20:
408 elif len(changeid) == 20:
409 try:
409 try:
410 self._node = changeid
410 self._node = changeid
411 self._rev = repo.changelog.rev(changeid)
411 self._rev = repo.changelog.rev(changeid)
412 return
412 return
413 except error.FilteredLookupError:
413 except error.FilteredLookupError:
414 raise
414 raise
415 except LookupError:
415 except LookupError:
416 # check if it might have come from damaged dirstate
416 # check if it might have come from damaged dirstate
417 #
417 #
418 # XXX we could avoid the unfiltered if we had a recognizable
418 # XXX we could avoid the unfiltered if we had a recognizable
419 # exception for filtered changeset access
419 # exception for filtered changeset access
420 if (repo.local()
420 if (repo.local()
421 and changeid in repo.unfiltered().dirstate.parents()):
421 and changeid in repo.unfiltered().dirstate.parents()):
422 msg = _("working directory has unknown parent '%s'!")
422 msg = _("working directory has unknown parent '%s'!")
423 raise error.Abort(msg % short(changeid))
423 raise error.Abort(msg % short(changeid))
424 changeid = hex(changeid) # for the error message
424 changeid = hex(changeid) # for the error message
425
425
426 elif len(changeid) == 40:
426 elif len(changeid) == 40:
427 try:
427 try:
428 self._node = bin(changeid)
428 self._node = bin(changeid)
429 self._rev = repo.changelog.rev(self._node)
429 self._rev = repo.changelog.rev(self._node)
430 return
430 return
431 except error.FilteredLookupError:
431 except error.FilteredLookupError:
432 raise
432 raise
433 except (TypeError, LookupError):
433 except (TypeError, LookupError):
434 pass
434 pass
435 else:
435 else:
436 raise error.ProgrammingError(
436 raise error.ProgrammingError(
437 "unsupported changeid '%s' of type %s" %
437 "unsupported changeid '%s' of type %s" %
438 (changeid, type(changeid)))
438 (changeid, type(changeid)))
439
439
440 # lookup failed
440 # lookup failed
441 except (error.FilteredIndexError, error.FilteredLookupError):
441 except (error.FilteredIndexError, error.FilteredLookupError):
442 raise error.FilteredRepoLookupError(_("filtered revision '%s'")
442 raise error.FilteredRepoLookupError(_("filtered revision '%s'")
443 % pycompat.bytestr(changeid))
443 % pycompat.bytestr(changeid))
444 except error.FilteredRepoLookupError:
444 except error.FilteredRepoLookupError:
445 raise
445 raise
446 except IndexError:
446 except IndexError:
447 pass
447 pass
448 raise error.RepoLookupError(
448 raise error.RepoLookupError(
449 _("unknown revision '%s'") % changeid)
449 _("unknown revision '%s'") % changeid)
450
450
451 def __hash__(self):
451 def __hash__(self):
452 try:
452 try:
453 return hash(self._rev)
453 return hash(self._rev)
454 except AttributeError:
454 except AttributeError:
455 return id(self)
455 return id(self)
456
456
457 def __nonzero__(self):
457 def __nonzero__(self):
458 return self._rev != nullrev
458 return self._rev != nullrev
459
459
460 __bool__ = __nonzero__
460 __bool__ = __nonzero__
461
461
462 @propertycache
462 @propertycache
463 def _changeset(self):
463 def _changeset(self):
464 return self._repo.changelog.changelogrevision(self.rev())
464 return self._repo.changelog.changelogrevision(self.rev())
465
465
466 @propertycache
466 @propertycache
467 def _manifest(self):
467 def _manifest(self):
468 return self._manifestctx.read()
468 return self._manifestctx.read()
469
469
470 @property
470 @property
471 def _manifestctx(self):
471 def _manifestctx(self):
472 return self._repo.manifestlog[self._changeset.manifest]
472 return self._repo.manifestlog[self._changeset.manifest]
473
473
474 @propertycache
474 @propertycache
475 def _manifestdelta(self):
475 def _manifestdelta(self):
476 return self._manifestctx.readdelta()
476 return self._manifestctx.readdelta()
477
477
478 @propertycache
478 @propertycache
479 def _parents(self):
479 def _parents(self):
480 repo = self._repo
480 repo = self._repo
481 p1, p2 = repo.changelog.parentrevs(self._rev)
481 p1, p2 = repo.changelog.parentrevs(self._rev)
482 if p2 == nullrev:
482 if p2 == nullrev:
483 return [changectx(repo, p1)]
483 return [changectx(repo, p1)]
484 return [changectx(repo, p1), changectx(repo, p2)]
484 return [changectx(repo, p1), changectx(repo, p2)]
485
485
486 def changeset(self):
486 def changeset(self):
487 c = self._changeset
487 c = self._changeset
488 return (
488 return (
489 c.manifest,
489 c.manifest,
490 c.user,
490 c.user,
491 c.date,
491 c.date,
492 c.files,
492 c.files,
493 c.description,
493 c.description,
494 c.extra,
494 c.extra,
495 )
495 )
496 def manifestnode(self):
496 def manifestnode(self):
497 return self._changeset.manifest
497 return self._changeset.manifest
498
498
499 def user(self):
499 def user(self):
500 return self._changeset.user
500 return self._changeset.user
501 def date(self):
501 def date(self):
502 return self._changeset.date
502 return self._changeset.date
503 def files(self):
503 def files(self):
504 return self._changeset.files
504 return self._changeset.files
505 def description(self):
505 def description(self):
506 return self._changeset.description
506 return self._changeset.description
507 def branch(self):
507 def branch(self):
508 return encoding.tolocal(self._changeset.extra.get("branch"))
508 return encoding.tolocal(self._changeset.extra.get("branch"))
509 def closesbranch(self):
509 def closesbranch(self):
510 return 'close' in self._changeset.extra
510 return 'close' in self._changeset.extra
511 def extra(self):
511 def extra(self):
512 """Return a dict of extra information."""
512 """Return a dict of extra information."""
513 return self._changeset.extra
513 return self._changeset.extra
514 def tags(self):
514 def tags(self):
515 """Return a list of byte tag names"""
515 """Return a list of byte tag names"""
516 return self._repo.nodetags(self._node)
516 return self._repo.nodetags(self._node)
517 def bookmarks(self):
517 def bookmarks(self):
518 """Return a list of byte bookmark names."""
518 """Return a list of byte bookmark names."""
519 return self._repo.nodebookmarks(self._node)
519 return self._repo.nodebookmarks(self._node)
520 def phase(self):
520 def phase(self):
521 return self._repo._phasecache.phase(self._repo, self._rev)
521 return self._repo._phasecache.phase(self._repo, self._rev)
522 def hidden(self):
522 def hidden(self):
523 return self._rev in repoview.filterrevs(self._repo, 'visible')
523 return self._rev in repoview.filterrevs(self._repo, 'visible')
524
524
525 def isinmemory(self):
525 def isinmemory(self):
526 return False
526 return False
527
527
528 def children(self):
528 def children(self):
529 """return list of changectx contexts for each child changeset.
529 """return list of changectx contexts for each child changeset.
530
530
531 This returns only the immediate child changesets. Use descendants() to
531 This returns only the immediate child changesets. Use descendants() to
532 recursively walk children.
532 recursively walk children.
533 """
533 """
534 c = self._repo.changelog.children(self._node)
534 c = self._repo.changelog.children(self._node)
535 return [changectx(self._repo, x) for x in c]
535 return [changectx(self._repo, x) for x in c]
536
536
537 def ancestors(self):
537 def ancestors(self):
538 for a in self._repo.changelog.ancestors([self._rev]):
538 for a in self._repo.changelog.ancestors([self._rev]):
539 yield changectx(self._repo, a)
539 yield changectx(self._repo, a)
540
540
541 def descendants(self):
541 def descendants(self):
542 """Recursively yield all children of the changeset.
542 """Recursively yield all children of the changeset.
543
543
544 For just the immediate children, use children()
544 For just the immediate children, use children()
545 """
545 """
546 for d in self._repo.changelog.descendants([self._rev]):
546 for d in self._repo.changelog.descendants([self._rev]):
547 yield changectx(self._repo, d)
547 yield changectx(self._repo, d)
548
548
549 def filectx(self, path, fileid=None, filelog=None):
549 def filectx(self, path, fileid=None, filelog=None):
550 """get a file context from this changeset"""
550 """get a file context from this changeset"""
551 if fileid is None:
551 if fileid is None:
552 fileid = self.filenode(path)
552 fileid = self.filenode(path)
553 return filectx(self._repo, path, fileid=fileid,
553 return filectx(self._repo, path, fileid=fileid,
554 changectx=self, filelog=filelog)
554 changectx=self, filelog=filelog)
555
555
556 def ancestor(self, c2, warn=False):
556 def ancestor(self, c2, warn=False):
557 """return the "best" ancestor context of self and c2
557 """return the "best" ancestor context of self and c2
558
558
559 If there are multiple candidates, it will show a message and check
559 If there are multiple candidates, it will show a message and check
560 merge.preferancestor configuration before falling back to the
560 merge.preferancestor configuration before falling back to the
561 revlog ancestor."""
561 revlog ancestor."""
562 # deal with workingctxs
562 # deal with workingctxs
563 n2 = c2._node
563 n2 = c2._node
564 if n2 is None:
564 if n2 is None:
565 n2 = c2._parents[0]._node
565 n2 = c2._parents[0]._node
566 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
566 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
567 if not cahs:
567 if not cahs:
568 anc = nullid
568 anc = nullid
569 elif len(cahs) == 1:
569 elif len(cahs) == 1:
570 anc = cahs[0]
570 anc = cahs[0]
571 else:
571 else:
572 # experimental config: merge.preferancestor
572 # experimental config: merge.preferancestor
573 for r in self._repo.ui.configlist('merge', 'preferancestor'):
573 for r in self._repo.ui.configlist('merge', 'preferancestor'):
574 try:
574 try:
575 ctx = scmutil.revsymbol(self._repo, r)
575 ctx = scmutil.revsymbol(self._repo, r)
576 except error.RepoLookupError:
576 except error.RepoLookupError:
577 continue
577 continue
578 anc = ctx.node()
578 anc = ctx.node()
579 if anc in cahs:
579 if anc in cahs:
580 break
580 break
581 else:
581 else:
582 anc = self._repo.changelog.ancestor(self._node, n2)
582 anc = self._repo.changelog.ancestor(self._node, n2)
583 if warn:
583 if warn:
584 self._repo.ui.status(
584 self._repo.ui.status(
585 (_("note: using %s as ancestor of %s and %s\n") %
585 (_("note: using %s as ancestor of %s and %s\n") %
586 (short(anc), short(self._node), short(n2))) +
586 (short(anc), short(self._node), short(n2))) +
587 ''.join(_(" alternatively, use --config "
587 ''.join(_(" alternatively, use --config "
588 "merge.preferancestor=%s\n") %
588 "merge.preferancestor=%s\n") %
589 short(n) for n in sorted(cahs) if n != anc))
589 short(n) for n in sorted(cahs) if n != anc))
590 return changectx(self._repo, anc)
590 return changectx(self._repo, anc)
591
591
592 def descendant(self, other):
592 def descendant(self, other):
593 """True if other is descendant of this changeset"""
593 """True if other is descendant of this changeset"""
594 return self._repo.changelog.isdescendantrev(other._rev, self._rev)
594 return self._repo.changelog.isancestorrev(self._rev, other._rev)
595
595
596 def walk(self, match):
596 def walk(self, match):
597 '''Generates matching file names.'''
597 '''Generates matching file names.'''
598
598
599 # Wrap match.bad method to have message with nodeid
599 # Wrap match.bad method to have message with nodeid
600 def bad(fn, msg):
600 def bad(fn, msg):
601 # The manifest doesn't know about subrepos, so don't complain about
601 # The manifest doesn't know about subrepos, so don't complain about
602 # paths into valid subrepos.
602 # paths into valid subrepos.
603 if any(fn == s or fn.startswith(s + '/')
603 if any(fn == s or fn.startswith(s + '/')
604 for s in self.substate):
604 for s in self.substate):
605 return
605 return
606 match.bad(fn, _('no such file in rev %s') % self)
606 match.bad(fn, _('no such file in rev %s') % self)
607
607
608 m = matchmod.badmatch(match, bad)
608 m = matchmod.badmatch(match, bad)
609 return self._manifest.walk(m)
609 return self._manifest.walk(m)
610
610
611 def matches(self, match):
611 def matches(self, match):
612 return self.walk(match)
612 return self.walk(match)
613
613
614 class basefilectx(object):
614 class basefilectx(object):
615 """A filecontext object represents the common logic for its children:
615 """A filecontext object represents the common logic for its children:
616 filectx: read-only access to a filerevision that is already present
616 filectx: read-only access to a filerevision that is already present
617 in the repo,
617 in the repo,
618 workingfilectx: a filecontext that represents files from the working
618 workingfilectx: a filecontext that represents files from the working
619 directory,
619 directory,
620 memfilectx: a filecontext that represents files in-memory,
620 memfilectx: a filecontext that represents files in-memory,
621 overlayfilectx: duplicate another filecontext with some fields overridden.
621 overlayfilectx: duplicate another filecontext with some fields overridden.
622 """
622 """
623 @propertycache
623 @propertycache
624 def _filelog(self):
624 def _filelog(self):
625 return self._repo.file(self._path)
625 return self._repo.file(self._path)
626
626
627 @propertycache
627 @propertycache
628 def _changeid(self):
628 def _changeid(self):
629 if r'_changeid' in self.__dict__:
629 if r'_changeid' in self.__dict__:
630 return self._changeid
630 return self._changeid
631 elif r'_changectx' in self.__dict__:
631 elif r'_changectx' in self.__dict__:
632 return self._changectx.rev()
632 return self._changectx.rev()
633 elif r'_descendantrev' in self.__dict__:
633 elif r'_descendantrev' in self.__dict__:
634 # this file context was created from a revision with a known
634 # this file context was created from a revision with a known
635 # descendant, we can (lazily) correct for linkrev aliases
635 # descendant, we can (lazily) correct for linkrev aliases
636 return self._adjustlinkrev(self._descendantrev)
636 return self._adjustlinkrev(self._descendantrev)
637 else:
637 else:
638 return self._filelog.linkrev(self._filerev)
638 return self._filelog.linkrev(self._filerev)
639
639
640 @propertycache
640 @propertycache
641 def _filenode(self):
641 def _filenode(self):
642 if r'_fileid' in self.__dict__:
642 if r'_fileid' in self.__dict__:
643 return self._filelog.lookup(self._fileid)
643 return self._filelog.lookup(self._fileid)
644 else:
644 else:
645 return self._changectx.filenode(self._path)
645 return self._changectx.filenode(self._path)
646
646
647 @propertycache
647 @propertycache
648 def _filerev(self):
648 def _filerev(self):
649 return self._filelog.rev(self._filenode)
649 return self._filelog.rev(self._filenode)
650
650
651 @propertycache
651 @propertycache
652 def _repopath(self):
652 def _repopath(self):
653 return self._path
653 return self._path
654
654
655 def __nonzero__(self):
655 def __nonzero__(self):
656 try:
656 try:
657 self._filenode
657 self._filenode
658 return True
658 return True
659 except error.LookupError:
659 except error.LookupError:
660 # file is missing
660 # file is missing
661 return False
661 return False
662
662
663 __bool__ = __nonzero__
663 __bool__ = __nonzero__
664
664
665 def __bytes__(self):
665 def __bytes__(self):
666 try:
666 try:
667 return "%s@%s" % (self.path(), self._changectx)
667 return "%s@%s" % (self.path(), self._changectx)
668 except error.LookupError:
668 except error.LookupError:
669 return "%s@???" % self.path()
669 return "%s@???" % self.path()
670
670
671 __str__ = encoding.strmethod(__bytes__)
671 __str__ = encoding.strmethod(__bytes__)
672
672
673 def __repr__(self):
673 def __repr__(self):
674 return r"<%s %s>" % (type(self).__name__, str(self))
674 return r"<%s %s>" % (type(self).__name__, str(self))
675
675
676 def __hash__(self):
676 def __hash__(self):
677 try:
677 try:
678 return hash((self._path, self._filenode))
678 return hash((self._path, self._filenode))
679 except AttributeError:
679 except AttributeError:
680 return id(self)
680 return id(self)
681
681
682 def __eq__(self, other):
682 def __eq__(self, other):
683 try:
683 try:
684 return (type(self) == type(other) and self._path == other._path
684 return (type(self) == type(other) and self._path == other._path
685 and self._filenode == other._filenode)
685 and self._filenode == other._filenode)
686 except AttributeError:
686 except AttributeError:
687 return False
687 return False
688
688
689 def __ne__(self, other):
689 def __ne__(self, other):
690 return not (self == other)
690 return not (self == other)
691
691
692 def filerev(self):
692 def filerev(self):
693 return self._filerev
693 return self._filerev
694 def filenode(self):
694 def filenode(self):
695 return self._filenode
695 return self._filenode
696 @propertycache
696 @propertycache
697 def _flags(self):
697 def _flags(self):
698 return self._changectx.flags(self._path)
698 return self._changectx.flags(self._path)
699 def flags(self):
699 def flags(self):
700 return self._flags
700 return self._flags
701 def filelog(self):
701 def filelog(self):
702 return self._filelog
702 return self._filelog
703 def rev(self):
703 def rev(self):
704 return self._changeid
704 return self._changeid
705 def linkrev(self):
705 def linkrev(self):
706 return self._filelog.linkrev(self._filerev)
706 return self._filelog.linkrev(self._filerev)
707 def node(self):
707 def node(self):
708 return self._changectx.node()
708 return self._changectx.node()
709 def hex(self):
709 def hex(self):
710 return self._changectx.hex()
710 return self._changectx.hex()
711 def user(self):
711 def user(self):
712 return self._changectx.user()
712 return self._changectx.user()
713 def date(self):
713 def date(self):
714 return self._changectx.date()
714 return self._changectx.date()
715 def files(self):
715 def files(self):
716 return self._changectx.files()
716 return self._changectx.files()
717 def description(self):
717 def description(self):
718 return self._changectx.description()
718 return self._changectx.description()
719 def branch(self):
719 def branch(self):
720 return self._changectx.branch()
720 return self._changectx.branch()
721 def extra(self):
721 def extra(self):
722 return self._changectx.extra()
722 return self._changectx.extra()
723 def phase(self):
723 def phase(self):
724 return self._changectx.phase()
724 return self._changectx.phase()
725 def phasestr(self):
725 def phasestr(self):
726 return self._changectx.phasestr()
726 return self._changectx.phasestr()
727 def obsolete(self):
727 def obsolete(self):
728 return self._changectx.obsolete()
728 return self._changectx.obsolete()
729 def instabilities(self):
729 def instabilities(self):
730 return self._changectx.instabilities()
730 return self._changectx.instabilities()
731 def manifest(self):
731 def manifest(self):
732 return self._changectx.manifest()
732 return self._changectx.manifest()
733 def changectx(self):
733 def changectx(self):
734 return self._changectx
734 return self._changectx
735 def renamed(self):
735 def renamed(self):
736 return self._copied
736 return self._copied
737 def repo(self):
737 def repo(self):
738 return self._repo
738 return self._repo
739 def size(self):
739 def size(self):
740 return len(self.data())
740 return len(self.data())
741
741
742 def path(self):
742 def path(self):
743 return self._path
743 return self._path
744
744
745 def isbinary(self):
745 def isbinary(self):
746 try:
746 try:
747 return stringutil.binary(self.data())
747 return stringutil.binary(self.data())
748 except IOError:
748 except IOError:
749 return False
749 return False
750 def isexec(self):
750 def isexec(self):
751 return 'x' in self.flags()
751 return 'x' in self.flags()
752 def islink(self):
752 def islink(self):
753 return 'l' in self.flags()
753 return 'l' in self.flags()
754
754
755 def isabsent(self):
755 def isabsent(self):
756 """whether this filectx represents a file not in self._changectx
756 """whether this filectx represents a file not in self._changectx
757
757
758 This is mainly for merge code to detect change/delete conflicts. This is
758 This is mainly for merge code to detect change/delete conflicts. This is
759 expected to be True for all subclasses of basectx."""
759 expected to be True for all subclasses of basectx."""
760 return False
760 return False
761
761
762 _customcmp = False
762 _customcmp = False
763 def cmp(self, fctx):
763 def cmp(self, fctx):
764 """compare with other file context
764 """compare with other file context
765
765
766 returns True if different than fctx.
766 returns True if different than fctx.
767 """
767 """
768 if fctx._customcmp:
768 if fctx._customcmp:
769 return fctx.cmp(self)
769 return fctx.cmp(self)
770
770
771 if (fctx._filenode is None
771 if (fctx._filenode is None
772 and (self._repo._encodefilterpats
772 and (self._repo._encodefilterpats
773 # if file data starts with '\1\n', empty metadata block is
773 # if file data starts with '\1\n', empty metadata block is
774 # prepended, which adds 4 bytes to filelog.size().
774 # prepended, which adds 4 bytes to filelog.size().
775 or self.size() - 4 == fctx.size())
775 or self.size() - 4 == fctx.size())
776 or self.size() == fctx.size()):
776 or self.size() == fctx.size()):
777 return self._filelog.cmp(self._filenode, fctx.data())
777 return self._filelog.cmp(self._filenode, fctx.data())
778
778
779 return True
779 return True
780
780
781 def _adjustlinkrev(self, srcrev, inclusive=False):
781 def _adjustlinkrev(self, srcrev, inclusive=False):
782 """return the first ancestor of <srcrev> introducing <fnode>
782 """return the first ancestor of <srcrev> introducing <fnode>
783
783
784 If the linkrev of the file revision does not point to an ancestor of
784 If the linkrev of the file revision does not point to an ancestor of
785 srcrev, we'll walk down the ancestors until we find one introducing
785 srcrev, we'll walk down the ancestors until we find one introducing
786 this file revision.
786 this file revision.
787
787
788 :srcrev: the changeset revision we search ancestors from
788 :srcrev: the changeset revision we search ancestors from
789 :inclusive: if true, the src revision will also be checked
789 :inclusive: if true, the src revision will also be checked
790 """
790 """
791 repo = self._repo
791 repo = self._repo
792 cl = repo.unfiltered().changelog
792 cl = repo.unfiltered().changelog
793 mfl = repo.manifestlog
793 mfl = repo.manifestlog
794 # fetch the linkrev
794 # fetch the linkrev
795 lkr = self.linkrev()
795 lkr = self.linkrev()
796 # hack to reuse ancestor computation when searching for renames
796 # hack to reuse ancestor computation when searching for renames
797 memberanc = getattr(self, '_ancestrycontext', None)
797 memberanc = getattr(self, '_ancestrycontext', None)
798 iteranc = None
798 iteranc = None
799 if srcrev is None:
799 if srcrev is None:
800 # wctx case, used by workingfilectx during mergecopy
800 # wctx case, used by workingfilectx during mergecopy
801 revs = [p.rev() for p in self._repo[None].parents()]
801 revs = [p.rev() for p in self._repo[None].parents()]
802 inclusive = True # we skipped the real (revless) source
802 inclusive = True # we skipped the real (revless) source
803 else:
803 else:
804 revs = [srcrev]
804 revs = [srcrev]
805 if memberanc is None:
805 if memberanc is None:
806 memberanc = iteranc = cl.ancestors(revs, lkr,
806 memberanc = iteranc = cl.ancestors(revs, lkr,
807 inclusive=inclusive)
807 inclusive=inclusive)
808 # check if this linkrev is an ancestor of srcrev
808 # check if this linkrev is an ancestor of srcrev
809 if lkr not in memberanc:
809 if lkr not in memberanc:
810 if iteranc is None:
810 if iteranc is None:
811 iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
811 iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
812 fnode = self._filenode
812 fnode = self._filenode
813 path = self._path
813 path = self._path
814 for a in iteranc:
814 for a in iteranc:
815 ac = cl.read(a) # get changeset data (we avoid object creation)
815 ac = cl.read(a) # get changeset data (we avoid object creation)
816 if path in ac[3]: # checking the 'files' field.
816 if path in ac[3]: # checking the 'files' field.
817 # The file has been touched, check if the content is
817 # The file has been touched, check if the content is
818 # similar to the one we search for.
818 # similar to the one we search for.
819 if fnode == mfl[ac[0]].readfast().get(path):
819 if fnode == mfl[ac[0]].readfast().get(path):
820 return a
820 return a
821 # In theory, we should never get out of that loop without a result.
821 # In theory, we should never get out of that loop without a result.
822 # But if manifest uses a buggy file revision (not children of the
822 # But if manifest uses a buggy file revision (not children of the
823 # one it replaces) we could. Such a buggy situation will likely
823 # one it replaces) we could. Such a buggy situation will likely
824 # result is crash somewhere else at to some point.
824 # result is crash somewhere else at to some point.
825 return lkr
825 return lkr
826
826
827 def introrev(self):
827 def introrev(self):
828 """return the rev of the changeset which introduced this file revision
828 """return the rev of the changeset which introduced this file revision
829
829
830 This method is different from linkrev because it take into account the
830 This method is different from linkrev because it take into account the
831 changeset the filectx was created from. It ensures the returned
831 changeset the filectx was created from. It ensures the returned
832 revision is one of its ancestors. This prevents bugs from
832 revision is one of its ancestors. This prevents bugs from
833 'linkrev-shadowing' when a file revision is used by multiple
833 'linkrev-shadowing' when a file revision is used by multiple
834 changesets.
834 changesets.
835 """
835 """
836 lkr = self.linkrev()
836 lkr = self.linkrev()
837 attrs = vars(self)
837 attrs = vars(self)
838 noctx = not (r'_changeid' in attrs or r'_changectx' in attrs)
838 noctx = not (r'_changeid' in attrs or r'_changectx' in attrs)
839 if noctx or self.rev() == lkr:
839 if noctx or self.rev() == lkr:
840 return self.linkrev()
840 return self.linkrev()
841 return self._adjustlinkrev(self.rev(), inclusive=True)
841 return self._adjustlinkrev(self.rev(), inclusive=True)
842
842
843 def introfilectx(self):
843 def introfilectx(self):
844 """Return filectx having identical contents, but pointing to the
844 """Return filectx having identical contents, but pointing to the
845 changeset revision where this filectx was introduced"""
845 changeset revision where this filectx was introduced"""
846 introrev = self.introrev()
846 introrev = self.introrev()
847 if self.rev() == introrev:
847 if self.rev() == introrev:
848 return self
848 return self
849 return self.filectx(self.filenode(), changeid=introrev)
849 return self.filectx(self.filenode(), changeid=introrev)
850
850
851 def _parentfilectx(self, path, fileid, filelog):
851 def _parentfilectx(self, path, fileid, filelog):
852 """create parent filectx keeping ancestry info for _adjustlinkrev()"""
852 """create parent filectx keeping ancestry info for _adjustlinkrev()"""
853 fctx = filectx(self._repo, path, fileid=fileid, filelog=filelog)
853 fctx = filectx(self._repo, path, fileid=fileid, filelog=filelog)
854 if r'_changeid' in vars(self) or r'_changectx' in vars(self):
854 if r'_changeid' in vars(self) or r'_changectx' in vars(self):
855 # If self is associated with a changeset (probably explicitly
855 # If self is associated with a changeset (probably explicitly
856 # fed), ensure the created filectx is associated with a
856 # fed), ensure the created filectx is associated with a
857 # changeset that is an ancestor of self.changectx.
857 # changeset that is an ancestor of self.changectx.
858 # This lets us later use _adjustlinkrev to get a correct link.
858 # This lets us later use _adjustlinkrev to get a correct link.
859 fctx._descendantrev = self.rev()
859 fctx._descendantrev = self.rev()
860 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
860 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
861 elif r'_descendantrev' in vars(self):
861 elif r'_descendantrev' in vars(self):
862 # Otherwise propagate _descendantrev if we have one associated.
862 # Otherwise propagate _descendantrev if we have one associated.
863 fctx._descendantrev = self._descendantrev
863 fctx._descendantrev = self._descendantrev
864 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
864 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
865 return fctx
865 return fctx
866
866
867 def parents(self):
867 def parents(self):
868 _path = self._path
868 _path = self._path
869 fl = self._filelog
869 fl = self._filelog
870 parents = self._filelog.parents(self._filenode)
870 parents = self._filelog.parents(self._filenode)
871 pl = [(_path, node, fl) for node in parents if node != nullid]
871 pl = [(_path, node, fl) for node in parents if node != nullid]
872
872
873 r = fl.renamed(self._filenode)
873 r = fl.renamed(self._filenode)
874 if r:
874 if r:
875 # - In the simple rename case, both parent are nullid, pl is empty.
875 # - In the simple rename case, both parent are nullid, pl is empty.
876 # - In case of merge, only one of the parent is null id and should
876 # - In case of merge, only one of the parent is null id and should
877 # be replaced with the rename information. This parent is -always-
877 # be replaced with the rename information. This parent is -always-
878 # the first one.
878 # the first one.
879 #
879 #
880 # As null id have always been filtered out in the previous list
880 # As null id have always been filtered out in the previous list
881 # comprehension, inserting to 0 will always result in "replacing
881 # comprehension, inserting to 0 will always result in "replacing
882 # first nullid parent with rename information.
882 # first nullid parent with rename information.
883 pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
883 pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
884
884
885 return [self._parentfilectx(path, fnode, l) for path, fnode, l in pl]
885 return [self._parentfilectx(path, fnode, l) for path, fnode, l in pl]
886
886
887 def p1(self):
887 def p1(self):
888 return self.parents()[0]
888 return self.parents()[0]
889
889
890 def p2(self):
890 def p2(self):
891 p = self.parents()
891 p = self.parents()
892 if len(p) == 2:
892 if len(p) == 2:
893 return p[1]
893 return p[1]
894 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
894 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
895
895
896 def annotate(self, follow=False, skiprevs=None, diffopts=None):
896 def annotate(self, follow=False, skiprevs=None, diffopts=None):
897 """Returns a list of annotateline objects for each line in the file
897 """Returns a list of annotateline objects for each line in the file
898
898
899 - line.fctx is the filectx of the node where that line was last changed
899 - line.fctx is the filectx of the node where that line was last changed
900 - line.lineno is the line number at the first appearance in the managed
900 - line.lineno is the line number at the first appearance in the managed
901 file
901 file
902 - line.text is the data on that line (including newline character)
902 - line.text is the data on that line (including newline character)
903 """
903 """
904 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
904 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
905
905
906 def parents(f):
906 def parents(f):
907 # Cut _descendantrev here to mitigate the penalty of lazy linkrev
907 # Cut _descendantrev here to mitigate the penalty of lazy linkrev
908 # adjustment. Otherwise, p._adjustlinkrev() would walk changelog
908 # adjustment. Otherwise, p._adjustlinkrev() would walk changelog
909 # from the topmost introrev (= srcrev) down to p.linkrev() if it
909 # from the topmost introrev (= srcrev) down to p.linkrev() if it
910 # isn't an ancestor of the srcrev.
910 # isn't an ancestor of the srcrev.
911 f._changeid
911 f._changeid
912 pl = f.parents()
912 pl = f.parents()
913
913
914 # Don't return renamed parents if we aren't following.
914 # Don't return renamed parents if we aren't following.
915 if not follow:
915 if not follow:
916 pl = [p for p in pl if p.path() == f.path()]
916 pl = [p for p in pl if p.path() == f.path()]
917
917
918 # renamed filectx won't have a filelog yet, so set it
918 # renamed filectx won't have a filelog yet, so set it
919 # from the cache to save time
919 # from the cache to save time
920 for p in pl:
920 for p in pl:
921 if not r'_filelog' in p.__dict__:
921 if not r'_filelog' in p.__dict__:
922 p._filelog = getlog(p.path())
922 p._filelog = getlog(p.path())
923
923
924 return pl
924 return pl
925
925
926 # use linkrev to find the first changeset where self appeared
926 # use linkrev to find the first changeset where self appeared
927 base = self.introfilectx()
927 base = self.introfilectx()
928 if getattr(base, '_ancestrycontext', None) is None:
928 if getattr(base, '_ancestrycontext', None) is None:
929 cl = self._repo.changelog
929 cl = self._repo.changelog
930 if base.rev() is None:
930 if base.rev() is None:
931 # wctx is not inclusive, but works because _ancestrycontext
931 # wctx is not inclusive, but works because _ancestrycontext
932 # is used to test filelog revisions
932 # is used to test filelog revisions
933 ac = cl.ancestors([p.rev() for p in base.parents()],
933 ac = cl.ancestors([p.rev() for p in base.parents()],
934 inclusive=True)
934 inclusive=True)
935 else:
935 else:
936 ac = cl.ancestors([base.rev()], inclusive=True)
936 ac = cl.ancestors([base.rev()], inclusive=True)
937 base._ancestrycontext = ac
937 base._ancestrycontext = ac
938
938
939 return dagop.annotate(base, parents, skiprevs=skiprevs,
939 return dagop.annotate(base, parents, skiprevs=skiprevs,
940 diffopts=diffopts)
940 diffopts=diffopts)
941
941
942 def ancestors(self, followfirst=False):
942 def ancestors(self, followfirst=False):
943 visit = {}
943 visit = {}
944 c = self
944 c = self
945 if followfirst:
945 if followfirst:
946 cut = 1
946 cut = 1
947 else:
947 else:
948 cut = None
948 cut = None
949
949
950 while True:
950 while True:
951 for parent in c.parents()[:cut]:
951 for parent in c.parents()[:cut]:
952 visit[(parent.linkrev(), parent.filenode())] = parent
952 visit[(parent.linkrev(), parent.filenode())] = parent
953 if not visit:
953 if not visit:
954 break
954 break
955 c = visit.pop(max(visit))
955 c = visit.pop(max(visit))
956 yield c
956 yield c
957
957
958 def decodeddata(self):
958 def decodeddata(self):
959 """Returns `data()` after running repository decoding filters.
959 """Returns `data()` after running repository decoding filters.
960
960
961 This is often equivalent to how the data would be expressed on disk.
961 This is often equivalent to how the data would be expressed on disk.
962 """
962 """
963 return self._repo.wwritedata(self.path(), self.data())
963 return self._repo.wwritedata(self.path(), self.data())
964
964
965 class filectx(basefilectx):
965 class filectx(basefilectx):
966 """A filecontext object makes access to data related to a particular
966 """A filecontext object makes access to data related to a particular
967 filerevision convenient."""
967 filerevision convenient."""
968 def __init__(self, repo, path, changeid=None, fileid=None,
968 def __init__(self, repo, path, changeid=None, fileid=None,
969 filelog=None, changectx=None):
969 filelog=None, changectx=None):
970 """changeid can be a changeset revision, node, or tag.
970 """changeid can be a changeset revision, node, or tag.
971 fileid can be a file revision or node."""
971 fileid can be a file revision or node."""
972 self._repo = repo
972 self._repo = repo
973 self._path = path
973 self._path = path
974
974
975 assert (changeid is not None
975 assert (changeid is not None
976 or fileid is not None
976 or fileid is not None
977 or changectx is not None), \
977 or changectx is not None), \
978 ("bad args: changeid=%r, fileid=%r, changectx=%r"
978 ("bad args: changeid=%r, fileid=%r, changectx=%r"
979 % (changeid, fileid, changectx))
979 % (changeid, fileid, changectx))
980
980
981 if filelog is not None:
981 if filelog is not None:
982 self._filelog = filelog
982 self._filelog = filelog
983
983
984 if changeid is not None:
984 if changeid is not None:
985 self._changeid = changeid
985 self._changeid = changeid
986 if changectx is not None:
986 if changectx is not None:
987 self._changectx = changectx
987 self._changectx = changectx
988 if fileid is not None:
988 if fileid is not None:
989 self._fileid = fileid
989 self._fileid = fileid
990
990
991 @propertycache
991 @propertycache
992 def _changectx(self):
992 def _changectx(self):
993 try:
993 try:
994 return changectx(self._repo, self._changeid)
994 return changectx(self._repo, self._changeid)
995 except error.FilteredRepoLookupError:
995 except error.FilteredRepoLookupError:
996 # Linkrev may point to any revision in the repository. When the
996 # Linkrev may point to any revision in the repository. When the
997 # repository is filtered this may lead to `filectx` trying to build
997 # repository is filtered this may lead to `filectx` trying to build
998 # `changectx` for filtered revision. In such case we fallback to
998 # `changectx` for filtered revision. In such case we fallback to
999 # creating `changectx` on the unfiltered version of the reposition.
999 # creating `changectx` on the unfiltered version of the reposition.
1000 # This fallback should not be an issue because `changectx` from
1000 # This fallback should not be an issue because `changectx` from
1001 # `filectx` are not used in complex operations that care about
1001 # `filectx` are not used in complex operations that care about
1002 # filtering.
1002 # filtering.
1003 #
1003 #
1004 # This fallback is a cheap and dirty fix that prevent several
1004 # This fallback is a cheap and dirty fix that prevent several
1005 # crashes. It does not ensure the behavior is correct. However the
1005 # crashes. It does not ensure the behavior is correct. However the
1006 # behavior was not correct before filtering either and "incorrect
1006 # behavior was not correct before filtering either and "incorrect
1007 # behavior" is seen as better as "crash"
1007 # behavior" is seen as better as "crash"
1008 #
1008 #
1009 # Linkrevs have several serious troubles with filtering that are
1009 # Linkrevs have several serious troubles with filtering that are
1010 # complicated to solve. Proper handling of the issue here should be
1010 # complicated to solve. Proper handling of the issue here should be
1011 # considered when solving linkrev issue are on the table.
1011 # considered when solving linkrev issue are on the table.
1012 return changectx(self._repo.unfiltered(), self._changeid)
1012 return changectx(self._repo.unfiltered(), self._changeid)
1013
1013
1014 def filectx(self, fileid, changeid=None):
1014 def filectx(self, fileid, changeid=None):
1015 '''opens an arbitrary revision of the file without
1015 '''opens an arbitrary revision of the file without
1016 opening a new filelog'''
1016 opening a new filelog'''
1017 return filectx(self._repo, self._path, fileid=fileid,
1017 return filectx(self._repo, self._path, fileid=fileid,
1018 filelog=self._filelog, changeid=changeid)
1018 filelog=self._filelog, changeid=changeid)
1019
1019
1020 def rawdata(self):
1020 def rawdata(self):
1021 return self._filelog.revision(self._filenode, raw=True)
1021 return self._filelog.revision(self._filenode, raw=True)
1022
1022
1023 def rawflags(self):
1023 def rawflags(self):
1024 """low-level revlog flags"""
1024 """low-level revlog flags"""
1025 return self._filelog.flags(self._filerev)
1025 return self._filelog.flags(self._filerev)
1026
1026
1027 def data(self):
1027 def data(self):
1028 try:
1028 try:
1029 return self._filelog.read(self._filenode)
1029 return self._filelog.read(self._filenode)
1030 except error.CensoredNodeError:
1030 except error.CensoredNodeError:
1031 if self._repo.ui.config("censor", "policy") == "ignore":
1031 if self._repo.ui.config("censor", "policy") == "ignore":
1032 return ""
1032 return ""
1033 raise error.Abort(_("censored node: %s") % short(self._filenode),
1033 raise error.Abort(_("censored node: %s") % short(self._filenode),
1034 hint=_("set censor.policy to ignore errors"))
1034 hint=_("set censor.policy to ignore errors"))
1035
1035
1036 def size(self):
1036 def size(self):
1037 return self._filelog.size(self._filerev)
1037 return self._filelog.size(self._filerev)
1038
1038
1039 @propertycache
1039 @propertycache
1040 def _copied(self):
1040 def _copied(self):
1041 """check if file was actually renamed in this changeset revision
1041 """check if file was actually renamed in this changeset revision
1042
1042
1043 If rename logged in file revision, we report copy for changeset only
1043 If rename logged in file revision, we report copy for changeset only
1044 if file revisions linkrev points back to the changeset in question
1044 if file revisions linkrev points back to the changeset in question
1045 or both changeset parents contain different file revisions.
1045 or both changeset parents contain different file revisions.
1046 """
1046 """
1047
1047
1048 renamed = self._filelog.renamed(self._filenode)
1048 renamed = self._filelog.renamed(self._filenode)
1049 if not renamed:
1049 if not renamed:
1050 return renamed
1050 return renamed
1051
1051
1052 if self.rev() == self.linkrev():
1052 if self.rev() == self.linkrev():
1053 return renamed
1053 return renamed
1054
1054
1055 name = self.path()
1055 name = self.path()
1056 fnode = self._filenode
1056 fnode = self._filenode
1057 for p in self._changectx.parents():
1057 for p in self._changectx.parents():
1058 try:
1058 try:
1059 if fnode == p.filenode(name):
1059 if fnode == p.filenode(name):
1060 return None
1060 return None
1061 except error.LookupError:
1061 except error.LookupError:
1062 pass
1062 pass
1063 return renamed
1063 return renamed
1064
1064
1065 def children(self):
1065 def children(self):
1066 # hard for renames
1066 # hard for renames
1067 c = self._filelog.children(self._filenode)
1067 c = self._filelog.children(self._filenode)
1068 return [filectx(self._repo, self._path, fileid=x,
1068 return [filectx(self._repo, self._path, fileid=x,
1069 filelog=self._filelog) for x in c]
1069 filelog=self._filelog) for x in c]
1070
1070
1071 class committablectx(basectx):
1071 class committablectx(basectx):
1072 """A committablectx object provides common functionality for a context that
1072 """A committablectx object provides common functionality for a context that
1073 wants the ability to commit, e.g. workingctx or memctx."""
1073 wants the ability to commit, e.g. workingctx or memctx."""
1074 def __init__(self, repo, text="", user=None, date=None, extra=None,
1074 def __init__(self, repo, text="", user=None, date=None, extra=None,
1075 changes=None):
1075 changes=None):
1076 super(committablectx, self).__init__(repo)
1076 super(committablectx, self).__init__(repo)
1077 self._rev = None
1077 self._rev = None
1078 self._node = None
1078 self._node = None
1079 self._text = text
1079 self._text = text
1080 if date:
1080 if date:
1081 self._date = dateutil.parsedate(date)
1081 self._date = dateutil.parsedate(date)
1082 if user:
1082 if user:
1083 self._user = user
1083 self._user = user
1084 if changes:
1084 if changes:
1085 self._status = changes
1085 self._status = changes
1086
1086
1087 self._extra = {}
1087 self._extra = {}
1088 if extra:
1088 if extra:
1089 self._extra = extra.copy()
1089 self._extra = extra.copy()
1090 if 'branch' not in self._extra:
1090 if 'branch' not in self._extra:
1091 try:
1091 try:
1092 branch = encoding.fromlocal(self._repo.dirstate.branch())
1092 branch = encoding.fromlocal(self._repo.dirstate.branch())
1093 except UnicodeDecodeError:
1093 except UnicodeDecodeError:
1094 raise error.Abort(_('branch name not in UTF-8!'))
1094 raise error.Abort(_('branch name not in UTF-8!'))
1095 self._extra['branch'] = branch
1095 self._extra['branch'] = branch
1096 if self._extra['branch'] == '':
1096 if self._extra['branch'] == '':
1097 self._extra['branch'] = 'default'
1097 self._extra['branch'] = 'default'
1098
1098
1099 def __bytes__(self):
1099 def __bytes__(self):
1100 return bytes(self._parents[0]) + "+"
1100 return bytes(self._parents[0]) + "+"
1101
1101
1102 __str__ = encoding.strmethod(__bytes__)
1102 __str__ = encoding.strmethod(__bytes__)
1103
1103
1104 def __nonzero__(self):
1104 def __nonzero__(self):
1105 return True
1105 return True
1106
1106
1107 __bool__ = __nonzero__
1107 __bool__ = __nonzero__
1108
1108
1109 def _buildflagfunc(self):
1109 def _buildflagfunc(self):
1110 # Create a fallback function for getting file flags when the
1110 # Create a fallback function for getting file flags when the
1111 # filesystem doesn't support them
1111 # filesystem doesn't support them
1112
1112
1113 copiesget = self._repo.dirstate.copies().get
1113 copiesget = self._repo.dirstate.copies().get
1114 parents = self.parents()
1114 parents = self.parents()
1115 if len(parents) < 2:
1115 if len(parents) < 2:
1116 # when we have one parent, it's easy: copy from parent
1116 # when we have one parent, it's easy: copy from parent
1117 man = parents[0].manifest()
1117 man = parents[0].manifest()
1118 def func(f):
1118 def func(f):
1119 f = copiesget(f, f)
1119 f = copiesget(f, f)
1120 return man.flags(f)
1120 return man.flags(f)
1121 else:
1121 else:
1122 # merges are tricky: we try to reconstruct the unstored
1122 # merges are tricky: we try to reconstruct the unstored
1123 # result from the merge (issue1802)
1123 # result from the merge (issue1802)
1124 p1, p2 = parents
1124 p1, p2 = parents
1125 pa = p1.ancestor(p2)
1125 pa = p1.ancestor(p2)
1126 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1126 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1127
1127
1128 def func(f):
1128 def func(f):
1129 f = copiesget(f, f) # may be wrong for merges with copies
1129 f = copiesget(f, f) # may be wrong for merges with copies
1130 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1130 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1131 if fl1 == fl2:
1131 if fl1 == fl2:
1132 return fl1
1132 return fl1
1133 if fl1 == fla:
1133 if fl1 == fla:
1134 return fl2
1134 return fl2
1135 if fl2 == fla:
1135 if fl2 == fla:
1136 return fl1
1136 return fl1
1137 return '' # punt for conflicts
1137 return '' # punt for conflicts
1138
1138
1139 return func
1139 return func
1140
1140
1141 @propertycache
1141 @propertycache
1142 def _flagfunc(self):
1142 def _flagfunc(self):
1143 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1143 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1144
1144
1145 @propertycache
1145 @propertycache
1146 def _status(self):
1146 def _status(self):
1147 return self._repo.status()
1147 return self._repo.status()
1148
1148
1149 @propertycache
1149 @propertycache
1150 def _user(self):
1150 def _user(self):
1151 return self._repo.ui.username()
1151 return self._repo.ui.username()
1152
1152
1153 @propertycache
1153 @propertycache
1154 def _date(self):
1154 def _date(self):
1155 ui = self._repo.ui
1155 ui = self._repo.ui
1156 date = ui.configdate('devel', 'default-date')
1156 date = ui.configdate('devel', 'default-date')
1157 if date is None:
1157 if date is None:
1158 date = dateutil.makedate()
1158 date = dateutil.makedate()
1159 return date
1159 return date
1160
1160
1161 def subrev(self, subpath):
1161 def subrev(self, subpath):
1162 return None
1162 return None
1163
1163
1164 def manifestnode(self):
1164 def manifestnode(self):
1165 return None
1165 return None
1166 def user(self):
1166 def user(self):
1167 return self._user or self._repo.ui.username()
1167 return self._user or self._repo.ui.username()
1168 def date(self):
1168 def date(self):
1169 return self._date
1169 return self._date
1170 def description(self):
1170 def description(self):
1171 return self._text
1171 return self._text
1172 def files(self):
1172 def files(self):
1173 return sorted(self._status.modified + self._status.added +
1173 return sorted(self._status.modified + self._status.added +
1174 self._status.removed)
1174 self._status.removed)
1175
1175
1176 def modified(self):
1176 def modified(self):
1177 return self._status.modified
1177 return self._status.modified
1178 def added(self):
1178 def added(self):
1179 return self._status.added
1179 return self._status.added
1180 def removed(self):
1180 def removed(self):
1181 return self._status.removed
1181 return self._status.removed
1182 def deleted(self):
1182 def deleted(self):
1183 return self._status.deleted
1183 return self._status.deleted
1184 def branch(self):
1184 def branch(self):
1185 return encoding.tolocal(self._extra['branch'])
1185 return encoding.tolocal(self._extra['branch'])
1186 def closesbranch(self):
1186 def closesbranch(self):
1187 return 'close' in self._extra
1187 return 'close' in self._extra
1188 def extra(self):
1188 def extra(self):
1189 return self._extra
1189 return self._extra
1190
1190
1191 def isinmemory(self):
1191 def isinmemory(self):
1192 return False
1192 return False
1193
1193
1194 def tags(self):
1194 def tags(self):
1195 return []
1195 return []
1196
1196
1197 def bookmarks(self):
1197 def bookmarks(self):
1198 b = []
1198 b = []
1199 for p in self.parents():
1199 for p in self.parents():
1200 b.extend(p.bookmarks())
1200 b.extend(p.bookmarks())
1201 return b
1201 return b
1202
1202
1203 def phase(self):
1203 def phase(self):
1204 phase = phases.draft # default phase to draft
1204 phase = phases.draft # default phase to draft
1205 for p in self.parents():
1205 for p in self.parents():
1206 phase = max(phase, p.phase())
1206 phase = max(phase, p.phase())
1207 return phase
1207 return phase
1208
1208
1209 def hidden(self):
1209 def hidden(self):
1210 return False
1210 return False
1211
1211
1212 def children(self):
1212 def children(self):
1213 return []
1213 return []
1214
1214
1215 def flags(self, path):
1215 def flags(self, path):
1216 if r'_manifest' in self.__dict__:
1216 if r'_manifest' in self.__dict__:
1217 try:
1217 try:
1218 return self._manifest.flags(path)
1218 return self._manifest.flags(path)
1219 except KeyError:
1219 except KeyError:
1220 return ''
1220 return ''
1221
1221
1222 try:
1222 try:
1223 return self._flagfunc(path)
1223 return self._flagfunc(path)
1224 except OSError:
1224 except OSError:
1225 return ''
1225 return ''
1226
1226
1227 def ancestor(self, c2):
1227 def ancestor(self, c2):
1228 """return the "best" ancestor context of self and c2"""
1228 """return the "best" ancestor context of self and c2"""
1229 return self._parents[0].ancestor(c2) # punt on two parents for now
1229 return self._parents[0].ancestor(c2) # punt on two parents for now
1230
1230
1231 def walk(self, match):
1231 def walk(self, match):
1232 '''Generates matching file names.'''
1232 '''Generates matching file names.'''
1233 return sorted(self._repo.dirstate.walk(match,
1233 return sorted(self._repo.dirstate.walk(match,
1234 subrepos=sorted(self.substate),
1234 subrepos=sorted(self.substate),
1235 unknown=True, ignored=False))
1235 unknown=True, ignored=False))
1236
1236
1237 def matches(self, match):
1237 def matches(self, match):
1238 ds = self._repo.dirstate
1238 ds = self._repo.dirstate
1239 return sorted(f for f in ds.matches(match) if ds[f] != 'r')
1239 return sorted(f for f in ds.matches(match) if ds[f] != 'r')
1240
1240
1241 def ancestors(self):
1241 def ancestors(self):
1242 for p in self._parents:
1242 for p in self._parents:
1243 yield p
1243 yield p
1244 for a in self._repo.changelog.ancestors(
1244 for a in self._repo.changelog.ancestors(
1245 [p.rev() for p in self._parents]):
1245 [p.rev() for p in self._parents]):
1246 yield changectx(self._repo, a)
1246 yield changectx(self._repo, a)
1247
1247
1248 def markcommitted(self, node):
1248 def markcommitted(self, node):
1249 """Perform post-commit cleanup necessary after committing this ctx
1249 """Perform post-commit cleanup necessary after committing this ctx
1250
1250
1251 Specifically, this updates backing stores this working context
1251 Specifically, this updates backing stores this working context
1252 wraps to reflect the fact that the changes reflected by this
1252 wraps to reflect the fact that the changes reflected by this
1253 workingctx have been committed. For example, it marks
1253 workingctx have been committed. For example, it marks
1254 modified and added files as normal in the dirstate.
1254 modified and added files as normal in the dirstate.
1255
1255
1256 """
1256 """
1257
1257
1258 with self._repo.dirstate.parentchange():
1258 with self._repo.dirstate.parentchange():
1259 for f in self.modified() + self.added():
1259 for f in self.modified() + self.added():
1260 self._repo.dirstate.normal(f)
1260 self._repo.dirstate.normal(f)
1261 for f in self.removed():
1261 for f in self.removed():
1262 self._repo.dirstate.drop(f)
1262 self._repo.dirstate.drop(f)
1263 self._repo.dirstate.setparents(node)
1263 self._repo.dirstate.setparents(node)
1264
1264
1265 # write changes out explicitly, because nesting wlock at
1265 # write changes out explicitly, because nesting wlock at
1266 # runtime may prevent 'wlock.release()' in 'repo.commit()'
1266 # runtime may prevent 'wlock.release()' in 'repo.commit()'
1267 # from immediately doing so for subsequent changing files
1267 # from immediately doing so for subsequent changing files
1268 self._repo.dirstate.write(self._repo.currenttransaction())
1268 self._repo.dirstate.write(self._repo.currenttransaction())
1269
1269
1270 def dirty(self, missing=False, merge=True, branch=True):
1270 def dirty(self, missing=False, merge=True, branch=True):
1271 return False
1271 return False
1272
1272
1273 class workingctx(committablectx):
1273 class workingctx(committablectx):
1274 """A workingctx object makes access to data related to
1274 """A workingctx object makes access to data related to
1275 the current working directory convenient.
1275 the current working directory convenient.
1276 date - any valid date string or (unixtime, offset), or None.
1276 date - any valid date string or (unixtime, offset), or None.
1277 user - username string, or None.
1277 user - username string, or None.
1278 extra - a dictionary of extra values, or None.
1278 extra - a dictionary of extra values, or None.
1279 changes - a list of file lists as returned by localrepo.status()
1279 changes - a list of file lists as returned by localrepo.status()
1280 or None to use the repository status.
1280 or None to use the repository status.
1281 """
1281 """
1282 def __init__(self, repo, text="", user=None, date=None, extra=None,
1282 def __init__(self, repo, text="", user=None, date=None, extra=None,
1283 changes=None):
1283 changes=None):
1284 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1284 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1285
1285
1286 def __iter__(self):
1286 def __iter__(self):
1287 d = self._repo.dirstate
1287 d = self._repo.dirstate
1288 for f in d:
1288 for f in d:
1289 if d[f] != 'r':
1289 if d[f] != 'r':
1290 yield f
1290 yield f
1291
1291
1292 def __contains__(self, key):
1292 def __contains__(self, key):
1293 return self._repo.dirstate[key] not in "?r"
1293 return self._repo.dirstate[key] not in "?r"
1294
1294
1295 def hex(self):
1295 def hex(self):
1296 return hex(wdirid)
1296 return hex(wdirid)
1297
1297
1298 @propertycache
1298 @propertycache
1299 def _parents(self):
1299 def _parents(self):
1300 p = self._repo.dirstate.parents()
1300 p = self._repo.dirstate.parents()
1301 if p[1] == nullid:
1301 if p[1] == nullid:
1302 p = p[:-1]
1302 p = p[:-1]
1303 return [changectx(self._repo, x) for x in p]
1303 return [changectx(self._repo, x) for x in p]
1304
1304
1305 def _fileinfo(self, path):
1305 def _fileinfo(self, path):
1306 # populate __dict__['_manifest'] as workingctx has no _manifestdelta
1306 # populate __dict__['_manifest'] as workingctx has no _manifestdelta
1307 self._manifest
1307 self._manifest
1308 return super(workingctx, self)._fileinfo(path)
1308 return super(workingctx, self)._fileinfo(path)
1309
1309
1310 def filectx(self, path, filelog=None):
1310 def filectx(self, path, filelog=None):
1311 """get a file context from the working directory"""
1311 """get a file context from the working directory"""
1312 return workingfilectx(self._repo, path, workingctx=self,
1312 return workingfilectx(self._repo, path, workingctx=self,
1313 filelog=filelog)
1313 filelog=filelog)
1314
1314
1315 def dirty(self, missing=False, merge=True, branch=True):
1315 def dirty(self, missing=False, merge=True, branch=True):
1316 "check whether a working directory is modified"
1316 "check whether a working directory is modified"
1317 # check subrepos first
1317 # check subrepos first
1318 for s in sorted(self.substate):
1318 for s in sorted(self.substate):
1319 if self.sub(s).dirty(missing=missing):
1319 if self.sub(s).dirty(missing=missing):
1320 return True
1320 return True
1321 # check current working dir
1321 # check current working dir
1322 return ((merge and self.p2()) or
1322 return ((merge and self.p2()) or
1323 (branch and self.branch() != self.p1().branch()) or
1323 (branch and self.branch() != self.p1().branch()) or
1324 self.modified() or self.added() or self.removed() or
1324 self.modified() or self.added() or self.removed() or
1325 (missing and self.deleted()))
1325 (missing and self.deleted()))
1326
1326
1327 def add(self, list, prefix=""):
1327 def add(self, list, prefix=""):
1328 with self._repo.wlock():
1328 with self._repo.wlock():
1329 ui, ds = self._repo.ui, self._repo.dirstate
1329 ui, ds = self._repo.ui, self._repo.dirstate
1330 uipath = lambda f: ds.pathto(pathutil.join(prefix, f))
1330 uipath = lambda f: ds.pathto(pathutil.join(prefix, f))
1331 rejected = []
1331 rejected = []
1332 lstat = self._repo.wvfs.lstat
1332 lstat = self._repo.wvfs.lstat
1333 for f in list:
1333 for f in list:
1334 # ds.pathto() returns an absolute file when this is invoked from
1334 # ds.pathto() returns an absolute file when this is invoked from
1335 # the keyword extension. That gets flagged as non-portable on
1335 # the keyword extension. That gets flagged as non-portable on
1336 # Windows, since it contains the drive letter and colon.
1336 # Windows, since it contains the drive letter and colon.
1337 scmutil.checkportable(ui, os.path.join(prefix, f))
1337 scmutil.checkportable(ui, os.path.join(prefix, f))
1338 try:
1338 try:
1339 st = lstat(f)
1339 st = lstat(f)
1340 except OSError:
1340 except OSError:
1341 ui.warn(_("%s does not exist!\n") % uipath(f))
1341 ui.warn(_("%s does not exist!\n") % uipath(f))
1342 rejected.append(f)
1342 rejected.append(f)
1343 continue
1343 continue
1344 limit = ui.configbytes('ui', 'large-file-limit')
1344 limit = ui.configbytes('ui', 'large-file-limit')
1345 if limit != 0 and st.st_size > limit:
1345 if limit != 0 and st.st_size > limit:
1346 ui.warn(_("%s: up to %d MB of RAM may be required "
1346 ui.warn(_("%s: up to %d MB of RAM may be required "
1347 "to manage this file\n"
1347 "to manage this file\n"
1348 "(use 'hg revert %s' to cancel the "
1348 "(use 'hg revert %s' to cancel the "
1349 "pending addition)\n")
1349 "pending addition)\n")
1350 % (f, 3 * st.st_size // 1000000, uipath(f)))
1350 % (f, 3 * st.st_size // 1000000, uipath(f)))
1351 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1351 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1352 ui.warn(_("%s not added: only files and symlinks "
1352 ui.warn(_("%s not added: only files and symlinks "
1353 "supported currently\n") % uipath(f))
1353 "supported currently\n") % uipath(f))
1354 rejected.append(f)
1354 rejected.append(f)
1355 elif ds[f] in 'amn':
1355 elif ds[f] in 'amn':
1356 ui.warn(_("%s already tracked!\n") % uipath(f))
1356 ui.warn(_("%s already tracked!\n") % uipath(f))
1357 elif ds[f] == 'r':
1357 elif ds[f] == 'r':
1358 ds.normallookup(f)
1358 ds.normallookup(f)
1359 else:
1359 else:
1360 ds.add(f)
1360 ds.add(f)
1361 return rejected
1361 return rejected
1362
1362
1363 def forget(self, files, prefix=""):
1363 def forget(self, files, prefix=""):
1364 with self._repo.wlock():
1364 with self._repo.wlock():
1365 ds = self._repo.dirstate
1365 ds = self._repo.dirstate
1366 uipath = lambda f: ds.pathto(pathutil.join(prefix, f))
1366 uipath = lambda f: ds.pathto(pathutil.join(prefix, f))
1367 rejected = []
1367 rejected = []
1368 for f in files:
1368 for f in files:
1369 if f not in self._repo.dirstate:
1369 if f not in self._repo.dirstate:
1370 self._repo.ui.warn(_("%s not tracked!\n") % uipath(f))
1370 self._repo.ui.warn(_("%s not tracked!\n") % uipath(f))
1371 rejected.append(f)
1371 rejected.append(f)
1372 elif self._repo.dirstate[f] != 'a':
1372 elif self._repo.dirstate[f] != 'a':
1373 self._repo.dirstate.remove(f)
1373 self._repo.dirstate.remove(f)
1374 else:
1374 else:
1375 self._repo.dirstate.drop(f)
1375 self._repo.dirstate.drop(f)
1376 return rejected
1376 return rejected
1377
1377
1378 def undelete(self, list):
1378 def undelete(self, list):
1379 pctxs = self.parents()
1379 pctxs = self.parents()
1380 with self._repo.wlock():
1380 with self._repo.wlock():
1381 ds = self._repo.dirstate
1381 ds = self._repo.dirstate
1382 for f in list:
1382 for f in list:
1383 if self._repo.dirstate[f] != 'r':
1383 if self._repo.dirstate[f] != 'r':
1384 self._repo.ui.warn(_("%s not removed!\n") % ds.pathto(f))
1384 self._repo.ui.warn(_("%s not removed!\n") % ds.pathto(f))
1385 else:
1385 else:
1386 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1386 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1387 t = fctx.data()
1387 t = fctx.data()
1388 self._repo.wwrite(f, t, fctx.flags())
1388 self._repo.wwrite(f, t, fctx.flags())
1389 self._repo.dirstate.normal(f)
1389 self._repo.dirstate.normal(f)
1390
1390
1391 def copy(self, source, dest):
1391 def copy(self, source, dest):
1392 try:
1392 try:
1393 st = self._repo.wvfs.lstat(dest)
1393 st = self._repo.wvfs.lstat(dest)
1394 except OSError as err:
1394 except OSError as err:
1395 if err.errno != errno.ENOENT:
1395 if err.errno != errno.ENOENT:
1396 raise
1396 raise
1397 self._repo.ui.warn(_("%s does not exist!\n")
1397 self._repo.ui.warn(_("%s does not exist!\n")
1398 % self._repo.dirstate.pathto(dest))
1398 % self._repo.dirstate.pathto(dest))
1399 return
1399 return
1400 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1400 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1401 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1401 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1402 "symbolic link\n")
1402 "symbolic link\n")
1403 % self._repo.dirstate.pathto(dest))
1403 % self._repo.dirstate.pathto(dest))
1404 else:
1404 else:
1405 with self._repo.wlock():
1405 with self._repo.wlock():
1406 if self._repo.dirstate[dest] in '?':
1406 if self._repo.dirstate[dest] in '?':
1407 self._repo.dirstate.add(dest)
1407 self._repo.dirstate.add(dest)
1408 elif self._repo.dirstate[dest] in 'r':
1408 elif self._repo.dirstate[dest] in 'r':
1409 self._repo.dirstate.normallookup(dest)
1409 self._repo.dirstate.normallookup(dest)
1410 self._repo.dirstate.copy(source, dest)
1410 self._repo.dirstate.copy(source, dest)
1411
1411
1412 def match(self, pats=None, include=None, exclude=None, default='glob',
1412 def match(self, pats=None, include=None, exclude=None, default='glob',
1413 listsubrepos=False, badfn=None):
1413 listsubrepos=False, badfn=None):
1414 r = self._repo
1414 r = self._repo
1415
1415
1416 # Only a case insensitive filesystem needs magic to translate user input
1416 # Only a case insensitive filesystem needs magic to translate user input
1417 # to actual case in the filesystem.
1417 # to actual case in the filesystem.
1418 icasefs = not util.fscasesensitive(r.root)
1418 icasefs = not util.fscasesensitive(r.root)
1419 return matchmod.match(r.root, r.getcwd(), pats, include, exclude,
1419 return matchmod.match(r.root, r.getcwd(), pats, include, exclude,
1420 default, auditor=r.auditor, ctx=self,
1420 default, auditor=r.auditor, ctx=self,
1421 listsubrepos=listsubrepos, badfn=badfn,
1421 listsubrepos=listsubrepos, badfn=badfn,
1422 icasefs=icasefs)
1422 icasefs=icasefs)
1423
1423
1424 def _filtersuspectsymlink(self, files):
1424 def _filtersuspectsymlink(self, files):
1425 if not files or self._repo.dirstate._checklink:
1425 if not files or self._repo.dirstate._checklink:
1426 return files
1426 return files
1427
1427
1428 # Symlink placeholders may get non-symlink-like contents
1428 # Symlink placeholders may get non-symlink-like contents
1429 # via user error or dereferencing by NFS or Samba servers,
1429 # via user error or dereferencing by NFS or Samba servers,
1430 # so we filter out any placeholders that don't look like a
1430 # so we filter out any placeholders that don't look like a
1431 # symlink
1431 # symlink
1432 sane = []
1432 sane = []
1433 for f in files:
1433 for f in files:
1434 if self.flags(f) == 'l':
1434 if self.flags(f) == 'l':
1435 d = self[f].data()
1435 d = self[f].data()
1436 if (d == '' or len(d) >= 1024 or '\n' in d
1436 if (d == '' or len(d) >= 1024 or '\n' in d
1437 or stringutil.binary(d)):
1437 or stringutil.binary(d)):
1438 self._repo.ui.debug('ignoring suspect symlink placeholder'
1438 self._repo.ui.debug('ignoring suspect symlink placeholder'
1439 ' "%s"\n' % f)
1439 ' "%s"\n' % f)
1440 continue
1440 continue
1441 sane.append(f)
1441 sane.append(f)
1442 return sane
1442 return sane
1443
1443
1444 def _checklookup(self, files):
1444 def _checklookup(self, files):
1445 # check for any possibly clean files
1445 # check for any possibly clean files
1446 if not files:
1446 if not files:
1447 return [], [], []
1447 return [], [], []
1448
1448
1449 modified = []
1449 modified = []
1450 deleted = []
1450 deleted = []
1451 fixup = []
1451 fixup = []
1452 pctx = self._parents[0]
1452 pctx = self._parents[0]
1453 # do a full compare of any files that might have changed
1453 # do a full compare of any files that might have changed
1454 for f in sorted(files):
1454 for f in sorted(files):
1455 try:
1455 try:
1456 # This will return True for a file that got replaced by a
1456 # This will return True for a file that got replaced by a
1457 # directory in the interim, but fixing that is pretty hard.
1457 # directory in the interim, but fixing that is pretty hard.
1458 if (f not in pctx or self.flags(f) != pctx.flags(f)
1458 if (f not in pctx or self.flags(f) != pctx.flags(f)
1459 or pctx[f].cmp(self[f])):
1459 or pctx[f].cmp(self[f])):
1460 modified.append(f)
1460 modified.append(f)
1461 else:
1461 else:
1462 fixup.append(f)
1462 fixup.append(f)
1463 except (IOError, OSError):
1463 except (IOError, OSError):
1464 # A file become inaccessible in between? Mark it as deleted,
1464 # A file become inaccessible in between? Mark it as deleted,
1465 # matching dirstate behavior (issue5584).
1465 # matching dirstate behavior (issue5584).
1466 # The dirstate has more complex behavior around whether a
1466 # The dirstate has more complex behavior around whether a
1467 # missing file matches a directory, etc, but we don't need to
1467 # missing file matches a directory, etc, but we don't need to
1468 # bother with that: if f has made it to this point, we're sure
1468 # bother with that: if f has made it to this point, we're sure
1469 # it's in the dirstate.
1469 # it's in the dirstate.
1470 deleted.append(f)
1470 deleted.append(f)
1471
1471
1472 return modified, deleted, fixup
1472 return modified, deleted, fixup
1473
1473
1474 def _poststatusfixup(self, status, fixup):
1474 def _poststatusfixup(self, status, fixup):
1475 """update dirstate for files that are actually clean"""
1475 """update dirstate for files that are actually clean"""
1476 poststatus = self._repo.postdsstatus()
1476 poststatus = self._repo.postdsstatus()
1477 if fixup or poststatus:
1477 if fixup or poststatus:
1478 try:
1478 try:
1479 oldid = self._repo.dirstate.identity()
1479 oldid = self._repo.dirstate.identity()
1480
1480
1481 # updating the dirstate is optional
1481 # updating the dirstate is optional
1482 # so we don't wait on the lock
1482 # so we don't wait on the lock
1483 # wlock can invalidate the dirstate, so cache normal _after_
1483 # wlock can invalidate the dirstate, so cache normal _after_
1484 # taking the lock
1484 # taking the lock
1485 with self._repo.wlock(False):
1485 with self._repo.wlock(False):
1486 if self._repo.dirstate.identity() == oldid:
1486 if self._repo.dirstate.identity() == oldid:
1487 if fixup:
1487 if fixup:
1488 normal = self._repo.dirstate.normal
1488 normal = self._repo.dirstate.normal
1489 for f in fixup:
1489 for f in fixup:
1490 normal(f)
1490 normal(f)
1491 # write changes out explicitly, because nesting
1491 # write changes out explicitly, because nesting
1492 # wlock at runtime may prevent 'wlock.release()'
1492 # wlock at runtime may prevent 'wlock.release()'
1493 # after this block from doing so for subsequent
1493 # after this block from doing so for subsequent
1494 # changing files
1494 # changing files
1495 tr = self._repo.currenttransaction()
1495 tr = self._repo.currenttransaction()
1496 self._repo.dirstate.write(tr)
1496 self._repo.dirstate.write(tr)
1497
1497
1498 if poststatus:
1498 if poststatus:
1499 for ps in poststatus:
1499 for ps in poststatus:
1500 ps(self, status)
1500 ps(self, status)
1501 else:
1501 else:
1502 # in this case, writing changes out breaks
1502 # in this case, writing changes out breaks
1503 # consistency, because .hg/dirstate was
1503 # consistency, because .hg/dirstate was
1504 # already changed simultaneously after last
1504 # already changed simultaneously after last
1505 # caching (see also issue5584 for detail)
1505 # caching (see also issue5584 for detail)
1506 self._repo.ui.debug('skip updating dirstate: '
1506 self._repo.ui.debug('skip updating dirstate: '
1507 'identity mismatch\n')
1507 'identity mismatch\n')
1508 except error.LockError:
1508 except error.LockError:
1509 pass
1509 pass
1510 finally:
1510 finally:
1511 # Even if the wlock couldn't be grabbed, clear out the list.
1511 # Even if the wlock couldn't be grabbed, clear out the list.
1512 self._repo.clearpostdsstatus()
1512 self._repo.clearpostdsstatus()
1513
1513
1514 def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
1514 def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
1515 '''Gets the status from the dirstate -- internal use only.'''
1515 '''Gets the status from the dirstate -- internal use only.'''
1516 subrepos = []
1516 subrepos = []
1517 if '.hgsub' in self:
1517 if '.hgsub' in self:
1518 subrepos = sorted(self.substate)
1518 subrepos = sorted(self.substate)
1519 cmp, s = self._repo.dirstate.status(match, subrepos, ignored=ignored,
1519 cmp, s = self._repo.dirstate.status(match, subrepos, ignored=ignored,
1520 clean=clean, unknown=unknown)
1520 clean=clean, unknown=unknown)
1521
1521
1522 # check for any possibly clean files
1522 # check for any possibly clean files
1523 fixup = []
1523 fixup = []
1524 if cmp:
1524 if cmp:
1525 modified2, deleted2, fixup = self._checklookup(cmp)
1525 modified2, deleted2, fixup = self._checklookup(cmp)
1526 s.modified.extend(modified2)
1526 s.modified.extend(modified2)
1527 s.deleted.extend(deleted2)
1527 s.deleted.extend(deleted2)
1528
1528
1529 if fixup and clean:
1529 if fixup and clean:
1530 s.clean.extend(fixup)
1530 s.clean.extend(fixup)
1531
1531
1532 self._poststatusfixup(s, fixup)
1532 self._poststatusfixup(s, fixup)
1533
1533
1534 if match.always():
1534 if match.always():
1535 # cache for performance
1535 # cache for performance
1536 if s.unknown or s.ignored or s.clean:
1536 if s.unknown or s.ignored or s.clean:
1537 # "_status" is cached with list*=False in the normal route
1537 # "_status" is cached with list*=False in the normal route
1538 self._status = scmutil.status(s.modified, s.added, s.removed,
1538 self._status = scmutil.status(s.modified, s.added, s.removed,
1539 s.deleted, [], [], [])
1539 s.deleted, [], [], [])
1540 else:
1540 else:
1541 self._status = s
1541 self._status = s
1542
1542
1543 return s
1543 return s
1544
1544
1545 @propertycache
1545 @propertycache
1546 def _manifest(self):
1546 def _manifest(self):
1547 """generate a manifest corresponding to the values in self._status
1547 """generate a manifest corresponding to the values in self._status
1548
1548
1549 This reuse the file nodeid from parent, but we use special node
1549 This reuse the file nodeid from parent, but we use special node
1550 identifiers for added and modified files. This is used by manifests
1550 identifiers for added and modified files. This is used by manifests
1551 merge to see that files are different and by update logic to avoid
1551 merge to see that files are different and by update logic to avoid
1552 deleting newly added files.
1552 deleting newly added files.
1553 """
1553 """
1554 return self._buildstatusmanifest(self._status)
1554 return self._buildstatusmanifest(self._status)
1555
1555
1556 def _buildstatusmanifest(self, status):
1556 def _buildstatusmanifest(self, status):
1557 """Builds a manifest that includes the given status results."""
1557 """Builds a manifest that includes the given status results."""
1558 parents = self.parents()
1558 parents = self.parents()
1559
1559
1560 man = parents[0].manifest().copy()
1560 man = parents[0].manifest().copy()
1561
1561
1562 ff = self._flagfunc
1562 ff = self._flagfunc
1563 for i, l in ((addednodeid, status.added),
1563 for i, l in ((addednodeid, status.added),
1564 (modifiednodeid, status.modified)):
1564 (modifiednodeid, status.modified)):
1565 for f in l:
1565 for f in l:
1566 man[f] = i
1566 man[f] = i
1567 try:
1567 try:
1568 man.setflag(f, ff(f))
1568 man.setflag(f, ff(f))
1569 except OSError:
1569 except OSError:
1570 pass
1570 pass
1571
1571
1572 for f in status.deleted + status.removed:
1572 for f in status.deleted + status.removed:
1573 if f in man:
1573 if f in man:
1574 del man[f]
1574 del man[f]
1575
1575
1576 return man
1576 return man
1577
1577
1578 def _buildstatus(self, other, s, match, listignored, listclean,
1578 def _buildstatus(self, other, s, match, listignored, listclean,
1579 listunknown):
1579 listunknown):
1580 """build a status with respect to another context
1580 """build a status with respect to another context
1581
1581
1582 This includes logic for maintaining the fast path of status when
1582 This includes logic for maintaining the fast path of status when
1583 comparing the working directory against its parent, which is to skip
1583 comparing the working directory against its parent, which is to skip
1584 building a new manifest if self (working directory) is not comparing
1584 building a new manifest if self (working directory) is not comparing
1585 against its parent (repo['.']).
1585 against its parent (repo['.']).
1586 """
1586 """
1587 s = self._dirstatestatus(match, listignored, listclean, listunknown)
1587 s = self._dirstatestatus(match, listignored, listclean, listunknown)
1588 # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
1588 # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
1589 # might have accidentally ended up with the entire contents of the file
1589 # might have accidentally ended up with the entire contents of the file
1590 # they are supposed to be linking to.
1590 # they are supposed to be linking to.
1591 s.modified[:] = self._filtersuspectsymlink(s.modified)
1591 s.modified[:] = self._filtersuspectsymlink(s.modified)
1592 if other != self._repo['.']:
1592 if other != self._repo['.']:
1593 s = super(workingctx, self)._buildstatus(other, s, match,
1593 s = super(workingctx, self)._buildstatus(other, s, match,
1594 listignored, listclean,
1594 listignored, listclean,
1595 listunknown)
1595 listunknown)
1596 return s
1596 return s
1597
1597
1598 def _matchstatus(self, other, match):
1598 def _matchstatus(self, other, match):
1599 """override the match method with a filter for directory patterns
1599 """override the match method with a filter for directory patterns
1600
1600
1601 We use inheritance to customize the match.bad method only in cases of
1601 We use inheritance to customize the match.bad method only in cases of
1602 workingctx since it belongs only to the working directory when
1602 workingctx since it belongs only to the working directory when
1603 comparing against the parent changeset.
1603 comparing against the parent changeset.
1604
1604
1605 If we aren't comparing against the working directory's parent, then we
1605 If we aren't comparing against the working directory's parent, then we
1606 just use the default match object sent to us.
1606 just use the default match object sent to us.
1607 """
1607 """
1608 if other != self._repo['.']:
1608 if other != self._repo['.']:
1609 def bad(f, msg):
1609 def bad(f, msg):
1610 # 'f' may be a directory pattern from 'match.files()',
1610 # 'f' may be a directory pattern from 'match.files()',
1611 # so 'f not in ctx1' is not enough
1611 # so 'f not in ctx1' is not enough
1612 if f not in other and not other.hasdir(f):
1612 if f not in other and not other.hasdir(f):
1613 self._repo.ui.warn('%s: %s\n' %
1613 self._repo.ui.warn('%s: %s\n' %
1614 (self._repo.dirstate.pathto(f), msg))
1614 (self._repo.dirstate.pathto(f), msg))
1615 match.bad = bad
1615 match.bad = bad
1616 return match
1616 return match
1617
1617
1618 def markcommitted(self, node):
1618 def markcommitted(self, node):
1619 super(workingctx, self).markcommitted(node)
1619 super(workingctx, self).markcommitted(node)
1620
1620
1621 sparse.aftercommit(self._repo, node)
1621 sparse.aftercommit(self._repo, node)
1622
1622
1623 class committablefilectx(basefilectx):
1623 class committablefilectx(basefilectx):
1624 """A committablefilectx provides common functionality for a file context
1624 """A committablefilectx provides common functionality for a file context
1625 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1625 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1626 def __init__(self, repo, path, filelog=None, ctx=None):
1626 def __init__(self, repo, path, filelog=None, ctx=None):
1627 self._repo = repo
1627 self._repo = repo
1628 self._path = path
1628 self._path = path
1629 self._changeid = None
1629 self._changeid = None
1630 self._filerev = self._filenode = None
1630 self._filerev = self._filenode = None
1631
1631
1632 if filelog is not None:
1632 if filelog is not None:
1633 self._filelog = filelog
1633 self._filelog = filelog
1634 if ctx:
1634 if ctx:
1635 self._changectx = ctx
1635 self._changectx = ctx
1636
1636
1637 def __nonzero__(self):
1637 def __nonzero__(self):
1638 return True
1638 return True
1639
1639
1640 __bool__ = __nonzero__
1640 __bool__ = __nonzero__
1641
1641
1642 def linkrev(self):
1642 def linkrev(self):
1643 # linked to self._changectx no matter if file is modified or not
1643 # linked to self._changectx no matter if file is modified or not
1644 return self.rev()
1644 return self.rev()
1645
1645
1646 def parents(self):
1646 def parents(self):
1647 '''return parent filectxs, following copies if necessary'''
1647 '''return parent filectxs, following copies if necessary'''
1648 def filenode(ctx, path):
1648 def filenode(ctx, path):
1649 return ctx._manifest.get(path, nullid)
1649 return ctx._manifest.get(path, nullid)
1650
1650
1651 path = self._path
1651 path = self._path
1652 fl = self._filelog
1652 fl = self._filelog
1653 pcl = self._changectx._parents
1653 pcl = self._changectx._parents
1654 renamed = self.renamed()
1654 renamed = self.renamed()
1655
1655
1656 if renamed:
1656 if renamed:
1657 pl = [renamed + (None,)]
1657 pl = [renamed + (None,)]
1658 else:
1658 else:
1659 pl = [(path, filenode(pcl[0], path), fl)]
1659 pl = [(path, filenode(pcl[0], path), fl)]
1660
1660
1661 for pc in pcl[1:]:
1661 for pc in pcl[1:]:
1662 pl.append((path, filenode(pc, path), fl))
1662 pl.append((path, filenode(pc, path), fl))
1663
1663
1664 return [self._parentfilectx(p, fileid=n, filelog=l)
1664 return [self._parentfilectx(p, fileid=n, filelog=l)
1665 for p, n, l in pl if n != nullid]
1665 for p, n, l in pl if n != nullid]
1666
1666
1667 def children(self):
1667 def children(self):
1668 return []
1668 return []
1669
1669
1670 class workingfilectx(committablefilectx):
1670 class workingfilectx(committablefilectx):
1671 """A workingfilectx object makes access to data related to a particular
1671 """A workingfilectx object makes access to data related to a particular
1672 file in the working directory convenient."""
1672 file in the working directory convenient."""
1673 def __init__(self, repo, path, filelog=None, workingctx=None):
1673 def __init__(self, repo, path, filelog=None, workingctx=None):
1674 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1674 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1675
1675
1676 @propertycache
1676 @propertycache
1677 def _changectx(self):
1677 def _changectx(self):
1678 return workingctx(self._repo)
1678 return workingctx(self._repo)
1679
1679
1680 def data(self):
1680 def data(self):
1681 return self._repo.wread(self._path)
1681 return self._repo.wread(self._path)
1682 def renamed(self):
1682 def renamed(self):
1683 rp = self._repo.dirstate.copied(self._path)
1683 rp = self._repo.dirstate.copied(self._path)
1684 if not rp:
1684 if not rp:
1685 return None
1685 return None
1686 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1686 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1687
1687
1688 def size(self):
1688 def size(self):
1689 return self._repo.wvfs.lstat(self._path).st_size
1689 return self._repo.wvfs.lstat(self._path).st_size
1690 def date(self):
1690 def date(self):
1691 t, tz = self._changectx.date()
1691 t, tz = self._changectx.date()
1692 try:
1692 try:
1693 return (self._repo.wvfs.lstat(self._path)[stat.ST_MTIME], tz)
1693 return (self._repo.wvfs.lstat(self._path)[stat.ST_MTIME], tz)
1694 except OSError as err:
1694 except OSError as err:
1695 if err.errno != errno.ENOENT:
1695 if err.errno != errno.ENOENT:
1696 raise
1696 raise
1697 return (t, tz)
1697 return (t, tz)
1698
1698
1699 def exists(self):
1699 def exists(self):
1700 return self._repo.wvfs.exists(self._path)
1700 return self._repo.wvfs.exists(self._path)
1701
1701
1702 def lexists(self):
1702 def lexists(self):
1703 return self._repo.wvfs.lexists(self._path)
1703 return self._repo.wvfs.lexists(self._path)
1704
1704
1705 def audit(self):
1705 def audit(self):
1706 return self._repo.wvfs.audit(self._path)
1706 return self._repo.wvfs.audit(self._path)
1707
1707
1708 def cmp(self, fctx):
1708 def cmp(self, fctx):
1709 """compare with other file context
1709 """compare with other file context
1710
1710
1711 returns True if different than fctx.
1711 returns True if different than fctx.
1712 """
1712 """
1713 # fctx should be a filectx (not a workingfilectx)
1713 # fctx should be a filectx (not a workingfilectx)
1714 # invert comparison to reuse the same code path
1714 # invert comparison to reuse the same code path
1715 return fctx.cmp(self)
1715 return fctx.cmp(self)
1716
1716
1717 def remove(self, ignoremissing=False):
1717 def remove(self, ignoremissing=False):
1718 """wraps unlink for a repo's working directory"""
1718 """wraps unlink for a repo's working directory"""
1719 rmdir = self._repo.ui.configbool('experimental', 'removeemptydirs')
1719 rmdir = self._repo.ui.configbool('experimental', 'removeemptydirs')
1720 self._repo.wvfs.unlinkpath(self._path, ignoremissing=ignoremissing,
1720 self._repo.wvfs.unlinkpath(self._path, ignoremissing=ignoremissing,
1721 rmdir=rmdir)
1721 rmdir=rmdir)
1722
1722
1723 def write(self, data, flags, backgroundclose=False, **kwargs):
1723 def write(self, data, flags, backgroundclose=False, **kwargs):
1724 """wraps repo.wwrite"""
1724 """wraps repo.wwrite"""
1725 self._repo.wwrite(self._path, data, flags,
1725 self._repo.wwrite(self._path, data, flags,
1726 backgroundclose=backgroundclose,
1726 backgroundclose=backgroundclose,
1727 **kwargs)
1727 **kwargs)
1728
1728
1729 def markcopied(self, src):
1729 def markcopied(self, src):
1730 """marks this file a copy of `src`"""
1730 """marks this file a copy of `src`"""
1731 if self._repo.dirstate[self._path] in "nma":
1731 if self._repo.dirstate[self._path] in "nma":
1732 self._repo.dirstate.copy(src, self._path)
1732 self._repo.dirstate.copy(src, self._path)
1733
1733
1734 def clearunknown(self):
1734 def clearunknown(self):
1735 """Removes conflicting items in the working directory so that
1735 """Removes conflicting items in the working directory so that
1736 ``write()`` can be called successfully.
1736 ``write()`` can be called successfully.
1737 """
1737 """
1738 wvfs = self._repo.wvfs
1738 wvfs = self._repo.wvfs
1739 f = self._path
1739 f = self._path
1740 wvfs.audit(f)
1740 wvfs.audit(f)
1741 if wvfs.isdir(f) and not wvfs.islink(f):
1741 if wvfs.isdir(f) and not wvfs.islink(f):
1742 wvfs.rmtree(f, forcibly=True)
1742 wvfs.rmtree(f, forcibly=True)
1743 if self._repo.ui.configbool('experimental', 'merge.checkpathconflicts'):
1743 if self._repo.ui.configbool('experimental', 'merge.checkpathconflicts'):
1744 for p in reversed(list(util.finddirs(f))):
1744 for p in reversed(list(util.finddirs(f))):
1745 if wvfs.isfileorlink(p):
1745 if wvfs.isfileorlink(p):
1746 wvfs.unlink(p)
1746 wvfs.unlink(p)
1747 break
1747 break
1748
1748
1749 def setflags(self, l, x):
1749 def setflags(self, l, x):
1750 self._repo.wvfs.setflags(self._path, l, x)
1750 self._repo.wvfs.setflags(self._path, l, x)
1751
1751
1752 class overlayworkingctx(committablectx):
1752 class overlayworkingctx(committablectx):
1753 """Wraps another mutable context with a write-back cache that can be
1753 """Wraps another mutable context with a write-back cache that can be
1754 converted into a commit context.
1754 converted into a commit context.
1755
1755
1756 self._cache[path] maps to a dict with keys: {
1756 self._cache[path] maps to a dict with keys: {
1757 'exists': bool?
1757 'exists': bool?
1758 'date': date?
1758 'date': date?
1759 'data': str?
1759 'data': str?
1760 'flags': str?
1760 'flags': str?
1761 'copied': str? (path or None)
1761 'copied': str? (path or None)
1762 }
1762 }
1763 If `exists` is True, `flags` must be non-None and 'date' is non-None. If it
1763 If `exists` is True, `flags` must be non-None and 'date' is non-None. If it
1764 is `False`, the file was deleted.
1764 is `False`, the file was deleted.
1765 """
1765 """
1766
1766
1767 def __init__(self, repo):
1767 def __init__(self, repo):
1768 super(overlayworkingctx, self).__init__(repo)
1768 super(overlayworkingctx, self).__init__(repo)
1769 self.clean()
1769 self.clean()
1770
1770
1771 def setbase(self, wrappedctx):
1771 def setbase(self, wrappedctx):
1772 self._wrappedctx = wrappedctx
1772 self._wrappedctx = wrappedctx
1773 self._parents = [wrappedctx]
1773 self._parents = [wrappedctx]
1774 # Drop old manifest cache as it is now out of date.
1774 # Drop old manifest cache as it is now out of date.
1775 # This is necessary when, e.g., rebasing several nodes with one
1775 # This is necessary when, e.g., rebasing several nodes with one
1776 # ``overlayworkingctx`` (e.g. with --collapse).
1776 # ``overlayworkingctx`` (e.g. with --collapse).
1777 util.clearcachedproperty(self, '_manifest')
1777 util.clearcachedproperty(self, '_manifest')
1778
1778
1779 def data(self, path):
1779 def data(self, path):
1780 if self.isdirty(path):
1780 if self.isdirty(path):
1781 if self._cache[path]['exists']:
1781 if self._cache[path]['exists']:
1782 if self._cache[path]['data']:
1782 if self._cache[path]['data']:
1783 return self._cache[path]['data']
1783 return self._cache[path]['data']
1784 else:
1784 else:
1785 # Must fallback here, too, because we only set flags.
1785 # Must fallback here, too, because we only set flags.
1786 return self._wrappedctx[path].data()
1786 return self._wrappedctx[path].data()
1787 else:
1787 else:
1788 raise error.ProgrammingError("No such file or directory: %s" %
1788 raise error.ProgrammingError("No such file or directory: %s" %
1789 path)
1789 path)
1790 else:
1790 else:
1791 return self._wrappedctx[path].data()
1791 return self._wrappedctx[path].data()
1792
1792
1793 @propertycache
1793 @propertycache
1794 def _manifest(self):
1794 def _manifest(self):
1795 parents = self.parents()
1795 parents = self.parents()
1796 man = parents[0].manifest().copy()
1796 man = parents[0].manifest().copy()
1797
1797
1798 flag = self._flagfunc
1798 flag = self._flagfunc
1799 for path in self.added():
1799 for path in self.added():
1800 man[path] = addednodeid
1800 man[path] = addednodeid
1801 man.setflag(path, flag(path))
1801 man.setflag(path, flag(path))
1802 for path in self.modified():
1802 for path in self.modified():
1803 man[path] = modifiednodeid
1803 man[path] = modifiednodeid
1804 man.setflag(path, flag(path))
1804 man.setflag(path, flag(path))
1805 for path in self.removed():
1805 for path in self.removed():
1806 del man[path]
1806 del man[path]
1807 return man
1807 return man
1808
1808
1809 @propertycache
1809 @propertycache
1810 def _flagfunc(self):
1810 def _flagfunc(self):
1811 def f(path):
1811 def f(path):
1812 return self._cache[path]['flags']
1812 return self._cache[path]['flags']
1813 return f
1813 return f
1814
1814
1815 def files(self):
1815 def files(self):
1816 return sorted(self.added() + self.modified() + self.removed())
1816 return sorted(self.added() + self.modified() + self.removed())
1817
1817
1818 def modified(self):
1818 def modified(self):
1819 return [f for f in self._cache.keys() if self._cache[f]['exists'] and
1819 return [f for f in self._cache.keys() if self._cache[f]['exists'] and
1820 self._existsinparent(f)]
1820 self._existsinparent(f)]
1821
1821
1822 def added(self):
1822 def added(self):
1823 return [f for f in self._cache.keys() if self._cache[f]['exists'] and
1823 return [f for f in self._cache.keys() if self._cache[f]['exists'] and
1824 not self._existsinparent(f)]
1824 not self._existsinparent(f)]
1825
1825
1826 def removed(self):
1826 def removed(self):
1827 return [f for f in self._cache.keys() if
1827 return [f for f in self._cache.keys() if
1828 not self._cache[f]['exists'] and self._existsinparent(f)]
1828 not self._cache[f]['exists'] and self._existsinparent(f)]
1829
1829
1830 def isinmemory(self):
1830 def isinmemory(self):
1831 return True
1831 return True
1832
1832
1833 def filedate(self, path):
1833 def filedate(self, path):
1834 if self.isdirty(path):
1834 if self.isdirty(path):
1835 return self._cache[path]['date']
1835 return self._cache[path]['date']
1836 else:
1836 else:
1837 return self._wrappedctx[path].date()
1837 return self._wrappedctx[path].date()
1838
1838
1839 def markcopied(self, path, origin):
1839 def markcopied(self, path, origin):
1840 if self.isdirty(path):
1840 if self.isdirty(path):
1841 self._cache[path]['copied'] = origin
1841 self._cache[path]['copied'] = origin
1842 else:
1842 else:
1843 raise error.ProgrammingError('markcopied() called on clean context')
1843 raise error.ProgrammingError('markcopied() called on clean context')
1844
1844
1845 def copydata(self, path):
1845 def copydata(self, path):
1846 if self.isdirty(path):
1846 if self.isdirty(path):
1847 return self._cache[path]['copied']
1847 return self._cache[path]['copied']
1848 else:
1848 else:
1849 raise error.ProgrammingError('copydata() called on clean context')
1849 raise error.ProgrammingError('copydata() called on clean context')
1850
1850
1851 def flags(self, path):
1851 def flags(self, path):
1852 if self.isdirty(path):
1852 if self.isdirty(path):
1853 if self._cache[path]['exists']:
1853 if self._cache[path]['exists']:
1854 return self._cache[path]['flags']
1854 return self._cache[path]['flags']
1855 else:
1855 else:
1856 raise error.ProgrammingError("No such file or directory: %s" %
1856 raise error.ProgrammingError("No such file or directory: %s" %
1857 self._path)
1857 self._path)
1858 else:
1858 else:
1859 return self._wrappedctx[path].flags()
1859 return self._wrappedctx[path].flags()
1860
1860
1861 def _existsinparent(self, path):
1861 def _existsinparent(self, path):
1862 try:
1862 try:
1863 # ``commitctx` raises a ``ManifestLookupError`` if a path does not
1863 # ``commitctx` raises a ``ManifestLookupError`` if a path does not
1864 # exist, unlike ``workingctx``, which returns a ``workingfilectx``
1864 # exist, unlike ``workingctx``, which returns a ``workingfilectx``
1865 # with an ``exists()`` function.
1865 # with an ``exists()`` function.
1866 self._wrappedctx[path]
1866 self._wrappedctx[path]
1867 return True
1867 return True
1868 except error.ManifestLookupError:
1868 except error.ManifestLookupError:
1869 return False
1869 return False
1870
1870
1871 def _auditconflicts(self, path):
1871 def _auditconflicts(self, path):
1872 """Replicates conflict checks done by wvfs.write().
1872 """Replicates conflict checks done by wvfs.write().
1873
1873
1874 Since we never write to the filesystem and never call `applyupdates` in
1874 Since we never write to the filesystem and never call `applyupdates` in
1875 IMM, we'll never check that a path is actually writable -- e.g., because
1875 IMM, we'll never check that a path is actually writable -- e.g., because
1876 it adds `a/foo`, but `a` is actually a file in the other commit.
1876 it adds `a/foo`, but `a` is actually a file in the other commit.
1877 """
1877 """
1878 def fail(path, component):
1878 def fail(path, component):
1879 # p1() is the base and we're receiving "writes" for p2()'s
1879 # p1() is the base and we're receiving "writes" for p2()'s
1880 # files.
1880 # files.
1881 if 'l' in self.p1()[component].flags():
1881 if 'l' in self.p1()[component].flags():
1882 raise error.Abort("error: %s conflicts with symlink %s "
1882 raise error.Abort("error: %s conflicts with symlink %s "
1883 "in %s." % (path, component,
1883 "in %s." % (path, component,
1884 self.p1().rev()))
1884 self.p1().rev()))
1885 else:
1885 else:
1886 raise error.Abort("error: '%s' conflicts with file '%s' in "
1886 raise error.Abort("error: '%s' conflicts with file '%s' in "
1887 "%s." % (path, component,
1887 "%s." % (path, component,
1888 self.p1().rev()))
1888 self.p1().rev()))
1889
1889
1890 # Test that each new directory to be created to write this path from p2
1890 # Test that each new directory to be created to write this path from p2
1891 # is not a file in p1.
1891 # is not a file in p1.
1892 components = path.split('/')
1892 components = path.split('/')
1893 for i in xrange(len(components)):
1893 for i in xrange(len(components)):
1894 component = "/".join(components[0:i])
1894 component = "/".join(components[0:i])
1895 if component in self.p1():
1895 if component in self.p1():
1896 fail(path, component)
1896 fail(path, component)
1897
1897
1898 # Test the other direction -- that this path from p2 isn't a directory
1898 # Test the other direction -- that this path from p2 isn't a directory
1899 # in p1 (test that p1 doesn't any paths matching `path/*`).
1899 # in p1 (test that p1 doesn't any paths matching `path/*`).
1900 match = matchmod.match('/', '', [path + '/'], default=b'relpath')
1900 match = matchmod.match('/', '', [path + '/'], default=b'relpath')
1901 matches = self.p1().manifest().matches(match)
1901 matches = self.p1().manifest().matches(match)
1902 if len(matches) > 0:
1902 if len(matches) > 0:
1903 if len(matches) == 1 and matches.keys()[0] == path:
1903 if len(matches) == 1 and matches.keys()[0] == path:
1904 return
1904 return
1905 raise error.Abort("error: file '%s' cannot be written because "
1905 raise error.Abort("error: file '%s' cannot be written because "
1906 " '%s/' is a folder in %s (containing %d "
1906 " '%s/' is a folder in %s (containing %d "
1907 "entries: %s)"
1907 "entries: %s)"
1908 % (path, path, self.p1(), len(matches),
1908 % (path, path, self.p1(), len(matches),
1909 ', '.join(matches.keys())))
1909 ', '.join(matches.keys())))
1910
1910
1911 def write(self, path, data, flags='', **kwargs):
1911 def write(self, path, data, flags='', **kwargs):
1912 if data is None:
1912 if data is None:
1913 raise error.ProgrammingError("data must be non-None")
1913 raise error.ProgrammingError("data must be non-None")
1914 self._auditconflicts(path)
1914 self._auditconflicts(path)
1915 self._markdirty(path, exists=True, data=data, date=dateutil.makedate(),
1915 self._markdirty(path, exists=True, data=data, date=dateutil.makedate(),
1916 flags=flags)
1916 flags=flags)
1917
1917
1918 def setflags(self, path, l, x):
1918 def setflags(self, path, l, x):
1919 self._markdirty(path, exists=True, date=dateutil.makedate(),
1919 self._markdirty(path, exists=True, date=dateutil.makedate(),
1920 flags=(l and 'l' or '') + (x and 'x' or ''))
1920 flags=(l and 'l' or '') + (x and 'x' or ''))
1921
1921
1922 def remove(self, path):
1922 def remove(self, path):
1923 self._markdirty(path, exists=False)
1923 self._markdirty(path, exists=False)
1924
1924
1925 def exists(self, path):
1925 def exists(self, path):
1926 """exists behaves like `lexists`, but needs to follow symlinks and
1926 """exists behaves like `lexists`, but needs to follow symlinks and
1927 return False if they are broken.
1927 return False if they are broken.
1928 """
1928 """
1929 if self.isdirty(path):
1929 if self.isdirty(path):
1930 # If this path exists and is a symlink, "follow" it by calling
1930 # If this path exists and is a symlink, "follow" it by calling
1931 # exists on the destination path.
1931 # exists on the destination path.
1932 if (self._cache[path]['exists'] and
1932 if (self._cache[path]['exists'] and
1933 'l' in self._cache[path]['flags']):
1933 'l' in self._cache[path]['flags']):
1934 return self.exists(self._cache[path]['data'].strip())
1934 return self.exists(self._cache[path]['data'].strip())
1935 else:
1935 else:
1936 return self._cache[path]['exists']
1936 return self._cache[path]['exists']
1937
1937
1938 return self._existsinparent(path)
1938 return self._existsinparent(path)
1939
1939
1940 def lexists(self, path):
1940 def lexists(self, path):
1941 """lexists returns True if the path exists"""
1941 """lexists returns True if the path exists"""
1942 if self.isdirty(path):
1942 if self.isdirty(path):
1943 return self._cache[path]['exists']
1943 return self._cache[path]['exists']
1944
1944
1945 return self._existsinparent(path)
1945 return self._existsinparent(path)
1946
1946
1947 def size(self, path):
1947 def size(self, path):
1948 if self.isdirty(path):
1948 if self.isdirty(path):
1949 if self._cache[path]['exists']:
1949 if self._cache[path]['exists']:
1950 return len(self._cache[path]['data'])
1950 return len(self._cache[path]['data'])
1951 else:
1951 else:
1952 raise error.ProgrammingError("No such file or directory: %s" %
1952 raise error.ProgrammingError("No such file or directory: %s" %
1953 self._path)
1953 self._path)
1954 return self._wrappedctx[path].size()
1954 return self._wrappedctx[path].size()
1955
1955
1956 def tomemctx(self, text, branch=None, extra=None, date=None, parents=None,
1956 def tomemctx(self, text, branch=None, extra=None, date=None, parents=None,
1957 user=None, editor=None):
1957 user=None, editor=None):
1958 """Converts this ``overlayworkingctx`` into a ``memctx`` ready to be
1958 """Converts this ``overlayworkingctx`` into a ``memctx`` ready to be
1959 committed.
1959 committed.
1960
1960
1961 ``text`` is the commit message.
1961 ``text`` is the commit message.
1962 ``parents`` (optional) are rev numbers.
1962 ``parents`` (optional) are rev numbers.
1963 """
1963 """
1964 # Default parents to the wrapped contexts' if not passed.
1964 # Default parents to the wrapped contexts' if not passed.
1965 if parents is None:
1965 if parents is None:
1966 parents = self._wrappedctx.parents()
1966 parents = self._wrappedctx.parents()
1967 if len(parents) == 1:
1967 if len(parents) == 1:
1968 parents = (parents[0], None)
1968 parents = (parents[0], None)
1969
1969
1970 # ``parents`` is passed as rev numbers; convert to ``commitctxs``.
1970 # ``parents`` is passed as rev numbers; convert to ``commitctxs``.
1971 if parents[1] is None:
1971 if parents[1] is None:
1972 parents = (self._repo[parents[0]], None)
1972 parents = (self._repo[parents[0]], None)
1973 else:
1973 else:
1974 parents = (self._repo[parents[0]], self._repo[parents[1]])
1974 parents = (self._repo[parents[0]], self._repo[parents[1]])
1975
1975
1976 files = self._cache.keys()
1976 files = self._cache.keys()
1977 def getfile(repo, memctx, path):
1977 def getfile(repo, memctx, path):
1978 if self._cache[path]['exists']:
1978 if self._cache[path]['exists']:
1979 return memfilectx(repo, memctx, path,
1979 return memfilectx(repo, memctx, path,
1980 self._cache[path]['data'],
1980 self._cache[path]['data'],
1981 'l' in self._cache[path]['flags'],
1981 'l' in self._cache[path]['flags'],
1982 'x' in self._cache[path]['flags'],
1982 'x' in self._cache[path]['flags'],
1983 self._cache[path]['copied'])
1983 self._cache[path]['copied'])
1984 else:
1984 else:
1985 # Returning None, but including the path in `files`, is
1985 # Returning None, but including the path in `files`, is
1986 # necessary for memctx to register a deletion.
1986 # necessary for memctx to register a deletion.
1987 return None
1987 return None
1988 return memctx(self._repo, parents, text, files, getfile, date=date,
1988 return memctx(self._repo, parents, text, files, getfile, date=date,
1989 extra=extra, user=user, branch=branch, editor=editor)
1989 extra=extra, user=user, branch=branch, editor=editor)
1990
1990
1991 def isdirty(self, path):
1991 def isdirty(self, path):
1992 return path in self._cache
1992 return path in self._cache
1993
1993
1994 def isempty(self):
1994 def isempty(self):
1995 # We need to discard any keys that are actually clean before the empty
1995 # We need to discard any keys that are actually clean before the empty
1996 # commit check.
1996 # commit check.
1997 self._compact()
1997 self._compact()
1998 return len(self._cache) == 0
1998 return len(self._cache) == 0
1999
1999
2000 def clean(self):
2000 def clean(self):
2001 self._cache = {}
2001 self._cache = {}
2002
2002
2003 def _compact(self):
2003 def _compact(self):
2004 """Removes keys from the cache that are actually clean, by comparing
2004 """Removes keys from the cache that are actually clean, by comparing
2005 them with the underlying context.
2005 them with the underlying context.
2006
2006
2007 This can occur during the merge process, e.g. by passing --tool :local
2007 This can occur during the merge process, e.g. by passing --tool :local
2008 to resolve a conflict.
2008 to resolve a conflict.
2009 """
2009 """
2010 keys = []
2010 keys = []
2011 for path in self._cache.keys():
2011 for path in self._cache.keys():
2012 cache = self._cache[path]
2012 cache = self._cache[path]
2013 try:
2013 try:
2014 underlying = self._wrappedctx[path]
2014 underlying = self._wrappedctx[path]
2015 if (underlying.data() == cache['data'] and
2015 if (underlying.data() == cache['data'] and
2016 underlying.flags() == cache['flags']):
2016 underlying.flags() == cache['flags']):
2017 keys.append(path)
2017 keys.append(path)
2018 except error.ManifestLookupError:
2018 except error.ManifestLookupError:
2019 # Path not in the underlying manifest (created).
2019 # Path not in the underlying manifest (created).
2020 continue
2020 continue
2021
2021
2022 for path in keys:
2022 for path in keys:
2023 del self._cache[path]
2023 del self._cache[path]
2024 return keys
2024 return keys
2025
2025
2026 def _markdirty(self, path, exists, data=None, date=None, flags=''):
2026 def _markdirty(self, path, exists, data=None, date=None, flags=''):
2027 self._cache[path] = {
2027 self._cache[path] = {
2028 'exists': exists,
2028 'exists': exists,
2029 'data': data,
2029 'data': data,
2030 'date': date,
2030 'date': date,
2031 'flags': flags,
2031 'flags': flags,
2032 'copied': None,
2032 'copied': None,
2033 }
2033 }
2034
2034
2035 def filectx(self, path, filelog=None):
2035 def filectx(self, path, filelog=None):
2036 return overlayworkingfilectx(self._repo, path, parent=self,
2036 return overlayworkingfilectx(self._repo, path, parent=self,
2037 filelog=filelog)
2037 filelog=filelog)
2038
2038
2039 class overlayworkingfilectx(committablefilectx):
2039 class overlayworkingfilectx(committablefilectx):
2040 """Wrap a ``workingfilectx`` but intercepts all writes into an in-memory
2040 """Wrap a ``workingfilectx`` but intercepts all writes into an in-memory
2041 cache, which can be flushed through later by calling ``flush()``."""
2041 cache, which can be flushed through later by calling ``flush()``."""
2042
2042
2043 def __init__(self, repo, path, filelog=None, parent=None):
2043 def __init__(self, repo, path, filelog=None, parent=None):
2044 super(overlayworkingfilectx, self).__init__(repo, path, filelog,
2044 super(overlayworkingfilectx, self).__init__(repo, path, filelog,
2045 parent)
2045 parent)
2046 self._repo = repo
2046 self._repo = repo
2047 self._parent = parent
2047 self._parent = parent
2048 self._path = path
2048 self._path = path
2049
2049
2050 def cmp(self, fctx):
2050 def cmp(self, fctx):
2051 return self.data() != fctx.data()
2051 return self.data() != fctx.data()
2052
2052
2053 def changectx(self):
2053 def changectx(self):
2054 return self._parent
2054 return self._parent
2055
2055
2056 def data(self):
2056 def data(self):
2057 return self._parent.data(self._path)
2057 return self._parent.data(self._path)
2058
2058
2059 def date(self):
2059 def date(self):
2060 return self._parent.filedate(self._path)
2060 return self._parent.filedate(self._path)
2061
2061
2062 def exists(self):
2062 def exists(self):
2063 return self.lexists()
2063 return self.lexists()
2064
2064
2065 def lexists(self):
2065 def lexists(self):
2066 return self._parent.exists(self._path)
2066 return self._parent.exists(self._path)
2067
2067
2068 def renamed(self):
2068 def renamed(self):
2069 path = self._parent.copydata(self._path)
2069 path = self._parent.copydata(self._path)
2070 if not path:
2070 if not path:
2071 return None
2071 return None
2072 return path, self._changectx._parents[0]._manifest.get(path, nullid)
2072 return path, self._changectx._parents[0]._manifest.get(path, nullid)
2073
2073
2074 def size(self):
2074 def size(self):
2075 return self._parent.size(self._path)
2075 return self._parent.size(self._path)
2076
2076
2077 def markcopied(self, origin):
2077 def markcopied(self, origin):
2078 self._parent.markcopied(self._path, origin)
2078 self._parent.markcopied(self._path, origin)
2079
2079
2080 def audit(self):
2080 def audit(self):
2081 pass
2081 pass
2082
2082
2083 def flags(self):
2083 def flags(self):
2084 return self._parent.flags(self._path)
2084 return self._parent.flags(self._path)
2085
2085
2086 def setflags(self, islink, isexec):
2086 def setflags(self, islink, isexec):
2087 return self._parent.setflags(self._path, islink, isexec)
2087 return self._parent.setflags(self._path, islink, isexec)
2088
2088
2089 def write(self, data, flags, backgroundclose=False, **kwargs):
2089 def write(self, data, flags, backgroundclose=False, **kwargs):
2090 return self._parent.write(self._path, data, flags, **kwargs)
2090 return self._parent.write(self._path, data, flags, **kwargs)
2091
2091
2092 def remove(self, ignoremissing=False):
2092 def remove(self, ignoremissing=False):
2093 return self._parent.remove(self._path)
2093 return self._parent.remove(self._path)
2094
2094
2095 def clearunknown(self):
2095 def clearunknown(self):
2096 pass
2096 pass
2097
2097
2098 class workingcommitctx(workingctx):
2098 class workingcommitctx(workingctx):
2099 """A workingcommitctx object makes access to data related to
2099 """A workingcommitctx object makes access to data related to
2100 the revision being committed convenient.
2100 the revision being committed convenient.
2101
2101
2102 This hides changes in the working directory, if they aren't
2102 This hides changes in the working directory, if they aren't
2103 committed in this context.
2103 committed in this context.
2104 """
2104 """
2105 def __init__(self, repo, changes,
2105 def __init__(self, repo, changes,
2106 text="", user=None, date=None, extra=None):
2106 text="", user=None, date=None, extra=None):
2107 super(workingctx, self).__init__(repo, text, user, date, extra,
2107 super(workingctx, self).__init__(repo, text, user, date, extra,
2108 changes)
2108 changes)
2109
2109
2110 def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
2110 def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
2111 """Return matched files only in ``self._status``
2111 """Return matched files only in ``self._status``
2112
2112
2113 Uncommitted files appear "clean" via this context, even if
2113 Uncommitted files appear "clean" via this context, even if
2114 they aren't actually so in the working directory.
2114 they aren't actually so in the working directory.
2115 """
2115 """
2116 if clean:
2116 if clean:
2117 clean = [f for f in self._manifest if f not in self._changedset]
2117 clean = [f for f in self._manifest if f not in self._changedset]
2118 else:
2118 else:
2119 clean = []
2119 clean = []
2120 return scmutil.status([f for f in self._status.modified if match(f)],
2120 return scmutil.status([f for f in self._status.modified if match(f)],
2121 [f for f in self._status.added if match(f)],
2121 [f for f in self._status.added if match(f)],
2122 [f for f in self._status.removed if match(f)],
2122 [f for f in self._status.removed if match(f)],
2123 [], [], [], clean)
2123 [], [], [], clean)
2124
2124
2125 @propertycache
2125 @propertycache
2126 def _changedset(self):
2126 def _changedset(self):
2127 """Return the set of files changed in this context
2127 """Return the set of files changed in this context
2128 """
2128 """
2129 changed = set(self._status.modified)
2129 changed = set(self._status.modified)
2130 changed.update(self._status.added)
2130 changed.update(self._status.added)
2131 changed.update(self._status.removed)
2131 changed.update(self._status.removed)
2132 return changed
2132 return changed
2133
2133
2134 def makecachingfilectxfn(func):
2134 def makecachingfilectxfn(func):
2135 """Create a filectxfn that caches based on the path.
2135 """Create a filectxfn that caches based on the path.
2136
2136
2137 We can't use util.cachefunc because it uses all arguments as the cache
2137 We can't use util.cachefunc because it uses all arguments as the cache
2138 key and this creates a cycle since the arguments include the repo and
2138 key and this creates a cycle since the arguments include the repo and
2139 memctx.
2139 memctx.
2140 """
2140 """
2141 cache = {}
2141 cache = {}
2142
2142
2143 def getfilectx(repo, memctx, path):
2143 def getfilectx(repo, memctx, path):
2144 if path not in cache:
2144 if path not in cache:
2145 cache[path] = func(repo, memctx, path)
2145 cache[path] = func(repo, memctx, path)
2146 return cache[path]
2146 return cache[path]
2147
2147
2148 return getfilectx
2148 return getfilectx
2149
2149
2150 def memfilefromctx(ctx):
2150 def memfilefromctx(ctx):
2151 """Given a context return a memfilectx for ctx[path]
2151 """Given a context return a memfilectx for ctx[path]
2152
2152
2153 This is a convenience method for building a memctx based on another
2153 This is a convenience method for building a memctx based on another
2154 context.
2154 context.
2155 """
2155 """
2156 def getfilectx(repo, memctx, path):
2156 def getfilectx(repo, memctx, path):
2157 fctx = ctx[path]
2157 fctx = ctx[path]
2158 # this is weird but apparently we only keep track of one parent
2158 # this is weird but apparently we only keep track of one parent
2159 # (why not only store that instead of a tuple?)
2159 # (why not only store that instead of a tuple?)
2160 copied = fctx.renamed()
2160 copied = fctx.renamed()
2161 if copied:
2161 if copied:
2162 copied = copied[0]
2162 copied = copied[0]
2163 return memfilectx(repo, memctx, path, fctx.data(),
2163 return memfilectx(repo, memctx, path, fctx.data(),
2164 islink=fctx.islink(), isexec=fctx.isexec(),
2164 islink=fctx.islink(), isexec=fctx.isexec(),
2165 copied=copied)
2165 copied=copied)
2166
2166
2167 return getfilectx
2167 return getfilectx
2168
2168
2169 def memfilefrompatch(patchstore):
2169 def memfilefrompatch(patchstore):
2170 """Given a patch (e.g. patchstore object) return a memfilectx
2170 """Given a patch (e.g. patchstore object) return a memfilectx
2171
2171
2172 This is a convenience method for building a memctx based on a patchstore.
2172 This is a convenience method for building a memctx based on a patchstore.
2173 """
2173 """
2174 def getfilectx(repo, memctx, path):
2174 def getfilectx(repo, memctx, path):
2175 data, mode, copied = patchstore.getfile(path)
2175 data, mode, copied = patchstore.getfile(path)
2176 if data is None:
2176 if data is None:
2177 return None
2177 return None
2178 islink, isexec = mode
2178 islink, isexec = mode
2179 return memfilectx(repo, memctx, path, data, islink=islink,
2179 return memfilectx(repo, memctx, path, data, islink=islink,
2180 isexec=isexec, copied=copied)
2180 isexec=isexec, copied=copied)
2181
2181
2182 return getfilectx
2182 return getfilectx
2183
2183
2184 class memctx(committablectx):
2184 class memctx(committablectx):
2185 """Use memctx to perform in-memory commits via localrepo.commitctx().
2185 """Use memctx to perform in-memory commits via localrepo.commitctx().
2186
2186
2187 Revision information is supplied at initialization time while
2187 Revision information is supplied at initialization time while
2188 related files data and is made available through a callback
2188 related files data and is made available through a callback
2189 mechanism. 'repo' is the current localrepo, 'parents' is a
2189 mechanism. 'repo' is the current localrepo, 'parents' is a
2190 sequence of two parent revisions identifiers (pass None for every
2190 sequence of two parent revisions identifiers (pass None for every
2191 missing parent), 'text' is the commit message and 'files' lists
2191 missing parent), 'text' is the commit message and 'files' lists
2192 names of files touched by the revision (normalized and relative to
2192 names of files touched by the revision (normalized and relative to
2193 repository root).
2193 repository root).
2194
2194
2195 filectxfn(repo, memctx, path) is a callable receiving the
2195 filectxfn(repo, memctx, path) is a callable receiving the
2196 repository, the current memctx object and the normalized path of
2196 repository, the current memctx object and the normalized path of
2197 requested file, relative to repository root. It is fired by the
2197 requested file, relative to repository root. It is fired by the
2198 commit function for every file in 'files', but calls order is
2198 commit function for every file in 'files', but calls order is
2199 undefined. If the file is available in the revision being
2199 undefined. If the file is available in the revision being
2200 committed (updated or added), filectxfn returns a memfilectx
2200 committed (updated or added), filectxfn returns a memfilectx
2201 object. If the file was removed, filectxfn return None for recent
2201 object. If the file was removed, filectxfn return None for recent
2202 Mercurial. Moved files are represented by marking the source file
2202 Mercurial. Moved files are represented by marking the source file
2203 removed and the new file added with copy information (see
2203 removed and the new file added with copy information (see
2204 memfilectx).
2204 memfilectx).
2205
2205
2206 user receives the committer name and defaults to current
2206 user receives the committer name and defaults to current
2207 repository username, date is the commit date in any format
2207 repository username, date is the commit date in any format
2208 supported by dateutil.parsedate() and defaults to current date, extra
2208 supported by dateutil.parsedate() and defaults to current date, extra
2209 is a dictionary of metadata or is left empty.
2209 is a dictionary of metadata or is left empty.
2210 """
2210 """
2211
2211
2212 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
2212 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
2213 # Extensions that need to retain compatibility across Mercurial 3.1 can use
2213 # Extensions that need to retain compatibility across Mercurial 3.1 can use
2214 # this field to determine what to do in filectxfn.
2214 # this field to determine what to do in filectxfn.
2215 _returnnoneformissingfiles = True
2215 _returnnoneformissingfiles = True
2216
2216
2217 def __init__(self, repo, parents, text, files, filectxfn, user=None,
2217 def __init__(self, repo, parents, text, files, filectxfn, user=None,
2218 date=None, extra=None, branch=None, editor=False):
2218 date=None, extra=None, branch=None, editor=False):
2219 super(memctx, self).__init__(repo, text, user, date, extra)
2219 super(memctx, self).__init__(repo, text, user, date, extra)
2220 self._rev = None
2220 self._rev = None
2221 self._node = None
2221 self._node = None
2222 parents = [(p or nullid) for p in parents]
2222 parents = [(p or nullid) for p in parents]
2223 p1, p2 = parents
2223 p1, p2 = parents
2224 self._parents = [self._repo[p] for p in (p1, p2)]
2224 self._parents = [self._repo[p] for p in (p1, p2)]
2225 files = sorted(set(files))
2225 files = sorted(set(files))
2226 self._files = files
2226 self._files = files
2227 if branch is not None:
2227 if branch is not None:
2228 self._extra['branch'] = encoding.fromlocal(branch)
2228 self._extra['branch'] = encoding.fromlocal(branch)
2229 self.substate = {}
2229 self.substate = {}
2230
2230
2231 if isinstance(filectxfn, patch.filestore):
2231 if isinstance(filectxfn, patch.filestore):
2232 filectxfn = memfilefrompatch(filectxfn)
2232 filectxfn = memfilefrompatch(filectxfn)
2233 elif not callable(filectxfn):
2233 elif not callable(filectxfn):
2234 # if store is not callable, wrap it in a function
2234 # if store is not callable, wrap it in a function
2235 filectxfn = memfilefromctx(filectxfn)
2235 filectxfn = memfilefromctx(filectxfn)
2236
2236
2237 # memoizing increases performance for e.g. vcs convert scenarios.
2237 # memoizing increases performance for e.g. vcs convert scenarios.
2238 self._filectxfn = makecachingfilectxfn(filectxfn)
2238 self._filectxfn = makecachingfilectxfn(filectxfn)
2239
2239
2240 if editor:
2240 if editor:
2241 self._text = editor(self._repo, self, [])
2241 self._text = editor(self._repo, self, [])
2242 self._repo.savecommitmessage(self._text)
2242 self._repo.savecommitmessage(self._text)
2243
2243
2244 def filectx(self, path, filelog=None):
2244 def filectx(self, path, filelog=None):
2245 """get a file context from the working directory
2245 """get a file context from the working directory
2246
2246
2247 Returns None if file doesn't exist and should be removed."""
2247 Returns None if file doesn't exist and should be removed."""
2248 return self._filectxfn(self._repo, self, path)
2248 return self._filectxfn(self._repo, self, path)
2249
2249
2250 def commit(self):
2250 def commit(self):
2251 """commit context to the repo"""
2251 """commit context to the repo"""
2252 return self._repo.commitctx(self)
2252 return self._repo.commitctx(self)
2253
2253
2254 @propertycache
2254 @propertycache
2255 def _manifest(self):
2255 def _manifest(self):
2256 """generate a manifest based on the return values of filectxfn"""
2256 """generate a manifest based on the return values of filectxfn"""
2257
2257
2258 # keep this simple for now; just worry about p1
2258 # keep this simple for now; just worry about p1
2259 pctx = self._parents[0]
2259 pctx = self._parents[0]
2260 man = pctx.manifest().copy()
2260 man = pctx.manifest().copy()
2261
2261
2262 for f in self._status.modified:
2262 for f in self._status.modified:
2263 p1node = nullid
2263 p1node = nullid
2264 p2node = nullid
2264 p2node = nullid
2265 p = pctx[f].parents() # if file isn't in pctx, check p2?
2265 p = pctx[f].parents() # if file isn't in pctx, check p2?
2266 if len(p) > 0:
2266 if len(p) > 0:
2267 p1node = p[0].filenode()
2267 p1node = p[0].filenode()
2268 if len(p) > 1:
2268 if len(p) > 1:
2269 p2node = p[1].filenode()
2269 p2node = p[1].filenode()
2270 man[f] = revlog.hash(self[f].data(), p1node, p2node)
2270 man[f] = revlog.hash(self[f].data(), p1node, p2node)
2271
2271
2272 for f in self._status.added:
2272 for f in self._status.added:
2273 man[f] = revlog.hash(self[f].data(), nullid, nullid)
2273 man[f] = revlog.hash(self[f].data(), nullid, nullid)
2274
2274
2275 for f in self._status.removed:
2275 for f in self._status.removed:
2276 if f in man:
2276 if f in man:
2277 del man[f]
2277 del man[f]
2278
2278
2279 return man
2279 return man
2280
2280
2281 @propertycache
2281 @propertycache
2282 def _status(self):
2282 def _status(self):
2283 """Calculate exact status from ``files`` specified at construction
2283 """Calculate exact status from ``files`` specified at construction
2284 """
2284 """
2285 man1 = self.p1().manifest()
2285 man1 = self.p1().manifest()
2286 p2 = self._parents[1]
2286 p2 = self._parents[1]
2287 # "1 < len(self._parents)" can't be used for checking
2287 # "1 < len(self._parents)" can't be used for checking
2288 # existence of the 2nd parent, because "memctx._parents" is
2288 # existence of the 2nd parent, because "memctx._parents" is
2289 # explicitly initialized by the list, of which length is 2.
2289 # explicitly initialized by the list, of which length is 2.
2290 if p2.node() != nullid:
2290 if p2.node() != nullid:
2291 man2 = p2.manifest()
2291 man2 = p2.manifest()
2292 managing = lambda f: f in man1 or f in man2
2292 managing = lambda f: f in man1 or f in man2
2293 else:
2293 else:
2294 managing = lambda f: f in man1
2294 managing = lambda f: f in man1
2295
2295
2296 modified, added, removed = [], [], []
2296 modified, added, removed = [], [], []
2297 for f in self._files:
2297 for f in self._files:
2298 if not managing(f):
2298 if not managing(f):
2299 added.append(f)
2299 added.append(f)
2300 elif self[f]:
2300 elif self[f]:
2301 modified.append(f)
2301 modified.append(f)
2302 else:
2302 else:
2303 removed.append(f)
2303 removed.append(f)
2304
2304
2305 return scmutil.status(modified, added, removed, [], [], [], [])
2305 return scmutil.status(modified, added, removed, [], [], [], [])
2306
2306
2307 class memfilectx(committablefilectx):
2307 class memfilectx(committablefilectx):
2308 """memfilectx represents an in-memory file to commit.
2308 """memfilectx represents an in-memory file to commit.
2309
2309
2310 See memctx and committablefilectx for more details.
2310 See memctx and committablefilectx for more details.
2311 """
2311 """
2312 def __init__(self, repo, changectx, path, data, islink=False,
2312 def __init__(self, repo, changectx, path, data, islink=False,
2313 isexec=False, copied=None):
2313 isexec=False, copied=None):
2314 """
2314 """
2315 path is the normalized file path relative to repository root.
2315 path is the normalized file path relative to repository root.
2316 data is the file content as a string.
2316 data is the file content as a string.
2317 islink is True if the file is a symbolic link.
2317 islink is True if the file is a symbolic link.
2318 isexec is True if the file is executable.
2318 isexec is True if the file is executable.
2319 copied is the source file path if current file was copied in the
2319 copied is the source file path if current file was copied in the
2320 revision being committed, or None."""
2320 revision being committed, or None."""
2321 super(memfilectx, self).__init__(repo, path, None, changectx)
2321 super(memfilectx, self).__init__(repo, path, None, changectx)
2322 self._data = data
2322 self._data = data
2323 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
2323 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
2324 self._copied = None
2324 self._copied = None
2325 if copied:
2325 if copied:
2326 self._copied = (copied, nullid)
2326 self._copied = (copied, nullid)
2327
2327
2328 def data(self):
2328 def data(self):
2329 return self._data
2329 return self._data
2330
2330
2331 def remove(self, ignoremissing=False):
2331 def remove(self, ignoremissing=False):
2332 """wraps unlink for a repo's working directory"""
2332 """wraps unlink for a repo's working directory"""
2333 # need to figure out what to do here
2333 # need to figure out what to do here
2334 del self._changectx[self._path]
2334 del self._changectx[self._path]
2335
2335
2336 def write(self, data, flags, **kwargs):
2336 def write(self, data, flags, **kwargs):
2337 """wraps repo.wwrite"""
2337 """wraps repo.wwrite"""
2338 self._data = data
2338 self._data = data
2339
2339
2340 class overlayfilectx(committablefilectx):
2340 class overlayfilectx(committablefilectx):
2341 """Like memfilectx but take an original filectx and optional parameters to
2341 """Like memfilectx but take an original filectx and optional parameters to
2342 override parts of it. This is useful when fctx.data() is expensive (i.e.
2342 override parts of it. This is useful when fctx.data() is expensive (i.e.
2343 flag processor is expensive) and raw data, flags, and filenode could be
2343 flag processor is expensive) and raw data, flags, and filenode could be
2344 reused (ex. rebase or mode-only amend a REVIDX_EXTSTORED file).
2344 reused (ex. rebase or mode-only amend a REVIDX_EXTSTORED file).
2345 """
2345 """
2346
2346
2347 def __init__(self, originalfctx, datafunc=None, path=None, flags=None,
2347 def __init__(self, originalfctx, datafunc=None, path=None, flags=None,
2348 copied=None, ctx=None):
2348 copied=None, ctx=None):
2349 """originalfctx: filecontext to duplicate
2349 """originalfctx: filecontext to duplicate
2350
2350
2351 datafunc: None or a function to override data (file content). It is a
2351 datafunc: None or a function to override data (file content). It is a
2352 function to be lazy. path, flags, copied, ctx: None or overridden value
2352 function to be lazy. path, flags, copied, ctx: None or overridden value
2353
2353
2354 copied could be (path, rev), or False. copied could also be just path,
2354 copied could be (path, rev), or False. copied could also be just path,
2355 and will be converted to (path, nullid). This simplifies some callers.
2355 and will be converted to (path, nullid). This simplifies some callers.
2356 """
2356 """
2357
2357
2358 if path is None:
2358 if path is None:
2359 path = originalfctx.path()
2359 path = originalfctx.path()
2360 if ctx is None:
2360 if ctx is None:
2361 ctx = originalfctx.changectx()
2361 ctx = originalfctx.changectx()
2362 ctxmatch = lambda: True
2362 ctxmatch = lambda: True
2363 else:
2363 else:
2364 ctxmatch = lambda: ctx == originalfctx.changectx()
2364 ctxmatch = lambda: ctx == originalfctx.changectx()
2365
2365
2366 repo = originalfctx.repo()
2366 repo = originalfctx.repo()
2367 flog = originalfctx.filelog()
2367 flog = originalfctx.filelog()
2368 super(overlayfilectx, self).__init__(repo, path, flog, ctx)
2368 super(overlayfilectx, self).__init__(repo, path, flog, ctx)
2369
2369
2370 if copied is None:
2370 if copied is None:
2371 copied = originalfctx.renamed()
2371 copied = originalfctx.renamed()
2372 copiedmatch = lambda: True
2372 copiedmatch = lambda: True
2373 else:
2373 else:
2374 if copied and not isinstance(copied, tuple):
2374 if copied and not isinstance(copied, tuple):
2375 # repo._filecommit will recalculate copyrev so nullid is okay
2375 # repo._filecommit will recalculate copyrev so nullid is okay
2376 copied = (copied, nullid)
2376 copied = (copied, nullid)
2377 copiedmatch = lambda: copied == originalfctx.renamed()
2377 copiedmatch = lambda: copied == originalfctx.renamed()
2378
2378
2379 # When data, copied (could affect data), ctx (could affect filelog
2379 # When data, copied (could affect data), ctx (could affect filelog
2380 # parents) are not overridden, rawdata, rawflags, and filenode may be
2380 # parents) are not overridden, rawdata, rawflags, and filenode may be
2381 # reused (repo._filecommit should double check filelog parents).
2381 # reused (repo._filecommit should double check filelog parents).
2382 #
2382 #
2383 # path, flags are not hashed in filelog (but in manifestlog) so they do
2383 # path, flags are not hashed in filelog (but in manifestlog) so they do
2384 # not affect reusable here.
2384 # not affect reusable here.
2385 #
2385 #
2386 # If ctx or copied is overridden to a same value with originalfctx,
2386 # If ctx or copied is overridden to a same value with originalfctx,
2387 # still consider it's reusable. originalfctx.renamed() may be a bit
2387 # still consider it's reusable. originalfctx.renamed() may be a bit
2388 # expensive so it's not called unless necessary. Assuming datafunc is
2388 # expensive so it's not called unless necessary. Assuming datafunc is
2389 # always expensive, do not call it for this "reusable" test.
2389 # always expensive, do not call it for this "reusable" test.
2390 reusable = datafunc is None and ctxmatch() and copiedmatch()
2390 reusable = datafunc is None and ctxmatch() and copiedmatch()
2391
2391
2392 if datafunc is None:
2392 if datafunc is None:
2393 datafunc = originalfctx.data
2393 datafunc = originalfctx.data
2394 if flags is None:
2394 if flags is None:
2395 flags = originalfctx.flags()
2395 flags = originalfctx.flags()
2396
2396
2397 self._datafunc = datafunc
2397 self._datafunc = datafunc
2398 self._flags = flags
2398 self._flags = flags
2399 self._copied = copied
2399 self._copied = copied
2400
2400
2401 if reusable:
2401 if reusable:
2402 # copy extra fields from originalfctx
2402 # copy extra fields from originalfctx
2403 attrs = ['rawdata', 'rawflags', '_filenode', '_filerev']
2403 attrs = ['rawdata', 'rawflags', '_filenode', '_filerev']
2404 for attr_ in attrs:
2404 for attr_ in attrs:
2405 if util.safehasattr(originalfctx, attr_):
2405 if util.safehasattr(originalfctx, attr_):
2406 setattr(self, attr_, getattr(originalfctx, attr_))
2406 setattr(self, attr_, getattr(originalfctx, attr_))
2407
2407
2408 def data(self):
2408 def data(self):
2409 return self._datafunc()
2409 return self._datafunc()
2410
2410
2411 class metadataonlyctx(committablectx):
2411 class metadataonlyctx(committablectx):
2412 """Like memctx but it's reusing the manifest of different commit.
2412 """Like memctx but it's reusing the manifest of different commit.
2413 Intended to be used by lightweight operations that are creating
2413 Intended to be used by lightweight operations that are creating
2414 metadata-only changes.
2414 metadata-only changes.
2415
2415
2416 Revision information is supplied at initialization time. 'repo' is the
2416 Revision information is supplied at initialization time. 'repo' is the
2417 current localrepo, 'ctx' is original revision which manifest we're reuisng
2417 current localrepo, 'ctx' is original revision which manifest we're reuisng
2418 'parents' is a sequence of two parent revisions identifiers (pass None for
2418 'parents' is a sequence of two parent revisions identifiers (pass None for
2419 every missing parent), 'text' is the commit.
2419 every missing parent), 'text' is the commit.
2420
2420
2421 user receives the committer name and defaults to current repository
2421 user receives the committer name and defaults to current repository
2422 username, date is the commit date in any format supported by
2422 username, date is the commit date in any format supported by
2423 dateutil.parsedate() and defaults to current date, extra is a dictionary of
2423 dateutil.parsedate() and defaults to current date, extra is a dictionary of
2424 metadata or is left empty.
2424 metadata or is left empty.
2425 """
2425 """
2426 def __init__(self, repo, originalctx, parents=None, text=None, user=None,
2426 def __init__(self, repo, originalctx, parents=None, text=None, user=None,
2427 date=None, extra=None, editor=False):
2427 date=None, extra=None, editor=False):
2428 if text is None:
2428 if text is None:
2429 text = originalctx.description()
2429 text = originalctx.description()
2430 super(metadataonlyctx, self).__init__(repo, text, user, date, extra)
2430 super(metadataonlyctx, self).__init__(repo, text, user, date, extra)
2431 self._rev = None
2431 self._rev = None
2432 self._node = None
2432 self._node = None
2433 self._originalctx = originalctx
2433 self._originalctx = originalctx
2434 self._manifestnode = originalctx.manifestnode()
2434 self._manifestnode = originalctx.manifestnode()
2435 if parents is None:
2435 if parents is None:
2436 parents = originalctx.parents()
2436 parents = originalctx.parents()
2437 else:
2437 else:
2438 parents = [repo[p] for p in parents if p is not None]
2438 parents = [repo[p] for p in parents if p is not None]
2439 parents = parents[:]
2439 parents = parents[:]
2440 while len(parents) < 2:
2440 while len(parents) < 2:
2441 parents.append(repo[nullid])
2441 parents.append(repo[nullid])
2442 p1, p2 = self._parents = parents
2442 p1, p2 = self._parents = parents
2443
2443
2444 # sanity check to ensure that the reused manifest parents are
2444 # sanity check to ensure that the reused manifest parents are
2445 # manifests of our commit parents
2445 # manifests of our commit parents
2446 mp1, mp2 = self.manifestctx().parents
2446 mp1, mp2 = self.manifestctx().parents
2447 if p1 != nullid and p1.manifestnode() != mp1:
2447 if p1 != nullid and p1.manifestnode() != mp1:
2448 raise RuntimeError('can\'t reuse the manifest: '
2448 raise RuntimeError('can\'t reuse the manifest: '
2449 'its p1 doesn\'t match the new ctx p1')
2449 'its p1 doesn\'t match the new ctx p1')
2450 if p2 != nullid and p2.manifestnode() != mp2:
2450 if p2 != nullid and p2.manifestnode() != mp2:
2451 raise RuntimeError('can\'t reuse the manifest: '
2451 raise RuntimeError('can\'t reuse the manifest: '
2452 'its p2 doesn\'t match the new ctx p2')
2452 'its p2 doesn\'t match the new ctx p2')
2453
2453
2454 self._files = originalctx.files()
2454 self._files = originalctx.files()
2455 self.substate = {}
2455 self.substate = {}
2456
2456
2457 if editor:
2457 if editor:
2458 self._text = editor(self._repo, self, [])
2458 self._text = editor(self._repo, self, [])
2459 self._repo.savecommitmessage(self._text)
2459 self._repo.savecommitmessage(self._text)
2460
2460
2461 def manifestnode(self):
2461 def manifestnode(self):
2462 return self._manifestnode
2462 return self._manifestnode
2463
2463
2464 @property
2464 @property
2465 def _manifestctx(self):
2465 def _manifestctx(self):
2466 return self._repo.manifestlog[self._manifestnode]
2466 return self._repo.manifestlog[self._manifestnode]
2467
2467
2468 def filectx(self, path, filelog=None):
2468 def filectx(self, path, filelog=None):
2469 return self._originalctx.filectx(path, filelog=filelog)
2469 return self._originalctx.filectx(path, filelog=filelog)
2470
2470
2471 def commit(self):
2471 def commit(self):
2472 """commit context to the repo"""
2472 """commit context to the repo"""
2473 return self._repo.commitctx(self)
2473 return self._repo.commitctx(self)
2474
2474
2475 @property
2475 @property
2476 def _manifest(self):
2476 def _manifest(self):
2477 return self._originalctx.manifest()
2477 return self._originalctx.manifest()
2478
2478
2479 @propertycache
2479 @propertycache
2480 def _status(self):
2480 def _status(self):
2481 """Calculate exact status from ``files`` specified in the ``origctx``
2481 """Calculate exact status from ``files`` specified in the ``origctx``
2482 and parents manifests.
2482 and parents manifests.
2483 """
2483 """
2484 man1 = self.p1().manifest()
2484 man1 = self.p1().manifest()
2485 p2 = self._parents[1]
2485 p2 = self._parents[1]
2486 # "1 < len(self._parents)" can't be used for checking
2486 # "1 < len(self._parents)" can't be used for checking
2487 # existence of the 2nd parent, because "metadataonlyctx._parents" is
2487 # existence of the 2nd parent, because "metadataonlyctx._parents" is
2488 # explicitly initialized by the list, of which length is 2.
2488 # explicitly initialized by the list, of which length is 2.
2489 if p2.node() != nullid:
2489 if p2.node() != nullid:
2490 man2 = p2.manifest()
2490 man2 = p2.manifest()
2491 managing = lambda f: f in man1 or f in man2
2491 managing = lambda f: f in man1 or f in man2
2492 else:
2492 else:
2493 managing = lambda f: f in man1
2493 managing = lambda f: f in man1
2494
2494
2495 modified, added, removed = [], [], []
2495 modified, added, removed = [], [], []
2496 for f in self._files:
2496 for f in self._files:
2497 if not managing(f):
2497 if not managing(f):
2498 added.append(f)
2498 added.append(f)
2499 elif f in self:
2499 elif f in self:
2500 modified.append(f)
2500 modified.append(f)
2501 else:
2501 else:
2502 removed.append(f)
2502 removed.append(f)
2503
2503
2504 return scmutil.status(modified, added, removed, [], [], [], [])
2504 return scmutil.status(modified, added, removed, [], [], [], [])
2505
2505
2506 class arbitraryfilectx(object):
2506 class arbitraryfilectx(object):
2507 """Allows you to use filectx-like functions on a file in an arbitrary
2507 """Allows you to use filectx-like functions on a file in an arbitrary
2508 location on disk, possibly not in the working directory.
2508 location on disk, possibly not in the working directory.
2509 """
2509 """
2510 def __init__(self, path, repo=None):
2510 def __init__(self, path, repo=None):
2511 # Repo is optional because contrib/simplemerge uses this class.
2511 # Repo is optional because contrib/simplemerge uses this class.
2512 self._repo = repo
2512 self._repo = repo
2513 self._path = path
2513 self._path = path
2514
2514
2515 def cmp(self, fctx):
2515 def cmp(self, fctx):
2516 # filecmp follows symlinks whereas `cmp` should not, so skip the fast
2516 # filecmp follows symlinks whereas `cmp` should not, so skip the fast
2517 # path if either side is a symlink.
2517 # path if either side is a symlink.
2518 symlinks = ('l' in self.flags() or 'l' in fctx.flags())
2518 symlinks = ('l' in self.flags() or 'l' in fctx.flags())
2519 if not symlinks and isinstance(fctx, workingfilectx) and self._repo:
2519 if not symlinks and isinstance(fctx, workingfilectx) and self._repo:
2520 # Add a fast-path for merge if both sides are disk-backed.
2520 # Add a fast-path for merge if both sides are disk-backed.
2521 # Note that filecmp uses the opposite return values (True if same)
2521 # Note that filecmp uses the opposite return values (True if same)
2522 # from our cmp functions (True if different).
2522 # from our cmp functions (True if different).
2523 return not filecmp.cmp(self.path(), self._repo.wjoin(fctx.path()))
2523 return not filecmp.cmp(self.path(), self._repo.wjoin(fctx.path()))
2524 return self.data() != fctx.data()
2524 return self.data() != fctx.data()
2525
2525
2526 def path(self):
2526 def path(self):
2527 return self._path
2527 return self._path
2528
2528
2529 def flags(self):
2529 def flags(self):
2530 return ''
2530 return ''
2531
2531
2532 def data(self):
2532 def data(self):
2533 return util.readfile(self._path)
2533 return util.readfile(self._path)
2534
2534
2535 def decodeddata(self):
2535 def decodeddata(self):
2536 with open(self._path, "rb") as f:
2536 with open(self._path, "rb") as f:
2537 return f.read()
2537 return f.read()
2538
2538
2539 def remove(self):
2539 def remove(self):
2540 util.unlink(self._path)
2540 util.unlink(self._path)
2541
2541
2542 def write(self, data, flags, **kwargs):
2542 def write(self, data, flags, **kwargs):
2543 assert not flags
2543 assert not flags
2544 with open(self._path, "w") as f:
2544 with open(self._path, "w") as f:
2545 f.write(data)
2545 f.write(data)
@@ -1,2906 +1,2900 b''
1 # revlog.py - storage back-end for mercurial
1 # revlog.py - storage back-end for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-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 """Storage back-end for Mercurial.
8 """Storage back-end for Mercurial.
9
9
10 This provides efficient delta storage with O(1) retrieve and append
10 This provides efficient delta storage with O(1) retrieve and append
11 and O(changes) merge between branches.
11 and O(changes) merge between branches.
12 """
12 """
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 import collections
16 import collections
17 import contextlib
17 import contextlib
18 import errno
18 import errno
19 import hashlib
19 import hashlib
20 import heapq
20 import heapq
21 import os
21 import os
22 import re
22 import re
23 import struct
23 import struct
24 import zlib
24 import zlib
25
25
26 # import stuff from node for others to import from revlog
26 # import stuff from node for others to import from revlog
27 from .node import (
27 from .node import (
28 bin,
28 bin,
29 hex,
29 hex,
30 nullid,
30 nullid,
31 nullrev,
31 nullrev,
32 wdirfilenodeids,
32 wdirfilenodeids,
33 wdirhex,
33 wdirhex,
34 wdirid,
34 wdirid,
35 wdirrev,
35 wdirrev,
36 )
36 )
37 from .i18n import _
37 from .i18n import _
38 from .thirdparty import (
38 from .thirdparty import (
39 attr,
39 attr,
40 )
40 )
41 from . import (
41 from . import (
42 ancestor,
42 ancestor,
43 error,
43 error,
44 mdiff,
44 mdiff,
45 policy,
45 policy,
46 pycompat,
46 pycompat,
47 templatefilters,
47 templatefilters,
48 util,
48 util,
49 )
49 )
50 from .utils import (
50 from .utils import (
51 stringutil,
51 stringutil,
52 )
52 )
53
53
54 parsers = policy.importmod(r'parsers')
54 parsers = policy.importmod(r'parsers')
55
55
56 # Aliased for performance.
56 # Aliased for performance.
57 _zlibdecompress = zlib.decompress
57 _zlibdecompress = zlib.decompress
58
58
59 # revlog header flags
59 # revlog header flags
60 REVLOGV0 = 0
60 REVLOGV0 = 0
61 REVLOGV1 = 1
61 REVLOGV1 = 1
62 # Dummy value until file format is finalized.
62 # Dummy value until file format is finalized.
63 # Reminder: change the bounds check in revlog.__init__ when this is changed.
63 # Reminder: change the bounds check in revlog.__init__ when this is changed.
64 REVLOGV2 = 0xDEAD
64 REVLOGV2 = 0xDEAD
65 FLAG_INLINE_DATA = (1 << 16)
65 FLAG_INLINE_DATA = (1 << 16)
66 FLAG_GENERALDELTA = (1 << 17)
66 FLAG_GENERALDELTA = (1 << 17)
67 REVLOG_DEFAULT_FLAGS = FLAG_INLINE_DATA
67 REVLOG_DEFAULT_FLAGS = FLAG_INLINE_DATA
68 REVLOG_DEFAULT_FORMAT = REVLOGV1
68 REVLOG_DEFAULT_FORMAT = REVLOGV1
69 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
69 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
70 REVLOGV1_FLAGS = FLAG_INLINE_DATA | FLAG_GENERALDELTA
70 REVLOGV1_FLAGS = FLAG_INLINE_DATA | FLAG_GENERALDELTA
71 REVLOGV2_FLAGS = REVLOGV1_FLAGS
71 REVLOGV2_FLAGS = REVLOGV1_FLAGS
72
72
73 # revlog index flags
73 # revlog index flags
74 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
74 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
75 REVIDX_ELLIPSIS = (1 << 14) # revision hash does not match data (narrowhg)
75 REVIDX_ELLIPSIS = (1 << 14) # revision hash does not match data (narrowhg)
76 REVIDX_EXTSTORED = (1 << 13) # revision data is stored externally
76 REVIDX_EXTSTORED = (1 << 13) # revision data is stored externally
77 REVIDX_DEFAULT_FLAGS = 0
77 REVIDX_DEFAULT_FLAGS = 0
78 # stable order in which flags need to be processed and their processors applied
78 # stable order in which flags need to be processed and their processors applied
79 REVIDX_FLAGS_ORDER = [
79 REVIDX_FLAGS_ORDER = [
80 REVIDX_ISCENSORED,
80 REVIDX_ISCENSORED,
81 REVIDX_ELLIPSIS,
81 REVIDX_ELLIPSIS,
82 REVIDX_EXTSTORED,
82 REVIDX_EXTSTORED,
83 ]
83 ]
84 REVIDX_KNOWN_FLAGS = util.bitsfrom(REVIDX_FLAGS_ORDER)
84 REVIDX_KNOWN_FLAGS = util.bitsfrom(REVIDX_FLAGS_ORDER)
85 # bitmark for flags that could cause rawdata content change
85 # bitmark for flags that could cause rawdata content change
86 REVIDX_RAWTEXT_CHANGING_FLAGS = REVIDX_ISCENSORED | REVIDX_EXTSTORED
86 REVIDX_RAWTEXT_CHANGING_FLAGS = REVIDX_ISCENSORED | REVIDX_EXTSTORED
87
87
88 # max size of revlog with inline data
88 # max size of revlog with inline data
89 _maxinline = 131072
89 _maxinline = 131072
90 _chunksize = 1048576
90 _chunksize = 1048576
91
91
92 RevlogError = error.RevlogError
92 RevlogError = error.RevlogError
93 LookupError = error.LookupError
93 LookupError = error.LookupError
94 CensoredNodeError = error.CensoredNodeError
94 CensoredNodeError = error.CensoredNodeError
95 ProgrammingError = error.ProgrammingError
95 ProgrammingError = error.ProgrammingError
96
96
97 # Store flag processors (cf. 'addflagprocessor()' to register)
97 # Store flag processors (cf. 'addflagprocessor()' to register)
98 _flagprocessors = {
98 _flagprocessors = {
99 REVIDX_ISCENSORED: None,
99 REVIDX_ISCENSORED: None,
100 }
100 }
101
101
102 _mdre = re.compile('\1\n')
102 _mdre = re.compile('\1\n')
103 def parsemeta(text):
103 def parsemeta(text):
104 """return (metadatadict, metadatasize)"""
104 """return (metadatadict, metadatasize)"""
105 # text can be buffer, so we can't use .startswith or .index
105 # text can be buffer, so we can't use .startswith or .index
106 if text[:2] != '\1\n':
106 if text[:2] != '\1\n':
107 return None, None
107 return None, None
108 s = _mdre.search(text, 2).start()
108 s = _mdre.search(text, 2).start()
109 mtext = text[2:s]
109 mtext = text[2:s]
110 meta = {}
110 meta = {}
111 for l in mtext.splitlines():
111 for l in mtext.splitlines():
112 k, v = l.split(": ", 1)
112 k, v = l.split(": ", 1)
113 meta[k] = v
113 meta[k] = v
114 return meta, (s + 2)
114 return meta, (s + 2)
115
115
116 def packmeta(meta, text):
116 def packmeta(meta, text):
117 keys = sorted(meta)
117 keys = sorted(meta)
118 metatext = "".join("%s: %s\n" % (k, meta[k]) for k in keys)
118 metatext = "".join("%s: %s\n" % (k, meta[k]) for k in keys)
119 return "\1\n%s\1\n%s" % (metatext, text)
119 return "\1\n%s\1\n%s" % (metatext, text)
120
120
121 def _censoredtext(text):
121 def _censoredtext(text):
122 m, offs = parsemeta(text)
122 m, offs = parsemeta(text)
123 return m and "censored" in m
123 return m and "censored" in m
124
124
125 def addflagprocessor(flag, processor):
125 def addflagprocessor(flag, processor):
126 """Register a flag processor on a revision data flag.
126 """Register a flag processor on a revision data flag.
127
127
128 Invariant:
128 Invariant:
129 - Flags need to be defined in REVIDX_KNOWN_FLAGS and REVIDX_FLAGS_ORDER,
129 - Flags need to be defined in REVIDX_KNOWN_FLAGS and REVIDX_FLAGS_ORDER,
130 and REVIDX_RAWTEXT_CHANGING_FLAGS if they can alter rawtext.
130 and REVIDX_RAWTEXT_CHANGING_FLAGS if they can alter rawtext.
131 - Only one flag processor can be registered on a specific flag.
131 - Only one flag processor can be registered on a specific flag.
132 - flagprocessors must be 3-tuples of functions (read, write, raw) with the
132 - flagprocessors must be 3-tuples of functions (read, write, raw) with the
133 following signatures:
133 following signatures:
134 - (read) f(self, rawtext) -> text, bool
134 - (read) f(self, rawtext) -> text, bool
135 - (write) f(self, text) -> rawtext, bool
135 - (write) f(self, text) -> rawtext, bool
136 - (raw) f(self, rawtext) -> bool
136 - (raw) f(self, rawtext) -> bool
137 "text" is presented to the user. "rawtext" is stored in revlog data, not
137 "text" is presented to the user. "rawtext" is stored in revlog data, not
138 directly visible to the user.
138 directly visible to the user.
139 The boolean returned by these transforms is used to determine whether
139 The boolean returned by these transforms is used to determine whether
140 the returned text can be used for hash integrity checking. For example,
140 the returned text can be used for hash integrity checking. For example,
141 if "write" returns False, then "text" is used to generate hash. If
141 if "write" returns False, then "text" is used to generate hash. If
142 "write" returns True, that basically means "rawtext" returned by "write"
142 "write" returns True, that basically means "rawtext" returned by "write"
143 should be used to generate hash. Usually, "write" and "read" return
143 should be used to generate hash. Usually, "write" and "read" return
144 different booleans. And "raw" returns a same boolean as "write".
144 different booleans. And "raw" returns a same boolean as "write".
145
145
146 Note: The 'raw' transform is used for changegroup generation and in some
146 Note: The 'raw' transform is used for changegroup generation and in some
147 debug commands. In this case the transform only indicates whether the
147 debug commands. In this case the transform only indicates whether the
148 contents can be used for hash integrity checks.
148 contents can be used for hash integrity checks.
149 """
149 """
150 if not flag & REVIDX_KNOWN_FLAGS:
150 if not flag & REVIDX_KNOWN_FLAGS:
151 msg = _("cannot register processor on unknown flag '%#x'.") % (flag)
151 msg = _("cannot register processor on unknown flag '%#x'.") % (flag)
152 raise ProgrammingError(msg)
152 raise ProgrammingError(msg)
153 if flag not in REVIDX_FLAGS_ORDER:
153 if flag not in REVIDX_FLAGS_ORDER:
154 msg = _("flag '%#x' undefined in REVIDX_FLAGS_ORDER.") % (flag)
154 msg = _("flag '%#x' undefined in REVIDX_FLAGS_ORDER.") % (flag)
155 raise ProgrammingError(msg)
155 raise ProgrammingError(msg)
156 if flag in _flagprocessors:
156 if flag in _flagprocessors:
157 msg = _("cannot register multiple processors on flag '%#x'.") % (flag)
157 msg = _("cannot register multiple processors on flag '%#x'.") % (flag)
158 raise error.Abort(msg)
158 raise error.Abort(msg)
159 _flagprocessors[flag] = processor
159 _flagprocessors[flag] = processor
160
160
161 def getoffset(q):
161 def getoffset(q):
162 return int(q >> 16)
162 return int(q >> 16)
163
163
164 def gettype(q):
164 def gettype(q):
165 return int(q & 0xFFFF)
165 return int(q & 0xFFFF)
166
166
167 def offset_type(offset, type):
167 def offset_type(offset, type):
168 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
168 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
169 raise ValueError('unknown revlog index flags')
169 raise ValueError('unknown revlog index flags')
170 return int(int(offset) << 16 | type)
170 return int(int(offset) << 16 | type)
171
171
172 _nullhash = hashlib.sha1(nullid)
172 _nullhash = hashlib.sha1(nullid)
173
173
174 def hash(text, p1, p2):
174 def hash(text, p1, p2):
175 """generate a hash from the given text and its parent hashes
175 """generate a hash from the given text and its parent hashes
176
176
177 This hash combines both the current file contents and its history
177 This hash combines both the current file contents and its history
178 in a manner that makes it easy to distinguish nodes with the same
178 in a manner that makes it easy to distinguish nodes with the same
179 content in the revision graph.
179 content in the revision graph.
180 """
180 """
181 # As of now, if one of the parent node is null, p2 is null
181 # As of now, if one of the parent node is null, p2 is null
182 if p2 == nullid:
182 if p2 == nullid:
183 # deep copy of a hash is faster than creating one
183 # deep copy of a hash is faster than creating one
184 s = _nullhash.copy()
184 s = _nullhash.copy()
185 s.update(p1)
185 s.update(p1)
186 else:
186 else:
187 # none of the parent nodes are nullid
187 # none of the parent nodes are nullid
188 if p1 < p2:
188 if p1 < p2:
189 a = p1
189 a = p1
190 b = p2
190 b = p2
191 else:
191 else:
192 a = p2
192 a = p2
193 b = p1
193 b = p1
194 s = hashlib.sha1(a)
194 s = hashlib.sha1(a)
195 s.update(b)
195 s.update(b)
196 s.update(text)
196 s.update(text)
197 return s.digest()
197 return s.digest()
198
198
199 class _testrevlog(object):
199 class _testrevlog(object):
200 """minimalist fake revlog to use in doctests"""
200 """minimalist fake revlog to use in doctests"""
201
201
202 def __init__(self, data, density=0.5, mingap=0):
202 def __init__(self, data, density=0.5, mingap=0):
203 """data is an list of revision payload boundaries"""
203 """data is an list of revision payload boundaries"""
204 self._data = data
204 self._data = data
205 self._srdensitythreshold = density
205 self._srdensitythreshold = density
206 self._srmingapsize = mingap
206 self._srmingapsize = mingap
207
207
208 def start(self, rev):
208 def start(self, rev):
209 if rev == 0:
209 if rev == 0:
210 return 0
210 return 0
211 return self._data[rev - 1]
211 return self._data[rev - 1]
212
212
213 def end(self, rev):
213 def end(self, rev):
214 return self._data[rev]
214 return self._data[rev]
215
215
216 def length(self, rev):
216 def length(self, rev):
217 return self.end(rev) - self.start(rev)
217 return self.end(rev) - self.start(rev)
218
218
219 def _trimchunk(revlog, revs, startidx, endidx=None):
219 def _trimchunk(revlog, revs, startidx, endidx=None):
220 """returns revs[startidx:endidx] without empty trailing revs
220 """returns revs[startidx:endidx] without empty trailing revs
221
221
222 Doctest Setup
222 Doctest Setup
223 >>> revlog = _testrevlog([
223 >>> revlog = _testrevlog([
224 ... 5, #0
224 ... 5, #0
225 ... 10, #1
225 ... 10, #1
226 ... 12, #2
226 ... 12, #2
227 ... 12, #3 (empty)
227 ... 12, #3 (empty)
228 ... 17, #4
228 ... 17, #4
229 ... 21, #5
229 ... 21, #5
230 ... 21, #6 (empty)
230 ... 21, #6 (empty)
231 ... ])
231 ... ])
232
232
233 Contiguous cases:
233 Contiguous cases:
234 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0)
234 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0)
235 [0, 1, 2, 3, 4, 5]
235 [0, 1, 2, 3, 4, 5]
236 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 5)
236 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 5)
237 [0, 1, 2, 3, 4]
237 [0, 1, 2, 3, 4]
238 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 4)
238 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 4)
239 [0, 1, 2]
239 [0, 1, 2]
240 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 2, 4)
240 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 2, 4)
241 [2]
241 [2]
242 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3)
242 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3)
243 [3, 4, 5]
243 [3, 4, 5]
244 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3, 5)
244 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3, 5)
245 [3, 4]
245 [3, 4]
246
246
247 Discontiguous cases:
247 Discontiguous cases:
248 >>> _trimchunk(revlog, [1, 3, 5, 6], 0)
248 >>> _trimchunk(revlog, [1, 3, 5, 6], 0)
249 [1, 3, 5]
249 [1, 3, 5]
250 >>> _trimchunk(revlog, [1, 3, 5, 6], 0, 2)
250 >>> _trimchunk(revlog, [1, 3, 5, 6], 0, 2)
251 [1]
251 [1]
252 >>> _trimchunk(revlog, [1, 3, 5, 6], 1, 3)
252 >>> _trimchunk(revlog, [1, 3, 5, 6], 1, 3)
253 [3, 5]
253 [3, 5]
254 >>> _trimchunk(revlog, [1, 3, 5, 6], 1)
254 >>> _trimchunk(revlog, [1, 3, 5, 6], 1)
255 [3, 5]
255 [3, 5]
256 """
256 """
257 length = revlog.length
257 length = revlog.length
258
258
259 if endidx is None:
259 if endidx is None:
260 endidx = len(revs)
260 endidx = len(revs)
261
261
262 # Trim empty revs at the end, but never the very first revision of a chain
262 # Trim empty revs at the end, but never the very first revision of a chain
263 while endidx > 1 and endidx > startidx and length(revs[endidx - 1]) == 0:
263 while endidx > 1 and endidx > startidx and length(revs[endidx - 1]) == 0:
264 endidx -= 1
264 endidx -= 1
265
265
266 return revs[startidx:endidx]
266 return revs[startidx:endidx]
267
267
268 def _segmentspan(revlog, revs):
268 def _segmentspan(revlog, revs):
269 """Get the byte span of a segment of revisions
269 """Get the byte span of a segment of revisions
270
270
271 revs is a sorted array of revision numbers
271 revs is a sorted array of revision numbers
272
272
273 >>> revlog = _testrevlog([
273 >>> revlog = _testrevlog([
274 ... 5, #0
274 ... 5, #0
275 ... 10, #1
275 ... 10, #1
276 ... 12, #2
276 ... 12, #2
277 ... 12, #3 (empty)
277 ... 12, #3 (empty)
278 ... 17, #4
278 ... 17, #4
279 ... ])
279 ... ])
280
280
281 >>> _segmentspan(revlog, [0, 1, 2, 3, 4])
281 >>> _segmentspan(revlog, [0, 1, 2, 3, 4])
282 17
282 17
283 >>> _segmentspan(revlog, [0, 4])
283 >>> _segmentspan(revlog, [0, 4])
284 17
284 17
285 >>> _segmentspan(revlog, [3, 4])
285 >>> _segmentspan(revlog, [3, 4])
286 5
286 5
287 >>> _segmentspan(revlog, [1, 2, 3,])
287 >>> _segmentspan(revlog, [1, 2, 3,])
288 7
288 7
289 >>> _segmentspan(revlog, [1, 3])
289 >>> _segmentspan(revlog, [1, 3])
290 7
290 7
291 """
291 """
292 if not revs:
292 if not revs:
293 return 0
293 return 0
294 return revlog.end(revs[-1]) - revlog.start(revs[0])
294 return revlog.end(revs[-1]) - revlog.start(revs[0])
295
295
296 def _slicechunk(revlog, revs, targetsize=None):
296 def _slicechunk(revlog, revs, targetsize=None):
297 """slice revs to reduce the amount of unrelated data to be read from disk.
297 """slice revs to reduce the amount of unrelated data to be read from disk.
298
298
299 ``revs`` is sliced into groups that should be read in one time.
299 ``revs`` is sliced into groups that should be read in one time.
300 Assume that revs are sorted.
300 Assume that revs are sorted.
301
301
302 The initial chunk is sliced until the overall density (payload/chunks-span
302 The initial chunk is sliced until the overall density (payload/chunks-span
303 ratio) is above `revlog._srdensitythreshold`. No gap smaller than
303 ratio) is above `revlog._srdensitythreshold`. No gap smaller than
304 `revlog._srmingapsize` is skipped.
304 `revlog._srmingapsize` is skipped.
305
305
306 If `targetsize` is set, no chunk larger than `targetsize` will be yield.
306 If `targetsize` is set, no chunk larger than `targetsize` will be yield.
307 For consistency with other slicing choice, this limit won't go lower than
307 For consistency with other slicing choice, this limit won't go lower than
308 `revlog._srmingapsize`.
308 `revlog._srmingapsize`.
309
309
310 If individual revisions chunk are larger than this limit, they will still
310 If individual revisions chunk are larger than this limit, they will still
311 be raised individually.
311 be raised individually.
312
312
313 >>> revlog = _testrevlog([
313 >>> revlog = _testrevlog([
314 ... 5, #00 (5)
314 ... 5, #00 (5)
315 ... 10, #01 (5)
315 ... 10, #01 (5)
316 ... 12, #02 (2)
316 ... 12, #02 (2)
317 ... 12, #03 (empty)
317 ... 12, #03 (empty)
318 ... 27, #04 (15)
318 ... 27, #04 (15)
319 ... 31, #05 (4)
319 ... 31, #05 (4)
320 ... 31, #06 (empty)
320 ... 31, #06 (empty)
321 ... 42, #07 (11)
321 ... 42, #07 (11)
322 ... 47, #08 (5)
322 ... 47, #08 (5)
323 ... 47, #09 (empty)
323 ... 47, #09 (empty)
324 ... 48, #10 (1)
324 ... 48, #10 (1)
325 ... 51, #11 (3)
325 ... 51, #11 (3)
326 ... 74, #12 (23)
326 ... 74, #12 (23)
327 ... 85, #13 (11)
327 ... 85, #13 (11)
328 ... 86, #14 (1)
328 ... 86, #14 (1)
329 ... 91, #15 (5)
329 ... 91, #15 (5)
330 ... ])
330 ... ])
331
331
332 >>> list(_slicechunk(revlog, list(range(16))))
332 >>> list(_slicechunk(revlog, list(range(16))))
333 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
333 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
334 >>> list(_slicechunk(revlog, [0, 15]))
334 >>> list(_slicechunk(revlog, [0, 15]))
335 [[0], [15]]
335 [[0], [15]]
336 >>> list(_slicechunk(revlog, [0, 11, 15]))
336 >>> list(_slicechunk(revlog, [0, 11, 15]))
337 [[0], [11], [15]]
337 [[0], [11], [15]]
338 >>> list(_slicechunk(revlog, [0, 11, 13, 15]))
338 >>> list(_slicechunk(revlog, [0, 11, 13, 15]))
339 [[0], [11, 13, 15]]
339 [[0], [11, 13, 15]]
340 >>> list(_slicechunk(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
340 >>> list(_slicechunk(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
341 [[1, 2], [5, 8, 10, 11], [14]]
341 [[1, 2], [5, 8, 10, 11], [14]]
342
342
343 Slicing with a maximum chunk size
343 Slicing with a maximum chunk size
344 >>> list(_slicechunk(revlog, [0, 11, 13, 15], 15))
344 >>> list(_slicechunk(revlog, [0, 11, 13, 15], 15))
345 [[0], [11], [13], [15]]
345 [[0], [11], [13], [15]]
346 >>> list(_slicechunk(revlog, [0, 11, 13, 15], 20))
346 >>> list(_slicechunk(revlog, [0, 11, 13, 15], 20))
347 [[0], [11], [13, 15]]
347 [[0], [11], [13, 15]]
348 """
348 """
349 if targetsize is not None:
349 if targetsize is not None:
350 targetsize = max(targetsize, revlog._srmingapsize)
350 targetsize = max(targetsize, revlog._srmingapsize)
351 for chunk in _slicechunktodensity(revlog, revs,
351 for chunk in _slicechunktodensity(revlog, revs,
352 revlog._srdensitythreshold,
352 revlog._srdensitythreshold,
353 revlog._srmingapsize):
353 revlog._srmingapsize):
354 for subchunk in _slicechunktosize(revlog, chunk, targetsize):
354 for subchunk in _slicechunktosize(revlog, chunk, targetsize):
355 yield subchunk
355 yield subchunk
356
356
357 def _slicechunktosize(revlog, revs, targetsize):
357 def _slicechunktosize(revlog, revs, targetsize):
358 """slice revs to match the target size
358 """slice revs to match the target size
359
359
360 This is intended to be used on chunk that density slicing selected by that
360 This is intended to be used on chunk that density slicing selected by that
361 are still too large compared to the read garantee of revlog. This might
361 are still too large compared to the read garantee of revlog. This might
362 happens when "minimal gap size" interrupted the slicing or when chain are
362 happens when "minimal gap size" interrupted the slicing or when chain are
363 built in a way that create large blocks next to each other.
363 built in a way that create large blocks next to each other.
364
364
365 >>> revlog = _testrevlog([
365 >>> revlog = _testrevlog([
366 ... 3, #0 (3)
366 ... 3, #0 (3)
367 ... 5, #1 (2)
367 ... 5, #1 (2)
368 ... 6, #2 (1)
368 ... 6, #2 (1)
369 ... 8, #3 (2)
369 ... 8, #3 (2)
370 ... 8, #4 (empty)
370 ... 8, #4 (empty)
371 ... 11, #5 (3)
371 ... 11, #5 (3)
372 ... 12, #6 (1)
372 ... 12, #6 (1)
373 ... 13, #7 (1)
373 ... 13, #7 (1)
374 ... 14, #8 (1)
374 ... 14, #8 (1)
375 ... ])
375 ... ])
376
376
377 Cases where chunk is already small enough
377 Cases where chunk is already small enough
378 >>> list(_slicechunktosize(revlog, [0], 3))
378 >>> list(_slicechunktosize(revlog, [0], 3))
379 [[0]]
379 [[0]]
380 >>> list(_slicechunktosize(revlog, [6, 7], 3))
380 >>> list(_slicechunktosize(revlog, [6, 7], 3))
381 [[6, 7]]
381 [[6, 7]]
382 >>> list(_slicechunktosize(revlog, [0], None))
382 >>> list(_slicechunktosize(revlog, [0], None))
383 [[0]]
383 [[0]]
384 >>> list(_slicechunktosize(revlog, [6, 7], None))
384 >>> list(_slicechunktosize(revlog, [6, 7], None))
385 [[6, 7]]
385 [[6, 7]]
386
386
387 cases where we need actual slicing
387 cases where we need actual slicing
388 >>> list(_slicechunktosize(revlog, [0, 1], 3))
388 >>> list(_slicechunktosize(revlog, [0, 1], 3))
389 [[0], [1]]
389 [[0], [1]]
390 >>> list(_slicechunktosize(revlog, [1, 3], 3))
390 >>> list(_slicechunktosize(revlog, [1, 3], 3))
391 [[1], [3]]
391 [[1], [3]]
392 >>> list(_slicechunktosize(revlog, [1, 2, 3], 3))
392 >>> list(_slicechunktosize(revlog, [1, 2, 3], 3))
393 [[1, 2], [3]]
393 [[1, 2], [3]]
394 >>> list(_slicechunktosize(revlog, [3, 5], 3))
394 >>> list(_slicechunktosize(revlog, [3, 5], 3))
395 [[3], [5]]
395 [[3], [5]]
396 >>> list(_slicechunktosize(revlog, [3, 4, 5], 3))
396 >>> list(_slicechunktosize(revlog, [3, 4, 5], 3))
397 [[3], [5]]
397 [[3], [5]]
398 >>> list(_slicechunktosize(revlog, [5, 6, 7, 8], 3))
398 >>> list(_slicechunktosize(revlog, [5, 6, 7, 8], 3))
399 [[5], [6, 7, 8]]
399 [[5], [6, 7, 8]]
400 >>> list(_slicechunktosize(revlog, [0, 1, 2, 3, 4, 5, 6, 7, 8], 3))
400 >>> list(_slicechunktosize(revlog, [0, 1, 2, 3, 4, 5, 6, 7, 8], 3))
401 [[0], [1, 2], [3], [5], [6, 7, 8]]
401 [[0], [1, 2], [3], [5], [6, 7, 8]]
402
402
403 Case with too large individual chunk (must return valid chunk)
403 Case with too large individual chunk (must return valid chunk)
404 >>> list(_slicechunktosize(revlog, [0, 1], 2))
404 >>> list(_slicechunktosize(revlog, [0, 1], 2))
405 [[0], [1]]
405 [[0], [1]]
406 >>> list(_slicechunktosize(revlog, [1, 3], 1))
406 >>> list(_slicechunktosize(revlog, [1, 3], 1))
407 [[1], [3]]
407 [[1], [3]]
408 >>> list(_slicechunktosize(revlog, [3, 4, 5], 2))
408 >>> list(_slicechunktosize(revlog, [3, 4, 5], 2))
409 [[3], [5]]
409 [[3], [5]]
410 """
410 """
411 assert targetsize is None or 0 <= targetsize
411 assert targetsize is None or 0 <= targetsize
412 if targetsize is None or _segmentspan(revlog, revs) <= targetsize:
412 if targetsize is None or _segmentspan(revlog, revs) <= targetsize:
413 yield revs
413 yield revs
414 return
414 return
415
415
416 startrevidx = 0
416 startrevidx = 0
417 startdata = revlog.start(revs[0])
417 startdata = revlog.start(revs[0])
418 endrevidx = 0
418 endrevidx = 0
419 iterrevs = enumerate(revs)
419 iterrevs = enumerate(revs)
420 next(iterrevs) # skip first rev.
420 next(iterrevs) # skip first rev.
421 for idx, r in iterrevs:
421 for idx, r in iterrevs:
422 span = revlog.end(r) - startdata
422 span = revlog.end(r) - startdata
423 if span <= targetsize:
423 if span <= targetsize:
424 endrevidx = idx
424 endrevidx = idx
425 else:
425 else:
426 chunk = _trimchunk(revlog, revs, startrevidx, endrevidx + 1)
426 chunk = _trimchunk(revlog, revs, startrevidx, endrevidx + 1)
427 if chunk:
427 if chunk:
428 yield chunk
428 yield chunk
429 startrevidx = idx
429 startrevidx = idx
430 startdata = revlog.start(r)
430 startdata = revlog.start(r)
431 endrevidx = idx
431 endrevidx = idx
432 yield _trimchunk(revlog, revs, startrevidx)
432 yield _trimchunk(revlog, revs, startrevidx)
433
433
434 def _slicechunktodensity(revlog, revs, targetdensity=0.5, mingapsize=0):
434 def _slicechunktodensity(revlog, revs, targetdensity=0.5, mingapsize=0):
435 """slice revs to reduce the amount of unrelated data to be read from disk.
435 """slice revs to reduce the amount of unrelated data to be read from disk.
436
436
437 ``revs`` is sliced into groups that should be read in one time.
437 ``revs`` is sliced into groups that should be read in one time.
438 Assume that revs are sorted.
438 Assume that revs are sorted.
439
439
440 The initial chunk is sliced until the overall density (payload/chunks-span
440 The initial chunk is sliced until the overall density (payload/chunks-span
441 ratio) is above `targetdensity`. No gap smaller than `mingapsize` is
441 ratio) is above `targetdensity`. No gap smaller than `mingapsize` is
442 skipped.
442 skipped.
443
443
444 >>> revlog = _testrevlog([
444 >>> revlog = _testrevlog([
445 ... 5, #00 (5)
445 ... 5, #00 (5)
446 ... 10, #01 (5)
446 ... 10, #01 (5)
447 ... 12, #02 (2)
447 ... 12, #02 (2)
448 ... 12, #03 (empty)
448 ... 12, #03 (empty)
449 ... 27, #04 (15)
449 ... 27, #04 (15)
450 ... 31, #05 (4)
450 ... 31, #05 (4)
451 ... 31, #06 (empty)
451 ... 31, #06 (empty)
452 ... 42, #07 (11)
452 ... 42, #07 (11)
453 ... 47, #08 (5)
453 ... 47, #08 (5)
454 ... 47, #09 (empty)
454 ... 47, #09 (empty)
455 ... 48, #10 (1)
455 ... 48, #10 (1)
456 ... 51, #11 (3)
456 ... 51, #11 (3)
457 ... 74, #12 (23)
457 ... 74, #12 (23)
458 ... 85, #13 (11)
458 ... 85, #13 (11)
459 ... 86, #14 (1)
459 ... 86, #14 (1)
460 ... 91, #15 (5)
460 ... 91, #15 (5)
461 ... ])
461 ... ])
462
462
463 >>> list(_slicechunktodensity(revlog, list(range(16))))
463 >>> list(_slicechunktodensity(revlog, list(range(16))))
464 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
464 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
465 >>> list(_slicechunktodensity(revlog, [0, 15]))
465 >>> list(_slicechunktodensity(revlog, [0, 15]))
466 [[0], [15]]
466 [[0], [15]]
467 >>> list(_slicechunktodensity(revlog, [0, 11, 15]))
467 >>> list(_slicechunktodensity(revlog, [0, 11, 15]))
468 [[0], [11], [15]]
468 [[0], [11], [15]]
469 >>> list(_slicechunktodensity(revlog, [0, 11, 13, 15]))
469 >>> list(_slicechunktodensity(revlog, [0, 11, 13, 15]))
470 [[0], [11, 13, 15]]
470 [[0], [11, 13, 15]]
471 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
471 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
472 [[1, 2], [5, 8, 10, 11], [14]]
472 [[1, 2], [5, 8, 10, 11], [14]]
473 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
473 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
474 ... mingapsize=20))
474 ... mingapsize=20))
475 [[1, 2, 3, 5, 8, 10, 11], [14]]
475 [[1, 2, 3, 5, 8, 10, 11], [14]]
476 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
476 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
477 ... targetdensity=0.95))
477 ... targetdensity=0.95))
478 [[1, 2], [5], [8, 10, 11], [14]]
478 [[1, 2], [5], [8, 10, 11], [14]]
479 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
479 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
480 ... targetdensity=0.95, mingapsize=12))
480 ... targetdensity=0.95, mingapsize=12))
481 [[1, 2], [5, 8, 10, 11], [14]]
481 [[1, 2], [5, 8, 10, 11], [14]]
482 """
482 """
483 start = revlog.start
483 start = revlog.start
484 length = revlog.length
484 length = revlog.length
485
485
486 if len(revs) <= 1:
486 if len(revs) <= 1:
487 yield revs
487 yield revs
488 return
488 return
489
489
490 readdata = deltachainspan = _segmentspan(revlog, revs)
490 readdata = deltachainspan = _segmentspan(revlog, revs)
491
491
492 if deltachainspan < mingapsize:
492 if deltachainspan < mingapsize:
493 yield revs
493 yield revs
494 return
494 return
495
495
496 chainpayload = sum(length(r) for r in revs)
496 chainpayload = sum(length(r) for r in revs)
497
497
498 if deltachainspan:
498 if deltachainspan:
499 density = chainpayload / float(deltachainspan)
499 density = chainpayload / float(deltachainspan)
500 else:
500 else:
501 density = 1.0
501 density = 1.0
502
502
503 if density >= targetdensity:
503 if density >= targetdensity:
504 yield revs
504 yield revs
505 return
505 return
506
506
507 # Store the gaps in a heap to have them sorted by decreasing size
507 # Store the gaps in a heap to have them sorted by decreasing size
508 gapsheap = []
508 gapsheap = []
509 heapq.heapify(gapsheap)
509 heapq.heapify(gapsheap)
510 prevend = None
510 prevend = None
511 for i, rev in enumerate(revs):
511 for i, rev in enumerate(revs):
512 revstart = start(rev)
512 revstart = start(rev)
513 revlen = length(rev)
513 revlen = length(rev)
514
514
515 # Skip empty revisions to form larger holes
515 # Skip empty revisions to form larger holes
516 if revlen == 0:
516 if revlen == 0:
517 continue
517 continue
518
518
519 if prevend is not None:
519 if prevend is not None:
520 gapsize = revstart - prevend
520 gapsize = revstart - prevend
521 # only consider holes that are large enough
521 # only consider holes that are large enough
522 if gapsize > mingapsize:
522 if gapsize > mingapsize:
523 heapq.heappush(gapsheap, (-gapsize, i))
523 heapq.heappush(gapsheap, (-gapsize, i))
524
524
525 prevend = revstart + revlen
525 prevend = revstart + revlen
526
526
527 # Collect the indices of the largest holes until the density is acceptable
527 # Collect the indices of the largest holes until the density is acceptable
528 indicesheap = []
528 indicesheap = []
529 heapq.heapify(indicesheap)
529 heapq.heapify(indicesheap)
530 while gapsheap and density < targetdensity:
530 while gapsheap and density < targetdensity:
531 oppgapsize, gapidx = heapq.heappop(gapsheap)
531 oppgapsize, gapidx = heapq.heappop(gapsheap)
532
532
533 heapq.heappush(indicesheap, gapidx)
533 heapq.heappush(indicesheap, gapidx)
534
534
535 # the gap sizes are stored as negatives to be sorted decreasingly
535 # the gap sizes are stored as negatives to be sorted decreasingly
536 # by the heap
536 # by the heap
537 readdata -= (-oppgapsize)
537 readdata -= (-oppgapsize)
538 if readdata > 0:
538 if readdata > 0:
539 density = chainpayload / float(readdata)
539 density = chainpayload / float(readdata)
540 else:
540 else:
541 density = 1.0
541 density = 1.0
542
542
543 # Cut the revs at collected indices
543 # Cut the revs at collected indices
544 previdx = 0
544 previdx = 0
545 while indicesheap:
545 while indicesheap:
546 idx = heapq.heappop(indicesheap)
546 idx = heapq.heappop(indicesheap)
547
547
548 chunk = _trimchunk(revlog, revs, previdx, idx)
548 chunk = _trimchunk(revlog, revs, previdx, idx)
549 if chunk:
549 if chunk:
550 yield chunk
550 yield chunk
551
551
552 previdx = idx
552 previdx = idx
553
553
554 chunk = _trimchunk(revlog, revs, previdx)
554 chunk = _trimchunk(revlog, revs, previdx)
555 if chunk:
555 if chunk:
556 yield chunk
556 yield chunk
557
557
558 @attr.s(slots=True, frozen=True)
558 @attr.s(slots=True, frozen=True)
559 class _deltainfo(object):
559 class _deltainfo(object):
560 distance = attr.ib()
560 distance = attr.ib()
561 deltalen = attr.ib()
561 deltalen = attr.ib()
562 data = attr.ib()
562 data = attr.ib()
563 base = attr.ib()
563 base = attr.ib()
564 chainbase = attr.ib()
564 chainbase = attr.ib()
565 chainlen = attr.ib()
565 chainlen = attr.ib()
566 compresseddeltalen = attr.ib()
566 compresseddeltalen = attr.ib()
567
567
568 class _deltacomputer(object):
568 class _deltacomputer(object):
569 def __init__(self, revlog):
569 def __init__(self, revlog):
570 self.revlog = revlog
570 self.revlog = revlog
571
571
572 def _getcandidaterevs(self, p1, p2, cachedelta):
572 def _getcandidaterevs(self, p1, p2, cachedelta):
573 """
573 """
574 Provides revisions that present an interest to be diffed against,
574 Provides revisions that present an interest to be diffed against,
575 grouped by level of easiness.
575 grouped by level of easiness.
576 """
576 """
577 revlog = self.revlog
577 revlog = self.revlog
578 gdelta = revlog._generaldelta
578 gdelta = revlog._generaldelta
579 curr = len(revlog)
579 curr = len(revlog)
580 prev = curr - 1
580 prev = curr - 1
581 p1r, p2r = revlog.rev(p1), revlog.rev(p2)
581 p1r, p2r = revlog.rev(p1), revlog.rev(p2)
582
582
583 # should we try to build a delta?
583 # should we try to build a delta?
584 if prev != nullrev and revlog.storedeltachains:
584 if prev != nullrev and revlog.storedeltachains:
585 tested = set()
585 tested = set()
586 # This condition is true most of the time when processing
586 # This condition is true most of the time when processing
587 # changegroup data into a generaldelta repo. The only time it
587 # changegroup data into a generaldelta repo. The only time it
588 # isn't true is if this is the first revision in a delta chain
588 # isn't true is if this is the first revision in a delta chain
589 # or if ``format.generaldelta=true`` disabled ``lazydeltabase``.
589 # or if ``format.generaldelta=true`` disabled ``lazydeltabase``.
590 if cachedelta and gdelta and revlog._lazydeltabase:
590 if cachedelta and gdelta and revlog._lazydeltabase:
591 # Assume what we received from the server is a good choice
591 # Assume what we received from the server is a good choice
592 # build delta will reuse the cache
592 # build delta will reuse the cache
593 yield (cachedelta[0],)
593 yield (cachedelta[0],)
594 tested.add(cachedelta[0])
594 tested.add(cachedelta[0])
595
595
596 if gdelta:
596 if gdelta:
597 # exclude already lazy tested base if any
597 # exclude already lazy tested base if any
598 parents = [p for p in (p1r, p2r)
598 parents = [p for p in (p1r, p2r)
599 if p != nullrev and p not in tested]
599 if p != nullrev and p not in tested]
600
600
601 if not revlog._aggressivemergedeltas and len(parents) == 2:
601 if not revlog._aggressivemergedeltas and len(parents) == 2:
602 parents.sort()
602 parents.sort()
603 # To minimize the chance of having to build a fulltext,
603 # To minimize the chance of having to build a fulltext,
604 # pick first whichever parent is closest to us (max rev)
604 # pick first whichever parent is closest to us (max rev)
605 yield (parents[1],)
605 yield (parents[1],)
606 # then the other one (min rev) if the first did not fit
606 # then the other one (min rev) if the first did not fit
607 yield (parents[0],)
607 yield (parents[0],)
608 tested.update(parents)
608 tested.update(parents)
609 elif len(parents) > 0:
609 elif len(parents) > 0:
610 # Test all parents (1 or 2), and keep the best candidate
610 # Test all parents (1 or 2), and keep the best candidate
611 yield parents
611 yield parents
612 tested.update(parents)
612 tested.update(parents)
613
613
614 if prev not in tested:
614 if prev not in tested:
615 # other approach failed try against prev to hopefully save us a
615 # other approach failed try against prev to hopefully save us a
616 # fulltext.
616 # fulltext.
617 yield (prev,)
617 yield (prev,)
618 tested.add(prev)
618 tested.add(prev)
619
619
620 def buildtext(self, revinfo, fh):
620 def buildtext(self, revinfo, fh):
621 """Builds a fulltext version of a revision
621 """Builds a fulltext version of a revision
622
622
623 revinfo: _revisioninfo instance that contains all needed info
623 revinfo: _revisioninfo instance that contains all needed info
624 fh: file handle to either the .i or the .d revlog file,
624 fh: file handle to either the .i or the .d revlog file,
625 depending on whether it is inlined or not
625 depending on whether it is inlined or not
626 """
626 """
627 btext = revinfo.btext
627 btext = revinfo.btext
628 if btext[0] is not None:
628 if btext[0] is not None:
629 return btext[0]
629 return btext[0]
630
630
631 revlog = self.revlog
631 revlog = self.revlog
632 cachedelta = revinfo.cachedelta
632 cachedelta = revinfo.cachedelta
633 flags = revinfo.flags
633 flags = revinfo.flags
634 node = revinfo.node
634 node = revinfo.node
635
635
636 baserev = cachedelta[0]
636 baserev = cachedelta[0]
637 delta = cachedelta[1]
637 delta = cachedelta[1]
638 # special case deltas which replace entire base; no need to decode
638 # special case deltas which replace entire base; no need to decode
639 # base revision. this neatly avoids censored bases, which throw when
639 # base revision. this neatly avoids censored bases, which throw when
640 # they're decoded.
640 # they're decoded.
641 hlen = struct.calcsize(">lll")
641 hlen = struct.calcsize(">lll")
642 if delta[:hlen] == mdiff.replacediffheader(revlog.rawsize(baserev),
642 if delta[:hlen] == mdiff.replacediffheader(revlog.rawsize(baserev),
643 len(delta) - hlen):
643 len(delta) - hlen):
644 btext[0] = delta[hlen:]
644 btext[0] = delta[hlen:]
645 else:
645 else:
646 # deltabase is rawtext before changed by flag processors, which is
646 # deltabase is rawtext before changed by flag processors, which is
647 # equivalent to non-raw text
647 # equivalent to non-raw text
648 basetext = revlog.revision(baserev, _df=fh, raw=False)
648 basetext = revlog.revision(baserev, _df=fh, raw=False)
649 btext[0] = mdiff.patch(basetext, delta)
649 btext[0] = mdiff.patch(basetext, delta)
650
650
651 try:
651 try:
652 res = revlog._processflags(btext[0], flags, 'read', raw=True)
652 res = revlog._processflags(btext[0], flags, 'read', raw=True)
653 btext[0], validatehash = res
653 btext[0], validatehash = res
654 if validatehash:
654 if validatehash:
655 revlog.checkhash(btext[0], node, p1=revinfo.p1, p2=revinfo.p2)
655 revlog.checkhash(btext[0], node, p1=revinfo.p1, p2=revinfo.p2)
656 if flags & REVIDX_ISCENSORED:
656 if flags & REVIDX_ISCENSORED:
657 raise RevlogError(_('node %s is not censored') % node)
657 raise RevlogError(_('node %s is not censored') % node)
658 except CensoredNodeError:
658 except CensoredNodeError:
659 # must pass the censored index flag to add censored revisions
659 # must pass the censored index flag to add censored revisions
660 if not flags & REVIDX_ISCENSORED:
660 if not flags & REVIDX_ISCENSORED:
661 raise
661 raise
662 return btext[0]
662 return btext[0]
663
663
664 def _builddeltadiff(self, base, revinfo, fh):
664 def _builddeltadiff(self, base, revinfo, fh):
665 revlog = self.revlog
665 revlog = self.revlog
666 t = self.buildtext(revinfo, fh)
666 t = self.buildtext(revinfo, fh)
667 if revlog.iscensored(base):
667 if revlog.iscensored(base):
668 # deltas based on a censored revision must replace the
668 # deltas based on a censored revision must replace the
669 # full content in one patch, so delta works everywhere
669 # full content in one patch, so delta works everywhere
670 header = mdiff.replacediffheader(revlog.rawsize(base), len(t))
670 header = mdiff.replacediffheader(revlog.rawsize(base), len(t))
671 delta = header + t
671 delta = header + t
672 else:
672 else:
673 ptext = revlog.revision(base, _df=fh, raw=True)
673 ptext = revlog.revision(base, _df=fh, raw=True)
674 delta = mdiff.textdiff(ptext, t)
674 delta = mdiff.textdiff(ptext, t)
675
675
676 return delta
676 return delta
677
677
678 def _builddeltainfo(self, revinfo, base, fh):
678 def _builddeltainfo(self, revinfo, base, fh):
679 # can we use the cached delta?
679 # can we use the cached delta?
680 if revinfo.cachedelta and revinfo.cachedelta[0] == base:
680 if revinfo.cachedelta and revinfo.cachedelta[0] == base:
681 delta = revinfo.cachedelta[1]
681 delta = revinfo.cachedelta[1]
682 else:
682 else:
683 delta = self._builddeltadiff(base, revinfo, fh)
683 delta = self._builddeltadiff(base, revinfo, fh)
684 revlog = self.revlog
684 revlog = self.revlog
685 header, data = revlog.compress(delta)
685 header, data = revlog.compress(delta)
686 deltalen = len(header) + len(data)
686 deltalen = len(header) + len(data)
687 chainbase = revlog.chainbase(base)
687 chainbase = revlog.chainbase(base)
688 offset = revlog.end(len(revlog) - 1)
688 offset = revlog.end(len(revlog) - 1)
689 dist = deltalen + offset - revlog.start(chainbase)
689 dist = deltalen + offset - revlog.start(chainbase)
690 if revlog._generaldelta:
690 if revlog._generaldelta:
691 deltabase = base
691 deltabase = base
692 else:
692 else:
693 deltabase = chainbase
693 deltabase = chainbase
694 chainlen, compresseddeltalen = revlog._chaininfo(base)
694 chainlen, compresseddeltalen = revlog._chaininfo(base)
695 chainlen += 1
695 chainlen += 1
696 compresseddeltalen += deltalen
696 compresseddeltalen += deltalen
697 return _deltainfo(dist, deltalen, (header, data), deltabase,
697 return _deltainfo(dist, deltalen, (header, data), deltabase,
698 chainbase, chainlen, compresseddeltalen)
698 chainbase, chainlen, compresseddeltalen)
699
699
700 def finddeltainfo(self, revinfo, fh):
700 def finddeltainfo(self, revinfo, fh):
701 """Find an acceptable delta against a candidate revision
701 """Find an acceptable delta against a candidate revision
702
702
703 revinfo: information about the revision (instance of _revisioninfo)
703 revinfo: information about the revision (instance of _revisioninfo)
704 fh: file handle to either the .i or the .d revlog file,
704 fh: file handle to either the .i or the .d revlog file,
705 depending on whether it is inlined or not
705 depending on whether it is inlined or not
706
706
707 Returns the first acceptable candidate revision, as ordered by
707 Returns the first acceptable candidate revision, as ordered by
708 _getcandidaterevs
708 _getcandidaterevs
709 """
709 """
710 cachedelta = revinfo.cachedelta
710 cachedelta = revinfo.cachedelta
711 p1 = revinfo.p1
711 p1 = revinfo.p1
712 p2 = revinfo.p2
712 p2 = revinfo.p2
713 revlog = self.revlog
713 revlog = self.revlog
714
714
715 deltainfo = None
715 deltainfo = None
716 for candidaterevs in self._getcandidaterevs(p1, p2, cachedelta):
716 for candidaterevs in self._getcandidaterevs(p1, p2, cachedelta):
717 nominateddeltas = []
717 nominateddeltas = []
718 for candidaterev in candidaterevs:
718 for candidaterev in candidaterevs:
719 # no delta for rawtext-changing revs (see "candelta" for why)
719 # no delta for rawtext-changing revs (see "candelta" for why)
720 if revlog.flags(candidaterev) & REVIDX_RAWTEXT_CHANGING_FLAGS:
720 if revlog.flags(candidaterev) & REVIDX_RAWTEXT_CHANGING_FLAGS:
721 continue
721 continue
722 candidatedelta = self._builddeltainfo(revinfo, candidaterev, fh)
722 candidatedelta = self._builddeltainfo(revinfo, candidaterev, fh)
723 if revlog._isgooddeltainfo(candidatedelta, revinfo):
723 if revlog._isgooddeltainfo(candidatedelta, revinfo):
724 nominateddeltas.append(candidatedelta)
724 nominateddeltas.append(candidatedelta)
725 if nominateddeltas:
725 if nominateddeltas:
726 deltainfo = min(nominateddeltas, key=lambda x: x.deltalen)
726 deltainfo = min(nominateddeltas, key=lambda x: x.deltalen)
727 break
727 break
728
728
729 return deltainfo
729 return deltainfo
730
730
731 @attr.s(slots=True, frozen=True)
731 @attr.s(slots=True, frozen=True)
732 class _revisioninfo(object):
732 class _revisioninfo(object):
733 """Information about a revision that allows building its fulltext
733 """Information about a revision that allows building its fulltext
734 node: expected hash of the revision
734 node: expected hash of the revision
735 p1, p2: parent revs of the revision
735 p1, p2: parent revs of the revision
736 btext: built text cache consisting of a one-element list
736 btext: built text cache consisting of a one-element list
737 cachedelta: (baserev, uncompressed_delta) or None
737 cachedelta: (baserev, uncompressed_delta) or None
738 flags: flags associated to the revision storage
738 flags: flags associated to the revision storage
739
739
740 One of btext[0] or cachedelta must be set.
740 One of btext[0] or cachedelta must be set.
741 """
741 """
742 node = attr.ib()
742 node = attr.ib()
743 p1 = attr.ib()
743 p1 = attr.ib()
744 p2 = attr.ib()
744 p2 = attr.ib()
745 btext = attr.ib()
745 btext = attr.ib()
746 textlen = attr.ib()
746 textlen = attr.ib()
747 cachedelta = attr.ib()
747 cachedelta = attr.ib()
748 flags = attr.ib()
748 flags = attr.ib()
749
749
750 # index v0:
750 # index v0:
751 # 4 bytes: offset
751 # 4 bytes: offset
752 # 4 bytes: compressed length
752 # 4 bytes: compressed length
753 # 4 bytes: base rev
753 # 4 bytes: base rev
754 # 4 bytes: link rev
754 # 4 bytes: link rev
755 # 20 bytes: parent 1 nodeid
755 # 20 bytes: parent 1 nodeid
756 # 20 bytes: parent 2 nodeid
756 # 20 bytes: parent 2 nodeid
757 # 20 bytes: nodeid
757 # 20 bytes: nodeid
758 indexformatv0 = struct.Struct(">4l20s20s20s")
758 indexformatv0 = struct.Struct(">4l20s20s20s")
759 indexformatv0_pack = indexformatv0.pack
759 indexformatv0_pack = indexformatv0.pack
760 indexformatv0_unpack = indexformatv0.unpack
760 indexformatv0_unpack = indexformatv0.unpack
761
761
762 class revlogoldio(object):
762 class revlogoldio(object):
763 def __init__(self):
763 def __init__(self):
764 self.size = indexformatv0.size
764 self.size = indexformatv0.size
765
765
766 def parseindex(self, data, inline):
766 def parseindex(self, data, inline):
767 s = self.size
767 s = self.size
768 index = []
768 index = []
769 nodemap = {nullid: nullrev}
769 nodemap = {nullid: nullrev}
770 n = off = 0
770 n = off = 0
771 l = len(data)
771 l = len(data)
772 while off + s <= l:
772 while off + s <= l:
773 cur = data[off:off + s]
773 cur = data[off:off + s]
774 off += s
774 off += s
775 e = indexformatv0_unpack(cur)
775 e = indexformatv0_unpack(cur)
776 # transform to revlogv1 format
776 # transform to revlogv1 format
777 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
777 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
778 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
778 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
779 index.append(e2)
779 index.append(e2)
780 nodemap[e[6]] = n
780 nodemap[e[6]] = n
781 n += 1
781 n += 1
782
782
783 # add the magic null revision at -1
783 # add the magic null revision at -1
784 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
784 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
785
785
786 return index, nodemap, None
786 return index, nodemap, None
787
787
788 def packentry(self, entry, node, version, rev):
788 def packentry(self, entry, node, version, rev):
789 if gettype(entry[0]):
789 if gettype(entry[0]):
790 raise RevlogError(_('index entry flags need revlog version 1'))
790 raise RevlogError(_('index entry flags need revlog version 1'))
791 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
791 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
792 node(entry[5]), node(entry[6]), entry[7])
792 node(entry[5]), node(entry[6]), entry[7])
793 return indexformatv0_pack(*e2)
793 return indexformatv0_pack(*e2)
794
794
795 # index ng:
795 # index ng:
796 # 6 bytes: offset
796 # 6 bytes: offset
797 # 2 bytes: flags
797 # 2 bytes: flags
798 # 4 bytes: compressed length
798 # 4 bytes: compressed length
799 # 4 bytes: uncompressed length
799 # 4 bytes: uncompressed length
800 # 4 bytes: base rev
800 # 4 bytes: base rev
801 # 4 bytes: link rev
801 # 4 bytes: link rev
802 # 4 bytes: parent 1 rev
802 # 4 bytes: parent 1 rev
803 # 4 bytes: parent 2 rev
803 # 4 bytes: parent 2 rev
804 # 32 bytes: nodeid
804 # 32 bytes: nodeid
805 indexformatng = struct.Struct(">Qiiiiii20s12x")
805 indexformatng = struct.Struct(">Qiiiiii20s12x")
806 indexformatng_pack = indexformatng.pack
806 indexformatng_pack = indexformatng.pack
807 versionformat = struct.Struct(">I")
807 versionformat = struct.Struct(">I")
808 versionformat_pack = versionformat.pack
808 versionformat_pack = versionformat.pack
809 versionformat_unpack = versionformat.unpack
809 versionformat_unpack = versionformat.unpack
810
810
811 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
811 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
812 # signed integer)
812 # signed integer)
813 _maxentrysize = 0x7fffffff
813 _maxentrysize = 0x7fffffff
814
814
815 class revlogio(object):
815 class revlogio(object):
816 def __init__(self):
816 def __init__(self):
817 self.size = indexformatng.size
817 self.size = indexformatng.size
818
818
819 def parseindex(self, data, inline):
819 def parseindex(self, data, inline):
820 # call the C implementation to parse the index data
820 # call the C implementation to parse the index data
821 index, cache = parsers.parse_index2(data, inline)
821 index, cache = parsers.parse_index2(data, inline)
822 return index, getattr(index, 'nodemap', None), cache
822 return index, getattr(index, 'nodemap', None), cache
823
823
824 def packentry(self, entry, node, version, rev):
824 def packentry(self, entry, node, version, rev):
825 p = indexformatng_pack(*entry)
825 p = indexformatng_pack(*entry)
826 if rev == 0:
826 if rev == 0:
827 p = versionformat_pack(version) + p[4:]
827 p = versionformat_pack(version) + p[4:]
828 return p
828 return p
829
829
830 class revlog(object):
830 class revlog(object):
831 """
831 """
832 the underlying revision storage object
832 the underlying revision storage object
833
833
834 A revlog consists of two parts, an index and the revision data.
834 A revlog consists of two parts, an index and the revision data.
835
835
836 The index is a file with a fixed record size containing
836 The index is a file with a fixed record size containing
837 information on each revision, including its nodeid (hash), the
837 information on each revision, including its nodeid (hash), the
838 nodeids of its parents, the position and offset of its data within
838 nodeids of its parents, the position and offset of its data within
839 the data file, and the revision it's based on. Finally, each entry
839 the data file, and the revision it's based on. Finally, each entry
840 contains a linkrev entry that can serve as a pointer to external
840 contains a linkrev entry that can serve as a pointer to external
841 data.
841 data.
842
842
843 The revision data itself is a linear collection of data chunks.
843 The revision data itself is a linear collection of data chunks.
844 Each chunk represents a revision and is usually represented as a
844 Each chunk represents a revision and is usually represented as a
845 delta against the previous chunk. To bound lookup time, runs of
845 delta against the previous chunk. To bound lookup time, runs of
846 deltas are limited to about 2 times the length of the original
846 deltas are limited to about 2 times the length of the original
847 version data. This makes retrieval of a version proportional to
847 version data. This makes retrieval of a version proportional to
848 its size, or O(1) relative to the number of revisions.
848 its size, or O(1) relative to the number of revisions.
849
849
850 Both pieces of the revlog are written to in an append-only
850 Both pieces of the revlog are written to in an append-only
851 fashion, which means we never need to rewrite a file to insert or
851 fashion, which means we never need to rewrite a file to insert or
852 remove data, and can use some simple techniques to avoid the need
852 remove data, and can use some simple techniques to avoid the need
853 for locking while reading.
853 for locking while reading.
854
854
855 If checkambig, indexfile is opened with checkambig=True at
855 If checkambig, indexfile is opened with checkambig=True at
856 writing, to avoid file stat ambiguity.
856 writing, to avoid file stat ambiguity.
857
857
858 If mmaplargeindex is True, and an mmapindexthreshold is set, the
858 If mmaplargeindex is True, and an mmapindexthreshold is set, the
859 index will be mmapped rather than read if it is larger than the
859 index will be mmapped rather than read if it is larger than the
860 configured threshold.
860 configured threshold.
861
861
862 If censorable is True, the revlog can have censored revisions.
862 If censorable is True, the revlog can have censored revisions.
863 """
863 """
864 def __init__(self, opener, indexfile, datafile=None, checkambig=False,
864 def __init__(self, opener, indexfile, datafile=None, checkambig=False,
865 mmaplargeindex=False, censorable=False):
865 mmaplargeindex=False, censorable=False):
866 """
866 """
867 create a revlog object
867 create a revlog object
868
868
869 opener is a function that abstracts the file opening operation
869 opener is a function that abstracts the file opening operation
870 and can be used to implement COW semantics or the like.
870 and can be used to implement COW semantics or the like.
871 """
871 """
872 self.indexfile = indexfile
872 self.indexfile = indexfile
873 self.datafile = datafile or (indexfile[:-2] + ".d")
873 self.datafile = datafile or (indexfile[:-2] + ".d")
874 self.opener = opener
874 self.opener = opener
875 # When True, indexfile is opened with checkambig=True at writing, to
875 # When True, indexfile is opened with checkambig=True at writing, to
876 # avoid file stat ambiguity.
876 # avoid file stat ambiguity.
877 self._checkambig = checkambig
877 self._checkambig = checkambig
878 self._censorable = censorable
878 self._censorable = censorable
879 # 3-tuple of (node, rev, text) for a raw revision.
879 # 3-tuple of (node, rev, text) for a raw revision.
880 self._cache = None
880 self._cache = None
881 # Maps rev to chain base rev.
881 # Maps rev to chain base rev.
882 self._chainbasecache = util.lrucachedict(100)
882 self._chainbasecache = util.lrucachedict(100)
883 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
883 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
884 self._chunkcache = (0, '')
884 self._chunkcache = (0, '')
885 # How much data to read and cache into the raw revlog data cache.
885 # How much data to read and cache into the raw revlog data cache.
886 self._chunkcachesize = 65536
886 self._chunkcachesize = 65536
887 self._maxchainlen = None
887 self._maxchainlen = None
888 self._aggressivemergedeltas = True
888 self._aggressivemergedeltas = True
889 self.index = []
889 self.index = []
890 # Mapping of partial identifiers to full nodes.
890 # Mapping of partial identifiers to full nodes.
891 self._pcache = {}
891 self._pcache = {}
892 # Mapping of revision integer to full node.
892 # Mapping of revision integer to full node.
893 self._nodecache = {nullid: nullrev}
893 self._nodecache = {nullid: nullrev}
894 self._nodepos = None
894 self._nodepos = None
895 self._compengine = 'zlib'
895 self._compengine = 'zlib'
896 self._maxdeltachainspan = -1
896 self._maxdeltachainspan = -1
897 self._withsparseread = False
897 self._withsparseread = False
898 self._srdensitythreshold = 0.50
898 self._srdensitythreshold = 0.50
899 self._srmingapsize = 262144
899 self._srmingapsize = 262144
900
900
901 mmapindexthreshold = None
901 mmapindexthreshold = None
902 v = REVLOG_DEFAULT_VERSION
902 v = REVLOG_DEFAULT_VERSION
903 opts = getattr(opener, 'options', None)
903 opts = getattr(opener, 'options', None)
904 if opts is not None:
904 if opts is not None:
905 if 'revlogv2' in opts:
905 if 'revlogv2' in opts:
906 # version 2 revlogs always use generaldelta.
906 # version 2 revlogs always use generaldelta.
907 v = REVLOGV2 | FLAG_GENERALDELTA | FLAG_INLINE_DATA
907 v = REVLOGV2 | FLAG_GENERALDELTA | FLAG_INLINE_DATA
908 elif 'revlogv1' in opts:
908 elif 'revlogv1' in opts:
909 if 'generaldelta' in opts:
909 if 'generaldelta' in opts:
910 v |= FLAG_GENERALDELTA
910 v |= FLAG_GENERALDELTA
911 else:
911 else:
912 v = 0
912 v = 0
913 if 'chunkcachesize' in opts:
913 if 'chunkcachesize' in opts:
914 self._chunkcachesize = opts['chunkcachesize']
914 self._chunkcachesize = opts['chunkcachesize']
915 if 'maxchainlen' in opts:
915 if 'maxchainlen' in opts:
916 self._maxchainlen = opts['maxchainlen']
916 self._maxchainlen = opts['maxchainlen']
917 if 'aggressivemergedeltas' in opts:
917 if 'aggressivemergedeltas' in opts:
918 self._aggressivemergedeltas = opts['aggressivemergedeltas']
918 self._aggressivemergedeltas = opts['aggressivemergedeltas']
919 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
919 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
920 if 'compengine' in opts:
920 if 'compengine' in opts:
921 self._compengine = opts['compengine']
921 self._compengine = opts['compengine']
922 if 'maxdeltachainspan' in opts:
922 if 'maxdeltachainspan' in opts:
923 self._maxdeltachainspan = opts['maxdeltachainspan']
923 self._maxdeltachainspan = opts['maxdeltachainspan']
924 if mmaplargeindex and 'mmapindexthreshold' in opts:
924 if mmaplargeindex and 'mmapindexthreshold' in opts:
925 mmapindexthreshold = opts['mmapindexthreshold']
925 mmapindexthreshold = opts['mmapindexthreshold']
926 self._withsparseread = bool(opts.get('with-sparse-read', False))
926 self._withsparseread = bool(opts.get('with-sparse-read', False))
927 if 'sparse-read-density-threshold' in opts:
927 if 'sparse-read-density-threshold' in opts:
928 self._srdensitythreshold = opts['sparse-read-density-threshold']
928 self._srdensitythreshold = opts['sparse-read-density-threshold']
929 if 'sparse-read-min-gap-size' in opts:
929 if 'sparse-read-min-gap-size' in opts:
930 self._srmingapsize = opts['sparse-read-min-gap-size']
930 self._srmingapsize = opts['sparse-read-min-gap-size']
931
931
932 if self._chunkcachesize <= 0:
932 if self._chunkcachesize <= 0:
933 raise RevlogError(_('revlog chunk cache size %r is not greater '
933 raise RevlogError(_('revlog chunk cache size %r is not greater '
934 'than 0') % self._chunkcachesize)
934 'than 0') % self._chunkcachesize)
935 elif self._chunkcachesize & (self._chunkcachesize - 1):
935 elif self._chunkcachesize & (self._chunkcachesize - 1):
936 raise RevlogError(_('revlog chunk cache size %r is not a power '
936 raise RevlogError(_('revlog chunk cache size %r is not a power '
937 'of 2') % self._chunkcachesize)
937 'of 2') % self._chunkcachesize)
938
938
939 indexdata = ''
939 indexdata = ''
940 self._initempty = True
940 self._initempty = True
941 try:
941 try:
942 with self._indexfp() as f:
942 with self._indexfp() as f:
943 if (mmapindexthreshold is not None and
943 if (mmapindexthreshold is not None and
944 self.opener.fstat(f).st_size >= mmapindexthreshold):
944 self.opener.fstat(f).st_size >= mmapindexthreshold):
945 indexdata = util.buffer(util.mmapread(f))
945 indexdata = util.buffer(util.mmapread(f))
946 else:
946 else:
947 indexdata = f.read()
947 indexdata = f.read()
948 if len(indexdata) > 0:
948 if len(indexdata) > 0:
949 v = versionformat_unpack(indexdata[:4])[0]
949 v = versionformat_unpack(indexdata[:4])[0]
950 self._initempty = False
950 self._initempty = False
951 except IOError as inst:
951 except IOError as inst:
952 if inst.errno != errno.ENOENT:
952 if inst.errno != errno.ENOENT:
953 raise
953 raise
954
954
955 self.version = v
955 self.version = v
956 self._inline = v & FLAG_INLINE_DATA
956 self._inline = v & FLAG_INLINE_DATA
957 self._generaldelta = v & FLAG_GENERALDELTA
957 self._generaldelta = v & FLAG_GENERALDELTA
958 flags = v & ~0xFFFF
958 flags = v & ~0xFFFF
959 fmt = v & 0xFFFF
959 fmt = v & 0xFFFF
960 if fmt == REVLOGV0:
960 if fmt == REVLOGV0:
961 if flags:
961 if flags:
962 raise RevlogError(_('unknown flags (%#04x) in version %d '
962 raise RevlogError(_('unknown flags (%#04x) in version %d '
963 'revlog %s') %
963 'revlog %s') %
964 (flags >> 16, fmt, self.indexfile))
964 (flags >> 16, fmt, self.indexfile))
965 elif fmt == REVLOGV1:
965 elif fmt == REVLOGV1:
966 if flags & ~REVLOGV1_FLAGS:
966 if flags & ~REVLOGV1_FLAGS:
967 raise RevlogError(_('unknown flags (%#04x) in version %d '
967 raise RevlogError(_('unknown flags (%#04x) in version %d '
968 'revlog %s') %
968 'revlog %s') %
969 (flags >> 16, fmt, self.indexfile))
969 (flags >> 16, fmt, self.indexfile))
970 elif fmt == REVLOGV2:
970 elif fmt == REVLOGV2:
971 if flags & ~REVLOGV2_FLAGS:
971 if flags & ~REVLOGV2_FLAGS:
972 raise RevlogError(_('unknown flags (%#04x) in version %d '
972 raise RevlogError(_('unknown flags (%#04x) in version %d '
973 'revlog %s') %
973 'revlog %s') %
974 (flags >> 16, fmt, self.indexfile))
974 (flags >> 16, fmt, self.indexfile))
975 else:
975 else:
976 raise RevlogError(_('unknown version (%d) in revlog %s') %
976 raise RevlogError(_('unknown version (%d) in revlog %s') %
977 (fmt, self.indexfile))
977 (fmt, self.indexfile))
978
978
979 self.storedeltachains = True
979 self.storedeltachains = True
980
980
981 self._io = revlogio()
981 self._io = revlogio()
982 if self.version == REVLOGV0:
982 if self.version == REVLOGV0:
983 self._io = revlogoldio()
983 self._io = revlogoldio()
984 try:
984 try:
985 d = self._io.parseindex(indexdata, self._inline)
985 d = self._io.parseindex(indexdata, self._inline)
986 except (ValueError, IndexError):
986 except (ValueError, IndexError):
987 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
987 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
988 self.index, nodemap, self._chunkcache = d
988 self.index, nodemap, self._chunkcache = d
989 if nodemap is not None:
989 if nodemap is not None:
990 self.nodemap = self._nodecache = nodemap
990 self.nodemap = self._nodecache = nodemap
991 if not self._chunkcache:
991 if not self._chunkcache:
992 self._chunkclear()
992 self._chunkclear()
993 # revnum -> (chain-length, sum-delta-length)
993 # revnum -> (chain-length, sum-delta-length)
994 self._chaininfocache = {}
994 self._chaininfocache = {}
995 # revlog header -> revlog compressor
995 # revlog header -> revlog compressor
996 self._decompressors = {}
996 self._decompressors = {}
997
997
998 @util.propertycache
998 @util.propertycache
999 def _compressor(self):
999 def _compressor(self):
1000 return util.compengines[self._compengine].revlogcompressor()
1000 return util.compengines[self._compengine].revlogcompressor()
1001
1001
1002 def _indexfp(self, mode='r'):
1002 def _indexfp(self, mode='r'):
1003 """file object for the revlog's index file"""
1003 """file object for the revlog's index file"""
1004 args = {r'mode': mode}
1004 args = {r'mode': mode}
1005 if mode != 'r':
1005 if mode != 'r':
1006 args[r'checkambig'] = self._checkambig
1006 args[r'checkambig'] = self._checkambig
1007 if mode == 'w':
1007 if mode == 'w':
1008 args[r'atomictemp'] = True
1008 args[r'atomictemp'] = True
1009 return self.opener(self.indexfile, **args)
1009 return self.opener(self.indexfile, **args)
1010
1010
1011 def _datafp(self, mode='r'):
1011 def _datafp(self, mode='r'):
1012 """file object for the revlog's data file"""
1012 """file object for the revlog's data file"""
1013 return self.opener(self.datafile, mode=mode)
1013 return self.opener(self.datafile, mode=mode)
1014
1014
1015 @contextlib.contextmanager
1015 @contextlib.contextmanager
1016 def _datareadfp(self, existingfp=None):
1016 def _datareadfp(self, existingfp=None):
1017 """file object suitable to read data"""
1017 """file object suitable to read data"""
1018 if existingfp is not None:
1018 if existingfp is not None:
1019 yield existingfp
1019 yield existingfp
1020 else:
1020 else:
1021 if self._inline:
1021 if self._inline:
1022 func = self._indexfp
1022 func = self._indexfp
1023 else:
1023 else:
1024 func = self._datafp
1024 func = self._datafp
1025 with func() as fp:
1025 with func() as fp:
1026 yield fp
1026 yield fp
1027
1027
1028 def tip(self):
1028 def tip(self):
1029 return self.node(len(self.index) - 2)
1029 return self.node(len(self.index) - 2)
1030 def __contains__(self, rev):
1030 def __contains__(self, rev):
1031 return 0 <= rev < len(self)
1031 return 0 <= rev < len(self)
1032 def __len__(self):
1032 def __len__(self):
1033 return len(self.index) - 1
1033 return len(self.index) - 1
1034 def __iter__(self):
1034 def __iter__(self):
1035 return iter(xrange(len(self)))
1035 return iter(xrange(len(self)))
1036 def revs(self, start=0, stop=None):
1036 def revs(self, start=0, stop=None):
1037 """iterate over all rev in this revlog (from start to stop)"""
1037 """iterate over all rev in this revlog (from start to stop)"""
1038 step = 1
1038 step = 1
1039 if stop is not None:
1039 if stop is not None:
1040 if start > stop:
1040 if start > stop:
1041 step = -1
1041 step = -1
1042 stop += step
1042 stop += step
1043 else:
1043 else:
1044 stop = len(self)
1044 stop = len(self)
1045 return xrange(start, stop, step)
1045 return xrange(start, stop, step)
1046
1046
1047 @util.propertycache
1047 @util.propertycache
1048 def nodemap(self):
1048 def nodemap(self):
1049 self.rev(self.node(0))
1049 self.rev(self.node(0))
1050 return self._nodecache
1050 return self._nodecache
1051
1051
1052 def hasnode(self, node):
1052 def hasnode(self, node):
1053 try:
1053 try:
1054 self.rev(node)
1054 self.rev(node)
1055 return True
1055 return True
1056 except KeyError:
1056 except KeyError:
1057 return False
1057 return False
1058
1058
1059 def candelta(self, baserev, rev):
1059 def candelta(self, baserev, rev):
1060 """whether two revisions (baserev, rev) can be delta-ed or not"""
1060 """whether two revisions (baserev, rev) can be delta-ed or not"""
1061 # Disable delta if either rev requires a content-changing flag
1061 # Disable delta if either rev requires a content-changing flag
1062 # processor (ex. LFS). This is because such flag processor can alter
1062 # processor (ex. LFS). This is because such flag processor can alter
1063 # the rawtext content that the delta will be based on, and two clients
1063 # the rawtext content that the delta will be based on, and two clients
1064 # could have a same revlog node with different flags (i.e. different
1064 # could have a same revlog node with different flags (i.e. different
1065 # rawtext contents) and the delta could be incompatible.
1065 # rawtext contents) and the delta could be incompatible.
1066 if ((self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS)
1066 if ((self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS)
1067 or (self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS)):
1067 or (self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS)):
1068 return False
1068 return False
1069 return True
1069 return True
1070
1070
1071 def clearcaches(self):
1071 def clearcaches(self):
1072 self._cache = None
1072 self._cache = None
1073 self._chainbasecache.clear()
1073 self._chainbasecache.clear()
1074 self._chunkcache = (0, '')
1074 self._chunkcache = (0, '')
1075 self._pcache = {}
1075 self._pcache = {}
1076
1076
1077 try:
1077 try:
1078 self._nodecache.clearcaches()
1078 self._nodecache.clearcaches()
1079 except AttributeError:
1079 except AttributeError:
1080 self._nodecache = {nullid: nullrev}
1080 self._nodecache = {nullid: nullrev}
1081 self._nodepos = None
1081 self._nodepos = None
1082
1082
1083 def rev(self, node):
1083 def rev(self, node):
1084 try:
1084 try:
1085 return self._nodecache[node]
1085 return self._nodecache[node]
1086 except TypeError:
1086 except TypeError:
1087 raise
1087 raise
1088 except RevlogError:
1088 except RevlogError:
1089 # parsers.c radix tree lookup failed
1089 # parsers.c radix tree lookup failed
1090 if node == wdirid or node in wdirfilenodeids:
1090 if node == wdirid or node in wdirfilenodeids:
1091 raise error.WdirUnsupported
1091 raise error.WdirUnsupported
1092 raise LookupError(node, self.indexfile, _('no node'))
1092 raise LookupError(node, self.indexfile, _('no node'))
1093 except KeyError:
1093 except KeyError:
1094 # pure python cache lookup failed
1094 # pure python cache lookup failed
1095 n = self._nodecache
1095 n = self._nodecache
1096 i = self.index
1096 i = self.index
1097 p = self._nodepos
1097 p = self._nodepos
1098 if p is None:
1098 if p is None:
1099 p = len(i) - 2
1099 p = len(i) - 2
1100 else:
1100 else:
1101 assert p < len(i)
1101 assert p < len(i)
1102 for r in xrange(p, -1, -1):
1102 for r in xrange(p, -1, -1):
1103 v = i[r][7]
1103 v = i[r][7]
1104 n[v] = r
1104 n[v] = r
1105 if v == node:
1105 if v == node:
1106 self._nodepos = r - 1
1106 self._nodepos = r - 1
1107 return r
1107 return r
1108 if node == wdirid or node in wdirfilenodeids:
1108 if node == wdirid or node in wdirfilenodeids:
1109 raise error.WdirUnsupported
1109 raise error.WdirUnsupported
1110 raise LookupError(node, self.indexfile, _('no node'))
1110 raise LookupError(node, self.indexfile, _('no node'))
1111
1111
1112 # Accessors for index entries.
1112 # Accessors for index entries.
1113
1113
1114 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
1114 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
1115 # are flags.
1115 # are flags.
1116 def start(self, rev):
1116 def start(self, rev):
1117 return int(self.index[rev][0] >> 16)
1117 return int(self.index[rev][0] >> 16)
1118
1118
1119 def flags(self, rev):
1119 def flags(self, rev):
1120 return self.index[rev][0] & 0xFFFF
1120 return self.index[rev][0] & 0xFFFF
1121
1121
1122 def length(self, rev):
1122 def length(self, rev):
1123 return self.index[rev][1]
1123 return self.index[rev][1]
1124
1124
1125 def rawsize(self, rev):
1125 def rawsize(self, rev):
1126 """return the length of the uncompressed text for a given revision"""
1126 """return the length of the uncompressed text for a given revision"""
1127 l = self.index[rev][2]
1127 l = self.index[rev][2]
1128 if l >= 0:
1128 if l >= 0:
1129 return l
1129 return l
1130
1130
1131 t = self.revision(rev, raw=True)
1131 t = self.revision(rev, raw=True)
1132 return len(t)
1132 return len(t)
1133
1133
1134 def size(self, rev):
1134 def size(self, rev):
1135 """length of non-raw text (processed by a "read" flag processor)"""
1135 """length of non-raw text (processed by a "read" flag processor)"""
1136 # fast path: if no "read" flag processor could change the content,
1136 # fast path: if no "read" flag processor could change the content,
1137 # size is rawsize. note: ELLIPSIS is known to not change the content.
1137 # size is rawsize. note: ELLIPSIS is known to not change the content.
1138 flags = self.flags(rev)
1138 flags = self.flags(rev)
1139 if flags & (REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
1139 if flags & (REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
1140 return self.rawsize(rev)
1140 return self.rawsize(rev)
1141
1141
1142 return len(self.revision(rev, raw=False))
1142 return len(self.revision(rev, raw=False))
1143
1143
1144 def chainbase(self, rev):
1144 def chainbase(self, rev):
1145 base = self._chainbasecache.get(rev)
1145 base = self._chainbasecache.get(rev)
1146 if base is not None:
1146 if base is not None:
1147 return base
1147 return base
1148
1148
1149 index = self.index
1149 index = self.index
1150 iterrev = rev
1150 iterrev = rev
1151 base = index[iterrev][3]
1151 base = index[iterrev][3]
1152 while base != iterrev:
1152 while base != iterrev:
1153 iterrev = base
1153 iterrev = base
1154 base = index[iterrev][3]
1154 base = index[iterrev][3]
1155
1155
1156 self._chainbasecache[rev] = base
1156 self._chainbasecache[rev] = base
1157 return base
1157 return base
1158
1158
1159 def linkrev(self, rev):
1159 def linkrev(self, rev):
1160 return self.index[rev][4]
1160 return self.index[rev][4]
1161
1161
1162 def parentrevs(self, rev):
1162 def parentrevs(self, rev):
1163 try:
1163 try:
1164 entry = self.index[rev]
1164 entry = self.index[rev]
1165 except IndexError:
1165 except IndexError:
1166 if rev == wdirrev:
1166 if rev == wdirrev:
1167 raise error.WdirUnsupported
1167 raise error.WdirUnsupported
1168 raise
1168 raise
1169
1169
1170 return entry[5], entry[6]
1170 return entry[5], entry[6]
1171
1171
1172 def node(self, rev):
1172 def node(self, rev):
1173 try:
1173 try:
1174 return self.index[rev][7]
1174 return self.index[rev][7]
1175 except IndexError:
1175 except IndexError:
1176 if rev == wdirrev:
1176 if rev == wdirrev:
1177 raise error.WdirUnsupported
1177 raise error.WdirUnsupported
1178 raise
1178 raise
1179
1179
1180 # Derived from index values.
1180 # Derived from index values.
1181
1181
1182 def end(self, rev):
1182 def end(self, rev):
1183 return self.start(rev) + self.length(rev)
1183 return self.start(rev) + self.length(rev)
1184
1184
1185 def parents(self, node):
1185 def parents(self, node):
1186 i = self.index
1186 i = self.index
1187 d = i[self.rev(node)]
1187 d = i[self.rev(node)]
1188 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
1188 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
1189
1189
1190 def chainlen(self, rev):
1190 def chainlen(self, rev):
1191 return self._chaininfo(rev)[0]
1191 return self._chaininfo(rev)[0]
1192
1192
1193 def _chaininfo(self, rev):
1193 def _chaininfo(self, rev):
1194 chaininfocache = self._chaininfocache
1194 chaininfocache = self._chaininfocache
1195 if rev in chaininfocache:
1195 if rev in chaininfocache:
1196 return chaininfocache[rev]
1196 return chaininfocache[rev]
1197 index = self.index
1197 index = self.index
1198 generaldelta = self._generaldelta
1198 generaldelta = self._generaldelta
1199 iterrev = rev
1199 iterrev = rev
1200 e = index[iterrev]
1200 e = index[iterrev]
1201 clen = 0
1201 clen = 0
1202 compresseddeltalen = 0
1202 compresseddeltalen = 0
1203 while iterrev != e[3]:
1203 while iterrev != e[3]:
1204 clen += 1
1204 clen += 1
1205 compresseddeltalen += e[1]
1205 compresseddeltalen += e[1]
1206 if generaldelta:
1206 if generaldelta:
1207 iterrev = e[3]
1207 iterrev = e[3]
1208 else:
1208 else:
1209 iterrev -= 1
1209 iterrev -= 1
1210 if iterrev in chaininfocache:
1210 if iterrev in chaininfocache:
1211 t = chaininfocache[iterrev]
1211 t = chaininfocache[iterrev]
1212 clen += t[0]
1212 clen += t[0]
1213 compresseddeltalen += t[1]
1213 compresseddeltalen += t[1]
1214 break
1214 break
1215 e = index[iterrev]
1215 e = index[iterrev]
1216 else:
1216 else:
1217 # Add text length of base since decompressing that also takes
1217 # Add text length of base since decompressing that also takes
1218 # work. For cache hits the length is already included.
1218 # work. For cache hits the length is already included.
1219 compresseddeltalen += e[1]
1219 compresseddeltalen += e[1]
1220 r = (clen, compresseddeltalen)
1220 r = (clen, compresseddeltalen)
1221 chaininfocache[rev] = r
1221 chaininfocache[rev] = r
1222 return r
1222 return r
1223
1223
1224 def _deltachain(self, rev, stoprev=None):
1224 def _deltachain(self, rev, stoprev=None):
1225 """Obtain the delta chain for a revision.
1225 """Obtain the delta chain for a revision.
1226
1226
1227 ``stoprev`` specifies a revision to stop at. If not specified, we
1227 ``stoprev`` specifies a revision to stop at. If not specified, we
1228 stop at the base of the chain.
1228 stop at the base of the chain.
1229
1229
1230 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
1230 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
1231 revs in ascending order and ``stopped`` is a bool indicating whether
1231 revs in ascending order and ``stopped`` is a bool indicating whether
1232 ``stoprev`` was hit.
1232 ``stoprev`` was hit.
1233 """
1233 """
1234 # Try C implementation.
1234 # Try C implementation.
1235 try:
1235 try:
1236 return self.index.deltachain(rev, stoprev, self._generaldelta)
1236 return self.index.deltachain(rev, stoprev, self._generaldelta)
1237 except AttributeError:
1237 except AttributeError:
1238 pass
1238 pass
1239
1239
1240 chain = []
1240 chain = []
1241
1241
1242 # Alias to prevent attribute lookup in tight loop.
1242 # Alias to prevent attribute lookup in tight loop.
1243 index = self.index
1243 index = self.index
1244 generaldelta = self._generaldelta
1244 generaldelta = self._generaldelta
1245
1245
1246 iterrev = rev
1246 iterrev = rev
1247 e = index[iterrev]
1247 e = index[iterrev]
1248 while iterrev != e[3] and iterrev != stoprev:
1248 while iterrev != e[3] and iterrev != stoprev:
1249 chain.append(iterrev)
1249 chain.append(iterrev)
1250 if generaldelta:
1250 if generaldelta:
1251 iterrev = e[3]
1251 iterrev = e[3]
1252 else:
1252 else:
1253 iterrev -= 1
1253 iterrev -= 1
1254 e = index[iterrev]
1254 e = index[iterrev]
1255
1255
1256 if iterrev == stoprev:
1256 if iterrev == stoprev:
1257 stopped = True
1257 stopped = True
1258 else:
1258 else:
1259 chain.append(iterrev)
1259 chain.append(iterrev)
1260 stopped = False
1260 stopped = False
1261
1261
1262 chain.reverse()
1262 chain.reverse()
1263 return chain, stopped
1263 return chain, stopped
1264
1264
1265 def ancestors(self, revs, stoprev=0, inclusive=False):
1265 def ancestors(self, revs, stoprev=0, inclusive=False):
1266 """Generate the ancestors of 'revs' in reverse topological order.
1266 """Generate the ancestors of 'revs' in reverse topological order.
1267 Does not generate revs lower than stoprev.
1267 Does not generate revs lower than stoprev.
1268
1268
1269 See the documentation for ancestor.lazyancestors for more details."""
1269 See the documentation for ancestor.lazyancestors for more details."""
1270
1270
1271 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
1271 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
1272 inclusive=inclusive)
1272 inclusive=inclusive)
1273
1273
1274 def descendants(self, revs):
1274 def descendants(self, revs):
1275 """Generate the descendants of 'revs' in revision order.
1275 """Generate the descendants of 'revs' in revision order.
1276
1276
1277 Yield a sequence of revision numbers starting with a child of
1277 Yield a sequence of revision numbers starting with a child of
1278 some rev in revs, i.e., each revision is *not* considered a
1278 some rev in revs, i.e., each revision is *not* considered a
1279 descendant of itself. Results are ordered by revision number (a
1279 descendant of itself. Results are ordered by revision number (a
1280 topological sort)."""
1280 topological sort)."""
1281 first = min(revs)
1281 first = min(revs)
1282 if first == nullrev:
1282 if first == nullrev:
1283 for i in self:
1283 for i in self:
1284 yield i
1284 yield i
1285 return
1285 return
1286
1286
1287 seen = set(revs)
1287 seen = set(revs)
1288 for i in self.revs(start=first + 1):
1288 for i in self.revs(start=first + 1):
1289 for x in self.parentrevs(i):
1289 for x in self.parentrevs(i):
1290 if x != nullrev and x in seen:
1290 if x != nullrev and x in seen:
1291 seen.add(i)
1291 seen.add(i)
1292 yield i
1292 yield i
1293 break
1293 break
1294
1294
1295 def findcommonmissing(self, common=None, heads=None):
1295 def findcommonmissing(self, common=None, heads=None):
1296 """Return a tuple of the ancestors of common and the ancestors of heads
1296 """Return a tuple of the ancestors of common and the ancestors of heads
1297 that are not ancestors of common. In revset terminology, we return the
1297 that are not ancestors of common. In revset terminology, we return the
1298 tuple:
1298 tuple:
1299
1299
1300 ::common, (::heads) - (::common)
1300 ::common, (::heads) - (::common)
1301
1301
1302 The list is sorted by revision number, meaning it is
1302 The list is sorted by revision number, meaning it is
1303 topologically sorted.
1303 topologically sorted.
1304
1304
1305 'heads' and 'common' are both lists of node IDs. If heads is
1305 'heads' and 'common' are both lists of node IDs. If heads is
1306 not supplied, uses all of the revlog's heads. If common is not
1306 not supplied, uses all of the revlog's heads. If common is not
1307 supplied, uses nullid."""
1307 supplied, uses nullid."""
1308 if common is None:
1308 if common is None:
1309 common = [nullid]
1309 common = [nullid]
1310 if heads is None:
1310 if heads is None:
1311 heads = self.heads()
1311 heads = self.heads()
1312
1312
1313 common = [self.rev(n) for n in common]
1313 common = [self.rev(n) for n in common]
1314 heads = [self.rev(n) for n in heads]
1314 heads = [self.rev(n) for n in heads]
1315
1315
1316 # we want the ancestors, but inclusive
1316 # we want the ancestors, but inclusive
1317 class lazyset(object):
1317 class lazyset(object):
1318 def __init__(self, lazyvalues):
1318 def __init__(self, lazyvalues):
1319 self.addedvalues = set()
1319 self.addedvalues = set()
1320 self.lazyvalues = lazyvalues
1320 self.lazyvalues = lazyvalues
1321
1321
1322 def __contains__(self, value):
1322 def __contains__(self, value):
1323 return value in self.addedvalues or value in self.lazyvalues
1323 return value in self.addedvalues or value in self.lazyvalues
1324
1324
1325 def __iter__(self):
1325 def __iter__(self):
1326 added = self.addedvalues
1326 added = self.addedvalues
1327 for r in added:
1327 for r in added:
1328 yield r
1328 yield r
1329 for r in self.lazyvalues:
1329 for r in self.lazyvalues:
1330 if not r in added:
1330 if not r in added:
1331 yield r
1331 yield r
1332
1332
1333 def add(self, value):
1333 def add(self, value):
1334 self.addedvalues.add(value)
1334 self.addedvalues.add(value)
1335
1335
1336 def update(self, values):
1336 def update(self, values):
1337 self.addedvalues.update(values)
1337 self.addedvalues.update(values)
1338
1338
1339 has = lazyset(self.ancestors(common))
1339 has = lazyset(self.ancestors(common))
1340 has.add(nullrev)
1340 has.add(nullrev)
1341 has.update(common)
1341 has.update(common)
1342
1342
1343 # take all ancestors from heads that aren't in has
1343 # take all ancestors from heads that aren't in has
1344 missing = set()
1344 missing = set()
1345 visit = collections.deque(r for r in heads if r not in has)
1345 visit = collections.deque(r for r in heads if r not in has)
1346 while visit:
1346 while visit:
1347 r = visit.popleft()
1347 r = visit.popleft()
1348 if r in missing:
1348 if r in missing:
1349 continue
1349 continue
1350 else:
1350 else:
1351 missing.add(r)
1351 missing.add(r)
1352 for p in self.parentrevs(r):
1352 for p in self.parentrevs(r):
1353 if p not in has:
1353 if p not in has:
1354 visit.append(p)
1354 visit.append(p)
1355 missing = list(missing)
1355 missing = list(missing)
1356 missing.sort()
1356 missing.sort()
1357 return has, [self.node(miss) for miss in missing]
1357 return has, [self.node(miss) for miss in missing]
1358
1358
1359 def incrementalmissingrevs(self, common=None):
1359 def incrementalmissingrevs(self, common=None):
1360 """Return an object that can be used to incrementally compute the
1360 """Return an object that can be used to incrementally compute the
1361 revision numbers of the ancestors of arbitrary sets that are not
1361 revision numbers of the ancestors of arbitrary sets that are not
1362 ancestors of common. This is an ancestor.incrementalmissingancestors
1362 ancestors of common. This is an ancestor.incrementalmissingancestors
1363 object.
1363 object.
1364
1364
1365 'common' is a list of revision numbers. If common is not supplied, uses
1365 'common' is a list of revision numbers. If common is not supplied, uses
1366 nullrev.
1366 nullrev.
1367 """
1367 """
1368 if common is None:
1368 if common is None:
1369 common = [nullrev]
1369 common = [nullrev]
1370
1370
1371 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1371 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1372
1372
1373 def findmissingrevs(self, common=None, heads=None):
1373 def findmissingrevs(self, common=None, heads=None):
1374 """Return the revision numbers of the ancestors of heads that
1374 """Return the revision numbers of the ancestors of heads that
1375 are not ancestors of common.
1375 are not ancestors of common.
1376
1376
1377 More specifically, return a list of revision numbers corresponding to
1377 More specifically, return a list of revision numbers corresponding to
1378 nodes N such that every N satisfies the following constraints:
1378 nodes N such that every N satisfies the following constraints:
1379
1379
1380 1. N is an ancestor of some node in 'heads'
1380 1. N is an ancestor of some node in 'heads'
1381 2. N is not an ancestor of any node in 'common'
1381 2. N is not an ancestor of any node in 'common'
1382
1382
1383 The list is sorted by revision number, meaning it is
1383 The list is sorted by revision number, meaning it is
1384 topologically sorted.
1384 topologically sorted.
1385
1385
1386 'heads' and 'common' are both lists of revision numbers. If heads is
1386 'heads' and 'common' are both lists of revision numbers. If heads is
1387 not supplied, uses all of the revlog's heads. If common is not
1387 not supplied, uses all of the revlog's heads. If common is not
1388 supplied, uses nullid."""
1388 supplied, uses nullid."""
1389 if common is None:
1389 if common is None:
1390 common = [nullrev]
1390 common = [nullrev]
1391 if heads is None:
1391 if heads is None:
1392 heads = self.headrevs()
1392 heads = self.headrevs()
1393
1393
1394 inc = self.incrementalmissingrevs(common=common)
1394 inc = self.incrementalmissingrevs(common=common)
1395 return inc.missingancestors(heads)
1395 return inc.missingancestors(heads)
1396
1396
1397 def findmissing(self, common=None, heads=None):
1397 def findmissing(self, common=None, heads=None):
1398 """Return the ancestors of heads that are not ancestors of common.
1398 """Return the ancestors of heads that are not ancestors of common.
1399
1399
1400 More specifically, return a list of nodes N such that every N
1400 More specifically, return a list of nodes N such that every N
1401 satisfies the following constraints:
1401 satisfies the following constraints:
1402
1402
1403 1. N is an ancestor of some node in 'heads'
1403 1. N is an ancestor of some node in 'heads'
1404 2. N is not an ancestor of any node in 'common'
1404 2. N is not an ancestor of any node in 'common'
1405
1405
1406 The list is sorted by revision number, meaning it is
1406 The list is sorted by revision number, meaning it is
1407 topologically sorted.
1407 topologically sorted.
1408
1408
1409 'heads' and 'common' are both lists of node IDs. If heads is
1409 'heads' and 'common' are both lists of node IDs. If heads is
1410 not supplied, uses all of the revlog's heads. If common is not
1410 not supplied, uses all of the revlog's heads. If common is not
1411 supplied, uses nullid."""
1411 supplied, uses nullid."""
1412 if common is None:
1412 if common is None:
1413 common = [nullid]
1413 common = [nullid]
1414 if heads is None:
1414 if heads is None:
1415 heads = self.heads()
1415 heads = self.heads()
1416
1416
1417 common = [self.rev(n) for n in common]
1417 common = [self.rev(n) for n in common]
1418 heads = [self.rev(n) for n in heads]
1418 heads = [self.rev(n) for n in heads]
1419
1419
1420 inc = self.incrementalmissingrevs(common=common)
1420 inc = self.incrementalmissingrevs(common=common)
1421 return [self.node(r) for r in inc.missingancestors(heads)]
1421 return [self.node(r) for r in inc.missingancestors(heads)]
1422
1422
1423 def nodesbetween(self, roots=None, heads=None):
1423 def nodesbetween(self, roots=None, heads=None):
1424 """Return a topological path from 'roots' to 'heads'.
1424 """Return a topological path from 'roots' to 'heads'.
1425
1425
1426 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1426 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1427 topologically sorted list of all nodes N that satisfy both of
1427 topologically sorted list of all nodes N that satisfy both of
1428 these constraints:
1428 these constraints:
1429
1429
1430 1. N is a descendant of some node in 'roots'
1430 1. N is a descendant of some node in 'roots'
1431 2. N is an ancestor of some node in 'heads'
1431 2. N is an ancestor of some node in 'heads'
1432
1432
1433 Every node is considered to be both a descendant and an ancestor
1433 Every node is considered to be both a descendant and an ancestor
1434 of itself, so every reachable node in 'roots' and 'heads' will be
1434 of itself, so every reachable node in 'roots' and 'heads' will be
1435 included in 'nodes'.
1435 included in 'nodes'.
1436
1436
1437 'outroots' is the list of reachable nodes in 'roots', i.e., the
1437 'outroots' is the list of reachable nodes in 'roots', i.e., the
1438 subset of 'roots' that is returned in 'nodes'. Likewise,
1438 subset of 'roots' that is returned in 'nodes'. Likewise,
1439 'outheads' is the subset of 'heads' that is also in 'nodes'.
1439 'outheads' is the subset of 'heads' that is also in 'nodes'.
1440
1440
1441 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1441 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1442 unspecified, uses nullid as the only root. If 'heads' is
1442 unspecified, uses nullid as the only root. If 'heads' is
1443 unspecified, uses list of all of the revlog's heads."""
1443 unspecified, uses list of all of the revlog's heads."""
1444 nonodes = ([], [], [])
1444 nonodes = ([], [], [])
1445 if roots is not None:
1445 if roots is not None:
1446 roots = list(roots)
1446 roots = list(roots)
1447 if not roots:
1447 if not roots:
1448 return nonodes
1448 return nonodes
1449 lowestrev = min([self.rev(n) for n in roots])
1449 lowestrev = min([self.rev(n) for n in roots])
1450 else:
1450 else:
1451 roots = [nullid] # Everybody's a descendant of nullid
1451 roots = [nullid] # Everybody's a descendant of nullid
1452 lowestrev = nullrev
1452 lowestrev = nullrev
1453 if (lowestrev == nullrev) and (heads is None):
1453 if (lowestrev == nullrev) and (heads is None):
1454 # We want _all_ the nodes!
1454 # We want _all_ the nodes!
1455 return ([self.node(r) for r in self], [nullid], list(self.heads()))
1455 return ([self.node(r) for r in self], [nullid], list(self.heads()))
1456 if heads is None:
1456 if heads is None:
1457 # All nodes are ancestors, so the latest ancestor is the last
1457 # All nodes are ancestors, so the latest ancestor is the last
1458 # node.
1458 # node.
1459 highestrev = len(self) - 1
1459 highestrev = len(self) - 1
1460 # Set ancestors to None to signal that every node is an ancestor.
1460 # Set ancestors to None to signal that every node is an ancestor.
1461 ancestors = None
1461 ancestors = None
1462 # Set heads to an empty dictionary for later discovery of heads
1462 # Set heads to an empty dictionary for later discovery of heads
1463 heads = {}
1463 heads = {}
1464 else:
1464 else:
1465 heads = list(heads)
1465 heads = list(heads)
1466 if not heads:
1466 if not heads:
1467 return nonodes
1467 return nonodes
1468 ancestors = set()
1468 ancestors = set()
1469 # Turn heads into a dictionary so we can remove 'fake' heads.
1469 # Turn heads into a dictionary so we can remove 'fake' heads.
1470 # Also, later we will be using it to filter out the heads we can't
1470 # Also, later we will be using it to filter out the heads we can't
1471 # find from roots.
1471 # find from roots.
1472 heads = dict.fromkeys(heads, False)
1472 heads = dict.fromkeys(heads, False)
1473 # Start at the top and keep marking parents until we're done.
1473 # Start at the top and keep marking parents until we're done.
1474 nodestotag = set(heads)
1474 nodestotag = set(heads)
1475 # Remember where the top was so we can use it as a limit later.
1475 # Remember where the top was so we can use it as a limit later.
1476 highestrev = max([self.rev(n) for n in nodestotag])
1476 highestrev = max([self.rev(n) for n in nodestotag])
1477 while nodestotag:
1477 while nodestotag:
1478 # grab a node to tag
1478 # grab a node to tag
1479 n = nodestotag.pop()
1479 n = nodestotag.pop()
1480 # Never tag nullid
1480 # Never tag nullid
1481 if n == nullid:
1481 if n == nullid:
1482 continue
1482 continue
1483 # A node's revision number represents its place in a
1483 # A node's revision number represents its place in a
1484 # topologically sorted list of nodes.
1484 # topologically sorted list of nodes.
1485 r = self.rev(n)
1485 r = self.rev(n)
1486 if r >= lowestrev:
1486 if r >= lowestrev:
1487 if n not in ancestors:
1487 if n not in ancestors:
1488 # If we are possibly a descendant of one of the roots
1488 # If we are possibly a descendant of one of the roots
1489 # and we haven't already been marked as an ancestor
1489 # and we haven't already been marked as an ancestor
1490 ancestors.add(n) # Mark as ancestor
1490 ancestors.add(n) # Mark as ancestor
1491 # Add non-nullid parents to list of nodes to tag.
1491 # Add non-nullid parents to list of nodes to tag.
1492 nodestotag.update([p for p in self.parents(n) if
1492 nodestotag.update([p for p in self.parents(n) if
1493 p != nullid])
1493 p != nullid])
1494 elif n in heads: # We've seen it before, is it a fake head?
1494 elif n in heads: # We've seen it before, is it a fake head?
1495 # So it is, real heads should not be the ancestors of
1495 # So it is, real heads should not be the ancestors of
1496 # any other heads.
1496 # any other heads.
1497 heads.pop(n)
1497 heads.pop(n)
1498 if not ancestors:
1498 if not ancestors:
1499 return nonodes
1499 return nonodes
1500 # Now that we have our set of ancestors, we want to remove any
1500 # Now that we have our set of ancestors, we want to remove any
1501 # roots that are not ancestors.
1501 # roots that are not ancestors.
1502
1502
1503 # If one of the roots was nullid, everything is included anyway.
1503 # If one of the roots was nullid, everything is included anyway.
1504 if lowestrev > nullrev:
1504 if lowestrev > nullrev:
1505 # But, since we weren't, let's recompute the lowest rev to not
1505 # But, since we weren't, let's recompute the lowest rev to not
1506 # include roots that aren't ancestors.
1506 # include roots that aren't ancestors.
1507
1507
1508 # Filter out roots that aren't ancestors of heads
1508 # Filter out roots that aren't ancestors of heads
1509 roots = [root for root in roots if root in ancestors]
1509 roots = [root for root in roots if root in ancestors]
1510 # Recompute the lowest revision
1510 # Recompute the lowest revision
1511 if roots:
1511 if roots:
1512 lowestrev = min([self.rev(root) for root in roots])
1512 lowestrev = min([self.rev(root) for root in roots])
1513 else:
1513 else:
1514 # No more roots? Return empty list
1514 # No more roots? Return empty list
1515 return nonodes
1515 return nonodes
1516 else:
1516 else:
1517 # We are descending from nullid, and don't need to care about
1517 # We are descending from nullid, and don't need to care about
1518 # any other roots.
1518 # any other roots.
1519 lowestrev = nullrev
1519 lowestrev = nullrev
1520 roots = [nullid]
1520 roots = [nullid]
1521 # Transform our roots list into a set.
1521 # Transform our roots list into a set.
1522 descendants = set(roots)
1522 descendants = set(roots)
1523 # Also, keep the original roots so we can filter out roots that aren't
1523 # Also, keep the original roots so we can filter out roots that aren't
1524 # 'real' roots (i.e. are descended from other roots).
1524 # 'real' roots (i.e. are descended from other roots).
1525 roots = descendants.copy()
1525 roots = descendants.copy()
1526 # Our topologically sorted list of output nodes.
1526 # Our topologically sorted list of output nodes.
1527 orderedout = []
1527 orderedout = []
1528 # Don't start at nullid since we don't want nullid in our output list,
1528 # Don't start at nullid since we don't want nullid in our output list,
1529 # and if nullid shows up in descendants, empty parents will look like
1529 # and if nullid shows up in descendants, empty parents will look like
1530 # they're descendants.
1530 # they're descendants.
1531 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1531 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1532 n = self.node(r)
1532 n = self.node(r)
1533 isdescendant = False
1533 isdescendant = False
1534 if lowestrev == nullrev: # Everybody is a descendant of nullid
1534 if lowestrev == nullrev: # Everybody is a descendant of nullid
1535 isdescendant = True
1535 isdescendant = True
1536 elif n in descendants:
1536 elif n in descendants:
1537 # n is already a descendant
1537 # n is already a descendant
1538 isdescendant = True
1538 isdescendant = True
1539 # This check only needs to be done here because all the roots
1539 # This check only needs to be done here because all the roots
1540 # will start being marked is descendants before the loop.
1540 # will start being marked is descendants before the loop.
1541 if n in roots:
1541 if n in roots:
1542 # If n was a root, check if it's a 'real' root.
1542 # If n was a root, check if it's a 'real' root.
1543 p = tuple(self.parents(n))
1543 p = tuple(self.parents(n))
1544 # If any of its parents are descendants, it's not a root.
1544 # If any of its parents are descendants, it's not a root.
1545 if (p[0] in descendants) or (p[1] in descendants):
1545 if (p[0] in descendants) or (p[1] in descendants):
1546 roots.remove(n)
1546 roots.remove(n)
1547 else:
1547 else:
1548 p = tuple(self.parents(n))
1548 p = tuple(self.parents(n))
1549 # A node is a descendant if either of its parents are
1549 # A node is a descendant if either of its parents are
1550 # descendants. (We seeded the dependents list with the roots
1550 # descendants. (We seeded the dependents list with the roots
1551 # up there, remember?)
1551 # up there, remember?)
1552 if (p[0] in descendants) or (p[1] in descendants):
1552 if (p[0] in descendants) or (p[1] in descendants):
1553 descendants.add(n)
1553 descendants.add(n)
1554 isdescendant = True
1554 isdescendant = True
1555 if isdescendant and ((ancestors is None) or (n in ancestors)):
1555 if isdescendant and ((ancestors is None) or (n in ancestors)):
1556 # Only include nodes that are both descendants and ancestors.
1556 # Only include nodes that are both descendants and ancestors.
1557 orderedout.append(n)
1557 orderedout.append(n)
1558 if (ancestors is not None) and (n in heads):
1558 if (ancestors is not None) and (n in heads):
1559 # We're trying to figure out which heads are reachable
1559 # We're trying to figure out which heads are reachable
1560 # from roots.
1560 # from roots.
1561 # Mark this head as having been reached
1561 # Mark this head as having been reached
1562 heads[n] = True
1562 heads[n] = True
1563 elif ancestors is None:
1563 elif ancestors is None:
1564 # Otherwise, we're trying to discover the heads.
1564 # Otherwise, we're trying to discover the heads.
1565 # Assume this is a head because if it isn't, the next step
1565 # Assume this is a head because if it isn't, the next step
1566 # will eventually remove it.
1566 # will eventually remove it.
1567 heads[n] = True
1567 heads[n] = True
1568 # But, obviously its parents aren't.
1568 # But, obviously its parents aren't.
1569 for p in self.parents(n):
1569 for p in self.parents(n):
1570 heads.pop(p, None)
1570 heads.pop(p, None)
1571 heads = [head for head, flag in heads.iteritems() if flag]
1571 heads = [head for head, flag in heads.iteritems() if flag]
1572 roots = list(roots)
1572 roots = list(roots)
1573 assert orderedout
1573 assert orderedout
1574 assert roots
1574 assert roots
1575 assert heads
1575 assert heads
1576 return (orderedout, roots, heads)
1576 return (orderedout, roots, heads)
1577
1577
1578 def headrevs(self):
1578 def headrevs(self):
1579 try:
1579 try:
1580 return self.index.headrevs()
1580 return self.index.headrevs()
1581 except AttributeError:
1581 except AttributeError:
1582 return self._headrevs()
1582 return self._headrevs()
1583
1583
1584 def computephases(self, roots):
1584 def computephases(self, roots):
1585 return self.index.computephasesmapsets(roots)
1585 return self.index.computephasesmapsets(roots)
1586
1586
1587 def _headrevs(self):
1587 def _headrevs(self):
1588 count = len(self)
1588 count = len(self)
1589 if not count:
1589 if not count:
1590 return [nullrev]
1590 return [nullrev]
1591 # we won't iter over filtered rev so nobody is a head at start
1591 # we won't iter over filtered rev so nobody is a head at start
1592 ishead = [0] * (count + 1)
1592 ishead = [0] * (count + 1)
1593 index = self.index
1593 index = self.index
1594 for r in self:
1594 for r in self:
1595 ishead[r] = 1 # I may be an head
1595 ishead[r] = 1 # I may be an head
1596 e = index[r]
1596 e = index[r]
1597 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1597 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1598 return [r for r, val in enumerate(ishead) if val]
1598 return [r for r, val in enumerate(ishead) if val]
1599
1599
1600 def heads(self, start=None, stop=None):
1600 def heads(self, start=None, stop=None):
1601 """return the list of all nodes that have no children
1601 """return the list of all nodes that have no children
1602
1602
1603 if start is specified, only heads that are descendants of
1603 if start is specified, only heads that are descendants of
1604 start will be returned
1604 start will be returned
1605 if stop is specified, it will consider all the revs from stop
1605 if stop is specified, it will consider all the revs from stop
1606 as if they had no children
1606 as if they had no children
1607 """
1607 """
1608 if start is None and stop is None:
1608 if start is None and stop is None:
1609 if not len(self):
1609 if not len(self):
1610 return [nullid]
1610 return [nullid]
1611 return [self.node(r) for r in self.headrevs()]
1611 return [self.node(r) for r in self.headrevs()]
1612
1612
1613 if start is None:
1613 if start is None:
1614 start = nullid
1614 start = nullid
1615 if stop is None:
1615 if stop is None:
1616 stop = []
1616 stop = []
1617 stoprevs = set([self.rev(n) for n in stop])
1617 stoprevs = set([self.rev(n) for n in stop])
1618 startrev = self.rev(start)
1618 startrev = self.rev(start)
1619 reachable = {startrev}
1619 reachable = {startrev}
1620 heads = {startrev}
1620 heads = {startrev}
1621
1621
1622 parentrevs = self.parentrevs
1622 parentrevs = self.parentrevs
1623 for r in self.revs(start=startrev + 1):
1623 for r in self.revs(start=startrev + 1):
1624 for p in parentrevs(r):
1624 for p in parentrevs(r):
1625 if p in reachable:
1625 if p in reachable:
1626 if r not in stoprevs:
1626 if r not in stoprevs:
1627 reachable.add(r)
1627 reachable.add(r)
1628 heads.add(r)
1628 heads.add(r)
1629 if p in heads and p not in stoprevs:
1629 if p in heads and p not in stoprevs:
1630 heads.remove(p)
1630 heads.remove(p)
1631
1631
1632 return [self.node(r) for r in heads]
1632 return [self.node(r) for r in heads]
1633
1633
1634 def children(self, node):
1634 def children(self, node):
1635 """find the children of a given node"""
1635 """find the children of a given node"""
1636 c = []
1636 c = []
1637 p = self.rev(node)
1637 p = self.rev(node)
1638 for r in self.revs(start=p + 1):
1638 for r in self.revs(start=p + 1):
1639 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1639 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1640 if prevs:
1640 if prevs:
1641 for pr in prevs:
1641 for pr in prevs:
1642 if pr == p:
1642 if pr == p:
1643 c.append(self.node(r))
1643 c.append(self.node(r))
1644 elif p == nullrev:
1644 elif p == nullrev:
1645 c.append(self.node(r))
1645 c.append(self.node(r))
1646 return c
1646 return c
1647
1647
1648 def isdescendantrev(self, a, b):
1649 """True if revision a is a descendant of revision b
1650
1651 A revision is considered a descendant of itself.
1652
1653 The implementation of this is trivial but the use of
1654 commonancestorsheads is not."""
1655 if b == nullrev:
1656 return True
1657 elif a == b:
1658 return True
1659 elif a < b:
1660 return False
1661 return b in self._commonancestorsheads(a, b)
1662
1663 def commonancestorsheads(self, a, b):
1648 def commonancestorsheads(self, a, b):
1664 """calculate all the heads of the common ancestors of nodes a and b"""
1649 """calculate all the heads of the common ancestors of nodes a and b"""
1665 a, b = self.rev(a), self.rev(b)
1650 a, b = self.rev(a), self.rev(b)
1666 ancs = self._commonancestorsheads(a, b)
1651 ancs = self._commonancestorsheads(a, b)
1667 return pycompat.maplist(self.node, ancs)
1652 return pycompat.maplist(self.node, ancs)
1668
1653
1669 def _commonancestorsheads(self, *revs):
1654 def _commonancestorsheads(self, *revs):
1670 """calculate all the heads of the common ancestors of revs"""
1655 """calculate all the heads of the common ancestors of revs"""
1671 try:
1656 try:
1672 ancs = self.index.commonancestorsheads(*revs)
1657 ancs = self.index.commonancestorsheads(*revs)
1673 except (AttributeError, OverflowError): # C implementation failed
1658 except (AttributeError, OverflowError): # C implementation failed
1674 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1659 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1675 return ancs
1660 return ancs
1676
1661
1677 def isancestor(self, a, b):
1662 def isancestor(self, a, b):
1678 """return True if node a is an ancestor of node b
1663 """return True if node a is an ancestor of node b
1679
1664
1680 A revision is considered an ancestor of itself."""
1665 A revision is considered an ancestor of itself."""
1681 a, b = self.rev(a), self.rev(b)
1666 a, b = self.rev(a), self.rev(b)
1682 return self.isancestorrev(a, b)
1667 return self.isancestorrev(a, b)
1683
1668
1684 def isancestorrev(self, a, b):
1669 def isancestorrev(self, a, b):
1685 """return True if revision a is an ancestor of revision b
1670 """return True if revision a is an ancestor of revision b
1686
1671
1687 A revision is considered an ancestor of itself."""
1672 A revision is considered an ancestor of itself.
1688 return self.isdescendantrev(b, a)
1673
1674 The implementation of this is trivial but the use of
1675 commonancestorsheads is not."""
1676 if a == nullrev:
1677 return True
1678 elif a == b:
1679 return True
1680 elif a > b:
1681 return False
1682 return a in self._commonancestorsheads(a, b)
1689
1683
1690 def ancestor(self, a, b):
1684 def ancestor(self, a, b):
1691 """calculate the "best" common ancestor of nodes a and b"""
1685 """calculate the "best" common ancestor of nodes a and b"""
1692
1686
1693 a, b = self.rev(a), self.rev(b)
1687 a, b = self.rev(a), self.rev(b)
1694 try:
1688 try:
1695 ancs = self.index.ancestors(a, b)
1689 ancs = self.index.ancestors(a, b)
1696 except (AttributeError, OverflowError):
1690 except (AttributeError, OverflowError):
1697 ancs = ancestor.ancestors(self.parentrevs, a, b)
1691 ancs = ancestor.ancestors(self.parentrevs, a, b)
1698 if ancs:
1692 if ancs:
1699 # choose a consistent winner when there's a tie
1693 # choose a consistent winner when there's a tie
1700 return min(map(self.node, ancs))
1694 return min(map(self.node, ancs))
1701 return nullid
1695 return nullid
1702
1696
1703 def _match(self, id):
1697 def _match(self, id):
1704 if isinstance(id, int):
1698 if isinstance(id, int):
1705 # rev
1699 # rev
1706 return self.node(id)
1700 return self.node(id)
1707 if len(id) == 20:
1701 if len(id) == 20:
1708 # possibly a binary node
1702 # possibly a binary node
1709 # odds of a binary node being all hex in ASCII are 1 in 10**25
1703 # odds of a binary node being all hex in ASCII are 1 in 10**25
1710 try:
1704 try:
1711 node = id
1705 node = id
1712 self.rev(node) # quick search the index
1706 self.rev(node) # quick search the index
1713 return node
1707 return node
1714 except LookupError:
1708 except LookupError:
1715 pass # may be partial hex id
1709 pass # may be partial hex id
1716 try:
1710 try:
1717 # str(rev)
1711 # str(rev)
1718 rev = int(id)
1712 rev = int(id)
1719 if "%d" % rev != id:
1713 if "%d" % rev != id:
1720 raise ValueError
1714 raise ValueError
1721 if rev < 0:
1715 if rev < 0:
1722 rev = len(self) + rev
1716 rev = len(self) + rev
1723 if rev < 0 or rev >= len(self):
1717 if rev < 0 or rev >= len(self):
1724 raise ValueError
1718 raise ValueError
1725 return self.node(rev)
1719 return self.node(rev)
1726 except (ValueError, OverflowError):
1720 except (ValueError, OverflowError):
1727 pass
1721 pass
1728 if len(id) == 40:
1722 if len(id) == 40:
1729 try:
1723 try:
1730 # a full hex nodeid?
1724 # a full hex nodeid?
1731 node = bin(id)
1725 node = bin(id)
1732 self.rev(node)
1726 self.rev(node)
1733 return node
1727 return node
1734 except (TypeError, LookupError):
1728 except (TypeError, LookupError):
1735 pass
1729 pass
1736
1730
1737 def _partialmatch(self, id):
1731 def _partialmatch(self, id):
1738 # we don't care wdirfilenodeids as they should be always full hash
1732 # we don't care wdirfilenodeids as they should be always full hash
1739 maybewdir = wdirhex.startswith(id)
1733 maybewdir = wdirhex.startswith(id)
1740 try:
1734 try:
1741 partial = self.index.partialmatch(id)
1735 partial = self.index.partialmatch(id)
1742 if partial and self.hasnode(partial):
1736 if partial and self.hasnode(partial):
1743 if maybewdir:
1737 if maybewdir:
1744 # single 'ff...' match in radix tree, ambiguous with wdir
1738 # single 'ff...' match in radix tree, ambiguous with wdir
1745 raise RevlogError
1739 raise RevlogError
1746 return partial
1740 return partial
1747 if maybewdir:
1741 if maybewdir:
1748 # no 'ff...' match in radix tree, wdir identified
1742 # no 'ff...' match in radix tree, wdir identified
1749 raise error.WdirUnsupported
1743 raise error.WdirUnsupported
1750 return None
1744 return None
1751 except RevlogError:
1745 except RevlogError:
1752 # parsers.c radix tree lookup gave multiple matches
1746 # parsers.c radix tree lookup gave multiple matches
1753 # fast path: for unfiltered changelog, radix tree is accurate
1747 # fast path: for unfiltered changelog, radix tree is accurate
1754 if not getattr(self, 'filteredrevs', None):
1748 if not getattr(self, 'filteredrevs', None):
1755 raise LookupError(id, self.indexfile,
1749 raise LookupError(id, self.indexfile,
1756 _('ambiguous identifier'))
1750 _('ambiguous identifier'))
1757 # fall through to slow path that filters hidden revisions
1751 # fall through to slow path that filters hidden revisions
1758 except (AttributeError, ValueError):
1752 except (AttributeError, ValueError):
1759 # we are pure python, or key was too short to search radix tree
1753 # we are pure python, or key was too short to search radix tree
1760 pass
1754 pass
1761
1755
1762 if id in self._pcache:
1756 if id in self._pcache:
1763 return self._pcache[id]
1757 return self._pcache[id]
1764
1758
1765 if len(id) <= 40:
1759 if len(id) <= 40:
1766 try:
1760 try:
1767 # hex(node)[:...]
1761 # hex(node)[:...]
1768 l = len(id) // 2 # grab an even number of digits
1762 l = len(id) // 2 # grab an even number of digits
1769 prefix = bin(id[:l * 2])
1763 prefix = bin(id[:l * 2])
1770 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1764 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1771 nl = [n for n in nl if hex(n).startswith(id) and
1765 nl = [n for n in nl if hex(n).startswith(id) and
1772 self.hasnode(n)]
1766 self.hasnode(n)]
1773 if len(nl) > 0:
1767 if len(nl) > 0:
1774 if len(nl) == 1 and not maybewdir:
1768 if len(nl) == 1 and not maybewdir:
1775 self._pcache[id] = nl[0]
1769 self._pcache[id] = nl[0]
1776 return nl[0]
1770 return nl[0]
1777 raise LookupError(id, self.indexfile,
1771 raise LookupError(id, self.indexfile,
1778 _('ambiguous identifier'))
1772 _('ambiguous identifier'))
1779 if maybewdir:
1773 if maybewdir:
1780 raise error.WdirUnsupported
1774 raise error.WdirUnsupported
1781 return None
1775 return None
1782 except TypeError:
1776 except TypeError:
1783 pass
1777 pass
1784
1778
1785 def lookup(self, id):
1779 def lookup(self, id):
1786 """locate a node based on:
1780 """locate a node based on:
1787 - revision number or str(revision number)
1781 - revision number or str(revision number)
1788 - nodeid or subset of hex nodeid
1782 - nodeid or subset of hex nodeid
1789 """
1783 """
1790 n = self._match(id)
1784 n = self._match(id)
1791 if n is not None:
1785 if n is not None:
1792 return n
1786 return n
1793 n = self._partialmatch(id)
1787 n = self._partialmatch(id)
1794 if n:
1788 if n:
1795 return n
1789 return n
1796
1790
1797 raise LookupError(id, self.indexfile, _('no match found'))
1791 raise LookupError(id, self.indexfile, _('no match found'))
1798
1792
1799 def shortest(self, node, minlength=1):
1793 def shortest(self, node, minlength=1):
1800 """Find the shortest unambiguous prefix that matches node."""
1794 """Find the shortest unambiguous prefix that matches node."""
1801 def isvalid(prefix):
1795 def isvalid(prefix):
1802 try:
1796 try:
1803 node = self._partialmatch(prefix)
1797 node = self._partialmatch(prefix)
1804 except error.RevlogError:
1798 except error.RevlogError:
1805 return False
1799 return False
1806 except error.WdirUnsupported:
1800 except error.WdirUnsupported:
1807 # single 'ff...' match
1801 # single 'ff...' match
1808 return True
1802 return True
1809 if node is None:
1803 if node is None:
1810 raise LookupError(node, self.indexfile, _('no node'))
1804 raise LookupError(node, self.indexfile, _('no node'))
1811 return True
1805 return True
1812
1806
1813 def maybewdir(prefix):
1807 def maybewdir(prefix):
1814 return all(c == 'f' for c in prefix)
1808 return all(c == 'f' for c in prefix)
1815
1809
1816 hexnode = hex(node)
1810 hexnode = hex(node)
1817
1811
1818 def disambiguate(hexnode, minlength):
1812 def disambiguate(hexnode, minlength):
1819 """Disambiguate against wdirid."""
1813 """Disambiguate against wdirid."""
1820 for length in range(minlength, 41):
1814 for length in range(minlength, 41):
1821 prefix = hexnode[:length]
1815 prefix = hexnode[:length]
1822 if not maybewdir(prefix):
1816 if not maybewdir(prefix):
1823 return prefix
1817 return prefix
1824
1818
1825 if not getattr(self, 'filteredrevs', None):
1819 if not getattr(self, 'filteredrevs', None):
1826 try:
1820 try:
1827 length = max(self.index.shortest(node), minlength)
1821 length = max(self.index.shortest(node), minlength)
1828 return disambiguate(hexnode, length)
1822 return disambiguate(hexnode, length)
1829 except RevlogError:
1823 except RevlogError:
1830 if node != wdirid:
1824 if node != wdirid:
1831 raise LookupError(node, self.indexfile, _('no node'))
1825 raise LookupError(node, self.indexfile, _('no node'))
1832 except AttributeError:
1826 except AttributeError:
1833 # Fall through to pure code
1827 # Fall through to pure code
1834 pass
1828 pass
1835
1829
1836 if node == wdirid:
1830 if node == wdirid:
1837 for length in range(minlength, 41):
1831 for length in range(minlength, 41):
1838 prefix = hexnode[:length]
1832 prefix = hexnode[:length]
1839 if isvalid(prefix):
1833 if isvalid(prefix):
1840 return prefix
1834 return prefix
1841
1835
1842 for length in range(minlength, 41):
1836 for length in range(minlength, 41):
1843 prefix = hexnode[:length]
1837 prefix = hexnode[:length]
1844 if isvalid(prefix):
1838 if isvalid(prefix):
1845 return disambiguate(hexnode, length)
1839 return disambiguate(hexnode, length)
1846
1840
1847 def cmp(self, node, text):
1841 def cmp(self, node, text):
1848 """compare text with a given file revision
1842 """compare text with a given file revision
1849
1843
1850 returns True if text is different than what is stored.
1844 returns True if text is different than what is stored.
1851 """
1845 """
1852 p1, p2 = self.parents(node)
1846 p1, p2 = self.parents(node)
1853 return hash(text, p1, p2) != node
1847 return hash(text, p1, p2) != node
1854
1848
1855 def _cachesegment(self, offset, data):
1849 def _cachesegment(self, offset, data):
1856 """Add a segment to the revlog cache.
1850 """Add a segment to the revlog cache.
1857
1851
1858 Accepts an absolute offset and the data that is at that location.
1852 Accepts an absolute offset and the data that is at that location.
1859 """
1853 """
1860 o, d = self._chunkcache
1854 o, d = self._chunkcache
1861 # try to add to existing cache
1855 # try to add to existing cache
1862 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1856 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1863 self._chunkcache = o, d + data
1857 self._chunkcache = o, d + data
1864 else:
1858 else:
1865 self._chunkcache = offset, data
1859 self._chunkcache = offset, data
1866
1860
1867 def _readsegment(self, offset, length, df=None):
1861 def _readsegment(self, offset, length, df=None):
1868 """Load a segment of raw data from the revlog.
1862 """Load a segment of raw data from the revlog.
1869
1863
1870 Accepts an absolute offset, length to read, and an optional existing
1864 Accepts an absolute offset, length to read, and an optional existing
1871 file handle to read from.
1865 file handle to read from.
1872
1866
1873 If an existing file handle is passed, it will be seeked and the
1867 If an existing file handle is passed, it will be seeked and the
1874 original seek position will NOT be restored.
1868 original seek position will NOT be restored.
1875
1869
1876 Returns a str or buffer of raw byte data.
1870 Returns a str or buffer of raw byte data.
1877 """
1871 """
1878 # Cache data both forward and backward around the requested
1872 # Cache data both forward and backward around the requested
1879 # data, in a fixed size window. This helps speed up operations
1873 # data, in a fixed size window. This helps speed up operations
1880 # involving reading the revlog backwards.
1874 # involving reading the revlog backwards.
1881 cachesize = self._chunkcachesize
1875 cachesize = self._chunkcachesize
1882 realoffset = offset & ~(cachesize - 1)
1876 realoffset = offset & ~(cachesize - 1)
1883 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1877 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1884 - realoffset)
1878 - realoffset)
1885 with self._datareadfp(df) as df:
1879 with self._datareadfp(df) as df:
1886 df.seek(realoffset)
1880 df.seek(realoffset)
1887 d = df.read(reallength)
1881 d = df.read(reallength)
1888 self._cachesegment(realoffset, d)
1882 self._cachesegment(realoffset, d)
1889 if offset != realoffset or reallength != length:
1883 if offset != realoffset or reallength != length:
1890 return util.buffer(d, offset - realoffset, length)
1884 return util.buffer(d, offset - realoffset, length)
1891 return d
1885 return d
1892
1886
1893 def _getsegment(self, offset, length, df=None):
1887 def _getsegment(self, offset, length, df=None):
1894 """Obtain a segment of raw data from the revlog.
1888 """Obtain a segment of raw data from the revlog.
1895
1889
1896 Accepts an absolute offset, length of bytes to obtain, and an
1890 Accepts an absolute offset, length of bytes to obtain, and an
1897 optional file handle to the already-opened revlog. If the file
1891 optional file handle to the already-opened revlog. If the file
1898 handle is used, it's original seek position will not be preserved.
1892 handle is used, it's original seek position will not be preserved.
1899
1893
1900 Requests for data may be returned from a cache.
1894 Requests for data may be returned from a cache.
1901
1895
1902 Returns a str or a buffer instance of raw byte data.
1896 Returns a str or a buffer instance of raw byte data.
1903 """
1897 """
1904 o, d = self._chunkcache
1898 o, d = self._chunkcache
1905 l = len(d)
1899 l = len(d)
1906
1900
1907 # is it in the cache?
1901 # is it in the cache?
1908 cachestart = offset - o
1902 cachestart = offset - o
1909 cacheend = cachestart + length
1903 cacheend = cachestart + length
1910 if cachestart >= 0 and cacheend <= l:
1904 if cachestart >= 0 and cacheend <= l:
1911 if cachestart == 0 and cacheend == l:
1905 if cachestart == 0 and cacheend == l:
1912 return d # avoid a copy
1906 return d # avoid a copy
1913 return util.buffer(d, cachestart, cacheend - cachestart)
1907 return util.buffer(d, cachestart, cacheend - cachestart)
1914
1908
1915 return self._readsegment(offset, length, df=df)
1909 return self._readsegment(offset, length, df=df)
1916
1910
1917 def _getsegmentforrevs(self, startrev, endrev, df=None):
1911 def _getsegmentforrevs(self, startrev, endrev, df=None):
1918 """Obtain a segment of raw data corresponding to a range of revisions.
1912 """Obtain a segment of raw data corresponding to a range of revisions.
1919
1913
1920 Accepts the start and end revisions and an optional already-open
1914 Accepts the start and end revisions and an optional already-open
1921 file handle to be used for reading. If the file handle is read, its
1915 file handle to be used for reading. If the file handle is read, its
1922 seek position will not be preserved.
1916 seek position will not be preserved.
1923
1917
1924 Requests for data may be satisfied by a cache.
1918 Requests for data may be satisfied by a cache.
1925
1919
1926 Returns a 2-tuple of (offset, data) for the requested range of
1920 Returns a 2-tuple of (offset, data) for the requested range of
1927 revisions. Offset is the integer offset from the beginning of the
1921 revisions. Offset is the integer offset from the beginning of the
1928 revlog and data is a str or buffer of the raw byte data.
1922 revlog and data is a str or buffer of the raw byte data.
1929
1923
1930 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1924 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1931 to determine where each revision's data begins and ends.
1925 to determine where each revision's data begins and ends.
1932 """
1926 """
1933 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1927 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1934 # (functions are expensive).
1928 # (functions are expensive).
1935 index = self.index
1929 index = self.index
1936 istart = index[startrev]
1930 istart = index[startrev]
1937 start = int(istart[0] >> 16)
1931 start = int(istart[0] >> 16)
1938 if startrev == endrev:
1932 if startrev == endrev:
1939 end = start + istart[1]
1933 end = start + istart[1]
1940 else:
1934 else:
1941 iend = index[endrev]
1935 iend = index[endrev]
1942 end = int(iend[0] >> 16) + iend[1]
1936 end = int(iend[0] >> 16) + iend[1]
1943
1937
1944 if self._inline:
1938 if self._inline:
1945 start += (startrev + 1) * self._io.size
1939 start += (startrev + 1) * self._io.size
1946 end += (endrev + 1) * self._io.size
1940 end += (endrev + 1) * self._io.size
1947 length = end - start
1941 length = end - start
1948
1942
1949 return start, self._getsegment(start, length, df=df)
1943 return start, self._getsegment(start, length, df=df)
1950
1944
1951 def _chunk(self, rev, df=None):
1945 def _chunk(self, rev, df=None):
1952 """Obtain a single decompressed chunk for a revision.
1946 """Obtain a single decompressed chunk for a revision.
1953
1947
1954 Accepts an integer revision and an optional already-open file handle
1948 Accepts an integer revision and an optional already-open file handle
1955 to be used for reading. If used, the seek position of the file will not
1949 to be used for reading. If used, the seek position of the file will not
1956 be preserved.
1950 be preserved.
1957
1951
1958 Returns a str holding uncompressed data for the requested revision.
1952 Returns a str holding uncompressed data for the requested revision.
1959 """
1953 """
1960 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1954 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1961
1955
1962 def _chunks(self, revs, df=None, targetsize=None):
1956 def _chunks(self, revs, df=None, targetsize=None):
1963 """Obtain decompressed chunks for the specified revisions.
1957 """Obtain decompressed chunks for the specified revisions.
1964
1958
1965 Accepts an iterable of numeric revisions that are assumed to be in
1959 Accepts an iterable of numeric revisions that are assumed to be in
1966 ascending order. Also accepts an optional already-open file handle
1960 ascending order. Also accepts an optional already-open file handle
1967 to be used for reading. If used, the seek position of the file will
1961 to be used for reading. If used, the seek position of the file will
1968 not be preserved.
1962 not be preserved.
1969
1963
1970 This function is similar to calling ``self._chunk()`` multiple times,
1964 This function is similar to calling ``self._chunk()`` multiple times,
1971 but is faster.
1965 but is faster.
1972
1966
1973 Returns a list with decompressed data for each requested revision.
1967 Returns a list with decompressed data for each requested revision.
1974 """
1968 """
1975 if not revs:
1969 if not revs:
1976 return []
1970 return []
1977 start = self.start
1971 start = self.start
1978 length = self.length
1972 length = self.length
1979 inline = self._inline
1973 inline = self._inline
1980 iosize = self._io.size
1974 iosize = self._io.size
1981 buffer = util.buffer
1975 buffer = util.buffer
1982
1976
1983 l = []
1977 l = []
1984 ladd = l.append
1978 ladd = l.append
1985
1979
1986 if not self._withsparseread:
1980 if not self._withsparseread:
1987 slicedchunks = (revs,)
1981 slicedchunks = (revs,)
1988 else:
1982 else:
1989 slicedchunks = _slicechunk(self, revs, targetsize)
1983 slicedchunks = _slicechunk(self, revs, targetsize)
1990
1984
1991 for revschunk in slicedchunks:
1985 for revschunk in slicedchunks:
1992 firstrev = revschunk[0]
1986 firstrev = revschunk[0]
1993 # Skip trailing revisions with empty diff
1987 # Skip trailing revisions with empty diff
1994 for lastrev in revschunk[::-1]:
1988 for lastrev in revschunk[::-1]:
1995 if length(lastrev) != 0:
1989 if length(lastrev) != 0:
1996 break
1990 break
1997
1991
1998 try:
1992 try:
1999 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1993 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
2000 except OverflowError:
1994 except OverflowError:
2001 # issue4215 - we can't cache a run of chunks greater than
1995 # issue4215 - we can't cache a run of chunks greater than
2002 # 2G on Windows
1996 # 2G on Windows
2003 return [self._chunk(rev, df=df) for rev in revschunk]
1997 return [self._chunk(rev, df=df) for rev in revschunk]
2004
1998
2005 decomp = self.decompress
1999 decomp = self.decompress
2006 for rev in revschunk:
2000 for rev in revschunk:
2007 chunkstart = start(rev)
2001 chunkstart = start(rev)
2008 if inline:
2002 if inline:
2009 chunkstart += (rev + 1) * iosize
2003 chunkstart += (rev + 1) * iosize
2010 chunklength = length(rev)
2004 chunklength = length(rev)
2011 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
2005 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
2012
2006
2013 return l
2007 return l
2014
2008
2015 def _chunkclear(self):
2009 def _chunkclear(self):
2016 """Clear the raw chunk cache."""
2010 """Clear the raw chunk cache."""
2017 self._chunkcache = (0, '')
2011 self._chunkcache = (0, '')
2018
2012
2019 def deltaparent(self, rev):
2013 def deltaparent(self, rev):
2020 """return deltaparent of the given revision"""
2014 """return deltaparent of the given revision"""
2021 base = self.index[rev][3]
2015 base = self.index[rev][3]
2022 if base == rev:
2016 if base == rev:
2023 return nullrev
2017 return nullrev
2024 elif self._generaldelta:
2018 elif self._generaldelta:
2025 return base
2019 return base
2026 else:
2020 else:
2027 return rev - 1
2021 return rev - 1
2028
2022
2029 def revdiff(self, rev1, rev2):
2023 def revdiff(self, rev1, rev2):
2030 """return or calculate a delta between two revisions
2024 """return or calculate a delta between two revisions
2031
2025
2032 The delta calculated is in binary form and is intended to be written to
2026 The delta calculated is in binary form and is intended to be written to
2033 revlog data directly. So this function needs raw revision data.
2027 revlog data directly. So this function needs raw revision data.
2034 """
2028 """
2035 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
2029 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
2036 return bytes(self._chunk(rev2))
2030 return bytes(self._chunk(rev2))
2037
2031
2038 return mdiff.textdiff(self.revision(rev1, raw=True),
2032 return mdiff.textdiff(self.revision(rev1, raw=True),
2039 self.revision(rev2, raw=True))
2033 self.revision(rev2, raw=True))
2040
2034
2041 def revision(self, nodeorrev, _df=None, raw=False):
2035 def revision(self, nodeorrev, _df=None, raw=False):
2042 """return an uncompressed revision of a given node or revision
2036 """return an uncompressed revision of a given node or revision
2043 number.
2037 number.
2044
2038
2045 _df - an existing file handle to read from. (internal-only)
2039 _df - an existing file handle to read from. (internal-only)
2046 raw - an optional argument specifying if the revision data is to be
2040 raw - an optional argument specifying if the revision data is to be
2047 treated as raw data when applying flag transforms. 'raw' should be set
2041 treated as raw data when applying flag transforms. 'raw' should be set
2048 to True when generating changegroups or in debug commands.
2042 to True when generating changegroups or in debug commands.
2049 """
2043 """
2050 if isinstance(nodeorrev, int):
2044 if isinstance(nodeorrev, int):
2051 rev = nodeorrev
2045 rev = nodeorrev
2052 node = self.node(rev)
2046 node = self.node(rev)
2053 else:
2047 else:
2054 node = nodeorrev
2048 node = nodeorrev
2055 rev = None
2049 rev = None
2056
2050
2057 cachedrev = None
2051 cachedrev = None
2058 flags = None
2052 flags = None
2059 rawtext = None
2053 rawtext = None
2060 if node == nullid:
2054 if node == nullid:
2061 return ""
2055 return ""
2062 if self._cache:
2056 if self._cache:
2063 if self._cache[0] == node:
2057 if self._cache[0] == node:
2064 # _cache only stores rawtext
2058 # _cache only stores rawtext
2065 if raw:
2059 if raw:
2066 return self._cache[2]
2060 return self._cache[2]
2067 # duplicated, but good for perf
2061 # duplicated, but good for perf
2068 if rev is None:
2062 if rev is None:
2069 rev = self.rev(node)
2063 rev = self.rev(node)
2070 if flags is None:
2064 if flags is None:
2071 flags = self.flags(rev)
2065 flags = self.flags(rev)
2072 # no extra flags set, no flag processor runs, text = rawtext
2066 # no extra flags set, no flag processor runs, text = rawtext
2073 if flags == REVIDX_DEFAULT_FLAGS:
2067 if flags == REVIDX_DEFAULT_FLAGS:
2074 return self._cache[2]
2068 return self._cache[2]
2075 # rawtext is reusable. need to run flag processor
2069 # rawtext is reusable. need to run flag processor
2076 rawtext = self._cache[2]
2070 rawtext = self._cache[2]
2077
2071
2078 cachedrev = self._cache[1]
2072 cachedrev = self._cache[1]
2079
2073
2080 # look up what we need to read
2074 # look up what we need to read
2081 if rawtext is None:
2075 if rawtext is None:
2082 if rev is None:
2076 if rev is None:
2083 rev = self.rev(node)
2077 rev = self.rev(node)
2084
2078
2085 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
2079 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
2086 if stopped:
2080 if stopped:
2087 rawtext = self._cache[2]
2081 rawtext = self._cache[2]
2088
2082
2089 # drop cache to save memory
2083 # drop cache to save memory
2090 self._cache = None
2084 self._cache = None
2091
2085
2092 targetsize = None
2086 targetsize = None
2093 rawsize = self.index[rev][2]
2087 rawsize = self.index[rev][2]
2094 if 0 <= rawsize:
2088 if 0 <= rawsize:
2095 targetsize = 4 * rawsize
2089 targetsize = 4 * rawsize
2096
2090
2097 bins = self._chunks(chain, df=_df, targetsize=targetsize)
2091 bins = self._chunks(chain, df=_df, targetsize=targetsize)
2098 if rawtext is None:
2092 if rawtext is None:
2099 rawtext = bytes(bins[0])
2093 rawtext = bytes(bins[0])
2100 bins = bins[1:]
2094 bins = bins[1:]
2101
2095
2102 rawtext = mdiff.patches(rawtext, bins)
2096 rawtext = mdiff.patches(rawtext, bins)
2103 self._cache = (node, rev, rawtext)
2097 self._cache = (node, rev, rawtext)
2104
2098
2105 if flags is None:
2099 if flags is None:
2106 if rev is None:
2100 if rev is None:
2107 rev = self.rev(node)
2101 rev = self.rev(node)
2108 flags = self.flags(rev)
2102 flags = self.flags(rev)
2109
2103
2110 text, validatehash = self._processflags(rawtext, flags, 'read', raw=raw)
2104 text, validatehash = self._processflags(rawtext, flags, 'read', raw=raw)
2111 if validatehash:
2105 if validatehash:
2112 self.checkhash(text, node, rev=rev)
2106 self.checkhash(text, node, rev=rev)
2113
2107
2114 return text
2108 return text
2115
2109
2116 def hash(self, text, p1, p2):
2110 def hash(self, text, p1, p2):
2117 """Compute a node hash.
2111 """Compute a node hash.
2118
2112
2119 Available as a function so that subclasses can replace the hash
2113 Available as a function so that subclasses can replace the hash
2120 as needed.
2114 as needed.
2121 """
2115 """
2122 return hash(text, p1, p2)
2116 return hash(text, p1, p2)
2123
2117
2124 def _processflags(self, text, flags, operation, raw=False):
2118 def _processflags(self, text, flags, operation, raw=False):
2125 """Inspect revision data flags and applies transforms defined by
2119 """Inspect revision data flags and applies transforms defined by
2126 registered flag processors.
2120 registered flag processors.
2127
2121
2128 ``text`` - the revision data to process
2122 ``text`` - the revision data to process
2129 ``flags`` - the revision flags
2123 ``flags`` - the revision flags
2130 ``operation`` - the operation being performed (read or write)
2124 ``operation`` - the operation being performed (read or write)
2131 ``raw`` - an optional argument describing if the raw transform should be
2125 ``raw`` - an optional argument describing if the raw transform should be
2132 applied.
2126 applied.
2133
2127
2134 This method processes the flags in the order (or reverse order if
2128 This method processes the flags in the order (or reverse order if
2135 ``operation`` is 'write') defined by REVIDX_FLAGS_ORDER, applying the
2129 ``operation`` is 'write') defined by REVIDX_FLAGS_ORDER, applying the
2136 flag processors registered for present flags. The order of flags defined
2130 flag processors registered for present flags. The order of flags defined
2137 in REVIDX_FLAGS_ORDER needs to be stable to allow non-commutativity.
2131 in REVIDX_FLAGS_ORDER needs to be stable to allow non-commutativity.
2138
2132
2139 Returns a 2-tuple of ``(text, validatehash)`` where ``text`` is the
2133 Returns a 2-tuple of ``(text, validatehash)`` where ``text`` is the
2140 processed text and ``validatehash`` is a bool indicating whether the
2134 processed text and ``validatehash`` is a bool indicating whether the
2141 returned text should be checked for hash integrity.
2135 returned text should be checked for hash integrity.
2142
2136
2143 Note: If the ``raw`` argument is set, it has precedence over the
2137 Note: If the ``raw`` argument is set, it has precedence over the
2144 operation and will only update the value of ``validatehash``.
2138 operation and will only update the value of ``validatehash``.
2145 """
2139 """
2146 # fast path: no flag processors will run
2140 # fast path: no flag processors will run
2147 if flags == 0:
2141 if flags == 0:
2148 return text, True
2142 return text, True
2149 if not operation in ('read', 'write'):
2143 if not operation in ('read', 'write'):
2150 raise ProgrammingError(_("invalid '%s' operation ") % (operation))
2144 raise ProgrammingError(_("invalid '%s' operation ") % (operation))
2151 # Check all flags are known.
2145 # Check all flags are known.
2152 if flags & ~REVIDX_KNOWN_FLAGS:
2146 if flags & ~REVIDX_KNOWN_FLAGS:
2153 raise RevlogError(_("incompatible revision flag '%#x'") %
2147 raise RevlogError(_("incompatible revision flag '%#x'") %
2154 (flags & ~REVIDX_KNOWN_FLAGS))
2148 (flags & ~REVIDX_KNOWN_FLAGS))
2155 validatehash = True
2149 validatehash = True
2156 # Depending on the operation (read or write), the order might be
2150 # Depending on the operation (read or write), the order might be
2157 # reversed due to non-commutative transforms.
2151 # reversed due to non-commutative transforms.
2158 orderedflags = REVIDX_FLAGS_ORDER
2152 orderedflags = REVIDX_FLAGS_ORDER
2159 if operation == 'write':
2153 if operation == 'write':
2160 orderedflags = reversed(orderedflags)
2154 orderedflags = reversed(orderedflags)
2161
2155
2162 for flag in orderedflags:
2156 for flag in orderedflags:
2163 # If a flagprocessor has been registered for a known flag, apply the
2157 # If a flagprocessor has been registered for a known flag, apply the
2164 # related operation transform and update result tuple.
2158 # related operation transform and update result tuple.
2165 if flag & flags:
2159 if flag & flags:
2166 vhash = True
2160 vhash = True
2167
2161
2168 if flag not in _flagprocessors:
2162 if flag not in _flagprocessors:
2169 message = _("missing processor for flag '%#x'") % (flag)
2163 message = _("missing processor for flag '%#x'") % (flag)
2170 raise RevlogError(message)
2164 raise RevlogError(message)
2171
2165
2172 processor = _flagprocessors[flag]
2166 processor = _flagprocessors[flag]
2173 if processor is not None:
2167 if processor is not None:
2174 readtransform, writetransform, rawtransform = processor
2168 readtransform, writetransform, rawtransform = processor
2175
2169
2176 if raw:
2170 if raw:
2177 vhash = rawtransform(self, text)
2171 vhash = rawtransform(self, text)
2178 elif operation == 'read':
2172 elif operation == 'read':
2179 text, vhash = readtransform(self, text)
2173 text, vhash = readtransform(self, text)
2180 else: # write operation
2174 else: # write operation
2181 text, vhash = writetransform(self, text)
2175 text, vhash = writetransform(self, text)
2182 validatehash = validatehash and vhash
2176 validatehash = validatehash and vhash
2183
2177
2184 return text, validatehash
2178 return text, validatehash
2185
2179
2186 def checkhash(self, text, node, p1=None, p2=None, rev=None):
2180 def checkhash(self, text, node, p1=None, p2=None, rev=None):
2187 """Check node hash integrity.
2181 """Check node hash integrity.
2188
2182
2189 Available as a function so that subclasses can extend hash mismatch
2183 Available as a function so that subclasses can extend hash mismatch
2190 behaviors as needed.
2184 behaviors as needed.
2191 """
2185 """
2192 try:
2186 try:
2193 if p1 is None and p2 is None:
2187 if p1 is None and p2 is None:
2194 p1, p2 = self.parents(node)
2188 p1, p2 = self.parents(node)
2195 if node != self.hash(text, p1, p2):
2189 if node != self.hash(text, p1, p2):
2196 revornode = rev
2190 revornode = rev
2197 if revornode is None:
2191 if revornode is None:
2198 revornode = templatefilters.short(hex(node))
2192 revornode = templatefilters.short(hex(node))
2199 raise RevlogError(_("integrity check failed on %s:%s")
2193 raise RevlogError(_("integrity check failed on %s:%s")
2200 % (self.indexfile, pycompat.bytestr(revornode)))
2194 % (self.indexfile, pycompat.bytestr(revornode)))
2201 except RevlogError:
2195 except RevlogError:
2202 if self._censorable and _censoredtext(text):
2196 if self._censorable and _censoredtext(text):
2203 raise error.CensoredNodeError(self.indexfile, node, text)
2197 raise error.CensoredNodeError(self.indexfile, node, text)
2204 raise
2198 raise
2205
2199
2206 def _enforceinlinesize(self, tr, fp=None):
2200 def _enforceinlinesize(self, tr, fp=None):
2207 """Check if the revlog is too big for inline and convert if so.
2201 """Check if the revlog is too big for inline and convert if so.
2208
2202
2209 This should be called after revisions are added to the revlog. If the
2203 This should be called after revisions are added to the revlog. If the
2210 revlog has grown too large to be an inline revlog, it will convert it
2204 revlog has grown too large to be an inline revlog, it will convert it
2211 to use multiple index and data files.
2205 to use multiple index and data files.
2212 """
2206 """
2213 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
2207 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
2214 return
2208 return
2215
2209
2216 trinfo = tr.find(self.indexfile)
2210 trinfo = tr.find(self.indexfile)
2217 if trinfo is None:
2211 if trinfo is None:
2218 raise RevlogError(_("%s not found in the transaction")
2212 raise RevlogError(_("%s not found in the transaction")
2219 % self.indexfile)
2213 % self.indexfile)
2220
2214
2221 trindex = trinfo[2]
2215 trindex = trinfo[2]
2222 if trindex is not None:
2216 if trindex is not None:
2223 dataoff = self.start(trindex)
2217 dataoff = self.start(trindex)
2224 else:
2218 else:
2225 # revlog was stripped at start of transaction, use all leftover data
2219 # revlog was stripped at start of transaction, use all leftover data
2226 trindex = len(self) - 1
2220 trindex = len(self) - 1
2227 dataoff = self.end(-2)
2221 dataoff = self.end(-2)
2228
2222
2229 tr.add(self.datafile, dataoff)
2223 tr.add(self.datafile, dataoff)
2230
2224
2231 if fp:
2225 if fp:
2232 fp.flush()
2226 fp.flush()
2233 fp.close()
2227 fp.close()
2234
2228
2235 with self._datafp('w') as df:
2229 with self._datafp('w') as df:
2236 for r in self:
2230 for r in self:
2237 df.write(self._getsegmentforrevs(r, r)[1])
2231 df.write(self._getsegmentforrevs(r, r)[1])
2238
2232
2239 with self._indexfp('w') as fp:
2233 with self._indexfp('w') as fp:
2240 self.version &= ~FLAG_INLINE_DATA
2234 self.version &= ~FLAG_INLINE_DATA
2241 self._inline = False
2235 self._inline = False
2242 io = self._io
2236 io = self._io
2243 for i in self:
2237 for i in self:
2244 e = io.packentry(self.index[i], self.node, self.version, i)
2238 e = io.packentry(self.index[i], self.node, self.version, i)
2245 fp.write(e)
2239 fp.write(e)
2246
2240
2247 # the temp file replace the real index when we exit the context
2241 # the temp file replace the real index when we exit the context
2248 # manager
2242 # manager
2249
2243
2250 tr.replace(self.indexfile, trindex * self._io.size)
2244 tr.replace(self.indexfile, trindex * self._io.size)
2251 self._chunkclear()
2245 self._chunkclear()
2252
2246
2253 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
2247 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
2254 node=None, flags=REVIDX_DEFAULT_FLAGS, deltacomputer=None):
2248 node=None, flags=REVIDX_DEFAULT_FLAGS, deltacomputer=None):
2255 """add a revision to the log
2249 """add a revision to the log
2256
2250
2257 text - the revision data to add
2251 text - the revision data to add
2258 transaction - the transaction object used for rollback
2252 transaction - the transaction object used for rollback
2259 link - the linkrev data to add
2253 link - the linkrev data to add
2260 p1, p2 - the parent nodeids of the revision
2254 p1, p2 - the parent nodeids of the revision
2261 cachedelta - an optional precomputed delta
2255 cachedelta - an optional precomputed delta
2262 node - nodeid of revision; typically node is not specified, and it is
2256 node - nodeid of revision; typically node is not specified, and it is
2263 computed by default as hash(text, p1, p2), however subclasses might
2257 computed by default as hash(text, p1, p2), however subclasses might
2264 use different hashing method (and override checkhash() in such case)
2258 use different hashing method (and override checkhash() in such case)
2265 flags - the known flags to set on the revision
2259 flags - the known flags to set on the revision
2266 deltacomputer - an optional _deltacomputer instance shared between
2260 deltacomputer - an optional _deltacomputer instance shared between
2267 multiple calls
2261 multiple calls
2268 """
2262 """
2269 if link == nullrev:
2263 if link == nullrev:
2270 raise RevlogError(_("attempted to add linkrev -1 to %s")
2264 raise RevlogError(_("attempted to add linkrev -1 to %s")
2271 % self.indexfile)
2265 % self.indexfile)
2272
2266
2273 if flags:
2267 if flags:
2274 node = node or self.hash(text, p1, p2)
2268 node = node or self.hash(text, p1, p2)
2275
2269
2276 rawtext, validatehash = self._processflags(text, flags, 'write')
2270 rawtext, validatehash = self._processflags(text, flags, 'write')
2277
2271
2278 # If the flag processor modifies the revision data, ignore any provided
2272 # If the flag processor modifies the revision data, ignore any provided
2279 # cachedelta.
2273 # cachedelta.
2280 if rawtext != text:
2274 if rawtext != text:
2281 cachedelta = None
2275 cachedelta = None
2282
2276
2283 if len(rawtext) > _maxentrysize:
2277 if len(rawtext) > _maxentrysize:
2284 raise RevlogError(
2278 raise RevlogError(
2285 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
2279 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
2286 % (self.indexfile, len(rawtext)))
2280 % (self.indexfile, len(rawtext)))
2287
2281
2288 node = node or self.hash(rawtext, p1, p2)
2282 node = node or self.hash(rawtext, p1, p2)
2289 if node in self.nodemap:
2283 if node in self.nodemap:
2290 return node
2284 return node
2291
2285
2292 if validatehash:
2286 if validatehash:
2293 self.checkhash(rawtext, node, p1=p1, p2=p2)
2287 self.checkhash(rawtext, node, p1=p1, p2=p2)
2294
2288
2295 return self.addrawrevision(rawtext, transaction, link, p1, p2, node,
2289 return self.addrawrevision(rawtext, transaction, link, p1, p2, node,
2296 flags, cachedelta=cachedelta,
2290 flags, cachedelta=cachedelta,
2297 deltacomputer=deltacomputer)
2291 deltacomputer=deltacomputer)
2298
2292
2299 def addrawrevision(self, rawtext, transaction, link, p1, p2, node, flags,
2293 def addrawrevision(self, rawtext, transaction, link, p1, p2, node, flags,
2300 cachedelta=None, deltacomputer=None):
2294 cachedelta=None, deltacomputer=None):
2301 """add a raw revision with known flags, node and parents
2295 """add a raw revision with known flags, node and parents
2302 useful when reusing a revision not stored in this revlog (ex: received
2296 useful when reusing a revision not stored in this revlog (ex: received
2303 over wire, or read from an external bundle).
2297 over wire, or read from an external bundle).
2304 """
2298 """
2305 dfh = None
2299 dfh = None
2306 if not self._inline:
2300 if not self._inline:
2307 dfh = self._datafp("a+")
2301 dfh = self._datafp("a+")
2308 ifh = self._indexfp("a+")
2302 ifh = self._indexfp("a+")
2309 try:
2303 try:
2310 return self._addrevision(node, rawtext, transaction, link, p1, p2,
2304 return self._addrevision(node, rawtext, transaction, link, p1, p2,
2311 flags, cachedelta, ifh, dfh,
2305 flags, cachedelta, ifh, dfh,
2312 deltacomputer=deltacomputer)
2306 deltacomputer=deltacomputer)
2313 finally:
2307 finally:
2314 if dfh:
2308 if dfh:
2315 dfh.close()
2309 dfh.close()
2316 ifh.close()
2310 ifh.close()
2317
2311
2318 def compress(self, data):
2312 def compress(self, data):
2319 """Generate a possibly-compressed representation of data."""
2313 """Generate a possibly-compressed representation of data."""
2320 if not data:
2314 if not data:
2321 return '', data
2315 return '', data
2322
2316
2323 compressed = self._compressor.compress(data)
2317 compressed = self._compressor.compress(data)
2324
2318
2325 if compressed:
2319 if compressed:
2326 # The revlog compressor added the header in the returned data.
2320 # The revlog compressor added the header in the returned data.
2327 return '', compressed
2321 return '', compressed
2328
2322
2329 if data[0:1] == '\0':
2323 if data[0:1] == '\0':
2330 return '', data
2324 return '', data
2331 return 'u', data
2325 return 'u', data
2332
2326
2333 def decompress(self, data):
2327 def decompress(self, data):
2334 """Decompress a revlog chunk.
2328 """Decompress a revlog chunk.
2335
2329
2336 The chunk is expected to begin with a header identifying the
2330 The chunk is expected to begin with a header identifying the
2337 format type so it can be routed to an appropriate decompressor.
2331 format type so it can be routed to an appropriate decompressor.
2338 """
2332 """
2339 if not data:
2333 if not data:
2340 return data
2334 return data
2341
2335
2342 # Revlogs are read much more frequently than they are written and many
2336 # Revlogs are read much more frequently than they are written and many
2343 # chunks only take microseconds to decompress, so performance is
2337 # chunks only take microseconds to decompress, so performance is
2344 # important here.
2338 # important here.
2345 #
2339 #
2346 # We can make a few assumptions about revlogs:
2340 # We can make a few assumptions about revlogs:
2347 #
2341 #
2348 # 1) the majority of chunks will be compressed (as opposed to inline
2342 # 1) the majority of chunks will be compressed (as opposed to inline
2349 # raw data).
2343 # raw data).
2350 # 2) decompressing *any* data will likely by at least 10x slower than
2344 # 2) decompressing *any* data will likely by at least 10x slower than
2351 # returning raw inline data.
2345 # returning raw inline data.
2352 # 3) we want to prioritize common and officially supported compression
2346 # 3) we want to prioritize common and officially supported compression
2353 # engines
2347 # engines
2354 #
2348 #
2355 # It follows that we want to optimize for "decompress compressed data
2349 # It follows that we want to optimize for "decompress compressed data
2356 # when encoded with common and officially supported compression engines"
2350 # when encoded with common and officially supported compression engines"
2357 # case over "raw data" and "data encoded by less common or non-official
2351 # case over "raw data" and "data encoded by less common or non-official
2358 # compression engines." That is why we have the inline lookup first
2352 # compression engines." That is why we have the inline lookup first
2359 # followed by the compengines lookup.
2353 # followed by the compengines lookup.
2360 #
2354 #
2361 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2355 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2362 # compressed chunks. And this matters for changelog and manifest reads.
2356 # compressed chunks. And this matters for changelog and manifest reads.
2363 t = data[0:1]
2357 t = data[0:1]
2364
2358
2365 if t == 'x':
2359 if t == 'x':
2366 try:
2360 try:
2367 return _zlibdecompress(data)
2361 return _zlibdecompress(data)
2368 except zlib.error as e:
2362 except zlib.error as e:
2369 raise RevlogError(_('revlog decompress error: %s') %
2363 raise RevlogError(_('revlog decompress error: %s') %
2370 stringutil.forcebytestr(e))
2364 stringutil.forcebytestr(e))
2371 # '\0' is more common than 'u' so it goes first.
2365 # '\0' is more common than 'u' so it goes first.
2372 elif t == '\0':
2366 elif t == '\0':
2373 return data
2367 return data
2374 elif t == 'u':
2368 elif t == 'u':
2375 return util.buffer(data, 1)
2369 return util.buffer(data, 1)
2376
2370
2377 try:
2371 try:
2378 compressor = self._decompressors[t]
2372 compressor = self._decompressors[t]
2379 except KeyError:
2373 except KeyError:
2380 try:
2374 try:
2381 engine = util.compengines.forrevlogheader(t)
2375 engine = util.compengines.forrevlogheader(t)
2382 compressor = engine.revlogcompressor()
2376 compressor = engine.revlogcompressor()
2383 self._decompressors[t] = compressor
2377 self._decompressors[t] = compressor
2384 except KeyError:
2378 except KeyError:
2385 raise RevlogError(_('unknown compression type %r') % t)
2379 raise RevlogError(_('unknown compression type %r') % t)
2386
2380
2387 return compressor.decompress(data)
2381 return compressor.decompress(data)
2388
2382
2389 def _isgooddeltainfo(self, deltainfo, revinfo):
2383 def _isgooddeltainfo(self, deltainfo, revinfo):
2390 """Returns True if the given delta is good. Good means that it is within
2384 """Returns True if the given delta is good. Good means that it is within
2391 the disk span, disk size, and chain length bounds that we know to be
2385 the disk span, disk size, and chain length bounds that we know to be
2392 performant."""
2386 performant."""
2393 if deltainfo is None:
2387 if deltainfo is None:
2394 return False
2388 return False
2395
2389
2396 # - 'deltainfo.distance' is the distance from the base revision --
2390 # - 'deltainfo.distance' is the distance from the base revision --
2397 # bounding it limits the amount of I/O we need to do.
2391 # bounding it limits the amount of I/O we need to do.
2398 # - 'deltainfo.compresseddeltalen' is the sum of the total size of
2392 # - 'deltainfo.compresseddeltalen' is the sum of the total size of
2399 # deltas we need to apply -- bounding it limits the amount of CPU
2393 # deltas we need to apply -- bounding it limits the amount of CPU
2400 # we consume.
2394 # we consume.
2401
2395
2402 textlen = revinfo.textlen
2396 textlen = revinfo.textlen
2403 defaultmax = textlen * 4
2397 defaultmax = textlen * 4
2404 maxdist = self._maxdeltachainspan
2398 maxdist = self._maxdeltachainspan
2405 if not maxdist:
2399 if not maxdist:
2406 maxdist = deltainfo.distance # ensure the conditional pass
2400 maxdist = deltainfo.distance # ensure the conditional pass
2407 maxdist = max(maxdist, defaultmax)
2401 maxdist = max(maxdist, defaultmax)
2408 if (deltainfo.distance > maxdist or deltainfo.deltalen > textlen or
2402 if (deltainfo.distance > maxdist or deltainfo.deltalen > textlen or
2409 deltainfo.compresseddeltalen > textlen * 2 or
2403 deltainfo.compresseddeltalen > textlen * 2 or
2410 (self._maxchainlen and deltainfo.chainlen > self._maxchainlen)):
2404 (self._maxchainlen and deltainfo.chainlen > self._maxchainlen)):
2411 return False
2405 return False
2412
2406
2413 return True
2407 return True
2414
2408
2415 def _addrevision(self, node, rawtext, transaction, link, p1, p2, flags,
2409 def _addrevision(self, node, rawtext, transaction, link, p1, p2, flags,
2416 cachedelta, ifh, dfh, alwayscache=False,
2410 cachedelta, ifh, dfh, alwayscache=False,
2417 deltacomputer=None):
2411 deltacomputer=None):
2418 """internal function to add revisions to the log
2412 """internal function to add revisions to the log
2419
2413
2420 see addrevision for argument descriptions.
2414 see addrevision for argument descriptions.
2421
2415
2422 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2416 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2423
2417
2424 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2418 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2425 be used.
2419 be used.
2426
2420
2427 invariants:
2421 invariants:
2428 - rawtext is optional (can be None); if not set, cachedelta must be set.
2422 - rawtext is optional (can be None); if not set, cachedelta must be set.
2429 if both are set, they must correspond to each other.
2423 if both are set, they must correspond to each other.
2430 """
2424 """
2431 if node == nullid:
2425 if node == nullid:
2432 raise RevlogError(_("%s: attempt to add null revision") %
2426 raise RevlogError(_("%s: attempt to add null revision") %
2433 (self.indexfile))
2427 (self.indexfile))
2434 if node == wdirid or node in wdirfilenodeids:
2428 if node == wdirid or node in wdirfilenodeids:
2435 raise RevlogError(_("%s: attempt to add wdir revision") %
2429 raise RevlogError(_("%s: attempt to add wdir revision") %
2436 (self.indexfile))
2430 (self.indexfile))
2437
2431
2438 if self._inline:
2432 if self._inline:
2439 fh = ifh
2433 fh = ifh
2440 else:
2434 else:
2441 fh = dfh
2435 fh = dfh
2442
2436
2443 btext = [rawtext]
2437 btext = [rawtext]
2444
2438
2445 curr = len(self)
2439 curr = len(self)
2446 prev = curr - 1
2440 prev = curr - 1
2447 offset = self.end(prev)
2441 offset = self.end(prev)
2448 p1r, p2r = self.rev(p1), self.rev(p2)
2442 p1r, p2r = self.rev(p1), self.rev(p2)
2449
2443
2450 # full versions are inserted when the needed deltas
2444 # full versions are inserted when the needed deltas
2451 # become comparable to the uncompressed text
2445 # become comparable to the uncompressed text
2452 if rawtext is None:
2446 if rawtext is None:
2453 # need rawtext size, before changed by flag processors, which is
2447 # need rawtext size, before changed by flag processors, which is
2454 # the non-raw size. use revlog explicitly to avoid filelog's extra
2448 # the non-raw size. use revlog explicitly to avoid filelog's extra
2455 # logic that might remove metadata size.
2449 # logic that might remove metadata size.
2456 textlen = mdiff.patchedsize(revlog.size(self, cachedelta[0]),
2450 textlen = mdiff.patchedsize(revlog.size(self, cachedelta[0]),
2457 cachedelta[1])
2451 cachedelta[1])
2458 else:
2452 else:
2459 textlen = len(rawtext)
2453 textlen = len(rawtext)
2460
2454
2461 if deltacomputer is None:
2455 if deltacomputer is None:
2462 deltacomputer = _deltacomputer(self)
2456 deltacomputer = _deltacomputer(self)
2463
2457
2464 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2458 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2465
2459
2466 # no delta for flag processor revision (see "candelta" for why)
2460 # no delta for flag processor revision (see "candelta" for why)
2467 # not calling candelta since only one revision needs test, also to
2461 # not calling candelta since only one revision needs test, also to
2468 # avoid overhead fetching flags again.
2462 # avoid overhead fetching flags again.
2469 if flags & REVIDX_RAWTEXT_CHANGING_FLAGS:
2463 if flags & REVIDX_RAWTEXT_CHANGING_FLAGS:
2470 deltainfo = None
2464 deltainfo = None
2471 else:
2465 else:
2472 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2466 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2473
2467
2474 if deltainfo is not None:
2468 if deltainfo is not None:
2475 base = deltainfo.base
2469 base = deltainfo.base
2476 chainbase = deltainfo.chainbase
2470 chainbase = deltainfo.chainbase
2477 data = deltainfo.data
2471 data = deltainfo.data
2478 l = deltainfo.deltalen
2472 l = deltainfo.deltalen
2479 else:
2473 else:
2480 rawtext = deltacomputer.buildtext(revinfo, fh)
2474 rawtext = deltacomputer.buildtext(revinfo, fh)
2481 data = self.compress(rawtext)
2475 data = self.compress(rawtext)
2482 l = len(data[1]) + len(data[0])
2476 l = len(data[1]) + len(data[0])
2483 base = chainbase = curr
2477 base = chainbase = curr
2484
2478
2485 e = (offset_type(offset, flags), l, textlen,
2479 e = (offset_type(offset, flags), l, textlen,
2486 base, link, p1r, p2r, node)
2480 base, link, p1r, p2r, node)
2487 self.index.insert(-1, e)
2481 self.index.insert(-1, e)
2488 self.nodemap[node] = curr
2482 self.nodemap[node] = curr
2489
2483
2490 entry = self._io.packentry(e, self.node, self.version, curr)
2484 entry = self._io.packentry(e, self.node, self.version, curr)
2491 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
2485 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
2492
2486
2493 if alwayscache and rawtext is None:
2487 if alwayscache and rawtext is None:
2494 rawtext = deltacomputer._buildtext(revinfo, fh)
2488 rawtext = deltacomputer._buildtext(revinfo, fh)
2495
2489
2496 if type(rawtext) == bytes: # only accept immutable objects
2490 if type(rawtext) == bytes: # only accept immutable objects
2497 self._cache = (node, curr, rawtext)
2491 self._cache = (node, curr, rawtext)
2498 self._chainbasecache[curr] = chainbase
2492 self._chainbasecache[curr] = chainbase
2499 return node
2493 return node
2500
2494
2501 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2495 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2502 # Files opened in a+ mode have inconsistent behavior on various
2496 # Files opened in a+ mode have inconsistent behavior on various
2503 # platforms. Windows requires that a file positioning call be made
2497 # platforms. Windows requires that a file positioning call be made
2504 # when the file handle transitions between reads and writes. See
2498 # when the file handle transitions between reads and writes. See
2505 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2499 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2506 # platforms, Python or the platform itself can be buggy. Some versions
2500 # platforms, Python or the platform itself can be buggy. Some versions
2507 # of Solaris have been observed to not append at the end of the file
2501 # of Solaris have been observed to not append at the end of the file
2508 # if the file was seeked to before the end. See issue4943 for more.
2502 # if the file was seeked to before the end. See issue4943 for more.
2509 #
2503 #
2510 # We work around this issue by inserting a seek() before writing.
2504 # We work around this issue by inserting a seek() before writing.
2511 # Note: This is likely not necessary on Python 3.
2505 # Note: This is likely not necessary on Python 3.
2512 ifh.seek(0, os.SEEK_END)
2506 ifh.seek(0, os.SEEK_END)
2513 if dfh:
2507 if dfh:
2514 dfh.seek(0, os.SEEK_END)
2508 dfh.seek(0, os.SEEK_END)
2515
2509
2516 curr = len(self) - 1
2510 curr = len(self) - 1
2517 if not self._inline:
2511 if not self._inline:
2518 transaction.add(self.datafile, offset)
2512 transaction.add(self.datafile, offset)
2519 transaction.add(self.indexfile, curr * len(entry))
2513 transaction.add(self.indexfile, curr * len(entry))
2520 if data[0]:
2514 if data[0]:
2521 dfh.write(data[0])
2515 dfh.write(data[0])
2522 dfh.write(data[1])
2516 dfh.write(data[1])
2523 ifh.write(entry)
2517 ifh.write(entry)
2524 else:
2518 else:
2525 offset += curr * self._io.size
2519 offset += curr * self._io.size
2526 transaction.add(self.indexfile, offset, curr)
2520 transaction.add(self.indexfile, offset, curr)
2527 ifh.write(entry)
2521 ifh.write(entry)
2528 ifh.write(data[0])
2522 ifh.write(data[0])
2529 ifh.write(data[1])
2523 ifh.write(data[1])
2530 self._enforceinlinesize(transaction, ifh)
2524 self._enforceinlinesize(transaction, ifh)
2531
2525
2532 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
2526 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
2533 """
2527 """
2534 add a delta group
2528 add a delta group
2535
2529
2536 given a set of deltas, add them to the revision log. the
2530 given a set of deltas, add them to the revision log. the
2537 first delta is against its parent, which should be in our
2531 first delta is against its parent, which should be in our
2538 log, the rest are against the previous delta.
2532 log, the rest are against the previous delta.
2539
2533
2540 If ``addrevisioncb`` is defined, it will be called with arguments of
2534 If ``addrevisioncb`` is defined, it will be called with arguments of
2541 this revlog and the node that was added.
2535 this revlog and the node that was added.
2542 """
2536 """
2543
2537
2544 nodes = []
2538 nodes = []
2545
2539
2546 r = len(self)
2540 r = len(self)
2547 end = 0
2541 end = 0
2548 if r:
2542 if r:
2549 end = self.end(r - 1)
2543 end = self.end(r - 1)
2550 ifh = self._indexfp("a+")
2544 ifh = self._indexfp("a+")
2551 isize = r * self._io.size
2545 isize = r * self._io.size
2552 if self._inline:
2546 if self._inline:
2553 transaction.add(self.indexfile, end + isize, r)
2547 transaction.add(self.indexfile, end + isize, r)
2554 dfh = None
2548 dfh = None
2555 else:
2549 else:
2556 transaction.add(self.indexfile, isize, r)
2550 transaction.add(self.indexfile, isize, r)
2557 transaction.add(self.datafile, end)
2551 transaction.add(self.datafile, end)
2558 dfh = self._datafp("a+")
2552 dfh = self._datafp("a+")
2559 def flush():
2553 def flush():
2560 if dfh:
2554 if dfh:
2561 dfh.flush()
2555 dfh.flush()
2562 ifh.flush()
2556 ifh.flush()
2563 try:
2557 try:
2564 deltacomputer = _deltacomputer(self)
2558 deltacomputer = _deltacomputer(self)
2565 # loop through our set of deltas
2559 # loop through our set of deltas
2566 for data in deltas:
2560 for data in deltas:
2567 node, p1, p2, linknode, deltabase, delta, flags = data
2561 node, p1, p2, linknode, deltabase, delta, flags = data
2568 link = linkmapper(linknode)
2562 link = linkmapper(linknode)
2569 flags = flags or REVIDX_DEFAULT_FLAGS
2563 flags = flags or REVIDX_DEFAULT_FLAGS
2570
2564
2571 nodes.append(node)
2565 nodes.append(node)
2572
2566
2573 if node in self.nodemap:
2567 if node in self.nodemap:
2574 # this can happen if two branches make the same change
2568 # this can happen if two branches make the same change
2575 continue
2569 continue
2576
2570
2577 for p in (p1, p2):
2571 for p in (p1, p2):
2578 if p not in self.nodemap:
2572 if p not in self.nodemap:
2579 raise LookupError(p, self.indexfile,
2573 raise LookupError(p, self.indexfile,
2580 _('unknown parent'))
2574 _('unknown parent'))
2581
2575
2582 if deltabase not in self.nodemap:
2576 if deltabase not in self.nodemap:
2583 raise LookupError(deltabase, self.indexfile,
2577 raise LookupError(deltabase, self.indexfile,
2584 _('unknown delta base'))
2578 _('unknown delta base'))
2585
2579
2586 baserev = self.rev(deltabase)
2580 baserev = self.rev(deltabase)
2587
2581
2588 if baserev != nullrev and self.iscensored(baserev):
2582 if baserev != nullrev and self.iscensored(baserev):
2589 # if base is censored, delta must be full replacement in a
2583 # if base is censored, delta must be full replacement in a
2590 # single patch operation
2584 # single patch operation
2591 hlen = struct.calcsize(">lll")
2585 hlen = struct.calcsize(">lll")
2592 oldlen = self.rawsize(baserev)
2586 oldlen = self.rawsize(baserev)
2593 newlen = len(delta) - hlen
2587 newlen = len(delta) - hlen
2594 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2588 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2595 raise error.CensoredBaseError(self.indexfile,
2589 raise error.CensoredBaseError(self.indexfile,
2596 self.node(baserev))
2590 self.node(baserev))
2597
2591
2598 if not flags and self._peek_iscensored(baserev, delta, flush):
2592 if not flags and self._peek_iscensored(baserev, delta, flush):
2599 flags |= REVIDX_ISCENSORED
2593 flags |= REVIDX_ISCENSORED
2600
2594
2601 # We assume consumers of addrevisioncb will want to retrieve
2595 # We assume consumers of addrevisioncb will want to retrieve
2602 # the added revision, which will require a call to
2596 # the added revision, which will require a call to
2603 # revision(). revision() will fast path if there is a cache
2597 # revision(). revision() will fast path if there is a cache
2604 # hit. So, we tell _addrevision() to always cache in this case.
2598 # hit. So, we tell _addrevision() to always cache in this case.
2605 # We're only using addgroup() in the context of changegroup
2599 # We're only using addgroup() in the context of changegroup
2606 # generation so the revision data can always be handled as raw
2600 # generation so the revision data can always be handled as raw
2607 # by the flagprocessor.
2601 # by the flagprocessor.
2608 self._addrevision(node, None, transaction, link,
2602 self._addrevision(node, None, transaction, link,
2609 p1, p2, flags, (baserev, delta),
2603 p1, p2, flags, (baserev, delta),
2610 ifh, dfh,
2604 ifh, dfh,
2611 alwayscache=bool(addrevisioncb),
2605 alwayscache=bool(addrevisioncb),
2612 deltacomputer=deltacomputer)
2606 deltacomputer=deltacomputer)
2613
2607
2614 if addrevisioncb:
2608 if addrevisioncb:
2615 addrevisioncb(self, node)
2609 addrevisioncb(self, node)
2616
2610
2617 if not dfh and not self._inline:
2611 if not dfh and not self._inline:
2618 # addrevision switched from inline to conventional
2612 # addrevision switched from inline to conventional
2619 # reopen the index
2613 # reopen the index
2620 ifh.close()
2614 ifh.close()
2621 dfh = self._datafp("a+")
2615 dfh = self._datafp("a+")
2622 ifh = self._indexfp("a+")
2616 ifh = self._indexfp("a+")
2623 finally:
2617 finally:
2624 if dfh:
2618 if dfh:
2625 dfh.close()
2619 dfh.close()
2626 ifh.close()
2620 ifh.close()
2627
2621
2628 return nodes
2622 return nodes
2629
2623
2630 def iscensored(self, rev):
2624 def iscensored(self, rev):
2631 """Check if a file revision is censored."""
2625 """Check if a file revision is censored."""
2632 if not self._censorable:
2626 if not self._censorable:
2633 return False
2627 return False
2634
2628
2635 return self.flags(rev) & REVIDX_ISCENSORED
2629 return self.flags(rev) & REVIDX_ISCENSORED
2636
2630
2637 def _peek_iscensored(self, baserev, delta, flush):
2631 def _peek_iscensored(self, baserev, delta, flush):
2638 """Quickly check if a delta produces a censored revision."""
2632 """Quickly check if a delta produces a censored revision."""
2639 if not self._censorable:
2633 if not self._censorable:
2640 return False
2634 return False
2641
2635
2642 # Fragile heuristic: unless new file meta keys are added alphabetically
2636 # Fragile heuristic: unless new file meta keys are added alphabetically
2643 # preceding "censored", all censored revisions are prefixed by
2637 # preceding "censored", all censored revisions are prefixed by
2644 # "\1\ncensored:". A delta producing such a censored revision must be a
2638 # "\1\ncensored:". A delta producing such a censored revision must be a
2645 # full-replacement delta, so we inspect the first and only patch in the
2639 # full-replacement delta, so we inspect the first and only patch in the
2646 # delta for this prefix.
2640 # delta for this prefix.
2647 hlen = struct.calcsize(">lll")
2641 hlen = struct.calcsize(">lll")
2648 if len(delta) <= hlen:
2642 if len(delta) <= hlen:
2649 return False
2643 return False
2650
2644
2651 oldlen = self.rawsize(baserev)
2645 oldlen = self.rawsize(baserev)
2652 newlen = len(delta) - hlen
2646 newlen = len(delta) - hlen
2653 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2647 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2654 return False
2648 return False
2655
2649
2656 add = "\1\ncensored:"
2650 add = "\1\ncensored:"
2657 addlen = len(add)
2651 addlen = len(add)
2658 return newlen >= addlen and delta[hlen:hlen + addlen] == add
2652 return newlen >= addlen and delta[hlen:hlen + addlen] == add
2659
2653
2660 def getstrippoint(self, minlink):
2654 def getstrippoint(self, minlink):
2661 """find the minimum rev that must be stripped to strip the linkrev
2655 """find the minimum rev that must be stripped to strip the linkrev
2662
2656
2663 Returns a tuple containing the minimum rev and a set of all revs that
2657 Returns a tuple containing the minimum rev and a set of all revs that
2664 have linkrevs that will be broken by this strip.
2658 have linkrevs that will be broken by this strip.
2665 """
2659 """
2666 brokenrevs = set()
2660 brokenrevs = set()
2667 strippoint = len(self)
2661 strippoint = len(self)
2668
2662
2669 heads = {}
2663 heads = {}
2670 futurelargelinkrevs = set()
2664 futurelargelinkrevs = set()
2671 for head in self.headrevs():
2665 for head in self.headrevs():
2672 headlinkrev = self.linkrev(head)
2666 headlinkrev = self.linkrev(head)
2673 heads[head] = headlinkrev
2667 heads[head] = headlinkrev
2674 if headlinkrev >= minlink:
2668 if headlinkrev >= minlink:
2675 futurelargelinkrevs.add(headlinkrev)
2669 futurelargelinkrevs.add(headlinkrev)
2676
2670
2677 # This algorithm involves walking down the rev graph, starting at the
2671 # This algorithm involves walking down the rev graph, starting at the
2678 # heads. Since the revs are topologically sorted according to linkrev,
2672 # heads. Since the revs are topologically sorted according to linkrev,
2679 # once all head linkrevs are below the minlink, we know there are
2673 # once all head linkrevs are below the minlink, we know there are
2680 # no more revs that could have a linkrev greater than minlink.
2674 # no more revs that could have a linkrev greater than minlink.
2681 # So we can stop walking.
2675 # So we can stop walking.
2682 while futurelargelinkrevs:
2676 while futurelargelinkrevs:
2683 strippoint -= 1
2677 strippoint -= 1
2684 linkrev = heads.pop(strippoint)
2678 linkrev = heads.pop(strippoint)
2685
2679
2686 if linkrev < minlink:
2680 if linkrev < minlink:
2687 brokenrevs.add(strippoint)
2681 brokenrevs.add(strippoint)
2688 else:
2682 else:
2689 futurelargelinkrevs.remove(linkrev)
2683 futurelargelinkrevs.remove(linkrev)
2690
2684
2691 for p in self.parentrevs(strippoint):
2685 for p in self.parentrevs(strippoint):
2692 if p != nullrev:
2686 if p != nullrev:
2693 plinkrev = self.linkrev(p)
2687 plinkrev = self.linkrev(p)
2694 heads[p] = plinkrev
2688 heads[p] = plinkrev
2695 if plinkrev >= minlink:
2689 if plinkrev >= minlink:
2696 futurelargelinkrevs.add(plinkrev)
2690 futurelargelinkrevs.add(plinkrev)
2697
2691
2698 return strippoint, brokenrevs
2692 return strippoint, brokenrevs
2699
2693
2700 def strip(self, minlink, transaction):
2694 def strip(self, minlink, transaction):
2701 """truncate the revlog on the first revision with a linkrev >= minlink
2695 """truncate the revlog on the first revision with a linkrev >= minlink
2702
2696
2703 This function is called when we're stripping revision minlink and
2697 This function is called when we're stripping revision minlink and
2704 its descendants from the repository.
2698 its descendants from the repository.
2705
2699
2706 We have to remove all revisions with linkrev >= minlink, because
2700 We have to remove all revisions with linkrev >= minlink, because
2707 the equivalent changelog revisions will be renumbered after the
2701 the equivalent changelog revisions will be renumbered after the
2708 strip.
2702 strip.
2709
2703
2710 So we truncate the revlog on the first of these revisions, and
2704 So we truncate the revlog on the first of these revisions, and
2711 trust that the caller has saved the revisions that shouldn't be
2705 trust that the caller has saved the revisions that shouldn't be
2712 removed and that it'll re-add them after this truncation.
2706 removed and that it'll re-add them after this truncation.
2713 """
2707 """
2714 if len(self) == 0:
2708 if len(self) == 0:
2715 return
2709 return
2716
2710
2717 rev, _ = self.getstrippoint(minlink)
2711 rev, _ = self.getstrippoint(minlink)
2718 if rev == len(self):
2712 if rev == len(self):
2719 return
2713 return
2720
2714
2721 # first truncate the files on disk
2715 # first truncate the files on disk
2722 end = self.start(rev)
2716 end = self.start(rev)
2723 if not self._inline:
2717 if not self._inline:
2724 transaction.add(self.datafile, end)
2718 transaction.add(self.datafile, end)
2725 end = rev * self._io.size
2719 end = rev * self._io.size
2726 else:
2720 else:
2727 end += rev * self._io.size
2721 end += rev * self._io.size
2728
2722
2729 transaction.add(self.indexfile, end)
2723 transaction.add(self.indexfile, end)
2730
2724
2731 # then reset internal state in memory to forget those revisions
2725 # then reset internal state in memory to forget those revisions
2732 self._cache = None
2726 self._cache = None
2733 self._chaininfocache = {}
2727 self._chaininfocache = {}
2734 self._chunkclear()
2728 self._chunkclear()
2735 for x in xrange(rev, len(self)):
2729 for x in xrange(rev, len(self)):
2736 del self.nodemap[self.node(x)]
2730 del self.nodemap[self.node(x)]
2737
2731
2738 del self.index[rev:-1]
2732 del self.index[rev:-1]
2739 self._nodepos = None
2733 self._nodepos = None
2740
2734
2741 def checksize(self):
2735 def checksize(self):
2742 expected = 0
2736 expected = 0
2743 if len(self):
2737 if len(self):
2744 expected = max(0, self.end(len(self) - 1))
2738 expected = max(0, self.end(len(self) - 1))
2745
2739
2746 try:
2740 try:
2747 with self._datafp() as f:
2741 with self._datafp() as f:
2748 f.seek(0, 2)
2742 f.seek(0, 2)
2749 actual = f.tell()
2743 actual = f.tell()
2750 dd = actual - expected
2744 dd = actual - expected
2751 except IOError as inst:
2745 except IOError as inst:
2752 if inst.errno != errno.ENOENT:
2746 if inst.errno != errno.ENOENT:
2753 raise
2747 raise
2754 dd = 0
2748 dd = 0
2755
2749
2756 try:
2750 try:
2757 f = self.opener(self.indexfile)
2751 f = self.opener(self.indexfile)
2758 f.seek(0, 2)
2752 f.seek(0, 2)
2759 actual = f.tell()
2753 actual = f.tell()
2760 f.close()
2754 f.close()
2761 s = self._io.size
2755 s = self._io.size
2762 i = max(0, actual // s)
2756 i = max(0, actual // s)
2763 di = actual - (i * s)
2757 di = actual - (i * s)
2764 if self._inline:
2758 if self._inline:
2765 databytes = 0
2759 databytes = 0
2766 for r in self:
2760 for r in self:
2767 databytes += max(0, self.length(r))
2761 databytes += max(0, self.length(r))
2768 dd = 0
2762 dd = 0
2769 di = actual - len(self) * s - databytes
2763 di = actual - len(self) * s - databytes
2770 except IOError as inst:
2764 except IOError as inst:
2771 if inst.errno != errno.ENOENT:
2765 if inst.errno != errno.ENOENT:
2772 raise
2766 raise
2773 di = 0
2767 di = 0
2774
2768
2775 return (dd, di)
2769 return (dd, di)
2776
2770
2777 def files(self):
2771 def files(self):
2778 res = [self.indexfile]
2772 res = [self.indexfile]
2779 if not self._inline:
2773 if not self._inline:
2780 res.append(self.datafile)
2774 res.append(self.datafile)
2781 return res
2775 return res
2782
2776
2783 DELTAREUSEALWAYS = 'always'
2777 DELTAREUSEALWAYS = 'always'
2784 DELTAREUSESAMEREVS = 'samerevs'
2778 DELTAREUSESAMEREVS = 'samerevs'
2785 DELTAREUSENEVER = 'never'
2779 DELTAREUSENEVER = 'never'
2786
2780
2787 DELTAREUSEFULLADD = 'fulladd'
2781 DELTAREUSEFULLADD = 'fulladd'
2788
2782
2789 DELTAREUSEALL = {'always', 'samerevs', 'never', 'fulladd'}
2783 DELTAREUSEALL = {'always', 'samerevs', 'never', 'fulladd'}
2790
2784
2791 def clone(self, tr, destrevlog, addrevisioncb=None,
2785 def clone(self, tr, destrevlog, addrevisioncb=None,
2792 deltareuse=DELTAREUSESAMEREVS, aggressivemergedeltas=None):
2786 deltareuse=DELTAREUSESAMEREVS, aggressivemergedeltas=None):
2793 """Copy this revlog to another, possibly with format changes.
2787 """Copy this revlog to another, possibly with format changes.
2794
2788
2795 The destination revlog will contain the same revisions and nodes.
2789 The destination revlog will contain the same revisions and nodes.
2796 However, it may not be bit-for-bit identical due to e.g. delta encoding
2790 However, it may not be bit-for-bit identical due to e.g. delta encoding
2797 differences.
2791 differences.
2798
2792
2799 The ``deltareuse`` argument control how deltas from the existing revlog
2793 The ``deltareuse`` argument control how deltas from the existing revlog
2800 are preserved in the destination revlog. The argument can have the
2794 are preserved in the destination revlog. The argument can have the
2801 following values:
2795 following values:
2802
2796
2803 DELTAREUSEALWAYS
2797 DELTAREUSEALWAYS
2804 Deltas will always be reused (if possible), even if the destination
2798 Deltas will always be reused (if possible), even if the destination
2805 revlog would not select the same revisions for the delta. This is the
2799 revlog would not select the same revisions for the delta. This is the
2806 fastest mode of operation.
2800 fastest mode of operation.
2807 DELTAREUSESAMEREVS
2801 DELTAREUSESAMEREVS
2808 Deltas will be reused if the destination revlog would pick the same
2802 Deltas will be reused if the destination revlog would pick the same
2809 revisions for the delta. This mode strikes a balance between speed
2803 revisions for the delta. This mode strikes a balance between speed
2810 and optimization.
2804 and optimization.
2811 DELTAREUSENEVER
2805 DELTAREUSENEVER
2812 Deltas will never be reused. This is the slowest mode of execution.
2806 Deltas will never be reused. This is the slowest mode of execution.
2813 This mode can be used to recompute deltas (e.g. if the diff/delta
2807 This mode can be used to recompute deltas (e.g. if the diff/delta
2814 algorithm changes).
2808 algorithm changes).
2815
2809
2816 Delta computation can be slow, so the choice of delta reuse policy can
2810 Delta computation can be slow, so the choice of delta reuse policy can
2817 significantly affect run time.
2811 significantly affect run time.
2818
2812
2819 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2813 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2820 two extremes. Deltas will be reused if they are appropriate. But if the
2814 two extremes. Deltas will be reused if they are appropriate. But if the
2821 delta could choose a better revision, it will do so. This means if you
2815 delta could choose a better revision, it will do so. This means if you
2822 are converting a non-generaldelta revlog to a generaldelta revlog,
2816 are converting a non-generaldelta revlog to a generaldelta revlog,
2823 deltas will be recomputed if the delta's parent isn't a parent of the
2817 deltas will be recomputed if the delta's parent isn't a parent of the
2824 revision.
2818 revision.
2825
2819
2826 In addition to the delta policy, the ``aggressivemergedeltas`` argument
2820 In addition to the delta policy, the ``aggressivemergedeltas`` argument
2827 controls whether to compute deltas against both parents for merges.
2821 controls whether to compute deltas against both parents for merges.
2828 By default, the current default is used.
2822 By default, the current default is used.
2829 """
2823 """
2830 if deltareuse not in self.DELTAREUSEALL:
2824 if deltareuse not in self.DELTAREUSEALL:
2831 raise ValueError(_('value for deltareuse invalid: %s') % deltareuse)
2825 raise ValueError(_('value for deltareuse invalid: %s') % deltareuse)
2832
2826
2833 if len(destrevlog):
2827 if len(destrevlog):
2834 raise ValueError(_('destination revlog is not empty'))
2828 raise ValueError(_('destination revlog is not empty'))
2835
2829
2836 if getattr(self, 'filteredrevs', None):
2830 if getattr(self, 'filteredrevs', None):
2837 raise ValueError(_('source revlog has filtered revisions'))
2831 raise ValueError(_('source revlog has filtered revisions'))
2838 if getattr(destrevlog, 'filteredrevs', None):
2832 if getattr(destrevlog, 'filteredrevs', None):
2839 raise ValueError(_('destination revlog has filtered revisions'))
2833 raise ValueError(_('destination revlog has filtered revisions'))
2840
2834
2841 # lazydeltabase controls whether to reuse a cached delta, if possible.
2835 # lazydeltabase controls whether to reuse a cached delta, if possible.
2842 oldlazydeltabase = destrevlog._lazydeltabase
2836 oldlazydeltabase = destrevlog._lazydeltabase
2843 oldamd = destrevlog._aggressivemergedeltas
2837 oldamd = destrevlog._aggressivemergedeltas
2844
2838
2845 try:
2839 try:
2846 if deltareuse == self.DELTAREUSEALWAYS:
2840 if deltareuse == self.DELTAREUSEALWAYS:
2847 destrevlog._lazydeltabase = True
2841 destrevlog._lazydeltabase = True
2848 elif deltareuse == self.DELTAREUSESAMEREVS:
2842 elif deltareuse == self.DELTAREUSESAMEREVS:
2849 destrevlog._lazydeltabase = False
2843 destrevlog._lazydeltabase = False
2850
2844
2851 destrevlog._aggressivemergedeltas = aggressivemergedeltas or oldamd
2845 destrevlog._aggressivemergedeltas = aggressivemergedeltas or oldamd
2852
2846
2853 populatecachedelta = deltareuse in (self.DELTAREUSEALWAYS,
2847 populatecachedelta = deltareuse in (self.DELTAREUSEALWAYS,
2854 self.DELTAREUSESAMEREVS)
2848 self.DELTAREUSESAMEREVS)
2855
2849
2856 deltacomputer = _deltacomputer(destrevlog)
2850 deltacomputer = _deltacomputer(destrevlog)
2857 index = self.index
2851 index = self.index
2858 for rev in self:
2852 for rev in self:
2859 entry = index[rev]
2853 entry = index[rev]
2860
2854
2861 # Some classes override linkrev to take filtered revs into
2855 # Some classes override linkrev to take filtered revs into
2862 # account. Use raw entry from index.
2856 # account. Use raw entry from index.
2863 flags = entry[0] & 0xffff
2857 flags = entry[0] & 0xffff
2864 linkrev = entry[4]
2858 linkrev = entry[4]
2865 p1 = index[entry[5]][7]
2859 p1 = index[entry[5]][7]
2866 p2 = index[entry[6]][7]
2860 p2 = index[entry[6]][7]
2867 node = entry[7]
2861 node = entry[7]
2868
2862
2869 # (Possibly) reuse the delta from the revlog if allowed and
2863 # (Possibly) reuse the delta from the revlog if allowed and
2870 # the revlog chunk is a delta.
2864 # the revlog chunk is a delta.
2871 cachedelta = None
2865 cachedelta = None
2872 rawtext = None
2866 rawtext = None
2873 if populatecachedelta:
2867 if populatecachedelta:
2874 dp = self.deltaparent(rev)
2868 dp = self.deltaparent(rev)
2875 if dp != nullrev:
2869 if dp != nullrev:
2876 cachedelta = (dp, bytes(self._chunk(rev)))
2870 cachedelta = (dp, bytes(self._chunk(rev)))
2877
2871
2878 if not cachedelta:
2872 if not cachedelta:
2879 rawtext = self.revision(rev, raw=True)
2873 rawtext = self.revision(rev, raw=True)
2880
2874
2881
2875
2882 if deltareuse == self.DELTAREUSEFULLADD:
2876 if deltareuse == self.DELTAREUSEFULLADD:
2883 destrevlog.addrevision(rawtext, tr, linkrev, p1, p2,
2877 destrevlog.addrevision(rawtext, tr, linkrev, p1, p2,
2884 cachedelta=cachedelta,
2878 cachedelta=cachedelta,
2885 node=node, flags=flags,
2879 node=node, flags=flags,
2886 deltacomputer=deltacomputer)
2880 deltacomputer=deltacomputer)
2887 else:
2881 else:
2888 ifh = destrevlog.opener(destrevlog.indexfile, 'a+',
2882 ifh = destrevlog.opener(destrevlog.indexfile, 'a+',
2889 checkambig=False)
2883 checkambig=False)
2890 dfh = None
2884 dfh = None
2891 if not destrevlog._inline:
2885 if not destrevlog._inline:
2892 dfh = destrevlog.opener(destrevlog.datafile, 'a+')
2886 dfh = destrevlog.opener(destrevlog.datafile, 'a+')
2893 try:
2887 try:
2894 destrevlog._addrevision(node, rawtext, tr, linkrev, p1,
2888 destrevlog._addrevision(node, rawtext, tr, linkrev, p1,
2895 p2, flags, cachedelta, ifh, dfh,
2889 p2, flags, cachedelta, ifh, dfh,
2896 deltacomputer=deltacomputer)
2890 deltacomputer=deltacomputer)
2897 finally:
2891 finally:
2898 if dfh:
2892 if dfh:
2899 dfh.close()
2893 dfh.close()
2900 ifh.close()
2894 ifh.close()
2901
2895
2902 if addrevisioncb:
2896 if addrevisioncb:
2903 addrevisioncb(self, rev, node)
2897 addrevisioncb(self, rev, node)
2904 finally:
2898 finally:
2905 destrevlog._lazydeltabase = oldlazydeltabase
2899 destrevlog._lazydeltabase = oldlazydeltabase
2906 destrevlog._aggressivemergedeltas = oldamd
2900 destrevlog._aggressivemergedeltas = oldamd
General Comments 0
You need to be logged in to leave comments. Login now