##// END OF EJS Templates
revlog: recommit 49fd21f32695 with a fix for issue6528...
Joerg Sonnenberger -
r49876:5b65721a default
parent child Browse files
Show More
@@ -1,3145 +1,3155 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 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2006, 2007 Olivia Mackall <olivia@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
8
9 import errno
9 import errno
10 import filecmp
10 import filecmp
11 import os
11 import os
12 import stat
12 import stat
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 hex,
16 hex,
17 nullrev,
17 nullrev,
18 short,
18 short,
19 )
19 )
20 from .pycompat import (
20 from .pycompat import (
21 getattr,
21 getattr,
22 )
22 )
23 from . import (
23 from . import (
24 dagop,
24 dagop,
25 encoding,
25 encoding,
26 error,
26 error,
27 fileset,
27 fileset,
28 match as matchmod,
28 match as matchmod,
29 mergestate as mergestatemod,
29 mergestate as mergestatemod,
30 metadata,
30 metadata,
31 obsolete as obsmod,
31 obsolete as obsmod,
32 patch,
32 patch,
33 pathutil,
33 pathutil,
34 phases,
34 phases,
35 pycompat,
35 pycompat,
36 repoview,
36 repoview,
37 scmutil,
37 scmutil,
38 sparse,
38 sparse,
39 subrepo,
39 subrepo,
40 subrepoutil,
40 subrepoutil,
41 util,
41 util,
42 )
42 )
43 from .utils import (
43 from .utils import (
44 dateutil,
44 dateutil,
45 stringutil,
45 stringutil,
46 )
46 )
47 from .dirstateutils import (
47 from .dirstateutils import (
48 timestamp,
48 timestamp,
49 )
49 )
50
50
51 propertycache = util.propertycache
51 propertycache = util.propertycache
52
52
53
53
54 class basectx:
54 class basectx:
55 """A basectx object represents the common logic for its children:
55 """A basectx object represents the common logic for its children:
56 changectx: read-only context that is already present in the repo,
56 changectx: read-only context that is already present in the repo,
57 workingctx: a context that represents the working directory and can
57 workingctx: a context that represents the working directory and can
58 be committed,
58 be committed,
59 memctx: a context that represents changes in-memory and can also
59 memctx: a context that represents changes in-memory and can also
60 be committed."""
60 be committed."""
61
61
62 def __init__(self, repo):
62 def __init__(self, repo):
63 self._repo = repo
63 self._repo = repo
64
64
65 def __bytes__(self):
65 def __bytes__(self):
66 return short(self.node())
66 return short(self.node())
67
67
68 __str__ = encoding.strmethod(__bytes__)
68 __str__ = encoding.strmethod(__bytes__)
69
69
70 def __repr__(self):
70 def __repr__(self):
71 return "<%s %s>" % (type(self).__name__, str(self))
71 return "<%s %s>" % (type(self).__name__, str(self))
72
72
73 def __eq__(self, other):
73 def __eq__(self, other):
74 try:
74 try:
75 return type(self) == type(other) and self._rev == other._rev
75 return type(self) == type(other) and self._rev == other._rev
76 except AttributeError:
76 except AttributeError:
77 return False
77 return False
78
78
79 def __ne__(self, other):
79 def __ne__(self, other):
80 return not (self == other)
80 return not (self == other)
81
81
82 def __contains__(self, key):
82 def __contains__(self, key):
83 return key in self._manifest
83 return key in self._manifest
84
84
85 def __getitem__(self, key):
85 def __getitem__(self, key):
86 return self.filectx(key)
86 return self.filectx(key)
87
87
88 def __iter__(self):
88 def __iter__(self):
89 return iter(self._manifest)
89 return iter(self._manifest)
90
90
91 def _buildstatusmanifest(self, status):
91 def _buildstatusmanifest(self, status):
92 """Builds a manifest that includes the given status results, if this is
92 """Builds a manifest that includes the given status results, if this is
93 a working copy context. For non-working copy contexts, it just returns
93 a working copy context. For non-working copy contexts, it just returns
94 the normal manifest."""
94 the normal manifest."""
95 return self.manifest()
95 return self.manifest()
96
96
97 def _matchstatus(self, other, match):
97 def _matchstatus(self, other, match):
98 """This internal method provides a way for child objects to override the
98 """This internal method provides a way for child objects to override the
99 match operator.
99 match operator.
100 """
100 """
101 return match
101 return match
102
102
103 def _buildstatus(
103 def _buildstatus(
104 self, other, s, match, listignored, listclean, listunknown
104 self, other, s, match, listignored, listclean, listunknown
105 ):
105 ):
106 """build a status with respect to another context"""
106 """build a status with respect to another context"""
107 # Load earliest manifest first for caching reasons. More specifically,
107 # Load earliest manifest first for caching reasons. More specifically,
108 # if you have revisions 1000 and 1001, 1001 is probably stored as a
108 # if you have revisions 1000 and 1001, 1001 is probably stored as a
109 # delta against 1000. Thus, if you read 1000 first, we'll reconstruct
109 # delta against 1000. Thus, if you read 1000 first, we'll reconstruct
110 # 1000 and cache it so that when you read 1001, we just need to apply a
110 # 1000 and cache it so that when you read 1001, we just need to apply a
111 # delta to what's in the cache. So that's one full reconstruction + one
111 # delta to what's in the cache. So that's one full reconstruction + one
112 # delta application.
112 # delta application.
113 mf2 = None
113 mf2 = None
114 if self.rev() is not None and self.rev() < other.rev():
114 if self.rev() is not None and self.rev() < other.rev():
115 mf2 = self._buildstatusmanifest(s)
115 mf2 = self._buildstatusmanifest(s)
116 mf1 = other._buildstatusmanifest(s)
116 mf1 = other._buildstatusmanifest(s)
117 if mf2 is None:
117 if mf2 is None:
118 mf2 = self._buildstatusmanifest(s)
118 mf2 = self._buildstatusmanifest(s)
119
119
120 modified, added = [], []
120 modified, added = [], []
121 removed = []
121 removed = []
122 clean = []
122 clean = []
123 deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
123 deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
124 deletedset = set(deleted)
124 deletedset = set(deleted)
125 d = mf1.diff(mf2, match=match, clean=listclean)
125 d = mf1.diff(mf2, match=match, clean=listclean)
126 for fn, value in d.items():
126 for fn, value in d.items():
127 if fn in deletedset:
127 if fn in deletedset:
128 continue
128 continue
129 if value is None:
129 if value is None:
130 clean.append(fn)
130 clean.append(fn)
131 continue
131 continue
132 (node1, flag1), (node2, flag2) = value
132 (node1, flag1), (node2, flag2) = value
133 if node1 is None:
133 if node1 is None:
134 added.append(fn)
134 added.append(fn)
135 elif node2 is None:
135 elif node2 is None:
136 removed.append(fn)
136 removed.append(fn)
137 elif flag1 != flag2:
137 elif flag1 != flag2:
138 modified.append(fn)
138 modified.append(fn)
139 elif node2 not in self._repo.nodeconstants.wdirfilenodeids:
139 elif node2 not in self._repo.nodeconstants.wdirfilenodeids:
140 # When comparing files between two commits, we save time by
140 # When comparing files between two commits, we save time by
141 # not comparing the file contents when the nodeids differ.
141 # not comparing the file contents when the nodeids differ.
142 # Note that this means we incorrectly report a reverted change
142 # Note that this means we incorrectly report a reverted change
143 # to a file as a modification.
143 # to a file as a modification.
144 modified.append(fn)
144 modified.append(fn)
145 elif self[fn].cmp(other[fn]):
145 elif self[fn].cmp(other[fn]):
146 modified.append(fn)
146 modified.append(fn)
147 else:
147 else:
148 clean.append(fn)
148 clean.append(fn)
149
149
150 if removed:
150 if removed:
151 # need to filter files if they are already reported as removed
151 # need to filter files if they are already reported as removed
152 unknown = [
152 unknown = [
153 fn
153 fn
154 for fn in unknown
154 for fn in unknown
155 if fn not in mf1 and (not match or match(fn))
155 if fn not in mf1 and (not match or match(fn))
156 ]
156 ]
157 ignored = [
157 ignored = [
158 fn
158 fn
159 for fn in ignored
159 for fn in ignored
160 if fn not in mf1 and (not match or match(fn))
160 if fn not in mf1 and (not match or match(fn))
161 ]
161 ]
162 # if they're deleted, don't report them as removed
162 # if they're deleted, don't report them as removed
163 removed = [fn for fn in removed if fn not in deletedset]
163 removed = [fn for fn in removed if fn not in deletedset]
164
164
165 return scmutil.status(
165 return scmutil.status(
166 modified, added, removed, deleted, unknown, ignored, clean
166 modified, added, removed, deleted, unknown, ignored, clean
167 )
167 )
168
168
169 @propertycache
169 @propertycache
170 def substate(self):
170 def substate(self):
171 return subrepoutil.state(self, self._repo.ui)
171 return subrepoutil.state(self, self._repo.ui)
172
172
173 def subrev(self, subpath):
173 def subrev(self, subpath):
174 return self.substate[subpath][1]
174 return self.substate[subpath][1]
175
175
176 def rev(self):
176 def rev(self):
177 return self._rev
177 return self._rev
178
178
179 def node(self):
179 def node(self):
180 return self._node
180 return self._node
181
181
182 def hex(self):
182 def hex(self):
183 return hex(self.node())
183 return hex(self.node())
184
184
185 def manifest(self):
185 def manifest(self):
186 return self._manifest
186 return self._manifest
187
187
188 def manifestctx(self):
188 def manifestctx(self):
189 return self._manifestctx
189 return self._manifestctx
190
190
191 def repo(self):
191 def repo(self):
192 return self._repo
192 return self._repo
193
193
194 def phasestr(self):
194 def phasestr(self):
195 return phases.phasenames[self.phase()]
195 return phases.phasenames[self.phase()]
196
196
197 def mutable(self):
197 def mutable(self):
198 return self.phase() > phases.public
198 return self.phase() > phases.public
199
199
200 def matchfileset(self, cwd, expr, badfn=None):
200 def matchfileset(self, cwd, expr, badfn=None):
201 return fileset.match(self, cwd, expr, badfn=badfn)
201 return fileset.match(self, cwd, expr, badfn=badfn)
202
202
203 def obsolete(self):
203 def obsolete(self):
204 """True if the changeset is obsolete"""
204 """True if the changeset is obsolete"""
205 return self.rev() in obsmod.getrevs(self._repo, b'obsolete')
205 return self.rev() in obsmod.getrevs(self._repo, b'obsolete')
206
206
207 def extinct(self):
207 def extinct(self):
208 """True if the changeset is extinct"""
208 """True if the changeset is extinct"""
209 return self.rev() in obsmod.getrevs(self._repo, b'extinct')
209 return self.rev() in obsmod.getrevs(self._repo, b'extinct')
210
210
211 def orphan(self):
211 def orphan(self):
212 """True if the changeset is not obsolete, but its ancestor is"""
212 """True if the changeset is not obsolete, but its ancestor is"""
213 return self.rev() in obsmod.getrevs(self._repo, b'orphan')
213 return self.rev() in obsmod.getrevs(self._repo, b'orphan')
214
214
215 def phasedivergent(self):
215 def phasedivergent(self):
216 """True if the changeset tries to be a successor of a public changeset
216 """True if the changeset tries to be a successor of a public changeset
217
217
218 Only non-public and non-obsolete changesets may be phase-divergent.
218 Only non-public and non-obsolete changesets may be phase-divergent.
219 """
219 """
220 return self.rev() in obsmod.getrevs(self._repo, b'phasedivergent')
220 return self.rev() in obsmod.getrevs(self._repo, b'phasedivergent')
221
221
222 def contentdivergent(self):
222 def contentdivergent(self):
223 """Is a successor of a changeset with multiple possible successor sets
223 """Is a successor of a changeset with multiple possible successor sets
224
224
225 Only non-public and non-obsolete changesets may be content-divergent.
225 Only non-public and non-obsolete changesets may be content-divergent.
226 """
226 """
227 return self.rev() in obsmod.getrevs(self._repo, b'contentdivergent')
227 return self.rev() in obsmod.getrevs(self._repo, b'contentdivergent')
228
228
229 def isunstable(self):
229 def isunstable(self):
230 """True if the changeset is either orphan, phase-divergent or
230 """True if the changeset is either orphan, phase-divergent or
231 content-divergent"""
231 content-divergent"""
232 return self.orphan() or self.phasedivergent() or self.contentdivergent()
232 return self.orphan() or self.phasedivergent() or self.contentdivergent()
233
233
234 def instabilities(self):
234 def instabilities(self):
235 """return the list of instabilities affecting this changeset.
235 """return the list of instabilities affecting this changeset.
236
236
237 Instabilities are returned as strings. possible values are:
237 Instabilities are returned as strings. possible values are:
238 - orphan,
238 - orphan,
239 - phase-divergent,
239 - phase-divergent,
240 - content-divergent.
240 - content-divergent.
241 """
241 """
242 instabilities = []
242 instabilities = []
243 if self.orphan():
243 if self.orphan():
244 instabilities.append(b'orphan')
244 instabilities.append(b'orphan')
245 if self.phasedivergent():
245 if self.phasedivergent():
246 instabilities.append(b'phase-divergent')
246 instabilities.append(b'phase-divergent')
247 if self.contentdivergent():
247 if self.contentdivergent():
248 instabilities.append(b'content-divergent')
248 instabilities.append(b'content-divergent')
249 return instabilities
249 return instabilities
250
250
251 def parents(self):
251 def parents(self):
252 """return contexts for each parent changeset"""
252 """return contexts for each parent changeset"""
253 return self._parents
253 return self._parents
254
254
255 def p1(self):
255 def p1(self):
256 return self._parents[0]
256 return self._parents[0]
257
257
258 def p2(self):
258 def p2(self):
259 parents = self._parents
259 parents = self._parents
260 if len(parents) == 2:
260 if len(parents) == 2:
261 return parents[1]
261 return parents[1]
262 return self._repo[nullrev]
262 return self._repo[nullrev]
263
263
264 def _fileinfo(self, path):
264 def _fileinfo(self, path):
265 if '_manifest' in self.__dict__:
265 if '_manifest' in self.__dict__:
266 try:
266 try:
267 return self._manifest.find(path)
267 return self._manifest.find(path)
268 except KeyError:
268 except KeyError:
269 raise error.ManifestLookupError(
269 raise error.ManifestLookupError(
270 self._node or b'None', path, _(b'not found in manifest')
270 self._node or b'None', path, _(b'not found in manifest')
271 )
271 )
272 if '_manifestdelta' in self.__dict__ or path in self.files():
272 if '_manifestdelta' in self.__dict__ or path in self.files():
273 if path in self._manifestdelta:
273 if path in self._manifestdelta:
274 return (
274 return (
275 self._manifestdelta[path],
275 self._manifestdelta[path],
276 self._manifestdelta.flags(path),
276 self._manifestdelta.flags(path),
277 )
277 )
278 mfl = self._repo.manifestlog
278 mfl = self._repo.manifestlog
279 try:
279 try:
280 node, flag = mfl[self._changeset.manifest].find(path)
280 node, flag = mfl[self._changeset.manifest].find(path)
281 except KeyError:
281 except KeyError:
282 raise error.ManifestLookupError(
282 raise error.ManifestLookupError(
283 self._node or b'None', path, _(b'not found in manifest')
283 self._node or b'None', path, _(b'not found in manifest')
284 )
284 )
285
285
286 return node, flag
286 return node, flag
287
287
288 def filenode(self, path):
288 def filenode(self, path):
289 return self._fileinfo(path)[0]
289 return self._fileinfo(path)[0]
290
290
291 def flags(self, path):
291 def flags(self, path):
292 try:
292 try:
293 return self._fileinfo(path)[1]
293 return self._fileinfo(path)[1]
294 except error.LookupError:
294 except error.LookupError:
295 return b''
295 return b''
296
296
297 @propertycache
297 @propertycache
298 def _copies(self):
298 def _copies(self):
299 return metadata.computechangesetcopies(self)
299 return metadata.computechangesetcopies(self)
300
300
301 def p1copies(self):
301 def p1copies(self):
302 return self._copies[0]
302 return self._copies[0]
303
303
304 def p2copies(self):
304 def p2copies(self):
305 return self._copies[1]
305 return self._copies[1]
306
306
307 def sub(self, path, allowcreate=True):
307 def sub(self, path, allowcreate=True):
308 '''return a subrepo for the stored revision of path, never wdir()'''
308 '''return a subrepo for the stored revision of path, never wdir()'''
309 return subrepo.subrepo(self, path, allowcreate=allowcreate)
309 return subrepo.subrepo(self, path, allowcreate=allowcreate)
310
310
311 def nullsub(self, path, pctx):
311 def nullsub(self, path, pctx):
312 return subrepo.nullsubrepo(self, path, pctx)
312 return subrepo.nullsubrepo(self, path, pctx)
313
313
314 def workingsub(self, path):
314 def workingsub(self, path):
315 """return a subrepo for the stored revision, or wdir if this is a wdir
315 """return a subrepo for the stored revision, or wdir if this is a wdir
316 context.
316 context.
317 """
317 """
318 return subrepo.subrepo(self, path, allowwdir=True)
318 return subrepo.subrepo(self, path, allowwdir=True)
319
319
320 def match(
320 def match(
321 self,
321 self,
322 pats=None,
322 pats=None,
323 include=None,
323 include=None,
324 exclude=None,
324 exclude=None,
325 default=b'glob',
325 default=b'glob',
326 listsubrepos=False,
326 listsubrepos=False,
327 badfn=None,
327 badfn=None,
328 cwd=None,
328 cwd=None,
329 ):
329 ):
330 r = self._repo
330 r = self._repo
331 if not cwd:
331 if not cwd:
332 cwd = r.getcwd()
332 cwd = r.getcwd()
333 return matchmod.match(
333 return matchmod.match(
334 r.root,
334 r.root,
335 cwd,
335 cwd,
336 pats,
336 pats,
337 include,
337 include,
338 exclude,
338 exclude,
339 default,
339 default,
340 auditor=r.nofsauditor,
340 auditor=r.nofsauditor,
341 ctx=self,
341 ctx=self,
342 listsubrepos=listsubrepos,
342 listsubrepos=listsubrepos,
343 badfn=badfn,
343 badfn=badfn,
344 )
344 )
345
345
346 def diff(
346 def diff(
347 self,
347 self,
348 ctx2=None,
348 ctx2=None,
349 match=None,
349 match=None,
350 changes=None,
350 changes=None,
351 opts=None,
351 opts=None,
352 losedatafn=None,
352 losedatafn=None,
353 pathfn=None,
353 pathfn=None,
354 copy=None,
354 copy=None,
355 copysourcematch=None,
355 copysourcematch=None,
356 hunksfilterfn=None,
356 hunksfilterfn=None,
357 ):
357 ):
358 """Returns a diff generator for the given contexts and matcher"""
358 """Returns a diff generator for the given contexts and matcher"""
359 if ctx2 is None:
359 if ctx2 is None:
360 ctx2 = self.p1()
360 ctx2 = self.p1()
361 if ctx2 is not None:
361 if ctx2 is not None:
362 ctx2 = self._repo[ctx2]
362 ctx2 = self._repo[ctx2]
363 return patch.diff(
363 return patch.diff(
364 self._repo,
364 self._repo,
365 ctx2,
365 ctx2,
366 self,
366 self,
367 match=match,
367 match=match,
368 changes=changes,
368 changes=changes,
369 opts=opts,
369 opts=opts,
370 losedatafn=losedatafn,
370 losedatafn=losedatafn,
371 pathfn=pathfn,
371 pathfn=pathfn,
372 copy=copy,
372 copy=copy,
373 copysourcematch=copysourcematch,
373 copysourcematch=copysourcematch,
374 hunksfilterfn=hunksfilterfn,
374 hunksfilterfn=hunksfilterfn,
375 )
375 )
376
376
377 def dirs(self):
377 def dirs(self):
378 return self._manifest.dirs()
378 return self._manifest.dirs()
379
379
380 def hasdir(self, dir):
380 def hasdir(self, dir):
381 return self._manifest.hasdir(dir)
381 return self._manifest.hasdir(dir)
382
382
383 def status(
383 def status(
384 self,
384 self,
385 other=None,
385 other=None,
386 match=None,
386 match=None,
387 listignored=False,
387 listignored=False,
388 listclean=False,
388 listclean=False,
389 listunknown=False,
389 listunknown=False,
390 listsubrepos=False,
390 listsubrepos=False,
391 ):
391 ):
392 """return status of files between two nodes or node and working
392 """return status of files between two nodes or node and working
393 directory.
393 directory.
394
394
395 If other is None, compare this node with working directory.
395 If other is None, compare this node with working directory.
396
396
397 ctx1.status(ctx2) returns the status of change from ctx1 to ctx2
397 ctx1.status(ctx2) returns the status of change from ctx1 to ctx2
398
398
399 Returns a mercurial.scmutils.status object.
399 Returns a mercurial.scmutils.status object.
400
400
401 Data can be accessed using either tuple notation:
401 Data can be accessed using either tuple notation:
402
402
403 (modified, added, removed, deleted, unknown, ignored, clean)
403 (modified, added, removed, deleted, unknown, ignored, clean)
404
404
405 or direct attribute access:
405 or direct attribute access:
406
406
407 s.modified, s.added, ...
407 s.modified, s.added, ...
408 """
408 """
409
409
410 ctx1 = self
410 ctx1 = self
411 ctx2 = self._repo[other]
411 ctx2 = self._repo[other]
412
412
413 # This next code block is, admittedly, fragile logic that tests for
413 # This next code block is, admittedly, fragile logic that tests for
414 # reversing the contexts and wouldn't need to exist if it weren't for
414 # reversing the contexts and wouldn't need to exist if it weren't for
415 # the fast (and common) code path of comparing the working directory
415 # the fast (and common) code path of comparing the working directory
416 # with its first parent.
416 # with its first parent.
417 #
417 #
418 # What we're aiming for here is the ability to call:
418 # What we're aiming for here is the ability to call:
419 #
419 #
420 # workingctx.status(parentctx)
420 # workingctx.status(parentctx)
421 #
421 #
422 # If we always built the manifest for each context and compared those,
422 # If we always built the manifest for each context and compared those,
423 # then we'd be done. But the special case of the above call means we
423 # then we'd be done. But the special case of the above call means we
424 # just copy the manifest of the parent.
424 # just copy the manifest of the parent.
425 reversed = False
425 reversed = False
426 if not isinstance(ctx1, changectx) and isinstance(ctx2, changectx):
426 if not isinstance(ctx1, changectx) and isinstance(ctx2, changectx):
427 reversed = True
427 reversed = True
428 ctx1, ctx2 = ctx2, ctx1
428 ctx1, ctx2 = ctx2, ctx1
429
429
430 match = self._repo.narrowmatch(match)
430 match = self._repo.narrowmatch(match)
431 match = ctx2._matchstatus(ctx1, match)
431 match = ctx2._matchstatus(ctx1, match)
432 r = scmutil.status([], [], [], [], [], [], [])
432 r = scmutil.status([], [], [], [], [], [], [])
433 r = ctx2._buildstatus(
433 r = ctx2._buildstatus(
434 ctx1, r, match, listignored, listclean, listunknown
434 ctx1, r, match, listignored, listclean, listunknown
435 )
435 )
436
436
437 if reversed:
437 if reversed:
438 # Reverse added and removed. Clear deleted, unknown and ignored as
438 # Reverse added and removed. Clear deleted, unknown and ignored as
439 # these make no sense to reverse.
439 # these make no sense to reverse.
440 r = scmutil.status(
440 r = scmutil.status(
441 r.modified, r.removed, r.added, [], [], [], r.clean
441 r.modified, r.removed, r.added, [], [], [], r.clean
442 )
442 )
443
443
444 if listsubrepos:
444 if listsubrepos:
445 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
445 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
446 try:
446 try:
447 rev2 = ctx2.subrev(subpath)
447 rev2 = ctx2.subrev(subpath)
448 except KeyError:
448 except KeyError:
449 # A subrepo that existed in node1 was deleted between
449 # A subrepo that existed in node1 was deleted between
450 # node1 and node2 (inclusive). Thus, ctx2's substate
450 # node1 and node2 (inclusive). Thus, ctx2's substate
451 # won't contain that subpath. The best we can do ignore it.
451 # won't contain that subpath. The best we can do ignore it.
452 rev2 = None
452 rev2 = None
453 submatch = matchmod.subdirmatcher(subpath, match)
453 submatch = matchmod.subdirmatcher(subpath, match)
454 s = sub.status(
454 s = sub.status(
455 rev2,
455 rev2,
456 match=submatch,
456 match=submatch,
457 ignored=listignored,
457 ignored=listignored,
458 clean=listclean,
458 clean=listclean,
459 unknown=listunknown,
459 unknown=listunknown,
460 listsubrepos=True,
460 listsubrepos=True,
461 )
461 )
462 for k in (
462 for k in (
463 'modified',
463 'modified',
464 'added',
464 'added',
465 'removed',
465 'removed',
466 'deleted',
466 'deleted',
467 'unknown',
467 'unknown',
468 'ignored',
468 'ignored',
469 'clean',
469 'clean',
470 ):
470 ):
471 rfiles, sfiles = getattr(r, k), getattr(s, k)
471 rfiles, sfiles = getattr(r, k), getattr(s, k)
472 rfiles.extend(b"%s/%s" % (subpath, f) for f in sfiles)
472 rfiles.extend(b"%s/%s" % (subpath, f) for f in sfiles)
473
473
474 r.modified.sort()
474 r.modified.sort()
475 r.added.sort()
475 r.added.sort()
476 r.removed.sort()
476 r.removed.sort()
477 r.deleted.sort()
477 r.deleted.sort()
478 r.unknown.sort()
478 r.unknown.sort()
479 r.ignored.sort()
479 r.ignored.sort()
480 r.clean.sort()
480 r.clean.sort()
481
481
482 return r
482 return r
483
483
484 def mergestate(self, clean=False):
484 def mergestate(self, clean=False):
485 """Get a mergestate object for this context."""
485 """Get a mergestate object for this context."""
486 raise NotImplementedError(
486 raise NotImplementedError(
487 '%s does not implement mergestate()' % self.__class__
487 '%s does not implement mergestate()' % self.__class__
488 )
488 )
489
489
490 def isempty(self):
490 def isempty(self):
491 return not (
491 return not (
492 len(self.parents()) > 1
492 len(self.parents()) > 1
493 or self.branch() != self.p1().branch()
493 or self.branch() != self.p1().branch()
494 or self.closesbranch()
494 or self.closesbranch()
495 or self.files()
495 or self.files()
496 )
496 )
497
497
498
498
499 class changectx(basectx):
499 class changectx(basectx):
500 """A changecontext object makes access to data related to a particular
500 """A changecontext object makes access to data related to a particular
501 changeset convenient. It represents a read-only context already present in
501 changeset convenient. It represents a read-only context already present in
502 the repo."""
502 the repo."""
503
503
504 def __init__(self, repo, rev, node, maybe_filtered=True):
504 def __init__(self, repo, rev, node, maybe_filtered=True):
505 super(changectx, self).__init__(repo)
505 super(changectx, self).__init__(repo)
506 self._rev = rev
506 self._rev = rev
507 self._node = node
507 self._node = node
508 # When maybe_filtered is True, the revision might be affected by
508 # When maybe_filtered is True, the revision might be affected by
509 # changelog filtering and operation through the filtered changelog must be used.
509 # changelog filtering and operation through the filtered changelog must be used.
510 #
510 #
511 # When maybe_filtered is False, the revision has already been checked
511 # When maybe_filtered is False, the revision has already been checked
512 # against filtering and is not filtered. Operation through the
512 # against filtering and is not filtered. Operation through the
513 # unfiltered changelog might be used in some case.
513 # unfiltered changelog might be used in some case.
514 self._maybe_filtered = maybe_filtered
514 self._maybe_filtered = maybe_filtered
515
515
516 def __hash__(self):
516 def __hash__(self):
517 try:
517 try:
518 return hash(self._rev)
518 return hash(self._rev)
519 except AttributeError:
519 except AttributeError:
520 return id(self)
520 return id(self)
521
521
522 def __nonzero__(self):
522 def __nonzero__(self):
523 return self._rev != nullrev
523 return self._rev != nullrev
524
524
525 __bool__ = __nonzero__
525 __bool__ = __nonzero__
526
526
527 @propertycache
527 @propertycache
528 def _changeset(self):
528 def _changeset(self):
529 if self._maybe_filtered:
529 if self._maybe_filtered:
530 repo = self._repo
530 repo = self._repo
531 else:
531 else:
532 repo = self._repo.unfiltered()
532 repo = self._repo.unfiltered()
533 return repo.changelog.changelogrevision(self.rev())
533 return repo.changelog.changelogrevision(self.rev())
534
534
535 @propertycache
535 @propertycache
536 def _manifest(self):
536 def _manifest(self):
537 return self._manifestctx.read()
537 return self._manifestctx.read()
538
538
539 @property
539 @property
540 def _manifestctx(self):
540 def _manifestctx(self):
541 return self._repo.manifestlog[self._changeset.manifest]
541 return self._repo.manifestlog[self._changeset.manifest]
542
542
543 @propertycache
543 @propertycache
544 def _manifestdelta(self):
544 def _manifestdelta(self):
545 return self._manifestctx.readdelta()
545 return self._manifestctx.readdelta()
546
546
547 @propertycache
547 @propertycache
548 def _parents(self):
548 def _parents(self):
549 repo = self._repo
549 repo = self._repo
550 if self._maybe_filtered:
550 if self._maybe_filtered:
551 cl = repo.changelog
551 cl = repo.changelog
552 else:
552 else:
553 cl = repo.unfiltered().changelog
553 cl = repo.unfiltered().changelog
554
554
555 p1, p2 = cl.parentrevs(self._rev)
555 p1, p2 = cl.parentrevs(self._rev)
556 if p2 == nullrev:
556 if p2 == nullrev:
557 return [changectx(repo, p1, cl.node(p1), maybe_filtered=False)]
557 return [changectx(repo, p1, cl.node(p1), maybe_filtered=False)]
558 return [
558 return [
559 changectx(repo, p1, cl.node(p1), maybe_filtered=False),
559 changectx(repo, p1, cl.node(p1), maybe_filtered=False),
560 changectx(repo, p2, cl.node(p2), maybe_filtered=False),
560 changectx(repo, p2, cl.node(p2), maybe_filtered=False),
561 ]
561 ]
562
562
563 def changeset(self):
563 def changeset(self):
564 c = self._changeset
564 c = self._changeset
565 return (
565 return (
566 c.manifest,
566 c.manifest,
567 c.user,
567 c.user,
568 c.date,
568 c.date,
569 c.files,
569 c.files,
570 c.description,
570 c.description,
571 c.extra,
571 c.extra,
572 )
572 )
573
573
574 def manifestnode(self):
574 def manifestnode(self):
575 return self._changeset.manifest
575 return self._changeset.manifest
576
576
577 def user(self):
577 def user(self):
578 return self._changeset.user
578 return self._changeset.user
579
579
580 def date(self):
580 def date(self):
581 return self._changeset.date
581 return self._changeset.date
582
582
583 def files(self):
583 def files(self):
584 return self._changeset.files
584 return self._changeset.files
585
585
586 def filesmodified(self):
586 def filesmodified(self):
587 modified = set(self.files())
587 modified = set(self.files())
588 modified.difference_update(self.filesadded())
588 modified.difference_update(self.filesadded())
589 modified.difference_update(self.filesremoved())
589 modified.difference_update(self.filesremoved())
590 return sorted(modified)
590 return sorted(modified)
591
591
592 def filesadded(self):
592 def filesadded(self):
593 filesadded = self._changeset.filesadded
593 filesadded = self._changeset.filesadded
594 compute_on_none = True
594 compute_on_none = True
595 if self._repo.filecopiesmode == b'changeset-sidedata':
595 if self._repo.filecopiesmode == b'changeset-sidedata':
596 compute_on_none = False
596 compute_on_none = False
597 else:
597 else:
598 source = self._repo.ui.config(b'experimental', b'copies.read-from')
598 source = self._repo.ui.config(b'experimental', b'copies.read-from')
599 if source == b'changeset-only':
599 if source == b'changeset-only':
600 compute_on_none = False
600 compute_on_none = False
601 elif source != b'compatibility':
601 elif source != b'compatibility':
602 # filelog mode, ignore any changelog content
602 # filelog mode, ignore any changelog content
603 filesadded = None
603 filesadded = None
604 if filesadded is None:
604 if filesadded is None:
605 if compute_on_none:
605 if compute_on_none:
606 filesadded = metadata.computechangesetfilesadded(self)
606 filesadded = metadata.computechangesetfilesadded(self)
607 else:
607 else:
608 filesadded = []
608 filesadded = []
609 return filesadded
609 return filesadded
610
610
611 def filesremoved(self):
611 def filesremoved(self):
612 filesremoved = self._changeset.filesremoved
612 filesremoved = self._changeset.filesremoved
613 compute_on_none = True
613 compute_on_none = True
614 if self._repo.filecopiesmode == b'changeset-sidedata':
614 if self._repo.filecopiesmode == b'changeset-sidedata':
615 compute_on_none = False
615 compute_on_none = False
616 else:
616 else:
617 source = self._repo.ui.config(b'experimental', b'copies.read-from')
617 source = self._repo.ui.config(b'experimental', b'copies.read-from')
618 if source == b'changeset-only':
618 if source == b'changeset-only':
619 compute_on_none = False
619 compute_on_none = False
620 elif source != b'compatibility':
620 elif source != b'compatibility':
621 # filelog mode, ignore any changelog content
621 # filelog mode, ignore any changelog content
622 filesremoved = None
622 filesremoved = None
623 if filesremoved is None:
623 if filesremoved is None:
624 if compute_on_none:
624 if compute_on_none:
625 filesremoved = metadata.computechangesetfilesremoved(self)
625 filesremoved = metadata.computechangesetfilesremoved(self)
626 else:
626 else:
627 filesremoved = []
627 filesremoved = []
628 return filesremoved
628 return filesremoved
629
629
630 @propertycache
630 @propertycache
631 def _copies(self):
631 def _copies(self):
632 p1copies = self._changeset.p1copies
632 p1copies = self._changeset.p1copies
633 p2copies = self._changeset.p2copies
633 p2copies = self._changeset.p2copies
634 compute_on_none = True
634 compute_on_none = True
635 if self._repo.filecopiesmode == b'changeset-sidedata':
635 if self._repo.filecopiesmode == b'changeset-sidedata':
636 compute_on_none = False
636 compute_on_none = False
637 else:
637 else:
638 source = self._repo.ui.config(b'experimental', b'copies.read-from')
638 source = self._repo.ui.config(b'experimental', b'copies.read-from')
639 # If config says to get copy metadata only from changeset, then
639 # If config says to get copy metadata only from changeset, then
640 # return that, defaulting to {} if there was no copy metadata. In
640 # return that, defaulting to {} if there was no copy metadata. In
641 # compatibility mode, we return copy data from the changeset if it
641 # compatibility mode, we return copy data from the changeset if it
642 # was recorded there, and otherwise we fall back to getting it from
642 # was recorded there, and otherwise we fall back to getting it from
643 # the filelogs (below).
643 # the filelogs (below).
644 #
644 #
645 # If we are in compatiblity mode and there is not data in the
645 # If we are in compatiblity mode and there is not data in the
646 # changeset), we get the copy metadata from the filelogs.
646 # changeset), we get the copy metadata from the filelogs.
647 #
647 #
648 # otherwise, when config said to read only from filelog, we get the
648 # otherwise, when config said to read only from filelog, we get the
649 # copy metadata from the filelogs.
649 # copy metadata from the filelogs.
650 if source == b'changeset-only':
650 if source == b'changeset-only':
651 compute_on_none = False
651 compute_on_none = False
652 elif source != b'compatibility':
652 elif source != b'compatibility':
653 # filelog mode, ignore any changelog content
653 # filelog mode, ignore any changelog content
654 p1copies = p2copies = None
654 p1copies = p2copies = None
655 if p1copies is None:
655 if p1copies is None:
656 if compute_on_none:
656 if compute_on_none:
657 p1copies, p2copies = super(changectx, self)._copies
657 p1copies, p2copies = super(changectx, self)._copies
658 else:
658 else:
659 if p1copies is None:
659 if p1copies is None:
660 p1copies = {}
660 p1copies = {}
661 if p2copies is None:
661 if p2copies is None:
662 p2copies = {}
662 p2copies = {}
663 return p1copies, p2copies
663 return p1copies, p2copies
664
664
665 def description(self):
665 def description(self):
666 return self._changeset.description
666 return self._changeset.description
667
667
668 def branch(self):
668 def branch(self):
669 return encoding.tolocal(self._changeset.extra.get(b"branch"))
669 return encoding.tolocal(self._changeset.extra.get(b"branch"))
670
670
671 def closesbranch(self):
671 def closesbranch(self):
672 return b'close' in self._changeset.extra
672 return b'close' in self._changeset.extra
673
673
674 def extra(self):
674 def extra(self):
675 """Return a dict of extra information."""
675 """Return a dict of extra information."""
676 return self._changeset.extra
676 return self._changeset.extra
677
677
678 def tags(self):
678 def tags(self):
679 """Return a list of byte tag names"""
679 """Return a list of byte tag names"""
680 return self._repo.nodetags(self._node)
680 return self._repo.nodetags(self._node)
681
681
682 def bookmarks(self):
682 def bookmarks(self):
683 """Return a list of byte bookmark names."""
683 """Return a list of byte bookmark names."""
684 return self._repo.nodebookmarks(self._node)
684 return self._repo.nodebookmarks(self._node)
685
685
686 def fast_rank(self):
686 def fast_rank(self):
687 repo = self._repo
687 repo = self._repo
688 if self._maybe_filtered:
688 if self._maybe_filtered:
689 cl = repo.changelog
689 cl = repo.changelog
690 else:
690 else:
691 cl = repo.unfiltered().changelog
691 cl = repo.unfiltered().changelog
692 return cl.fast_rank(self._rev)
692 return cl.fast_rank(self._rev)
693
693
694 def phase(self):
694 def phase(self):
695 return self._repo._phasecache.phase(self._repo, self._rev)
695 return self._repo._phasecache.phase(self._repo, self._rev)
696
696
697 def hidden(self):
697 def hidden(self):
698 return self._rev in repoview.filterrevs(self._repo, b'visible')
698 return self._rev in repoview.filterrevs(self._repo, b'visible')
699
699
700 def isinmemory(self):
700 def isinmemory(self):
701 return False
701 return False
702
702
703 def children(self):
703 def children(self):
704 """return list of changectx contexts for each child changeset.
704 """return list of changectx contexts for each child changeset.
705
705
706 This returns only the immediate child changesets. Use descendants() to
706 This returns only the immediate child changesets. Use descendants() to
707 recursively walk children.
707 recursively walk children.
708 """
708 """
709 c = self._repo.changelog.children(self._node)
709 c = self._repo.changelog.children(self._node)
710 return [self._repo[x] for x in c]
710 return [self._repo[x] for x in c]
711
711
712 def ancestors(self):
712 def ancestors(self):
713 for a in self._repo.changelog.ancestors([self._rev]):
713 for a in self._repo.changelog.ancestors([self._rev]):
714 yield self._repo[a]
714 yield self._repo[a]
715
715
716 def descendants(self):
716 def descendants(self):
717 """Recursively yield all children of the changeset.
717 """Recursively yield all children of the changeset.
718
718
719 For just the immediate children, use children()
719 For just the immediate children, use children()
720 """
720 """
721 for d in self._repo.changelog.descendants([self._rev]):
721 for d in self._repo.changelog.descendants([self._rev]):
722 yield self._repo[d]
722 yield self._repo[d]
723
723
724 def filectx(self, path, fileid=None, filelog=None):
724 def filectx(self, path, fileid=None, filelog=None):
725 """get a file context from this changeset"""
725 """get a file context from this changeset"""
726 if fileid is None:
726 if fileid is None:
727 fileid = self.filenode(path)
727 fileid = self.filenode(path)
728 return filectx(
728 return filectx(
729 self._repo, path, fileid=fileid, changectx=self, filelog=filelog
729 self._repo, path, fileid=fileid, changectx=self, filelog=filelog
730 )
730 )
731
731
732 def ancestor(self, c2, warn=False):
732 def ancestor(self, c2, warn=False):
733 """return the "best" ancestor context of self and c2
733 """return the "best" ancestor context of self and c2
734
734
735 If there are multiple candidates, it will show a message and check
735 If there are multiple candidates, it will show a message and check
736 merge.preferancestor configuration before falling back to the
736 merge.preferancestor configuration before falling back to the
737 revlog ancestor."""
737 revlog ancestor."""
738 # deal with workingctxs
738 # deal with workingctxs
739 n2 = c2._node
739 n2 = c2._node
740 if n2 is None:
740 if n2 is None:
741 n2 = c2._parents[0]._node
741 n2 = c2._parents[0]._node
742 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
742 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
743 if not cahs:
743 if not cahs:
744 anc = self._repo.nodeconstants.nullid
744 anc = self._repo.nodeconstants.nullid
745 elif len(cahs) == 1:
745 elif len(cahs) == 1:
746 anc = cahs[0]
746 anc = cahs[0]
747 else:
747 else:
748 # experimental config: merge.preferancestor
748 # experimental config: merge.preferancestor
749 for r in self._repo.ui.configlist(b'merge', b'preferancestor'):
749 for r in self._repo.ui.configlist(b'merge', b'preferancestor'):
750 try:
750 try:
751 ctx = scmutil.revsymbol(self._repo, r)
751 ctx = scmutil.revsymbol(self._repo, r)
752 except error.RepoLookupError:
752 except error.RepoLookupError:
753 continue
753 continue
754 anc = ctx.node()
754 anc = ctx.node()
755 if anc in cahs:
755 if anc in cahs:
756 break
756 break
757 else:
757 else:
758 anc = self._repo.changelog.ancestor(self._node, n2)
758 anc = self._repo.changelog.ancestor(self._node, n2)
759 if warn:
759 if warn:
760 self._repo.ui.status(
760 self._repo.ui.status(
761 (
761 (
762 _(b"note: using %s as ancestor of %s and %s\n")
762 _(b"note: using %s as ancestor of %s and %s\n")
763 % (short(anc), short(self._node), short(n2))
763 % (short(anc), short(self._node), short(n2))
764 )
764 )
765 + b''.join(
765 + b''.join(
766 _(
766 _(
767 b" alternatively, use --config "
767 b" alternatively, use --config "
768 b"merge.preferancestor=%s\n"
768 b"merge.preferancestor=%s\n"
769 )
769 )
770 % short(n)
770 % short(n)
771 for n in sorted(cahs)
771 for n in sorted(cahs)
772 if n != anc
772 if n != anc
773 )
773 )
774 )
774 )
775 return self._repo[anc]
775 return self._repo[anc]
776
776
777 def isancestorof(self, other):
777 def isancestorof(self, other):
778 """True if this changeset is an ancestor of other"""
778 """True if this changeset is an ancestor of other"""
779 return self._repo.changelog.isancestorrev(self._rev, other._rev)
779 return self._repo.changelog.isancestorrev(self._rev, other._rev)
780
780
781 def walk(self, match):
781 def walk(self, match):
782 '''Generates matching file names.'''
782 '''Generates matching file names.'''
783
783
784 # Wrap match.bad method to have message with nodeid
784 # Wrap match.bad method to have message with nodeid
785 def bad(fn, msg):
785 def bad(fn, msg):
786 # The manifest doesn't know about subrepos, so don't complain about
786 # The manifest doesn't know about subrepos, so don't complain about
787 # paths into valid subrepos.
787 # paths into valid subrepos.
788 if any(fn == s or fn.startswith(s + b'/') for s in self.substate):
788 if any(fn == s or fn.startswith(s + b'/') for s in self.substate):
789 return
789 return
790 match.bad(fn, _(b'no such file in rev %s') % self)
790 match.bad(fn, _(b'no such file in rev %s') % self)
791
791
792 m = matchmod.badmatch(self._repo.narrowmatch(match), bad)
792 m = matchmod.badmatch(self._repo.narrowmatch(match), bad)
793 return self._manifest.walk(m)
793 return self._manifest.walk(m)
794
794
795 def matches(self, match):
795 def matches(self, match):
796 return self.walk(match)
796 return self.walk(match)
797
797
798
798
799 class basefilectx:
799 class basefilectx:
800 """A filecontext object represents the common logic for its children:
800 """A filecontext object represents the common logic for its children:
801 filectx: read-only access to a filerevision that is already present
801 filectx: read-only access to a filerevision that is already present
802 in the repo,
802 in the repo,
803 workingfilectx: a filecontext that represents files from the working
803 workingfilectx: a filecontext that represents files from the working
804 directory,
804 directory,
805 memfilectx: a filecontext that represents files in-memory,
805 memfilectx: a filecontext that represents files in-memory,
806 """
806 """
807
807
808 @propertycache
808 @propertycache
809 def _filelog(self):
809 def _filelog(self):
810 return self._repo.file(self._path)
810 return self._repo.file(self._path)
811
811
812 @propertycache
812 @propertycache
813 def _changeid(self):
813 def _changeid(self):
814 if '_changectx' in self.__dict__:
814 if '_changectx' in self.__dict__:
815 return self._changectx.rev()
815 return self._changectx.rev()
816 elif '_descendantrev' in self.__dict__:
816 elif '_descendantrev' in self.__dict__:
817 # this file context was created from a revision with a known
817 # this file context was created from a revision with a known
818 # descendant, we can (lazily) correct for linkrev aliases
818 # descendant, we can (lazily) correct for linkrev aliases
819 return self._adjustlinkrev(self._descendantrev)
819 return self._adjustlinkrev(self._descendantrev)
820 else:
820 else:
821 return self._filelog.linkrev(self._filerev)
821 return self._filelog.linkrev(self._filerev)
822
822
823 @propertycache
823 @propertycache
824 def _filenode(self):
824 def _filenode(self):
825 if '_fileid' in self.__dict__:
825 if '_fileid' in self.__dict__:
826 return self._filelog.lookup(self._fileid)
826 return self._filelog.lookup(self._fileid)
827 else:
827 else:
828 return self._changectx.filenode(self._path)
828 return self._changectx.filenode(self._path)
829
829
830 @propertycache
830 @propertycache
831 def _filerev(self):
831 def _filerev(self):
832 return self._filelog.rev(self._filenode)
832 return self._filelog.rev(self._filenode)
833
833
834 @propertycache
834 @propertycache
835 def _repopath(self):
835 def _repopath(self):
836 return self._path
836 return self._path
837
837
838 def __nonzero__(self):
838 def __nonzero__(self):
839 try:
839 try:
840 self._filenode
840 self._filenode
841 return True
841 return True
842 except error.LookupError:
842 except error.LookupError:
843 # file is missing
843 # file is missing
844 return False
844 return False
845
845
846 __bool__ = __nonzero__
846 __bool__ = __nonzero__
847
847
848 def __bytes__(self):
848 def __bytes__(self):
849 try:
849 try:
850 return b"%s@%s" % (self.path(), self._changectx)
850 return b"%s@%s" % (self.path(), self._changectx)
851 except error.LookupError:
851 except error.LookupError:
852 return b"%s@???" % self.path()
852 return b"%s@???" % self.path()
853
853
854 __str__ = encoding.strmethod(__bytes__)
854 __str__ = encoding.strmethod(__bytes__)
855
855
856 def __repr__(self):
856 def __repr__(self):
857 return "<%s %s>" % (type(self).__name__, str(self))
857 return "<%s %s>" % (type(self).__name__, str(self))
858
858
859 def __hash__(self):
859 def __hash__(self):
860 try:
860 try:
861 return hash((self._path, self._filenode))
861 return hash((self._path, self._filenode))
862 except AttributeError:
862 except AttributeError:
863 return id(self)
863 return id(self)
864
864
865 def __eq__(self, other):
865 def __eq__(self, other):
866 try:
866 try:
867 return (
867 return (
868 type(self) == type(other)
868 type(self) == type(other)
869 and self._path == other._path
869 and self._path == other._path
870 and self._filenode == other._filenode
870 and self._filenode == other._filenode
871 )
871 )
872 except AttributeError:
872 except AttributeError:
873 return False
873 return False
874
874
875 def __ne__(self, other):
875 def __ne__(self, other):
876 return not (self == other)
876 return not (self == other)
877
877
878 def filerev(self):
878 def filerev(self):
879 return self._filerev
879 return self._filerev
880
880
881 def filenode(self):
881 def filenode(self):
882 return self._filenode
882 return self._filenode
883
883
884 @propertycache
884 @propertycache
885 def _flags(self):
885 def _flags(self):
886 return self._changectx.flags(self._path)
886 return self._changectx.flags(self._path)
887
887
888 def flags(self):
888 def flags(self):
889 return self._flags
889 return self._flags
890
890
891 def filelog(self):
891 def filelog(self):
892 return self._filelog
892 return self._filelog
893
893
894 def rev(self):
894 def rev(self):
895 return self._changeid
895 return self._changeid
896
896
897 def linkrev(self):
897 def linkrev(self):
898 return self._filelog.linkrev(self._filerev)
898 return self._filelog.linkrev(self._filerev)
899
899
900 def node(self):
900 def node(self):
901 return self._changectx.node()
901 return self._changectx.node()
902
902
903 def hex(self):
903 def hex(self):
904 return self._changectx.hex()
904 return self._changectx.hex()
905
905
906 def user(self):
906 def user(self):
907 return self._changectx.user()
907 return self._changectx.user()
908
908
909 def date(self):
909 def date(self):
910 return self._changectx.date()
910 return self._changectx.date()
911
911
912 def files(self):
912 def files(self):
913 return self._changectx.files()
913 return self._changectx.files()
914
914
915 def description(self):
915 def description(self):
916 return self._changectx.description()
916 return self._changectx.description()
917
917
918 def branch(self):
918 def branch(self):
919 return self._changectx.branch()
919 return self._changectx.branch()
920
920
921 def extra(self):
921 def extra(self):
922 return self._changectx.extra()
922 return self._changectx.extra()
923
923
924 def phase(self):
924 def phase(self):
925 return self._changectx.phase()
925 return self._changectx.phase()
926
926
927 def phasestr(self):
927 def phasestr(self):
928 return self._changectx.phasestr()
928 return self._changectx.phasestr()
929
929
930 def obsolete(self):
930 def obsolete(self):
931 return self._changectx.obsolete()
931 return self._changectx.obsolete()
932
932
933 def instabilities(self):
933 def instabilities(self):
934 return self._changectx.instabilities()
934 return self._changectx.instabilities()
935
935
936 def manifest(self):
936 def manifest(self):
937 return self._changectx.manifest()
937 return self._changectx.manifest()
938
938
939 def changectx(self):
939 def changectx(self):
940 return self._changectx
940 return self._changectx
941
941
942 def renamed(self):
942 def renamed(self):
943 return self._copied
943 return self._copied
944
944
945 def copysource(self):
945 def copysource(self):
946 return self._copied and self._copied[0]
946 return self._copied and self._copied[0]
947
947
948 def repo(self):
948 def repo(self):
949 return self._repo
949 return self._repo
950
950
951 def size(self):
951 def size(self):
952 return len(self.data())
952 return len(self.data())
953
953
954 def path(self):
954 def path(self):
955 return self._path
955 return self._path
956
956
957 def isbinary(self):
957 def isbinary(self):
958 try:
958 try:
959 return stringutil.binary(self.data())
959 return stringutil.binary(self.data())
960 except IOError:
960 except IOError:
961 return False
961 return False
962
962
963 def isexec(self):
963 def isexec(self):
964 return b'x' in self.flags()
964 return b'x' in self.flags()
965
965
966 def islink(self):
966 def islink(self):
967 return b'l' in self.flags()
967 return b'l' in self.flags()
968
968
969 def isabsent(self):
969 def isabsent(self):
970 """whether this filectx represents a file not in self._changectx
970 """whether this filectx represents a file not in self._changectx
971
971
972 This is mainly for merge code to detect change/delete conflicts. This is
972 This is mainly for merge code to detect change/delete conflicts. This is
973 expected to be True for all subclasses of basectx."""
973 expected to be True for all subclasses of basectx."""
974 return False
974 return False
975
975
976 _customcmp = False
976 _customcmp = False
977
977
978 def cmp(self, fctx):
978 def cmp(self, fctx):
979 """compare with other file context
979 """compare with other file context
980
980
981 returns True if different than fctx.
981 returns True if different than fctx.
982 """
982 """
983 if fctx._customcmp:
983 if fctx._customcmp:
984 return fctx.cmp(self)
984 return fctx.cmp(self)
985
985
986 if self._filenode is None:
986 if self._filenode is None:
987 raise error.ProgrammingError(
987 raise error.ProgrammingError(
988 b'filectx.cmp() must be reimplemented if not backed by revlog'
988 b'filectx.cmp() must be reimplemented if not backed by revlog'
989 )
989 )
990
990
991 if fctx._filenode is None:
991 if fctx._filenode is None:
992 if self._repo._encodefilterpats:
992 if self._repo._encodefilterpats:
993 # can't rely on size() because wdir content may be decoded
993 # can't rely on size() because wdir content may be decoded
994 return self._filelog.cmp(self._filenode, fctx.data())
994 return self._filelog.cmp(self._filenode, fctx.data())
995 # filelog.size() has two special cases:
996 # - censored metadata
997 # - copy/rename tracking
998 # The first is detected by peaking into the delta,
999 # the second is detected by abusing parent order
1000 # in the revlog index as flag bit. This leaves files using
1001 # the dummy encoding and non-standard meta attributes.
1002 # The following check is a special case for the empty
1003 # metadata block used if the raw file content starts with '\1\n'.
1004 # Cases of arbitrary metadata flags are currently mishandled.
995 if self.size() - 4 == fctx.size():
1005 if self.size() - 4 == fctx.size():
996 # size() can match:
1006 # size() can match:
997 # if file data starts with '\1\n', empty metadata block is
1007 # if file data starts with '\1\n', empty metadata block is
998 # prepended, which adds 4 bytes to filelog.size().
1008 # prepended, which adds 4 bytes to filelog.size().
999 return self._filelog.cmp(self._filenode, fctx.data())
1009 return self._filelog.cmp(self._filenode, fctx.data())
1000 if self.size() == fctx.size() or self.flags() == b'l':
1010 if self.size() == fctx.size() or self.flags() == b'l':
1001 # size() matches: need to compare content
1011 # size() matches: need to compare content
1002 # issue6456: Always compare symlinks because size can represent
1012 # issue6456: Always compare symlinks because size can represent
1003 # encrypted string for EXT-4 encryption(fscrypt).
1013 # encrypted string for EXT-4 encryption(fscrypt).
1004 return self._filelog.cmp(self._filenode, fctx.data())
1014 return self._filelog.cmp(self._filenode, fctx.data())
1005
1015
1006 # size() differs
1016 # size() differs
1007 return True
1017 return True
1008
1018
1009 def _adjustlinkrev(self, srcrev, inclusive=False, stoprev=None):
1019 def _adjustlinkrev(self, srcrev, inclusive=False, stoprev=None):
1010 """return the first ancestor of <srcrev> introducing <fnode>
1020 """return the first ancestor of <srcrev> introducing <fnode>
1011
1021
1012 If the linkrev of the file revision does not point to an ancestor of
1022 If the linkrev of the file revision does not point to an ancestor of
1013 srcrev, we'll walk down the ancestors until we find one introducing
1023 srcrev, we'll walk down the ancestors until we find one introducing
1014 this file revision.
1024 this file revision.
1015
1025
1016 :srcrev: the changeset revision we search ancestors from
1026 :srcrev: the changeset revision we search ancestors from
1017 :inclusive: if true, the src revision will also be checked
1027 :inclusive: if true, the src revision will also be checked
1018 :stoprev: an optional revision to stop the walk at. If no introduction
1028 :stoprev: an optional revision to stop the walk at. If no introduction
1019 of this file content could be found before this floor
1029 of this file content could be found before this floor
1020 revision, the function will returns "None" and stops its
1030 revision, the function will returns "None" and stops its
1021 iteration.
1031 iteration.
1022 """
1032 """
1023 repo = self._repo
1033 repo = self._repo
1024 cl = repo.unfiltered().changelog
1034 cl = repo.unfiltered().changelog
1025 mfl = repo.manifestlog
1035 mfl = repo.manifestlog
1026 # fetch the linkrev
1036 # fetch the linkrev
1027 lkr = self.linkrev()
1037 lkr = self.linkrev()
1028 if srcrev == lkr:
1038 if srcrev == lkr:
1029 return lkr
1039 return lkr
1030 # hack to reuse ancestor computation when searching for renames
1040 # hack to reuse ancestor computation when searching for renames
1031 memberanc = getattr(self, '_ancestrycontext', None)
1041 memberanc = getattr(self, '_ancestrycontext', None)
1032 iteranc = None
1042 iteranc = None
1033 if srcrev is None:
1043 if srcrev is None:
1034 # wctx case, used by workingfilectx during mergecopy
1044 # wctx case, used by workingfilectx during mergecopy
1035 revs = [p.rev() for p in self._repo[None].parents()]
1045 revs = [p.rev() for p in self._repo[None].parents()]
1036 inclusive = True # we skipped the real (revless) source
1046 inclusive = True # we skipped the real (revless) source
1037 else:
1047 else:
1038 revs = [srcrev]
1048 revs = [srcrev]
1039 if memberanc is None:
1049 if memberanc is None:
1040 memberanc = iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
1050 memberanc = iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
1041 # check if this linkrev is an ancestor of srcrev
1051 # check if this linkrev is an ancestor of srcrev
1042 if lkr not in memberanc:
1052 if lkr not in memberanc:
1043 if iteranc is None:
1053 if iteranc is None:
1044 iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
1054 iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
1045 fnode = self._filenode
1055 fnode = self._filenode
1046 path = self._path
1056 path = self._path
1047 for a in iteranc:
1057 for a in iteranc:
1048 if stoprev is not None and a < stoprev:
1058 if stoprev is not None and a < stoprev:
1049 return None
1059 return None
1050 ac = cl.read(a) # get changeset data (we avoid object creation)
1060 ac = cl.read(a) # get changeset data (we avoid object creation)
1051 if path in ac[3]: # checking the 'files' field.
1061 if path in ac[3]: # checking the 'files' field.
1052 # The file has been touched, check if the content is
1062 # The file has been touched, check if the content is
1053 # similar to the one we search for.
1063 # similar to the one we search for.
1054 if fnode == mfl[ac[0]].readfast().get(path):
1064 if fnode == mfl[ac[0]].readfast().get(path):
1055 return a
1065 return a
1056 # In theory, we should never get out of that loop without a result.
1066 # In theory, we should never get out of that loop without a result.
1057 # But if manifest uses a buggy file revision (not children of the
1067 # But if manifest uses a buggy file revision (not children of the
1058 # one it replaces) we could. Such a buggy situation will likely
1068 # one it replaces) we could. Such a buggy situation will likely
1059 # result is crash somewhere else at to some point.
1069 # result is crash somewhere else at to some point.
1060 return lkr
1070 return lkr
1061
1071
1062 def isintroducedafter(self, changelogrev):
1072 def isintroducedafter(self, changelogrev):
1063 """True if a filectx has been introduced after a given floor revision"""
1073 """True if a filectx has been introduced after a given floor revision"""
1064 if self.linkrev() >= changelogrev:
1074 if self.linkrev() >= changelogrev:
1065 return True
1075 return True
1066 introrev = self._introrev(stoprev=changelogrev)
1076 introrev = self._introrev(stoprev=changelogrev)
1067 if introrev is None:
1077 if introrev is None:
1068 return False
1078 return False
1069 return introrev >= changelogrev
1079 return introrev >= changelogrev
1070
1080
1071 def introrev(self):
1081 def introrev(self):
1072 """return the rev of the changeset which introduced this file revision
1082 """return the rev of the changeset which introduced this file revision
1073
1083
1074 This method is different from linkrev because it take into account the
1084 This method is different from linkrev because it take into account the
1075 changeset the filectx was created from. It ensures the returned
1085 changeset the filectx was created from. It ensures the returned
1076 revision is one of its ancestors. This prevents bugs from
1086 revision is one of its ancestors. This prevents bugs from
1077 'linkrev-shadowing' when a file revision is used by multiple
1087 'linkrev-shadowing' when a file revision is used by multiple
1078 changesets.
1088 changesets.
1079 """
1089 """
1080 return self._introrev()
1090 return self._introrev()
1081
1091
1082 def _introrev(self, stoprev=None):
1092 def _introrev(self, stoprev=None):
1083 """
1093 """
1084 Same as `introrev` but, with an extra argument to limit changelog
1094 Same as `introrev` but, with an extra argument to limit changelog
1085 iteration range in some internal usecase.
1095 iteration range in some internal usecase.
1086
1096
1087 If `stoprev` is set, the `introrev` will not be searched past that
1097 If `stoprev` is set, the `introrev` will not be searched past that
1088 `stoprev` revision and "None" might be returned. This is useful to
1098 `stoprev` revision and "None" might be returned. This is useful to
1089 limit the iteration range.
1099 limit the iteration range.
1090 """
1100 """
1091 toprev = None
1101 toprev = None
1092 attrs = vars(self)
1102 attrs = vars(self)
1093 if '_changeid' in attrs:
1103 if '_changeid' in attrs:
1094 # We have a cached value already
1104 # We have a cached value already
1095 toprev = self._changeid
1105 toprev = self._changeid
1096 elif '_changectx' in attrs:
1106 elif '_changectx' in attrs:
1097 # We know which changelog entry we are coming from
1107 # We know which changelog entry we are coming from
1098 toprev = self._changectx.rev()
1108 toprev = self._changectx.rev()
1099
1109
1100 if toprev is not None:
1110 if toprev is not None:
1101 return self._adjustlinkrev(toprev, inclusive=True, stoprev=stoprev)
1111 return self._adjustlinkrev(toprev, inclusive=True, stoprev=stoprev)
1102 elif '_descendantrev' in attrs:
1112 elif '_descendantrev' in attrs:
1103 introrev = self._adjustlinkrev(self._descendantrev, stoprev=stoprev)
1113 introrev = self._adjustlinkrev(self._descendantrev, stoprev=stoprev)
1104 # be nice and cache the result of the computation
1114 # be nice and cache the result of the computation
1105 if introrev is not None:
1115 if introrev is not None:
1106 self._changeid = introrev
1116 self._changeid = introrev
1107 return introrev
1117 return introrev
1108 else:
1118 else:
1109 return self.linkrev()
1119 return self.linkrev()
1110
1120
1111 def introfilectx(self):
1121 def introfilectx(self):
1112 """Return filectx having identical contents, but pointing to the
1122 """Return filectx having identical contents, but pointing to the
1113 changeset revision where this filectx was introduced"""
1123 changeset revision where this filectx was introduced"""
1114 introrev = self.introrev()
1124 introrev = self.introrev()
1115 if self.rev() == introrev:
1125 if self.rev() == introrev:
1116 return self
1126 return self
1117 return self.filectx(self.filenode(), changeid=introrev)
1127 return self.filectx(self.filenode(), changeid=introrev)
1118
1128
1119 def _parentfilectx(self, path, fileid, filelog):
1129 def _parentfilectx(self, path, fileid, filelog):
1120 """create parent filectx keeping ancestry info for _adjustlinkrev()"""
1130 """create parent filectx keeping ancestry info for _adjustlinkrev()"""
1121 fctx = filectx(self._repo, path, fileid=fileid, filelog=filelog)
1131 fctx = filectx(self._repo, path, fileid=fileid, filelog=filelog)
1122 if '_changeid' in vars(self) or '_changectx' in vars(self):
1132 if '_changeid' in vars(self) or '_changectx' in vars(self):
1123 # If self is associated with a changeset (probably explicitly
1133 # If self is associated with a changeset (probably explicitly
1124 # fed), ensure the created filectx is associated with a
1134 # fed), ensure the created filectx is associated with a
1125 # changeset that is an ancestor of self.changectx.
1135 # changeset that is an ancestor of self.changectx.
1126 # This lets us later use _adjustlinkrev to get a correct link.
1136 # This lets us later use _adjustlinkrev to get a correct link.
1127 fctx._descendantrev = self.rev()
1137 fctx._descendantrev = self.rev()
1128 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
1138 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
1129 elif '_descendantrev' in vars(self):
1139 elif '_descendantrev' in vars(self):
1130 # Otherwise propagate _descendantrev if we have one associated.
1140 # Otherwise propagate _descendantrev if we have one associated.
1131 fctx._descendantrev = self._descendantrev
1141 fctx._descendantrev = self._descendantrev
1132 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
1142 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
1133 return fctx
1143 return fctx
1134
1144
1135 def parents(self):
1145 def parents(self):
1136 _path = self._path
1146 _path = self._path
1137 fl = self._filelog
1147 fl = self._filelog
1138 parents = self._filelog.parents(self._filenode)
1148 parents = self._filelog.parents(self._filenode)
1139 pl = [
1149 pl = [
1140 (_path, node, fl)
1150 (_path, node, fl)
1141 for node in parents
1151 for node in parents
1142 if node != self._repo.nodeconstants.nullid
1152 if node != self._repo.nodeconstants.nullid
1143 ]
1153 ]
1144
1154
1145 r = fl.renamed(self._filenode)
1155 r = fl.renamed(self._filenode)
1146 if r:
1156 if r:
1147 # - In the simple rename case, both parent are nullid, pl is empty.
1157 # - In the simple rename case, both parent are nullid, pl is empty.
1148 # - In case of merge, only one of the parent is null id and should
1158 # - In case of merge, only one of the parent is null id and should
1149 # be replaced with the rename information. This parent is -always-
1159 # be replaced with the rename information. This parent is -always-
1150 # the first one.
1160 # the first one.
1151 #
1161 #
1152 # As null id have always been filtered out in the previous list
1162 # As null id have always been filtered out in the previous list
1153 # comprehension, inserting to 0 will always result in "replacing
1163 # comprehension, inserting to 0 will always result in "replacing
1154 # first nullid parent with rename information.
1164 # first nullid parent with rename information.
1155 pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
1165 pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
1156
1166
1157 return [self._parentfilectx(path, fnode, l) for path, fnode, l in pl]
1167 return [self._parentfilectx(path, fnode, l) for path, fnode, l in pl]
1158
1168
1159 def p1(self):
1169 def p1(self):
1160 return self.parents()[0]
1170 return self.parents()[0]
1161
1171
1162 def p2(self):
1172 def p2(self):
1163 p = self.parents()
1173 p = self.parents()
1164 if len(p) == 2:
1174 if len(p) == 2:
1165 return p[1]
1175 return p[1]
1166 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
1176 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
1167
1177
1168 def annotate(self, follow=False, skiprevs=None, diffopts=None):
1178 def annotate(self, follow=False, skiprevs=None, diffopts=None):
1169 """Returns a list of annotateline objects for each line in the file
1179 """Returns a list of annotateline objects for each line in the file
1170
1180
1171 - line.fctx is the filectx of the node where that line was last changed
1181 - line.fctx is the filectx of the node where that line was last changed
1172 - line.lineno is the line number at the first appearance in the managed
1182 - line.lineno is the line number at the first appearance in the managed
1173 file
1183 file
1174 - line.text is the data on that line (including newline character)
1184 - line.text is the data on that line (including newline character)
1175 """
1185 """
1176 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
1186 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
1177
1187
1178 def parents(f):
1188 def parents(f):
1179 # Cut _descendantrev here to mitigate the penalty of lazy linkrev
1189 # Cut _descendantrev here to mitigate the penalty of lazy linkrev
1180 # adjustment. Otherwise, p._adjustlinkrev() would walk changelog
1190 # adjustment. Otherwise, p._adjustlinkrev() would walk changelog
1181 # from the topmost introrev (= srcrev) down to p.linkrev() if it
1191 # from the topmost introrev (= srcrev) down to p.linkrev() if it
1182 # isn't an ancestor of the srcrev.
1192 # isn't an ancestor of the srcrev.
1183 f._changeid
1193 f._changeid
1184 pl = f.parents()
1194 pl = f.parents()
1185
1195
1186 # Don't return renamed parents if we aren't following.
1196 # Don't return renamed parents if we aren't following.
1187 if not follow:
1197 if not follow:
1188 pl = [p for p in pl if p.path() == f.path()]
1198 pl = [p for p in pl if p.path() == f.path()]
1189
1199
1190 # renamed filectx won't have a filelog yet, so set it
1200 # renamed filectx won't have a filelog yet, so set it
1191 # from the cache to save time
1201 # from the cache to save time
1192 for p in pl:
1202 for p in pl:
1193 if not '_filelog' in p.__dict__:
1203 if not '_filelog' in p.__dict__:
1194 p._filelog = getlog(p.path())
1204 p._filelog = getlog(p.path())
1195
1205
1196 return pl
1206 return pl
1197
1207
1198 # use linkrev to find the first changeset where self appeared
1208 # use linkrev to find the first changeset where self appeared
1199 base = self.introfilectx()
1209 base = self.introfilectx()
1200 if getattr(base, '_ancestrycontext', None) is None:
1210 if getattr(base, '_ancestrycontext', None) is None:
1201 # it is safe to use an unfiltered repository here because we are
1211 # it is safe to use an unfiltered repository here because we are
1202 # walking ancestors only.
1212 # walking ancestors only.
1203 cl = self._repo.unfiltered().changelog
1213 cl = self._repo.unfiltered().changelog
1204 if base.rev() is None:
1214 if base.rev() is None:
1205 # wctx is not inclusive, but works because _ancestrycontext
1215 # wctx is not inclusive, but works because _ancestrycontext
1206 # is used to test filelog revisions
1216 # is used to test filelog revisions
1207 ac = cl.ancestors(
1217 ac = cl.ancestors(
1208 [p.rev() for p in base.parents()], inclusive=True
1218 [p.rev() for p in base.parents()], inclusive=True
1209 )
1219 )
1210 else:
1220 else:
1211 ac = cl.ancestors([base.rev()], inclusive=True)
1221 ac = cl.ancestors([base.rev()], inclusive=True)
1212 base._ancestrycontext = ac
1222 base._ancestrycontext = ac
1213
1223
1214 return dagop.annotate(
1224 return dagop.annotate(
1215 base, parents, skiprevs=skiprevs, diffopts=diffopts
1225 base, parents, skiprevs=skiprevs, diffopts=diffopts
1216 )
1226 )
1217
1227
1218 def ancestors(self, followfirst=False):
1228 def ancestors(self, followfirst=False):
1219 visit = {}
1229 visit = {}
1220 c = self
1230 c = self
1221 if followfirst:
1231 if followfirst:
1222 cut = 1
1232 cut = 1
1223 else:
1233 else:
1224 cut = None
1234 cut = None
1225
1235
1226 while True:
1236 while True:
1227 for parent in c.parents()[:cut]:
1237 for parent in c.parents()[:cut]:
1228 visit[(parent.linkrev(), parent.filenode())] = parent
1238 visit[(parent.linkrev(), parent.filenode())] = parent
1229 if not visit:
1239 if not visit:
1230 break
1240 break
1231 c = visit.pop(max(visit))
1241 c = visit.pop(max(visit))
1232 yield c
1242 yield c
1233
1243
1234 def decodeddata(self):
1244 def decodeddata(self):
1235 """Returns `data()` after running repository decoding filters.
1245 """Returns `data()` after running repository decoding filters.
1236
1246
1237 This is often equivalent to how the data would be expressed on disk.
1247 This is often equivalent to how the data would be expressed on disk.
1238 """
1248 """
1239 return self._repo.wwritedata(self.path(), self.data())
1249 return self._repo.wwritedata(self.path(), self.data())
1240
1250
1241
1251
1242 class filectx(basefilectx):
1252 class filectx(basefilectx):
1243 """A filecontext object makes access to data related to a particular
1253 """A filecontext object makes access to data related to a particular
1244 filerevision convenient."""
1254 filerevision convenient."""
1245
1255
1246 def __init__(
1256 def __init__(
1247 self,
1257 self,
1248 repo,
1258 repo,
1249 path,
1259 path,
1250 changeid=None,
1260 changeid=None,
1251 fileid=None,
1261 fileid=None,
1252 filelog=None,
1262 filelog=None,
1253 changectx=None,
1263 changectx=None,
1254 ):
1264 ):
1255 """changeid must be a revision number, if specified.
1265 """changeid must be a revision number, if specified.
1256 fileid can be a file revision or node."""
1266 fileid can be a file revision or node."""
1257 self._repo = repo
1267 self._repo = repo
1258 self._path = path
1268 self._path = path
1259
1269
1260 assert (
1270 assert (
1261 changeid is not None or fileid is not None or changectx is not None
1271 changeid is not None or fileid is not None or changectx is not None
1262 ), b"bad args: changeid=%r, fileid=%r, changectx=%r" % (
1272 ), b"bad args: changeid=%r, fileid=%r, changectx=%r" % (
1263 changeid,
1273 changeid,
1264 fileid,
1274 fileid,
1265 changectx,
1275 changectx,
1266 )
1276 )
1267
1277
1268 if filelog is not None:
1278 if filelog is not None:
1269 self._filelog = filelog
1279 self._filelog = filelog
1270
1280
1271 if changeid is not None:
1281 if changeid is not None:
1272 self._changeid = changeid
1282 self._changeid = changeid
1273 if changectx is not None:
1283 if changectx is not None:
1274 self._changectx = changectx
1284 self._changectx = changectx
1275 if fileid is not None:
1285 if fileid is not None:
1276 self._fileid = fileid
1286 self._fileid = fileid
1277
1287
1278 @propertycache
1288 @propertycache
1279 def _changectx(self):
1289 def _changectx(self):
1280 try:
1290 try:
1281 return self._repo[self._changeid]
1291 return self._repo[self._changeid]
1282 except error.FilteredRepoLookupError:
1292 except error.FilteredRepoLookupError:
1283 # Linkrev may point to any revision in the repository. When the
1293 # Linkrev may point to any revision in the repository. When the
1284 # repository is filtered this may lead to `filectx` trying to build
1294 # repository is filtered this may lead to `filectx` trying to build
1285 # `changectx` for filtered revision. In such case we fallback to
1295 # `changectx` for filtered revision. In such case we fallback to
1286 # creating `changectx` on the unfiltered version of the reposition.
1296 # creating `changectx` on the unfiltered version of the reposition.
1287 # This fallback should not be an issue because `changectx` from
1297 # This fallback should not be an issue because `changectx` from
1288 # `filectx` are not used in complex operations that care about
1298 # `filectx` are not used in complex operations that care about
1289 # filtering.
1299 # filtering.
1290 #
1300 #
1291 # This fallback is a cheap and dirty fix that prevent several
1301 # This fallback is a cheap and dirty fix that prevent several
1292 # crashes. It does not ensure the behavior is correct. However the
1302 # crashes. It does not ensure the behavior is correct. However the
1293 # behavior was not correct before filtering either and "incorrect
1303 # behavior was not correct before filtering either and "incorrect
1294 # behavior" is seen as better as "crash"
1304 # behavior" is seen as better as "crash"
1295 #
1305 #
1296 # Linkrevs have several serious troubles with filtering that are
1306 # Linkrevs have several serious troubles with filtering that are
1297 # complicated to solve. Proper handling of the issue here should be
1307 # complicated to solve. Proper handling of the issue here should be
1298 # considered when solving linkrev issue are on the table.
1308 # considered when solving linkrev issue are on the table.
1299 return self._repo.unfiltered()[self._changeid]
1309 return self._repo.unfiltered()[self._changeid]
1300
1310
1301 def filectx(self, fileid, changeid=None):
1311 def filectx(self, fileid, changeid=None):
1302 """opens an arbitrary revision of the file without
1312 """opens an arbitrary revision of the file without
1303 opening a new filelog"""
1313 opening a new filelog"""
1304 return filectx(
1314 return filectx(
1305 self._repo,
1315 self._repo,
1306 self._path,
1316 self._path,
1307 fileid=fileid,
1317 fileid=fileid,
1308 filelog=self._filelog,
1318 filelog=self._filelog,
1309 changeid=changeid,
1319 changeid=changeid,
1310 )
1320 )
1311
1321
1312 def rawdata(self):
1322 def rawdata(self):
1313 return self._filelog.rawdata(self._filenode)
1323 return self._filelog.rawdata(self._filenode)
1314
1324
1315 def rawflags(self):
1325 def rawflags(self):
1316 """low-level revlog flags"""
1326 """low-level revlog flags"""
1317 return self._filelog.flags(self._filerev)
1327 return self._filelog.flags(self._filerev)
1318
1328
1319 def data(self):
1329 def data(self):
1320 try:
1330 try:
1321 return self._filelog.read(self._filenode)
1331 return self._filelog.read(self._filenode)
1322 except error.CensoredNodeError:
1332 except error.CensoredNodeError:
1323 if self._repo.ui.config(b"censor", b"policy") == b"ignore":
1333 if self._repo.ui.config(b"censor", b"policy") == b"ignore":
1324 return b""
1334 return b""
1325 raise error.Abort(
1335 raise error.Abort(
1326 _(b"censored node: %s") % short(self._filenode),
1336 _(b"censored node: %s") % short(self._filenode),
1327 hint=_(b"set censor.policy to ignore errors"),
1337 hint=_(b"set censor.policy to ignore errors"),
1328 )
1338 )
1329
1339
1330 def size(self):
1340 def size(self):
1331 return self._filelog.size(self._filerev)
1341 return self._filelog.size(self._filerev)
1332
1342
1333 @propertycache
1343 @propertycache
1334 def _copied(self):
1344 def _copied(self):
1335 """check if file was actually renamed in this changeset revision
1345 """check if file was actually renamed in this changeset revision
1336
1346
1337 If rename logged in file revision, we report copy for changeset only
1347 If rename logged in file revision, we report copy for changeset only
1338 if file revisions linkrev points back to the changeset in question
1348 if file revisions linkrev points back to the changeset in question
1339 or both changeset parents contain different file revisions.
1349 or both changeset parents contain different file revisions.
1340 """
1350 """
1341
1351
1342 renamed = self._filelog.renamed(self._filenode)
1352 renamed = self._filelog.renamed(self._filenode)
1343 if not renamed:
1353 if not renamed:
1344 return None
1354 return None
1345
1355
1346 if self.rev() == self.linkrev():
1356 if self.rev() == self.linkrev():
1347 return renamed
1357 return renamed
1348
1358
1349 name = self.path()
1359 name = self.path()
1350 fnode = self._filenode
1360 fnode = self._filenode
1351 for p in self._changectx.parents():
1361 for p in self._changectx.parents():
1352 try:
1362 try:
1353 if fnode == p.filenode(name):
1363 if fnode == p.filenode(name):
1354 return None
1364 return None
1355 except error.LookupError:
1365 except error.LookupError:
1356 pass
1366 pass
1357 return renamed
1367 return renamed
1358
1368
1359 def children(self):
1369 def children(self):
1360 # hard for renames
1370 # hard for renames
1361 c = self._filelog.children(self._filenode)
1371 c = self._filelog.children(self._filenode)
1362 return [
1372 return [
1363 filectx(self._repo, self._path, fileid=x, filelog=self._filelog)
1373 filectx(self._repo, self._path, fileid=x, filelog=self._filelog)
1364 for x in c
1374 for x in c
1365 ]
1375 ]
1366
1376
1367
1377
1368 class committablectx(basectx):
1378 class committablectx(basectx):
1369 """A committablectx object provides common functionality for a context that
1379 """A committablectx object provides common functionality for a context that
1370 wants the ability to commit, e.g. workingctx or memctx."""
1380 wants the ability to commit, e.g. workingctx or memctx."""
1371
1381
1372 def __init__(
1382 def __init__(
1373 self,
1383 self,
1374 repo,
1384 repo,
1375 text=b"",
1385 text=b"",
1376 user=None,
1386 user=None,
1377 date=None,
1387 date=None,
1378 extra=None,
1388 extra=None,
1379 changes=None,
1389 changes=None,
1380 branch=None,
1390 branch=None,
1381 ):
1391 ):
1382 super(committablectx, self).__init__(repo)
1392 super(committablectx, self).__init__(repo)
1383 self._rev = None
1393 self._rev = None
1384 self._node = None
1394 self._node = None
1385 self._text = text
1395 self._text = text
1386 if date:
1396 if date:
1387 self._date = dateutil.parsedate(date)
1397 self._date = dateutil.parsedate(date)
1388 if user:
1398 if user:
1389 self._user = user
1399 self._user = user
1390 if changes:
1400 if changes:
1391 self._status = changes
1401 self._status = changes
1392
1402
1393 self._extra = {}
1403 self._extra = {}
1394 if extra:
1404 if extra:
1395 self._extra = extra.copy()
1405 self._extra = extra.copy()
1396 if branch is not None:
1406 if branch is not None:
1397 self._extra[b'branch'] = encoding.fromlocal(branch)
1407 self._extra[b'branch'] = encoding.fromlocal(branch)
1398 if not self._extra.get(b'branch'):
1408 if not self._extra.get(b'branch'):
1399 self._extra[b'branch'] = b'default'
1409 self._extra[b'branch'] = b'default'
1400
1410
1401 def __bytes__(self):
1411 def __bytes__(self):
1402 return bytes(self._parents[0]) + b"+"
1412 return bytes(self._parents[0]) + b"+"
1403
1413
1404 def hex(self):
1414 def hex(self):
1405 self._repo.nodeconstants.wdirhex
1415 self._repo.nodeconstants.wdirhex
1406
1416
1407 __str__ = encoding.strmethod(__bytes__)
1417 __str__ = encoding.strmethod(__bytes__)
1408
1418
1409 def __nonzero__(self):
1419 def __nonzero__(self):
1410 return True
1420 return True
1411
1421
1412 __bool__ = __nonzero__
1422 __bool__ = __nonzero__
1413
1423
1414 @propertycache
1424 @propertycache
1415 def _status(self):
1425 def _status(self):
1416 return self._repo.status()
1426 return self._repo.status()
1417
1427
1418 @propertycache
1428 @propertycache
1419 def _user(self):
1429 def _user(self):
1420 return self._repo.ui.username()
1430 return self._repo.ui.username()
1421
1431
1422 @propertycache
1432 @propertycache
1423 def _date(self):
1433 def _date(self):
1424 ui = self._repo.ui
1434 ui = self._repo.ui
1425 date = ui.configdate(b'devel', b'default-date')
1435 date = ui.configdate(b'devel', b'default-date')
1426 if date is None:
1436 if date is None:
1427 date = dateutil.makedate()
1437 date = dateutil.makedate()
1428 return date
1438 return date
1429
1439
1430 def subrev(self, subpath):
1440 def subrev(self, subpath):
1431 return None
1441 return None
1432
1442
1433 def manifestnode(self):
1443 def manifestnode(self):
1434 return None
1444 return None
1435
1445
1436 def user(self):
1446 def user(self):
1437 return self._user or self._repo.ui.username()
1447 return self._user or self._repo.ui.username()
1438
1448
1439 def date(self):
1449 def date(self):
1440 return self._date
1450 return self._date
1441
1451
1442 def description(self):
1452 def description(self):
1443 return self._text
1453 return self._text
1444
1454
1445 def files(self):
1455 def files(self):
1446 return sorted(
1456 return sorted(
1447 self._status.modified + self._status.added + self._status.removed
1457 self._status.modified + self._status.added + self._status.removed
1448 )
1458 )
1449
1459
1450 def modified(self):
1460 def modified(self):
1451 return self._status.modified
1461 return self._status.modified
1452
1462
1453 def added(self):
1463 def added(self):
1454 return self._status.added
1464 return self._status.added
1455
1465
1456 def removed(self):
1466 def removed(self):
1457 return self._status.removed
1467 return self._status.removed
1458
1468
1459 def deleted(self):
1469 def deleted(self):
1460 return self._status.deleted
1470 return self._status.deleted
1461
1471
1462 filesmodified = modified
1472 filesmodified = modified
1463 filesadded = added
1473 filesadded = added
1464 filesremoved = removed
1474 filesremoved = removed
1465
1475
1466 def branch(self):
1476 def branch(self):
1467 return encoding.tolocal(self._extra[b'branch'])
1477 return encoding.tolocal(self._extra[b'branch'])
1468
1478
1469 def closesbranch(self):
1479 def closesbranch(self):
1470 return b'close' in self._extra
1480 return b'close' in self._extra
1471
1481
1472 def extra(self):
1482 def extra(self):
1473 return self._extra
1483 return self._extra
1474
1484
1475 def isinmemory(self):
1485 def isinmemory(self):
1476 return False
1486 return False
1477
1487
1478 def tags(self):
1488 def tags(self):
1479 return []
1489 return []
1480
1490
1481 def bookmarks(self):
1491 def bookmarks(self):
1482 b = []
1492 b = []
1483 for p in self.parents():
1493 for p in self.parents():
1484 b.extend(p.bookmarks())
1494 b.extend(p.bookmarks())
1485 return b
1495 return b
1486
1496
1487 def phase(self):
1497 def phase(self):
1488 phase = phases.newcommitphase(self._repo.ui)
1498 phase = phases.newcommitphase(self._repo.ui)
1489 for p in self.parents():
1499 for p in self.parents():
1490 phase = max(phase, p.phase())
1500 phase = max(phase, p.phase())
1491 return phase
1501 return phase
1492
1502
1493 def hidden(self):
1503 def hidden(self):
1494 return False
1504 return False
1495
1505
1496 def children(self):
1506 def children(self):
1497 return []
1507 return []
1498
1508
1499 def flags(self, path):
1509 def flags(self, path):
1500 if '_manifest' in self.__dict__:
1510 if '_manifest' in self.__dict__:
1501 try:
1511 try:
1502 return self._manifest.flags(path)
1512 return self._manifest.flags(path)
1503 except KeyError:
1513 except KeyError:
1504 return b''
1514 return b''
1505
1515
1506 try:
1516 try:
1507 return self._flagfunc(path)
1517 return self._flagfunc(path)
1508 except OSError:
1518 except OSError:
1509 return b''
1519 return b''
1510
1520
1511 def ancestor(self, c2):
1521 def ancestor(self, c2):
1512 """return the "best" ancestor context of self and c2"""
1522 """return the "best" ancestor context of self and c2"""
1513 return self._parents[0].ancestor(c2) # punt on two parents for now
1523 return self._parents[0].ancestor(c2) # punt on two parents for now
1514
1524
1515 def ancestors(self):
1525 def ancestors(self):
1516 for p in self._parents:
1526 for p in self._parents:
1517 yield p
1527 yield p
1518 for a in self._repo.changelog.ancestors(
1528 for a in self._repo.changelog.ancestors(
1519 [p.rev() for p in self._parents]
1529 [p.rev() for p in self._parents]
1520 ):
1530 ):
1521 yield self._repo[a]
1531 yield self._repo[a]
1522
1532
1523 def markcommitted(self, node):
1533 def markcommitted(self, node):
1524 """Perform post-commit cleanup necessary after committing this ctx
1534 """Perform post-commit cleanup necessary after committing this ctx
1525
1535
1526 Specifically, this updates backing stores this working context
1536 Specifically, this updates backing stores this working context
1527 wraps to reflect the fact that the changes reflected by this
1537 wraps to reflect the fact that the changes reflected by this
1528 workingctx have been committed. For example, it marks
1538 workingctx have been committed. For example, it marks
1529 modified and added files as normal in the dirstate.
1539 modified and added files as normal in the dirstate.
1530
1540
1531 """
1541 """
1532
1542
1533 def dirty(self, missing=False, merge=True, branch=True):
1543 def dirty(self, missing=False, merge=True, branch=True):
1534 return False
1544 return False
1535
1545
1536
1546
1537 class workingctx(committablectx):
1547 class workingctx(committablectx):
1538 """A workingctx object makes access to data related to
1548 """A workingctx object makes access to data related to
1539 the current working directory convenient.
1549 the current working directory convenient.
1540 date - any valid date string or (unixtime, offset), or None.
1550 date - any valid date string or (unixtime, offset), or None.
1541 user - username string, or None.
1551 user - username string, or None.
1542 extra - a dictionary of extra values, or None.
1552 extra - a dictionary of extra values, or None.
1543 changes - a list of file lists as returned by localrepo.status()
1553 changes - a list of file lists as returned by localrepo.status()
1544 or None to use the repository status.
1554 or None to use the repository status.
1545 """
1555 """
1546
1556
1547 def __init__(
1557 def __init__(
1548 self, repo, text=b"", user=None, date=None, extra=None, changes=None
1558 self, repo, text=b"", user=None, date=None, extra=None, changes=None
1549 ):
1559 ):
1550 branch = None
1560 branch = None
1551 if not extra or b'branch' not in extra:
1561 if not extra or b'branch' not in extra:
1552 try:
1562 try:
1553 branch = repo.dirstate.branch()
1563 branch = repo.dirstate.branch()
1554 except UnicodeDecodeError:
1564 except UnicodeDecodeError:
1555 raise error.Abort(_(b'branch name not in UTF-8!'))
1565 raise error.Abort(_(b'branch name not in UTF-8!'))
1556 super(workingctx, self).__init__(
1566 super(workingctx, self).__init__(
1557 repo, text, user, date, extra, changes, branch=branch
1567 repo, text, user, date, extra, changes, branch=branch
1558 )
1568 )
1559
1569
1560 def __iter__(self):
1570 def __iter__(self):
1561 d = self._repo.dirstate
1571 d = self._repo.dirstate
1562 for f in d:
1572 for f in d:
1563 if d.get_entry(f).tracked:
1573 if d.get_entry(f).tracked:
1564 yield f
1574 yield f
1565
1575
1566 def __contains__(self, key):
1576 def __contains__(self, key):
1567 return self._repo.dirstate.get_entry(key).tracked
1577 return self._repo.dirstate.get_entry(key).tracked
1568
1578
1569 def hex(self):
1579 def hex(self):
1570 return self._repo.nodeconstants.wdirhex
1580 return self._repo.nodeconstants.wdirhex
1571
1581
1572 @propertycache
1582 @propertycache
1573 def _parents(self):
1583 def _parents(self):
1574 p = self._repo.dirstate.parents()
1584 p = self._repo.dirstate.parents()
1575 if p[1] == self._repo.nodeconstants.nullid:
1585 if p[1] == self._repo.nodeconstants.nullid:
1576 p = p[:-1]
1586 p = p[:-1]
1577 # use unfiltered repo to delay/avoid loading obsmarkers
1587 # use unfiltered repo to delay/avoid loading obsmarkers
1578 unfi = self._repo.unfiltered()
1588 unfi = self._repo.unfiltered()
1579 return [
1589 return [
1580 changectx(
1590 changectx(
1581 self._repo, unfi.changelog.rev(n), n, maybe_filtered=False
1591 self._repo, unfi.changelog.rev(n), n, maybe_filtered=False
1582 )
1592 )
1583 for n in p
1593 for n in p
1584 ]
1594 ]
1585
1595
1586 def setparents(self, p1node, p2node=None):
1596 def setparents(self, p1node, p2node=None):
1587 if p2node is None:
1597 if p2node is None:
1588 p2node = self._repo.nodeconstants.nullid
1598 p2node = self._repo.nodeconstants.nullid
1589 dirstate = self._repo.dirstate
1599 dirstate = self._repo.dirstate
1590 with dirstate.parentchange():
1600 with dirstate.parentchange():
1591 copies = dirstate.setparents(p1node, p2node)
1601 copies = dirstate.setparents(p1node, p2node)
1592 pctx = self._repo[p1node]
1602 pctx = self._repo[p1node]
1593 if copies:
1603 if copies:
1594 # Adjust copy records, the dirstate cannot do it, it
1604 # Adjust copy records, the dirstate cannot do it, it
1595 # requires access to parents manifests. Preserve them
1605 # requires access to parents manifests. Preserve them
1596 # only for entries added to first parent.
1606 # only for entries added to first parent.
1597 for f in copies:
1607 for f in copies:
1598 if f not in pctx and copies[f] in pctx:
1608 if f not in pctx and copies[f] in pctx:
1599 dirstate.copy(copies[f], f)
1609 dirstate.copy(copies[f], f)
1600 if p2node == self._repo.nodeconstants.nullid:
1610 if p2node == self._repo.nodeconstants.nullid:
1601 for f, s in sorted(dirstate.copies().items()):
1611 for f, s in sorted(dirstate.copies().items()):
1602 if f not in pctx and s not in pctx:
1612 if f not in pctx and s not in pctx:
1603 dirstate.copy(None, f)
1613 dirstate.copy(None, f)
1604
1614
1605 def _fileinfo(self, path):
1615 def _fileinfo(self, path):
1606 # populate __dict__['_manifest'] as workingctx has no _manifestdelta
1616 # populate __dict__['_manifest'] as workingctx has no _manifestdelta
1607 self._manifest
1617 self._manifest
1608 return super(workingctx, self)._fileinfo(path)
1618 return super(workingctx, self)._fileinfo(path)
1609
1619
1610 def _buildflagfunc(self):
1620 def _buildflagfunc(self):
1611 # Create a fallback function for getting file flags when the
1621 # Create a fallback function for getting file flags when the
1612 # filesystem doesn't support them
1622 # filesystem doesn't support them
1613
1623
1614 copiesget = self._repo.dirstate.copies().get
1624 copiesget = self._repo.dirstate.copies().get
1615 parents = self.parents()
1625 parents = self.parents()
1616 if len(parents) < 2:
1626 if len(parents) < 2:
1617 # when we have one parent, it's easy: copy from parent
1627 # when we have one parent, it's easy: copy from parent
1618 man = parents[0].manifest()
1628 man = parents[0].manifest()
1619
1629
1620 def func(f):
1630 def func(f):
1621 f = copiesget(f, f)
1631 f = copiesget(f, f)
1622 return man.flags(f)
1632 return man.flags(f)
1623
1633
1624 else:
1634 else:
1625 # merges are tricky: we try to reconstruct the unstored
1635 # merges are tricky: we try to reconstruct the unstored
1626 # result from the merge (issue1802)
1636 # result from the merge (issue1802)
1627 p1, p2 = parents
1637 p1, p2 = parents
1628 pa = p1.ancestor(p2)
1638 pa = p1.ancestor(p2)
1629 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1639 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1630
1640
1631 def func(f):
1641 def func(f):
1632 f = copiesget(f, f) # may be wrong for merges with copies
1642 f = copiesget(f, f) # may be wrong for merges with copies
1633 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1643 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1634 if fl1 == fl2:
1644 if fl1 == fl2:
1635 return fl1
1645 return fl1
1636 if fl1 == fla:
1646 if fl1 == fla:
1637 return fl2
1647 return fl2
1638 if fl2 == fla:
1648 if fl2 == fla:
1639 return fl1
1649 return fl1
1640 return b'' # punt for conflicts
1650 return b'' # punt for conflicts
1641
1651
1642 return func
1652 return func
1643
1653
1644 @propertycache
1654 @propertycache
1645 def _flagfunc(self):
1655 def _flagfunc(self):
1646 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1656 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1647
1657
1648 def flags(self, path):
1658 def flags(self, path):
1649 try:
1659 try:
1650 return self._flagfunc(path)
1660 return self._flagfunc(path)
1651 except OSError:
1661 except OSError:
1652 return b''
1662 return b''
1653
1663
1654 def filectx(self, path, filelog=None):
1664 def filectx(self, path, filelog=None):
1655 """get a file context from the working directory"""
1665 """get a file context from the working directory"""
1656 return workingfilectx(
1666 return workingfilectx(
1657 self._repo, path, workingctx=self, filelog=filelog
1667 self._repo, path, workingctx=self, filelog=filelog
1658 )
1668 )
1659
1669
1660 def dirty(self, missing=False, merge=True, branch=True):
1670 def dirty(self, missing=False, merge=True, branch=True):
1661 """check whether a working directory is modified"""
1671 """check whether a working directory is modified"""
1662 # check subrepos first
1672 # check subrepos first
1663 for s in sorted(self.substate):
1673 for s in sorted(self.substate):
1664 if self.sub(s).dirty(missing=missing):
1674 if self.sub(s).dirty(missing=missing):
1665 return True
1675 return True
1666 # check current working dir
1676 # check current working dir
1667 return (
1677 return (
1668 (merge and self.p2())
1678 (merge and self.p2())
1669 or (branch and self.branch() != self.p1().branch())
1679 or (branch and self.branch() != self.p1().branch())
1670 or self.modified()
1680 or self.modified()
1671 or self.added()
1681 or self.added()
1672 or self.removed()
1682 or self.removed()
1673 or (missing and self.deleted())
1683 or (missing and self.deleted())
1674 )
1684 )
1675
1685
1676 def add(self, list, prefix=b""):
1686 def add(self, list, prefix=b""):
1677 with self._repo.wlock():
1687 with self._repo.wlock():
1678 ui, ds = self._repo.ui, self._repo.dirstate
1688 ui, ds = self._repo.ui, self._repo.dirstate
1679 uipath = lambda f: ds.pathto(pathutil.join(prefix, f))
1689 uipath = lambda f: ds.pathto(pathutil.join(prefix, f))
1680 rejected = []
1690 rejected = []
1681 lstat = self._repo.wvfs.lstat
1691 lstat = self._repo.wvfs.lstat
1682 for f in list:
1692 for f in list:
1683 # ds.pathto() returns an absolute file when this is invoked from
1693 # ds.pathto() returns an absolute file when this is invoked from
1684 # the keyword extension. That gets flagged as non-portable on
1694 # the keyword extension. That gets flagged as non-portable on
1685 # Windows, since it contains the drive letter and colon.
1695 # Windows, since it contains the drive letter and colon.
1686 scmutil.checkportable(ui, os.path.join(prefix, f))
1696 scmutil.checkportable(ui, os.path.join(prefix, f))
1687 try:
1697 try:
1688 st = lstat(f)
1698 st = lstat(f)
1689 except OSError:
1699 except OSError:
1690 ui.warn(_(b"%s does not exist!\n") % uipath(f))
1700 ui.warn(_(b"%s does not exist!\n") % uipath(f))
1691 rejected.append(f)
1701 rejected.append(f)
1692 continue
1702 continue
1693 limit = ui.configbytes(b'ui', b'large-file-limit')
1703 limit = ui.configbytes(b'ui', b'large-file-limit')
1694 if limit != 0 and st.st_size > limit:
1704 if limit != 0 and st.st_size > limit:
1695 ui.warn(
1705 ui.warn(
1696 _(
1706 _(
1697 b"%s: up to %d MB of RAM may be required "
1707 b"%s: up to %d MB of RAM may be required "
1698 b"to manage this file\n"
1708 b"to manage this file\n"
1699 b"(use 'hg revert %s' to cancel the "
1709 b"(use 'hg revert %s' to cancel the "
1700 b"pending addition)\n"
1710 b"pending addition)\n"
1701 )
1711 )
1702 % (f, 3 * st.st_size // 1000000, uipath(f))
1712 % (f, 3 * st.st_size // 1000000, uipath(f))
1703 )
1713 )
1704 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1714 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1705 ui.warn(
1715 ui.warn(
1706 _(
1716 _(
1707 b"%s not added: only files and symlinks "
1717 b"%s not added: only files and symlinks "
1708 b"supported currently\n"
1718 b"supported currently\n"
1709 )
1719 )
1710 % uipath(f)
1720 % uipath(f)
1711 )
1721 )
1712 rejected.append(f)
1722 rejected.append(f)
1713 elif not ds.set_tracked(f):
1723 elif not ds.set_tracked(f):
1714 ui.warn(_(b"%s already tracked!\n") % uipath(f))
1724 ui.warn(_(b"%s already tracked!\n") % uipath(f))
1715 return rejected
1725 return rejected
1716
1726
1717 def forget(self, files, prefix=b""):
1727 def forget(self, files, prefix=b""):
1718 with self._repo.wlock():
1728 with self._repo.wlock():
1719 ds = self._repo.dirstate
1729 ds = self._repo.dirstate
1720 uipath = lambda f: ds.pathto(pathutil.join(prefix, f))
1730 uipath = lambda f: ds.pathto(pathutil.join(prefix, f))
1721 rejected = []
1731 rejected = []
1722 for f in files:
1732 for f in files:
1723 if not ds.set_untracked(f):
1733 if not ds.set_untracked(f):
1724 self._repo.ui.warn(_(b"%s not tracked!\n") % uipath(f))
1734 self._repo.ui.warn(_(b"%s not tracked!\n") % uipath(f))
1725 rejected.append(f)
1735 rejected.append(f)
1726 return rejected
1736 return rejected
1727
1737
1728 def copy(self, source, dest):
1738 def copy(self, source, dest):
1729 try:
1739 try:
1730 st = self._repo.wvfs.lstat(dest)
1740 st = self._repo.wvfs.lstat(dest)
1731 except OSError as err:
1741 except OSError as err:
1732 if err.errno != errno.ENOENT:
1742 if err.errno != errno.ENOENT:
1733 raise
1743 raise
1734 self._repo.ui.warn(
1744 self._repo.ui.warn(
1735 _(b"%s does not exist!\n") % self._repo.dirstate.pathto(dest)
1745 _(b"%s does not exist!\n") % self._repo.dirstate.pathto(dest)
1736 )
1746 )
1737 return
1747 return
1738 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1748 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1739 self._repo.ui.warn(
1749 self._repo.ui.warn(
1740 _(b"copy failed: %s is not a file or a symbolic link\n")
1750 _(b"copy failed: %s is not a file or a symbolic link\n")
1741 % self._repo.dirstate.pathto(dest)
1751 % self._repo.dirstate.pathto(dest)
1742 )
1752 )
1743 else:
1753 else:
1744 with self._repo.wlock():
1754 with self._repo.wlock():
1745 ds = self._repo.dirstate
1755 ds = self._repo.dirstate
1746 ds.set_tracked(dest)
1756 ds.set_tracked(dest)
1747 ds.copy(source, dest)
1757 ds.copy(source, dest)
1748
1758
1749 def match(
1759 def match(
1750 self,
1760 self,
1751 pats=None,
1761 pats=None,
1752 include=None,
1762 include=None,
1753 exclude=None,
1763 exclude=None,
1754 default=b'glob',
1764 default=b'glob',
1755 listsubrepos=False,
1765 listsubrepos=False,
1756 badfn=None,
1766 badfn=None,
1757 cwd=None,
1767 cwd=None,
1758 ):
1768 ):
1759 r = self._repo
1769 r = self._repo
1760 if not cwd:
1770 if not cwd:
1761 cwd = r.getcwd()
1771 cwd = r.getcwd()
1762
1772
1763 # Only a case insensitive filesystem needs magic to translate user input
1773 # Only a case insensitive filesystem needs magic to translate user input
1764 # to actual case in the filesystem.
1774 # to actual case in the filesystem.
1765 icasefs = not util.fscasesensitive(r.root)
1775 icasefs = not util.fscasesensitive(r.root)
1766 return matchmod.match(
1776 return matchmod.match(
1767 r.root,
1777 r.root,
1768 cwd,
1778 cwd,
1769 pats,
1779 pats,
1770 include,
1780 include,
1771 exclude,
1781 exclude,
1772 default,
1782 default,
1773 auditor=r.auditor,
1783 auditor=r.auditor,
1774 ctx=self,
1784 ctx=self,
1775 listsubrepos=listsubrepos,
1785 listsubrepos=listsubrepos,
1776 badfn=badfn,
1786 badfn=badfn,
1777 icasefs=icasefs,
1787 icasefs=icasefs,
1778 )
1788 )
1779
1789
1780 def _filtersuspectsymlink(self, files):
1790 def _filtersuspectsymlink(self, files):
1781 if not files or self._repo.dirstate._checklink:
1791 if not files or self._repo.dirstate._checklink:
1782 return files
1792 return files
1783
1793
1784 # Symlink placeholders may get non-symlink-like contents
1794 # Symlink placeholders may get non-symlink-like contents
1785 # via user error or dereferencing by NFS or Samba servers,
1795 # via user error or dereferencing by NFS or Samba servers,
1786 # so we filter out any placeholders that don't look like a
1796 # so we filter out any placeholders that don't look like a
1787 # symlink
1797 # symlink
1788 sane = []
1798 sane = []
1789 for f in files:
1799 for f in files:
1790 if self.flags(f) == b'l':
1800 if self.flags(f) == b'l':
1791 d = self[f].data()
1801 d = self[f].data()
1792 if (
1802 if (
1793 d == b''
1803 d == b''
1794 or len(d) >= 1024
1804 or len(d) >= 1024
1795 or b'\n' in d
1805 or b'\n' in d
1796 or stringutil.binary(d)
1806 or stringutil.binary(d)
1797 ):
1807 ):
1798 self._repo.ui.debug(
1808 self._repo.ui.debug(
1799 b'ignoring suspect symlink placeholder "%s"\n' % f
1809 b'ignoring suspect symlink placeholder "%s"\n' % f
1800 )
1810 )
1801 continue
1811 continue
1802 sane.append(f)
1812 sane.append(f)
1803 return sane
1813 return sane
1804
1814
1805 def _checklookup(self, files, mtime_boundary):
1815 def _checklookup(self, files, mtime_boundary):
1806 # check for any possibly clean files
1816 # check for any possibly clean files
1807 if not files:
1817 if not files:
1808 return [], [], [], []
1818 return [], [], [], []
1809
1819
1810 modified = []
1820 modified = []
1811 deleted = []
1821 deleted = []
1812 clean = []
1822 clean = []
1813 fixup = []
1823 fixup = []
1814 pctx = self._parents[0]
1824 pctx = self._parents[0]
1815 # do a full compare of any files that might have changed
1825 # do a full compare of any files that might have changed
1816 for f in sorted(files):
1826 for f in sorted(files):
1817 try:
1827 try:
1818 # This will return True for a file that got replaced by a
1828 # This will return True for a file that got replaced by a
1819 # directory in the interim, but fixing that is pretty hard.
1829 # directory in the interim, but fixing that is pretty hard.
1820 if (
1830 if (
1821 f not in pctx
1831 f not in pctx
1822 or self.flags(f) != pctx.flags(f)
1832 or self.flags(f) != pctx.flags(f)
1823 or pctx[f].cmp(self[f])
1833 or pctx[f].cmp(self[f])
1824 ):
1834 ):
1825 modified.append(f)
1835 modified.append(f)
1826 elif mtime_boundary is None:
1836 elif mtime_boundary is None:
1827 clean.append(f)
1837 clean.append(f)
1828 else:
1838 else:
1829 s = self[f].lstat()
1839 s = self[f].lstat()
1830 mode = s.st_mode
1840 mode = s.st_mode
1831 size = s.st_size
1841 size = s.st_size
1832 file_mtime = timestamp.reliable_mtime_of(s, mtime_boundary)
1842 file_mtime = timestamp.reliable_mtime_of(s, mtime_boundary)
1833 if file_mtime is not None:
1843 if file_mtime is not None:
1834 cache_info = (mode, size, file_mtime)
1844 cache_info = (mode, size, file_mtime)
1835 fixup.append((f, cache_info))
1845 fixup.append((f, cache_info))
1836 else:
1846 else:
1837 clean.append(f)
1847 clean.append(f)
1838 except (IOError, OSError):
1848 except (IOError, OSError):
1839 # A file become inaccessible in between? Mark it as deleted,
1849 # A file become inaccessible in between? Mark it as deleted,
1840 # matching dirstate behavior (issue5584).
1850 # matching dirstate behavior (issue5584).
1841 # The dirstate has more complex behavior around whether a
1851 # The dirstate has more complex behavior around whether a
1842 # missing file matches a directory, etc, but we don't need to
1852 # missing file matches a directory, etc, but we don't need to
1843 # bother with that: if f has made it to this point, we're sure
1853 # bother with that: if f has made it to this point, we're sure
1844 # it's in the dirstate.
1854 # it's in the dirstate.
1845 deleted.append(f)
1855 deleted.append(f)
1846
1856
1847 return modified, deleted, clean, fixup
1857 return modified, deleted, clean, fixup
1848
1858
1849 def _poststatusfixup(self, status, fixup):
1859 def _poststatusfixup(self, status, fixup):
1850 """update dirstate for files that are actually clean"""
1860 """update dirstate for files that are actually clean"""
1851 poststatus = self._repo.postdsstatus()
1861 poststatus = self._repo.postdsstatus()
1852 if fixup or poststatus or self._repo.dirstate._dirty:
1862 if fixup or poststatus or self._repo.dirstate._dirty:
1853 try:
1863 try:
1854 oldid = self._repo.dirstate.identity()
1864 oldid = self._repo.dirstate.identity()
1855
1865
1856 # updating the dirstate is optional
1866 # updating the dirstate is optional
1857 # so we don't wait on the lock
1867 # so we don't wait on the lock
1858 # wlock can invalidate the dirstate, so cache normal _after_
1868 # wlock can invalidate the dirstate, so cache normal _after_
1859 # taking the lock
1869 # taking the lock
1860 with self._repo.wlock(False):
1870 with self._repo.wlock(False):
1861 dirstate = self._repo.dirstate
1871 dirstate = self._repo.dirstate
1862 if dirstate.identity() == oldid:
1872 if dirstate.identity() == oldid:
1863 if fixup:
1873 if fixup:
1864 if dirstate.pendingparentchange():
1874 if dirstate.pendingparentchange():
1865 normal = lambda f, pfd: dirstate.update_file(
1875 normal = lambda f, pfd: dirstate.update_file(
1866 f, p1_tracked=True, wc_tracked=True
1876 f, p1_tracked=True, wc_tracked=True
1867 )
1877 )
1868 else:
1878 else:
1869 normal = dirstate.set_clean
1879 normal = dirstate.set_clean
1870 for f, pdf in fixup:
1880 for f, pdf in fixup:
1871 normal(f, pdf)
1881 normal(f, pdf)
1872 # write changes out explicitly, because nesting
1882 # write changes out explicitly, because nesting
1873 # wlock at runtime may prevent 'wlock.release()'
1883 # wlock at runtime may prevent 'wlock.release()'
1874 # after this block from doing so for subsequent
1884 # after this block from doing so for subsequent
1875 # changing files
1885 # changing files
1876 tr = self._repo.currenttransaction()
1886 tr = self._repo.currenttransaction()
1877 self._repo.dirstate.write(tr)
1887 self._repo.dirstate.write(tr)
1878
1888
1879 if poststatus:
1889 if poststatus:
1880 for ps in poststatus:
1890 for ps in poststatus:
1881 ps(self, status)
1891 ps(self, status)
1882 else:
1892 else:
1883 # in this case, writing changes out breaks
1893 # in this case, writing changes out breaks
1884 # consistency, because .hg/dirstate was
1894 # consistency, because .hg/dirstate was
1885 # already changed simultaneously after last
1895 # already changed simultaneously after last
1886 # caching (see also issue5584 for detail)
1896 # caching (see also issue5584 for detail)
1887 self._repo.ui.debug(
1897 self._repo.ui.debug(
1888 b'skip updating dirstate: identity mismatch\n'
1898 b'skip updating dirstate: identity mismatch\n'
1889 )
1899 )
1890 except error.LockError:
1900 except error.LockError:
1891 pass
1901 pass
1892 finally:
1902 finally:
1893 # Even if the wlock couldn't be grabbed, clear out the list.
1903 # Even if the wlock couldn't be grabbed, clear out the list.
1894 self._repo.clearpostdsstatus()
1904 self._repo.clearpostdsstatus()
1895
1905
1896 def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
1906 def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
1897 '''Gets the status from the dirstate -- internal use only.'''
1907 '''Gets the status from the dirstate -- internal use only.'''
1898 subrepos = []
1908 subrepos = []
1899 if b'.hgsub' in self:
1909 if b'.hgsub' in self:
1900 subrepos = sorted(self.substate)
1910 subrepos = sorted(self.substate)
1901 cmp, s, mtime_boundary = self._repo.dirstate.status(
1911 cmp, s, mtime_boundary = self._repo.dirstate.status(
1902 match, subrepos, ignored=ignored, clean=clean, unknown=unknown
1912 match, subrepos, ignored=ignored, clean=clean, unknown=unknown
1903 )
1913 )
1904
1914
1905 # check for any possibly clean files
1915 # check for any possibly clean files
1906 fixup = []
1916 fixup = []
1907 if cmp:
1917 if cmp:
1908 modified2, deleted2, clean_set, fixup = self._checklookup(
1918 modified2, deleted2, clean_set, fixup = self._checklookup(
1909 cmp, mtime_boundary
1919 cmp, mtime_boundary
1910 )
1920 )
1911 s.modified.extend(modified2)
1921 s.modified.extend(modified2)
1912 s.deleted.extend(deleted2)
1922 s.deleted.extend(deleted2)
1913
1923
1914 if clean_set and clean:
1924 if clean_set and clean:
1915 s.clean.extend(clean_set)
1925 s.clean.extend(clean_set)
1916 if fixup and clean:
1926 if fixup and clean:
1917 s.clean.extend((f for f, _ in fixup))
1927 s.clean.extend((f for f, _ in fixup))
1918
1928
1919 self._poststatusfixup(s, fixup)
1929 self._poststatusfixup(s, fixup)
1920
1930
1921 if match.always():
1931 if match.always():
1922 # cache for performance
1932 # cache for performance
1923 if s.unknown or s.ignored or s.clean:
1933 if s.unknown or s.ignored or s.clean:
1924 # "_status" is cached with list*=False in the normal route
1934 # "_status" is cached with list*=False in the normal route
1925 self._status = scmutil.status(
1935 self._status = scmutil.status(
1926 s.modified, s.added, s.removed, s.deleted, [], [], []
1936 s.modified, s.added, s.removed, s.deleted, [], [], []
1927 )
1937 )
1928 else:
1938 else:
1929 self._status = s
1939 self._status = s
1930
1940
1931 return s
1941 return s
1932
1942
1933 @propertycache
1943 @propertycache
1934 def _copies(self):
1944 def _copies(self):
1935 p1copies = {}
1945 p1copies = {}
1936 p2copies = {}
1946 p2copies = {}
1937 parents = self._repo.dirstate.parents()
1947 parents = self._repo.dirstate.parents()
1938 p1manifest = self._repo[parents[0]].manifest()
1948 p1manifest = self._repo[parents[0]].manifest()
1939 p2manifest = self._repo[parents[1]].manifest()
1949 p2manifest = self._repo[parents[1]].manifest()
1940 changedset = set(self.added()) | set(self.modified())
1950 changedset = set(self.added()) | set(self.modified())
1941 narrowmatch = self._repo.narrowmatch()
1951 narrowmatch = self._repo.narrowmatch()
1942 for dst, src in self._repo.dirstate.copies().items():
1952 for dst, src in self._repo.dirstate.copies().items():
1943 if dst not in changedset or not narrowmatch(dst):
1953 if dst not in changedset or not narrowmatch(dst):
1944 continue
1954 continue
1945 if src in p1manifest:
1955 if src in p1manifest:
1946 p1copies[dst] = src
1956 p1copies[dst] = src
1947 elif src in p2manifest:
1957 elif src in p2manifest:
1948 p2copies[dst] = src
1958 p2copies[dst] = src
1949 return p1copies, p2copies
1959 return p1copies, p2copies
1950
1960
1951 @propertycache
1961 @propertycache
1952 def _manifest(self):
1962 def _manifest(self):
1953 """generate a manifest corresponding to the values in self._status
1963 """generate a manifest corresponding to the values in self._status
1954
1964
1955 This reuse the file nodeid from parent, but we use special node
1965 This reuse the file nodeid from parent, but we use special node
1956 identifiers for added and modified files. This is used by manifests
1966 identifiers for added and modified files. This is used by manifests
1957 merge to see that files are different and by update logic to avoid
1967 merge to see that files are different and by update logic to avoid
1958 deleting newly added files.
1968 deleting newly added files.
1959 """
1969 """
1960 return self._buildstatusmanifest(self._status)
1970 return self._buildstatusmanifest(self._status)
1961
1971
1962 def _buildstatusmanifest(self, status):
1972 def _buildstatusmanifest(self, status):
1963 """Builds a manifest that includes the given status results."""
1973 """Builds a manifest that includes the given status results."""
1964 parents = self.parents()
1974 parents = self.parents()
1965
1975
1966 man = parents[0].manifest().copy()
1976 man = parents[0].manifest().copy()
1967
1977
1968 ff = self._flagfunc
1978 ff = self._flagfunc
1969 for i, l in (
1979 for i, l in (
1970 (self._repo.nodeconstants.addednodeid, status.added),
1980 (self._repo.nodeconstants.addednodeid, status.added),
1971 (self._repo.nodeconstants.modifiednodeid, status.modified),
1981 (self._repo.nodeconstants.modifiednodeid, status.modified),
1972 ):
1982 ):
1973 for f in l:
1983 for f in l:
1974 man[f] = i
1984 man[f] = i
1975 try:
1985 try:
1976 man.setflag(f, ff(f))
1986 man.setflag(f, ff(f))
1977 except OSError:
1987 except OSError:
1978 pass
1988 pass
1979
1989
1980 for f in status.deleted + status.removed:
1990 for f in status.deleted + status.removed:
1981 if f in man:
1991 if f in man:
1982 del man[f]
1992 del man[f]
1983
1993
1984 return man
1994 return man
1985
1995
1986 def _buildstatus(
1996 def _buildstatus(
1987 self, other, s, match, listignored, listclean, listunknown
1997 self, other, s, match, listignored, listclean, listunknown
1988 ):
1998 ):
1989 """build a status with respect to another context
1999 """build a status with respect to another context
1990
2000
1991 This includes logic for maintaining the fast path of status when
2001 This includes logic for maintaining the fast path of status when
1992 comparing the working directory against its parent, which is to skip
2002 comparing the working directory against its parent, which is to skip
1993 building a new manifest if self (working directory) is not comparing
2003 building a new manifest if self (working directory) is not comparing
1994 against its parent (repo['.']).
2004 against its parent (repo['.']).
1995 """
2005 """
1996 s = self._dirstatestatus(match, listignored, listclean, listunknown)
2006 s = self._dirstatestatus(match, listignored, listclean, listunknown)
1997 # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
2007 # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
1998 # might have accidentally ended up with the entire contents of the file
2008 # might have accidentally ended up with the entire contents of the file
1999 # they are supposed to be linking to.
2009 # they are supposed to be linking to.
2000 s.modified[:] = self._filtersuspectsymlink(s.modified)
2010 s.modified[:] = self._filtersuspectsymlink(s.modified)
2001 if other != self._repo[b'.']:
2011 if other != self._repo[b'.']:
2002 s = super(workingctx, self)._buildstatus(
2012 s = super(workingctx, self)._buildstatus(
2003 other, s, match, listignored, listclean, listunknown
2013 other, s, match, listignored, listclean, listunknown
2004 )
2014 )
2005 return s
2015 return s
2006
2016
2007 def _matchstatus(self, other, match):
2017 def _matchstatus(self, other, match):
2008 """override the match method with a filter for directory patterns
2018 """override the match method with a filter for directory patterns
2009
2019
2010 We use inheritance to customize the match.bad method only in cases of
2020 We use inheritance to customize the match.bad method only in cases of
2011 workingctx since it belongs only to the working directory when
2021 workingctx since it belongs only to the working directory when
2012 comparing against the parent changeset.
2022 comparing against the parent changeset.
2013
2023
2014 If we aren't comparing against the working directory's parent, then we
2024 If we aren't comparing against the working directory's parent, then we
2015 just use the default match object sent to us.
2025 just use the default match object sent to us.
2016 """
2026 """
2017 if other != self._repo[b'.']:
2027 if other != self._repo[b'.']:
2018
2028
2019 def bad(f, msg):
2029 def bad(f, msg):
2020 # 'f' may be a directory pattern from 'match.files()',
2030 # 'f' may be a directory pattern from 'match.files()',
2021 # so 'f not in ctx1' is not enough
2031 # so 'f not in ctx1' is not enough
2022 if f not in other and not other.hasdir(f):
2032 if f not in other and not other.hasdir(f):
2023 self._repo.ui.warn(
2033 self._repo.ui.warn(
2024 b'%s: %s\n' % (self._repo.dirstate.pathto(f), msg)
2034 b'%s: %s\n' % (self._repo.dirstate.pathto(f), msg)
2025 )
2035 )
2026
2036
2027 match.bad = bad
2037 match.bad = bad
2028 return match
2038 return match
2029
2039
2030 def walk(self, match):
2040 def walk(self, match):
2031 '''Generates matching file names.'''
2041 '''Generates matching file names.'''
2032 return sorted(
2042 return sorted(
2033 self._repo.dirstate.walk(
2043 self._repo.dirstate.walk(
2034 self._repo.narrowmatch(match),
2044 self._repo.narrowmatch(match),
2035 subrepos=sorted(self.substate),
2045 subrepos=sorted(self.substate),
2036 unknown=True,
2046 unknown=True,
2037 ignored=False,
2047 ignored=False,
2038 )
2048 )
2039 )
2049 )
2040
2050
2041 def matches(self, match):
2051 def matches(self, match):
2042 match = self._repo.narrowmatch(match)
2052 match = self._repo.narrowmatch(match)
2043 ds = self._repo.dirstate
2053 ds = self._repo.dirstate
2044 return sorted(f for f in ds.matches(match) if ds.get_entry(f).tracked)
2054 return sorted(f for f in ds.matches(match) if ds.get_entry(f).tracked)
2045
2055
2046 def markcommitted(self, node):
2056 def markcommitted(self, node):
2047 with self._repo.dirstate.parentchange():
2057 with self._repo.dirstate.parentchange():
2048 for f in self.modified() + self.added():
2058 for f in self.modified() + self.added():
2049 self._repo.dirstate.update_file(
2059 self._repo.dirstate.update_file(
2050 f, p1_tracked=True, wc_tracked=True
2060 f, p1_tracked=True, wc_tracked=True
2051 )
2061 )
2052 for f in self.removed():
2062 for f in self.removed():
2053 self._repo.dirstate.update_file(
2063 self._repo.dirstate.update_file(
2054 f, p1_tracked=False, wc_tracked=False
2064 f, p1_tracked=False, wc_tracked=False
2055 )
2065 )
2056 self._repo.dirstate.setparents(node)
2066 self._repo.dirstate.setparents(node)
2057 self._repo._quick_access_changeid_invalidate()
2067 self._repo._quick_access_changeid_invalidate()
2058
2068
2059 sparse.aftercommit(self._repo, node)
2069 sparse.aftercommit(self._repo, node)
2060
2070
2061 # write changes out explicitly, because nesting wlock at
2071 # write changes out explicitly, because nesting wlock at
2062 # runtime may prevent 'wlock.release()' in 'repo.commit()'
2072 # runtime may prevent 'wlock.release()' in 'repo.commit()'
2063 # from immediately doing so for subsequent changing files
2073 # from immediately doing so for subsequent changing files
2064 self._repo.dirstate.write(self._repo.currenttransaction())
2074 self._repo.dirstate.write(self._repo.currenttransaction())
2065
2075
2066 def mergestate(self, clean=False):
2076 def mergestate(self, clean=False):
2067 if clean:
2077 if clean:
2068 return mergestatemod.mergestate.clean(self._repo)
2078 return mergestatemod.mergestate.clean(self._repo)
2069 return mergestatemod.mergestate.read(self._repo)
2079 return mergestatemod.mergestate.read(self._repo)
2070
2080
2071
2081
2072 class committablefilectx(basefilectx):
2082 class committablefilectx(basefilectx):
2073 """A committablefilectx provides common functionality for a file context
2083 """A committablefilectx provides common functionality for a file context
2074 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
2084 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
2075
2085
2076 def __init__(self, repo, path, filelog=None, ctx=None):
2086 def __init__(self, repo, path, filelog=None, ctx=None):
2077 self._repo = repo
2087 self._repo = repo
2078 self._path = path
2088 self._path = path
2079 self._changeid = None
2089 self._changeid = None
2080 self._filerev = self._filenode = None
2090 self._filerev = self._filenode = None
2081
2091
2082 if filelog is not None:
2092 if filelog is not None:
2083 self._filelog = filelog
2093 self._filelog = filelog
2084 if ctx:
2094 if ctx:
2085 self._changectx = ctx
2095 self._changectx = ctx
2086
2096
2087 def __nonzero__(self):
2097 def __nonzero__(self):
2088 return True
2098 return True
2089
2099
2090 __bool__ = __nonzero__
2100 __bool__ = __nonzero__
2091
2101
2092 def linkrev(self):
2102 def linkrev(self):
2093 # linked to self._changectx no matter if file is modified or not
2103 # linked to self._changectx no matter if file is modified or not
2094 return self.rev()
2104 return self.rev()
2095
2105
2096 def renamed(self):
2106 def renamed(self):
2097 path = self.copysource()
2107 path = self.copysource()
2098 if not path:
2108 if not path:
2099 return None
2109 return None
2100 return (
2110 return (
2101 path,
2111 path,
2102 self._changectx._parents[0]._manifest.get(
2112 self._changectx._parents[0]._manifest.get(
2103 path, self._repo.nodeconstants.nullid
2113 path, self._repo.nodeconstants.nullid
2104 ),
2114 ),
2105 )
2115 )
2106
2116
2107 def parents(self):
2117 def parents(self):
2108 '''return parent filectxs, following copies if necessary'''
2118 '''return parent filectxs, following copies if necessary'''
2109
2119
2110 def filenode(ctx, path):
2120 def filenode(ctx, path):
2111 return ctx._manifest.get(path, self._repo.nodeconstants.nullid)
2121 return ctx._manifest.get(path, self._repo.nodeconstants.nullid)
2112
2122
2113 path = self._path
2123 path = self._path
2114 fl = self._filelog
2124 fl = self._filelog
2115 pcl = self._changectx._parents
2125 pcl = self._changectx._parents
2116 renamed = self.renamed()
2126 renamed = self.renamed()
2117
2127
2118 if renamed:
2128 if renamed:
2119 pl = [renamed + (None,)]
2129 pl = [renamed + (None,)]
2120 else:
2130 else:
2121 pl = [(path, filenode(pcl[0], path), fl)]
2131 pl = [(path, filenode(pcl[0], path), fl)]
2122
2132
2123 for pc in pcl[1:]:
2133 for pc in pcl[1:]:
2124 pl.append((path, filenode(pc, path), fl))
2134 pl.append((path, filenode(pc, path), fl))
2125
2135
2126 return [
2136 return [
2127 self._parentfilectx(p, fileid=n, filelog=l)
2137 self._parentfilectx(p, fileid=n, filelog=l)
2128 for p, n, l in pl
2138 for p, n, l in pl
2129 if n != self._repo.nodeconstants.nullid
2139 if n != self._repo.nodeconstants.nullid
2130 ]
2140 ]
2131
2141
2132 def children(self):
2142 def children(self):
2133 return []
2143 return []
2134
2144
2135
2145
2136 class workingfilectx(committablefilectx):
2146 class workingfilectx(committablefilectx):
2137 """A workingfilectx object makes access to data related to a particular
2147 """A workingfilectx object makes access to data related to a particular
2138 file in the working directory convenient."""
2148 file in the working directory convenient."""
2139
2149
2140 def __init__(self, repo, path, filelog=None, workingctx=None):
2150 def __init__(self, repo, path, filelog=None, workingctx=None):
2141 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
2151 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
2142
2152
2143 @propertycache
2153 @propertycache
2144 def _changectx(self):
2154 def _changectx(self):
2145 return workingctx(self._repo)
2155 return workingctx(self._repo)
2146
2156
2147 def data(self):
2157 def data(self):
2148 return self._repo.wread(self._path)
2158 return self._repo.wread(self._path)
2149
2159
2150 def copysource(self):
2160 def copysource(self):
2151 return self._repo.dirstate.copied(self._path)
2161 return self._repo.dirstate.copied(self._path)
2152
2162
2153 def size(self):
2163 def size(self):
2154 return self._repo.wvfs.lstat(self._path).st_size
2164 return self._repo.wvfs.lstat(self._path).st_size
2155
2165
2156 def lstat(self):
2166 def lstat(self):
2157 return self._repo.wvfs.lstat(self._path)
2167 return self._repo.wvfs.lstat(self._path)
2158
2168
2159 def date(self):
2169 def date(self):
2160 t, tz = self._changectx.date()
2170 t, tz = self._changectx.date()
2161 try:
2171 try:
2162 return (self._repo.wvfs.lstat(self._path)[stat.ST_MTIME], tz)
2172 return (self._repo.wvfs.lstat(self._path)[stat.ST_MTIME], tz)
2163 except OSError as err:
2173 except OSError as err:
2164 if err.errno != errno.ENOENT:
2174 if err.errno != errno.ENOENT:
2165 raise
2175 raise
2166 return (t, tz)
2176 return (t, tz)
2167
2177
2168 def exists(self):
2178 def exists(self):
2169 return self._repo.wvfs.exists(self._path)
2179 return self._repo.wvfs.exists(self._path)
2170
2180
2171 def lexists(self):
2181 def lexists(self):
2172 return self._repo.wvfs.lexists(self._path)
2182 return self._repo.wvfs.lexists(self._path)
2173
2183
2174 def audit(self):
2184 def audit(self):
2175 return self._repo.wvfs.audit(self._path)
2185 return self._repo.wvfs.audit(self._path)
2176
2186
2177 def cmp(self, fctx):
2187 def cmp(self, fctx):
2178 """compare with other file context
2188 """compare with other file context
2179
2189
2180 returns True if different than fctx.
2190 returns True if different than fctx.
2181 """
2191 """
2182 # fctx should be a filectx (not a workingfilectx)
2192 # fctx should be a filectx (not a workingfilectx)
2183 # invert comparison to reuse the same code path
2193 # invert comparison to reuse the same code path
2184 return fctx.cmp(self)
2194 return fctx.cmp(self)
2185
2195
2186 def remove(self, ignoremissing=False):
2196 def remove(self, ignoremissing=False):
2187 """wraps unlink for a repo's working directory"""
2197 """wraps unlink for a repo's working directory"""
2188 rmdir = self._repo.ui.configbool(b'experimental', b'removeemptydirs')
2198 rmdir = self._repo.ui.configbool(b'experimental', b'removeemptydirs')
2189 self._repo.wvfs.unlinkpath(
2199 self._repo.wvfs.unlinkpath(
2190 self._path, ignoremissing=ignoremissing, rmdir=rmdir
2200 self._path, ignoremissing=ignoremissing, rmdir=rmdir
2191 )
2201 )
2192
2202
2193 def write(self, data, flags, backgroundclose=False, **kwargs):
2203 def write(self, data, flags, backgroundclose=False, **kwargs):
2194 """wraps repo.wwrite"""
2204 """wraps repo.wwrite"""
2195 return self._repo.wwrite(
2205 return self._repo.wwrite(
2196 self._path, data, flags, backgroundclose=backgroundclose, **kwargs
2206 self._path, data, flags, backgroundclose=backgroundclose, **kwargs
2197 )
2207 )
2198
2208
2199 def markcopied(self, src):
2209 def markcopied(self, src):
2200 """marks this file a copy of `src`"""
2210 """marks this file a copy of `src`"""
2201 self._repo.dirstate.copy(src, self._path)
2211 self._repo.dirstate.copy(src, self._path)
2202
2212
2203 def clearunknown(self):
2213 def clearunknown(self):
2204 """Removes conflicting items in the working directory so that
2214 """Removes conflicting items in the working directory so that
2205 ``write()`` can be called successfully.
2215 ``write()`` can be called successfully.
2206 """
2216 """
2207 wvfs = self._repo.wvfs
2217 wvfs = self._repo.wvfs
2208 f = self._path
2218 f = self._path
2209 wvfs.audit(f)
2219 wvfs.audit(f)
2210 if self._repo.ui.configbool(
2220 if self._repo.ui.configbool(
2211 b'experimental', b'merge.checkpathconflicts'
2221 b'experimental', b'merge.checkpathconflicts'
2212 ):
2222 ):
2213 # remove files under the directory as they should already be
2223 # remove files under the directory as they should already be
2214 # warned and backed up
2224 # warned and backed up
2215 if wvfs.isdir(f) and not wvfs.islink(f):
2225 if wvfs.isdir(f) and not wvfs.islink(f):
2216 wvfs.rmtree(f, forcibly=True)
2226 wvfs.rmtree(f, forcibly=True)
2217 for p in reversed(list(pathutil.finddirs(f))):
2227 for p in reversed(list(pathutil.finddirs(f))):
2218 if wvfs.isfileorlink(p):
2228 if wvfs.isfileorlink(p):
2219 wvfs.unlink(p)
2229 wvfs.unlink(p)
2220 break
2230 break
2221 else:
2231 else:
2222 # don't remove files if path conflicts are not processed
2232 # don't remove files if path conflicts are not processed
2223 if wvfs.isdir(f) and not wvfs.islink(f):
2233 if wvfs.isdir(f) and not wvfs.islink(f):
2224 wvfs.removedirs(f)
2234 wvfs.removedirs(f)
2225
2235
2226 def setflags(self, l, x):
2236 def setflags(self, l, x):
2227 self._repo.wvfs.setflags(self._path, l, x)
2237 self._repo.wvfs.setflags(self._path, l, x)
2228
2238
2229
2239
2230 class overlayworkingctx(committablectx):
2240 class overlayworkingctx(committablectx):
2231 """Wraps another mutable context with a write-back cache that can be
2241 """Wraps another mutable context with a write-back cache that can be
2232 converted into a commit context.
2242 converted into a commit context.
2233
2243
2234 self._cache[path] maps to a dict with keys: {
2244 self._cache[path] maps to a dict with keys: {
2235 'exists': bool?
2245 'exists': bool?
2236 'date': date?
2246 'date': date?
2237 'data': str?
2247 'data': str?
2238 'flags': str?
2248 'flags': str?
2239 'copied': str? (path or None)
2249 'copied': str? (path or None)
2240 }
2250 }
2241 If `exists` is True, `flags` must be non-None and 'date' is non-None. If it
2251 If `exists` is True, `flags` must be non-None and 'date' is non-None. If it
2242 is `False`, the file was deleted.
2252 is `False`, the file was deleted.
2243 """
2253 """
2244
2254
2245 def __init__(self, repo):
2255 def __init__(self, repo):
2246 super(overlayworkingctx, self).__init__(repo)
2256 super(overlayworkingctx, self).__init__(repo)
2247 self.clean()
2257 self.clean()
2248
2258
2249 def setbase(self, wrappedctx):
2259 def setbase(self, wrappedctx):
2250 self._wrappedctx = wrappedctx
2260 self._wrappedctx = wrappedctx
2251 self._parents = [wrappedctx]
2261 self._parents = [wrappedctx]
2252 # Drop old manifest cache as it is now out of date.
2262 # Drop old manifest cache as it is now out of date.
2253 # This is necessary when, e.g., rebasing several nodes with one
2263 # This is necessary when, e.g., rebasing several nodes with one
2254 # ``overlayworkingctx`` (e.g. with --collapse).
2264 # ``overlayworkingctx`` (e.g. with --collapse).
2255 util.clearcachedproperty(self, b'_manifest')
2265 util.clearcachedproperty(self, b'_manifest')
2256
2266
2257 def setparents(self, p1node, p2node=None):
2267 def setparents(self, p1node, p2node=None):
2258 if p2node is None:
2268 if p2node is None:
2259 p2node = self._repo.nodeconstants.nullid
2269 p2node = self._repo.nodeconstants.nullid
2260 assert p1node == self._wrappedctx.node()
2270 assert p1node == self._wrappedctx.node()
2261 self._parents = [self._wrappedctx, self._repo.unfiltered()[p2node]]
2271 self._parents = [self._wrappedctx, self._repo.unfiltered()[p2node]]
2262
2272
2263 def data(self, path):
2273 def data(self, path):
2264 if self.isdirty(path):
2274 if self.isdirty(path):
2265 if self._cache[path][b'exists']:
2275 if self._cache[path][b'exists']:
2266 if self._cache[path][b'data'] is not None:
2276 if self._cache[path][b'data'] is not None:
2267 return self._cache[path][b'data']
2277 return self._cache[path][b'data']
2268 else:
2278 else:
2269 # Must fallback here, too, because we only set flags.
2279 # Must fallback here, too, because we only set flags.
2270 return self._wrappedctx[path].data()
2280 return self._wrappedctx[path].data()
2271 else:
2281 else:
2272 raise error.ProgrammingError(
2282 raise error.ProgrammingError(
2273 b"No such file or directory: %s" % path
2283 b"No such file or directory: %s" % path
2274 )
2284 )
2275 else:
2285 else:
2276 return self._wrappedctx[path].data()
2286 return self._wrappedctx[path].data()
2277
2287
2278 @propertycache
2288 @propertycache
2279 def _manifest(self):
2289 def _manifest(self):
2280 parents = self.parents()
2290 parents = self.parents()
2281 man = parents[0].manifest().copy()
2291 man = parents[0].manifest().copy()
2282
2292
2283 flag = self._flagfunc
2293 flag = self._flagfunc
2284 for path in self.added():
2294 for path in self.added():
2285 man[path] = self._repo.nodeconstants.addednodeid
2295 man[path] = self._repo.nodeconstants.addednodeid
2286 man.setflag(path, flag(path))
2296 man.setflag(path, flag(path))
2287 for path in self.modified():
2297 for path in self.modified():
2288 man[path] = self._repo.nodeconstants.modifiednodeid
2298 man[path] = self._repo.nodeconstants.modifiednodeid
2289 man.setflag(path, flag(path))
2299 man.setflag(path, flag(path))
2290 for path in self.removed():
2300 for path in self.removed():
2291 del man[path]
2301 del man[path]
2292 return man
2302 return man
2293
2303
2294 @propertycache
2304 @propertycache
2295 def _flagfunc(self):
2305 def _flagfunc(self):
2296 def f(path):
2306 def f(path):
2297 return self._cache[path][b'flags']
2307 return self._cache[path][b'flags']
2298
2308
2299 return f
2309 return f
2300
2310
2301 def files(self):
2311 def files(self):
2302 return sorted(self.added() + self.modified() + self.removed())
2312 return sorted(self.added() + self.modified() + self.removed())
2303
2313
2304 def modified(self):
2314 def modified(self):
2305 return [
2315 return [
2306 f
2316 f
2307 for f in self._cache.keys()
2317 for f in self._cache.keys()
2308 if self._cache[f][b'exists'] and self._existsinparent(f)
2318 if self._cache[f][b'exists'] and self._existsinparent(f)
2309 ]
2319 ]
2310
2320
2311 def added(self):
2321 def added(self):
2312 return [
2322 return [
2313 f
2323 f
2314 for f in self._cache.keys()
2324 for f in self._cache.keys()
2315 if self._cache[f][b'exists'] and not self._existsinparent(f)
2325 if self._cache[f][b'exists'] and not self._existsinparent(f)
2316 ]
2326 ]
2317
2327
2318 def removed(self):
2328 def removed(self):
2319 return [
2329 return [
2320 f
2330 f
2321 for f in self._cache.keys()
2331 for f in self._cache.keys()
2322 if not self._cache[f][b'exists'] and self._existsinparent(f)
2332 if not self._cache[f][b'exists'] and self._existsinparent(f)
2323 ]
2333 ]
2324
2334
2325 def p1copies(self):
2335 def p1copies(self):
2326 copies = {}
2336 copies = {}
2327 narrowmatch = self._repo.narrowmatch()
2337 narrowmatch = self._repo.narrowmatch()
2328 for f in self._cache.keys():
2338 for f in self._cache.keys():
2329 if not narrowmatch(f):
2339 if not narrowmatch(f):
2330 continue
2340 continue
2331 copies.pop(f, None) # delete if it exists
2341 copies.pop(f, None) # delete if it exists
2332 source = self._cache[f][b'copied']
2342 source = self._cache[f][b'copied']
2333 if source:
2343 if source:
2334 copies[f] = source
2344 copies[f] = source
2335 return copies
2345 return copies
2336
2346
2337 def p2copies(self):
2347 def p2copies(self):
2338 copies = {}
2348 copies = {}
2339 narrowmatch = self._repo.narrowmatch()
2349 narrowmatch = self._repo.narrowmatch()
2340 for f in self._cache.keys():
2350 for f in self._cache.keys():
2341 if not narrowmatch(f):
2351 if not narrowmatch(f):
2342 continue
2352 continue
2343 copies.pop(f, None) # delete if it exists
2353 copies.pop(f, None) # delete if it exists
2344 source = self._cache[f][b'copied']
2354 source = self._cache[f][b'copied']
2345 if source:
2355 if source:
2346 copies[f] = source
2356 copies[f] = source
2347 return copies
2357 return copies
2348
2358
2349 def isinmemory(self):
2359 def isinmemory(self):
2350 return True
2360 return True
2351
2361
2352 def filedate(self, path):
2362 def filedate(self, path):
2353 if self.isdirty(path):
2363 if self.isdirty(path):
2354 return self._cache[path][b'date']
2364 return self._cache[path][b'date']
2355 else:
2365 else:
2356 return self._wrappedctx[path].date()
2366 return self._wrappedctx[path].date()
2357
2367
2358 def markcopied(self, path, origin):
2368 def markcopied(self, path, origin):
2359 self._markdirty(
2369 self._markdirty(
2360 path,
2370 path,
2361 exists=True,
2371 exists=True,
2362 date=self.filedate(path),
2372 date=self.filedate(path),
2363 flags=self.flags(path),
2373 flags=self.flags(path),
2364 copied=origin,
2374 copied=origin,
2365 )
2375 )
2366
2376
2367 def copydata(self, path):
2377 def copydata(self, path):
2368 if self.isdirty(path):
2378 if self.isdirty(path):
2369 return self._cache[path][b'copied']
2379 return self._cache[path][b'copied']
2370 else:
2380 else:
2371 return None
2381 return None
2372
2382
2373 def flags(self, path):
2383 def flags(self, path):
2374 if self.isdirty(path):
2384 if self.isdirty(path):
2375 if self._cache[path][b'exists']:
2385 if self._cache[path][b'exists']:
2376 return self._cache[path][b'flags']
2386 return self._cache[path][b'flags']
2377 else:
2387 else:
2378 raise error.ProgrammingError(
2388 raise error.ProgrammingError(
2379 b"No such file or directory: %s" % path
2389 b"No such file or directory: %s" % path
2380 )
2390 )
2381 else:
2391 else:
2382 return self._wrappedctx[path].flags()
2392 return self._wrappedctx[path].flags()
2383
2393
2384 def __contains__(self, key):
2394 def __contains__(self, key):
2385 if key in self._cache:
2395 if key in self._cache:
2386 return self._cache[key][b'exists']
2396 return self._cache[key][b'exists']
2387 return key in self.p1()
2397 return key in self.p1()
2388
2398
2389 def _existsinparent(self, path):
2399 def _existsinparent(self, path):
2390 try:
2400 try:
2391 # ``commitctx` raises a ``ManifestLookupError`` if a path does not
2401 # ``commitctx` raises a ``ManifestLookupError`` if a path does not
2392 # exist, unlike ``workingctx``, which returns a ``workingfilectx``
2402 # exist, unlike ``workingctx``, which returns a ``workingfilectx``
2393 # with an ``exists()`` function.
2403 # with an ``exists()`` function.
2394 self._wrappedctx[path]
2404 self._wrappedctx[path]
2395 return True
2405 return True
2396 except error.ManifestLookupError:
2406 except error.ManifestLookupError:
2397 return False
2407 return False
2398
2408
2399 def _auditconflicts(self, path):
2409 def _auditconflicts(self, path):
2400 """Replicates conflict checks done by wvfs.write().
2410 """Replicates conflict checks done by wvfs.write().
2401
2411
2402 Since we never write to the filesystem and never call `applyupdates` in
2412 Since we never write to the filesystem and never call `applyupdates` in
2403 IMM, we'll never check that a path is actually writable -- e.g., because
2413 IMM, we'll never check that a path is actually writable -- e.g., because
2404 it adds `a/foo`, but `a` is actually a file in the other commit.
2414 it adds `a/foo`, but `a` is actually a file in the other commit.
2405 """
2415 """
2406
2416
2407 def fail(path, component):
2417 def fail(path, component):
2408 # p1() is the base and we're receiving "writes" for p2()'s
2418 # p1() is the base and we're receiving "writes" for p2()'s
2409 # files.
2419 # files.
2410 if b'l' in self.p1()[component].flags():
2420 if b'l' in self.p1()[component].flags():
2411 raise error.Abort(
2421 raise error.Abort(
2412 b"error: %s conflicts with symlink %s "
2422 b"error: %s conflicts with symlink %s "
2413 b"in %d." % (path, component, self.p1().rev())
2423 b"in %d." % (path, component, self.p1().rev())
2414 )
2424 )
2415 else:
2425 else:
2416 raise error.Abort(
2426 raise error.Abort(
2417 b"error: '%s' conflicts with file '%s' in "
2427 b"error: '%s' conflicts with file '%s' in "
2418 b"%d." % (path, component, self.p1().rev())
2428 b"%d." % (path, component, self.p1().rev())
2419 )
2429 )
2420
2430
2421 # Test that each new directory to be created to write this path from p2
2431 # Test that each new directory to be created to write this path from p2
2422 # is not a file in p1.
2432 # is not a file in p1.
2423 components = path.split(b'/')
2433 components = path.split(b'/')
2424 for i in pycompat.xrange(len(components)):
2434 for i in pycompat.xrange(len(components)):
2425 component = b"/".join(components[0:i])
2435 component = b"/".join(components[0:i])
2426 if component in self:
2436 if component in self:
2427 fail(path, component)
2437 fail(path, component)
2428
2438
2429 # Test the other direction -- that this path from p2 isn't a directory
2439 # Test the other direction -- that this path from p2 isn't a directory
2430 # in p1 (test that p1 doesn't have any paths matching `path/*`).
2440 # in p1 (test that p1 doesn't have any paths matching `path/*`).
2431 match = self.match([path], default=b'path')
2441 match = self.match([path], default=b'path')
2432 mfiles = list(self.p1().manifest().walk(match))
2442 mfiles = list(self.p1().manifest().walk(match))
2433 if len(mfiles) > 0:
2443 if len(mfiles) > 0:
2434 if len(mfiles) == 1 and mfiles[0] == path:
2444 if len(mfiles) == 1 and mfiles[0] == path:
2435 return
2445 return
2436 # omit the files which are deleted in current IMM wctx
2446 # omit the files which are deleted in current IMM wctx
2437 mfiles = [m for m in mfiles if m in self]
2447 mfiles = [m for m in mfiles if m in self]
2438 if not mfiles:
2448 if not mfiles:
2439 return
2449 return
2440 raise error.Abort(
2450 raise error.Abort(
2441 b"error: file '%s' cannot be written because "
2451 b"error: file '%s' cannot be written because "
2442 b" '%s/' is a directory in %s (containing %d "
2452 b" '%s/' is a directory in %s (containing %d "
2443 b"entries: %s)"
2453 b"entries: %s)"
2444 % (path, path, self.p1(), len(mfiles), b', '.join(mfiles))
2454 % (path, path, self.p1(), len(mfiles), b', '.join(mfiles))
2445 )
2455 )
2446
2456
2447 def write(self, path, data, flags=b'', **kwargs):
2457 def write(self, path, data, flags=b'', **kwargs):
2448 if data is None:
2458 if data is None:
2449 raise error.ProgrammingError(b"data must be non-None")
2459 raise error.ProgrammingError(b"data must be non-None")
2450 self._auditconflicts(path)
2460 self._auditconflicts(path)
2451 self._markdirty(
2461 self._markdirty(
2452 path, exists=True, data=data, date=dateutil.makedate(), flags=flags
2462 path, exists=True, data=data, date=dateutil.makedate(), flags=flags
2453 )
2463 )
2454
2464
2455 def setflags(self, path, l, x):
2465 def setflags(self, path, l, x):
2456 flag = b''
2466 flag = b''
2457 if l:
2467 if l:
2458 flag = b'l'
2468 flag = b'l'
2459 elif x:
2469 elif x:
2460 flag = b'x'
2470 flag = b'x'
2461 self._markdirty(path, exists=True, date=dateutil.makedate(), flags=flag)
2471 self._markdirty(path, exists=True, date=dateutil.makedate(), flags=flag)
2462
2472
2463 def remove(self, path):
2473 def remove(self, path):
2464 self._markdirty(path, exists=False)
2474 self._markdirty(path, exists=False)
2465
2475
2466 def exists(self, path):
2476 def exists(self, path):
2467 """exists behaves like `lexists`, but needs to follow symlinks and
2477 """exists behaves like `lexists`, but needs to follow symlinks and
2468 return False if they are broken.
2478 return False if they are broken.
2469 """
2479 """
2470 if self.isdirty(path):
2480 if self.isdirty(path):
2471 # If this path exists and is a symlink, "follow" it by calling
2481 # If this path exists and is a symlink, "follow" it by calling
2472 # exists on the destination path.
2482 # exists on the destination path.
2473 if (
2483 if (
2474 self._cache[path][b'exists']
2484 self._cache[path][b'exists']
2475 and b'l' in self._cache[path][b'flags']
2485 and b'l' in self._cache[path][b'flags']
2476 ):
2486 ):
2477 return self.exists(self._cache[path][b'data'].strip())
2487 return self.exists(self._cache[path][b'data'].strip())
2478 else:
2488 else:
2479 return self._cache[path][b'exists']
2489 return self._cache[path][b'exists']
2480
2490
2481 return self._existsinparent(path)
2491 return self._existsinparent(path)
2482
2492
2483 def lexists(self, path):
2493 def lexists(self, path):
2484 """lexists returns True if the path exists"""
2494 """lexists returns True if the path exists"""
2485 if self.isdirty(path):
2495 if self.isdirty(path):
2486 return self._cache[path][b'exists']
2496 return self._cache[path][b'exists']
2487
2497
2488 return self._existsinparent(path)
2498 return self._existsinparent(path)
2489
2499
2490 def size(self, path):
2500 def size(self, path):
2491 if self.isdirty(path):
2501 if self.isdirty(path):
2492 if self._cache[path][b'exists']:
2502 if self._cache[path][b'exists']:
2493 return len(self._cache[path][b'data'])
2503 return len(self._cache[path][b'data'])
2494 else:
2504 else:
2495 raise error.ProgrammingError(
2505 raise error.ProgrammingError(
2496 b"No such file or directory: %s" % path
2506 b"No such file or directory: %s" % path
2497 )
2507 )
2498 return self._wrappedctx[path].size()
2508 return self._wrappedctx[path].size()
2499
2509
2500 def tomemctx(
2510 def tomemctx(
2501 self,
2511 self,
2502 text,
2512 text,
2503 branch=None,
2513 branch=None,
2504 extra=None,
2514 extra=None,
2505 date=None,
2515 date=None,
2506 parents=None,
2516 parents=None,
2507 user=None,
2517 user=None,
2508 editor=None,
2518 editor=None,
2509 ):
2519 ):
2510 """Converts this ``overlayworkingctx`` into a ``memctx`` ready to be
2520 """Converts this ``overlayworkingctx`` into a ``memctx`` ready to be
2511 committed.
2521 committed.
2512
2522
2513 ``text`` is the commit message.
2523 ``text`` is the commit message.
2514 ``parents`` (optional) are rev numbers.
2524 ``parents`` (optional) are rev numbers.
2515 """
2525 """
2516 # Default parents to the wrapped context if not passed.
2526 # Default parents to the wrapped context if not passed.
2517 if parents is None:
2527 if parents is None:
2518 parents = self.parents()
2528 parents = self.parents()
2519 if len(parents) == 1:
2529 if len(parents) == 1:
2520 parents = (parents[0], None)
2530 parents = (parents[0], None)
2521
2531
2522 # ``parents`` is passed as rev numbers; convert to ``commitctxs``.
2532 # ``parents`` is passed as rev numbers; convert to ``commitctxs``.
2523 if parents[1] is None:
2533 if parents[1] is None:
2524 parents = (self._repo[parents[0]], None)
2534 parents = (self._repo[parents[0]], None)
2525 else:
2535 else:
2526 parents = (self._repo[parents[0]], self._repo[parents[1]])
2536 parents = (self._repo[parents[0]], self._repo[parents[1]])
2527
2537
2528 files = self.files()
2538 files = self.files()
2529
2539
2530 def getfile(repo, memctx, path):
2540 def getfile(repo, memctx, path):
2531 if self._cache[path][b'exists']:
2541 if self._cache[path][b'exists']:
2532 return memfilectx(
2542 return memfilectx(
2533 repo,
2543 repo,
2534 memctx,
2544 memctx,
2535 path,
2545 path,
2536 self._cache[path][b'data'],
2546 self._cache[path][b'data'],
2537 b'l' in self._cache[path][b'flags'],
2547 b'l' in self._cache[path][b'flags'],
2538 b'x' in self._cache[path][b'flags'],
2548 b'x' in self._cache[path][b'flags'],
2539 self._cache[path][b'copied'],
2549 self._cache[path][b'copied'],
2540 )
2550 )
2541 else:
2551 else:
2542 # Returning None, but including the path in `files`, is
2552 # Returning None, but including the path in `files`, is
2543 # necessary for memctx to register a deletion.
2553 # necessary for memctx to register a deletion.
2544 return None
2554 return None
2545
2555
2546 if branch is None:
2556 if branch is None:
2547 branch = self._wrappedctx.branch()
2557 branch = self._wrappedctx.branch()
2548
2558
2549 return memctx(
2559 return memctx(
2550 self._repo,
2560 self._repo,
2551 parents,
2561 parents,
2552 text,
2562 text,
2553 files,
2563 files,
2554 getfile,
2564 getfile,
2555 date=date,
2565 date=date,
2556 extra=extra,
2566 extra=extra,
2557 user=user,
2567 user=user,
2558 branch=branch,
2568 branch=branch,
2559 editor=editor,
2569 editor=editor,
2560 )
2570 )
2561
2571
2562 def tomemctx_for_amend(self, precursor):
2572 def tomemctx_for_amend(self, precursor):
2563 extra = precursor.extra().copy()
2573 extra = precursor.extra().copy()
2564 extra[b'amend_source'] = precursor.hex()
2574 extra[b'amend_source'] = precursor.hex()
2565 return self.tomemctx(
2575 return self.tomemctx(
2566 text=precursor.description(),
2576 text=precursor.description(),
2567 branch=precursor.branch(),
2577 branch=precursor.branch(),
2568 extra=extra,
2578 extra=extra,
2569 date=precursor.date(),
2579 date=precursor.date(),
2570 user=precursor.user(),
2580 user=precursor.user(),
2571 )
2581 )
2572
2582
2573 def isdirty(self, path):
2583 def isdirty(self, path):
2574 return path in self._cache
2584 return path in self._cache
2575
2585
2576 def clean(self):
2586 def clean(self):
2577 self._mergestate = None
2587 self._mergestate = None
2578 self._cache = {}
2588 self._cache = {}
2579
2589
2580 def _compact(self):
2590 def _compact(self):
2581 """Removes keys from the cache that are actually clean, by comparing
2591 """Removes keys from the cache that are actually clean, by comparing
2582 them with the underlying context.
2592 them with the underlying context.
2583
2593
2584 This can occur during the merge process, e.g. by passing --tool :local
2594 This can occur during the merge process, e.g. by passing --tool :local
2585 to resolve a conflict.
2595 to resolve a conflict.
2586 """
2596 """
2587 keys = []
2597 keys = []
2588 # This won't be perfect, but can help performance significantly when
2598 # This won't be perfect, but can help performance significantly when
2589 # using things like remotefilelog.
2599 # using things like remotefilelog.
2590 scmutil.prefetchfiles(
2600 scmutil.prefetchfiles(
2591 self.repo(),
2601 self.repo(),
2592 [
2602 [
2593 (
2603 (
2594 self.p1().rev(),
2604 self.p1().rev(),
2595 scmutil.matchfiles(self.repo(), self._cache.keys()),
2605 scmutil.matchfiles(self.repo(), self._cache.keys()),
2596 )
2606 )
2597 ],
2607 ],
2598 )
2608 )
2599
2609
2600 for path in self._cache.keys():
2610 for path in self._cache.keys():
2601 cache = self._cache[path]
2611 cache = self._cache[path]
2602 try:
2612 try:
2603 underlying = self._wrappedctx[path]
2613 underlying = self._wrappedctx[path]
2604 if (
2614 if (
2605 underlying.data() == cache[b'data']
2615 underlying.data() == cache[b'data']
2606 and underlying.flags() == cache[b'flags']
2616 and underlying.flags() == cache[b'flags']
2607 ):
2617 ):
2608 keys.append(path)
2618 keys.append(path)
2609 except error.ManifestLookupError:
2619 except error.ManifestLookupError:
2610 # Path not in the underlying manifest (created).
2620 # Path not in the underlying manifest (created).
2611 continue
2621 continue
2612
2622
2613 for path in keys:
2623 for path in keys:
2614 del self._cache[path]
2624 del self._cache[path]
2615 return keys
2625 return keys
2616
2626
2617 def _markdirty(
2627 def _markdirty(
2618 self, path, exists, data=None, date=None, flags=b'', copied=None
2628 self, path, exists, data=None, date=None, flags=b'', copied=None
2619 ):
2629 ):
2620 # data not provided, let's see if we already have some; if not, let's
2630 # data not provided, let's see if we already have some; if not, let's
2621 # grab it from our underlying context, so that we always have data if
2631 # grab it from our underlying context, so that we always have data if
2622 # the file is marked as existing.
2632 # the file is marked as existing.
2623 if exists and data is None:
2633 if exists and data is None:
2624 oldentry = self._cache.get(path) or {}
2634 oldentry = self._cache.get(path) or {}
2625 data = oldentry.get(b'data')
2635 data = oldentry.get(b'data')
2626 if data is None:
2636 if data is None:
2627 data = self._wrappedctx[path].data()
2637 data = self._wrappedctx[path].data()
2628
2638
2629 self._cache[path] = {
2639 self._cache[path] = {
2630 b'exists': exists,
2640 b'exists': exists,
2631 b'data': data,
2641 b'data': data,
2632 b'date': date,
2642 b'date': date,
2633 b'flags': flags,
2643 b'flags': flags,
2634 b'copied': copied,
2644 b'copied': copied,
2635 }
2645 }
2636 util.clearcachedproperty(self, b'_manifest')
2646 util.clearcachedproperty(self, b'_manifest')
2637
2647
2638 def filectx(self, path, filelog=None):
2648 def filectx(self, path, filelog=None):
2639 return overlayworkingfilectx(
2649 return overlayworkingfilectx(
2640 self._repo, path, parent=self, filelog=filelog
2650 self._repo, path, parent=self, filelog=filelog
2641 )
2651 )
2642
2652
2643 def mergestate(self, clean=False):
2653 def mergestate(self, clean=False):
2644 if clean or self._mergestate is None:
2654 if clean or self._mergestate is None:
2645 self._mergestate = mergestatemod.memmergestate(self._repo)
2655 self._mergestate = mergestatemod.memmergestate(self._repo)
2646 return self._mergestate
2656 return self._mergestate
2647
2657
2648
2658
2649 class overlayworkingfilectx(committablefilectx):
2659 class overlayworkingfilectx(committablefilectx):
2650 """Wrap a ``workingfilectx`` but intercepts all writes into an in-memory
2660 """Wrap a ``workingfilectx`` but intercepts all writes into an in-memory
2651 cache, which can be flushed through later by calling ``flush()``."""
2661 cache, which can be flushed through later by calling ``flush()``."""
2652
2662
2653 def __init__(self, repo, path, filelog=None, parent=None):
2663 def __init__(self, repo, path, filelog=None, parent=None):
2654 super(overlayworkingfilectx, self).__init__(repo, path, filelog, parent)
2664 super(overlayworkingfilectx, self).__init__(repo, path, filelog, parent)
2655 self._repo = repo
2665 self._repo = repo
2656 self._parent = parent
2666 self._parent = parent
2657 self._path = path
2667 self._path = path
2658
2668
2659 def cmp(self, fctx):
2669 def cmp(self, fctx):
2660 return self.data() != fctx.data()
2670 return self.data() != fctx.data()
2661
2671
2662 def changectx(self):
2672 def changectx(self):
2663 return self._parent
2673 return self._parent
2664
2674
2665 def data(self):
2675 def data(self):
2666 return self._parent.data(self._path)
2676 return self._parent.data(self._path)
2667
2677
2668 def date(self):
2678 def date(self):
2669 return self._parent.filedate(self._path)
2679 return self._parent.filedate(self._path)
2670
2680
2671 def exists(self):
2681 def exists(self):
2672 return self.lexists()
2682 return self.lexists()
2673
2683
2674 def lexists(self):
2684 def lexists(self):
2675 return self._parent.exists(self._path)
2685 return self._parent.exists(self._path)
2676
2686
2677 def copysource(self):
2687 def copysource(self):
2678 return self._parent.copydata(self._path)
2688 return self._parent.copydata(self._path)
2679
2689
2680 def size(self):
2690 def size(self):
2681 return self._parent.size(self._path)
2691 return self._parent.size(self._path)
2682
2692
2683 def markcopied(self, origin):
2693 def markcopied(self, origin):
2684 self._parent.markcopied(self._path, origin)
2694 self._parent.markcopied(self._path, origin)
2685
2695
2686 def audit(self):
2696 def audit(self):
2687 pass
2697 pass
2688
2698
2689 def flags(self):
2699 def flags(self):
2690 return self._parent.flags(self._path)
2700 return self._parent.flags(self._path)
2691
2701
2692 def setflags(self, islink, isexec):
2702 def setflags(self, islink, isexec):
2693 return self._parent.setflags(self._path, islink, isexec)
2703 return self._parent.setflags(self._path, islink, isexec)
2694
2704
2695 def write(self, data, flags, backgroundclose=False, **kwargs):
2705 def write(self, data, flags, backgroundclose=False, **kwargs):
2696 return self._parent.write(self._path, data, flags, **kwargs)
2706 return self._parent.write(self._path, data, flags, **kwargs)
2697
2707
2698 def remove(self, ignoremissing=False):
2708 def remove(self, ignoremissing=False):
2699 return self._parent.remove(self._path)
2709 return self._parent.remove(self._path)
2700
2710
2701 def clearunknown(self):
2711 def clearunknown(self):
2702 pass
2712 pass
2703
2713
2704
2714
2705 class workingcommitctx(workingctx):
2715 class workingcommitctx(workingctx):
2706 """A workingcommitctx object makes access to data related to
2716 """A workingcommitctx object makes access to data related to
2707 the revision being committed convenient.
2717 the revision being committed convenient.
2708
2718
2709 This hides changes in the working directory, if they aren't
2719 This hides changes in the working directory, if they aren't
2710 committed in this context.
2720 committed in this context.
2711 """
2721 """
2712
2722
2713 def __init__(
2723 def __init__(
2714 self, repo, changes, text=b"", user=None, date=None, extra=None
2724 self, repo, changes, text=b"", user=None, date=None, extra=None
2715 ):
2725 ):
2716 super(workingcommitctx, self).__init__(
2726 super(workingcommitctx, self).__init__(
2717 repo, text, user, date, extra, changes
2727 repo, text, user, date, extra, changes
2718 )
2728 )
2719
2729
2720 def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
2730 def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
2721 """Return matched files only in ``self._status``
2731 """Return matched files only in ``self._status``
2722
2732
2723 Uncommitted files appear "clean" via this context, even if
2733 Uncommitted files appear "clean" via this context, even if
2724 they aren't actually so in the working directory.
2734 they aren't actually so in the working directory.
2725 """
2735 """
2726 if clean:
2736 if clean:
2727 clean = [f for f in self._manifest if f not in self._changedset]
2737 clean = [f for f in self._manifest if f not in self._changedset]
2728 else:
2738 else:
2729 clean = []
2739 clean = []
2730 return scmutil.status(
2740 return scmutil.status(
2731 [f for f in self._status.modified if match(f)],
2741 [f for f in self._status.modified if match(f)],
2732 [f for f in self._status.added if match(f)],
2742 [f for f in self._status.added if match(f)],
2733 [f for f in self._status.removed if match(f)],
2743 [f for f in self._status.removed if match(f)],
2734 [],
2744 [],
2735 [],
2745 [],
2736 [],
2746 [],
2737 clean,
2747 clean,
2738 )
2748 )
2739
2749
2740 @propertycache
2750 @propertycache
2741 def _changedset(self):
2751 def _changedset(self):
2742 """Return the set of files changed in this context"""
2752 """Return the set of files changed in this context"""
2743 changed = set(self._status.modified)
2753 changed = set(self._status.modified)
2744 changed.update(self._status.added)
2754 changed.update(self._status.added)
2745 changed.update(self._status.removed)
2755 changed.update(self._status.removed)
2746 return changed
2756 return changed
2747
2757
2748
2758
2749 def makecachingfilectxfn(func):
2759 def makecachingfilectxfn(func):
2750 """Create a filectxfn that caches based on the path.
2760 """Create a filectxfn that caches based on the path.
2751
2761
2752 We can't use util.cachefunc because it uses all arguments as the cache
2762 We can't use util.cachefunc because it uses all arguments as the cache
2753 key and this creates a cycle since the arguments include the repo and
2763 key and this creates a cycle since the arguments include the repo and
2754 memctx.
2764 memctx.
2755 """
2765 """
2756 cache = {}
2766 cache = {}
2757
2767
2758 def getfilectx(repo, memctx, path):
2768 def getfilectx(repo, memctx, path):
2759 if path not in cache:
2769 if path not in cache:
2760 cache[path] = func(repo, memctx, path)
2770 cache[path] = func(repo, memctx, path)
2761 return cache[path]
2771 return cache[path]
2762
2772
2763 return getfilectx
2773 return getfilectx
2764
2774
2765
2775
2766 def memfilefromctx(ctx):
2776 def memfilefromctx(ctx):
2767 """Given a context return a memfilectx for ctx[path]
2777 """Given a context return a memfilectx for ctx[path]
2768
2778
2769 This is a convenience method for building a memctx based on another
2779 This is a convenience method for building a memctx based on another
2770 context.
2780 context.
2771 """
2781 """
2772
2782
2773 def getfilectx(repo, memctx, path):
2783 def getfilectx(repo, memctx, path):
2774 fctx = ctx[path]
2784 fctx = ctx[path]
2775 copysource = fctx.copysource()
2785 copysource = fctx.copysource()
2776 return memfilectx(
2786 return memfilectx(
2777 repo,
2787 repo,
2778 memctx,
2788 memctx,
2779 path,
2789 path,
2780 fctx.data(),
2790 fctx.data(),
2781 islink=fctx.islink(),
2791 islink=fctx.islink(),
2782 isexec=fctx.isexec(),
2792 isexec=fctx.isexec(),
2783 copysource=copysource,
2793 copysource=copysource,
2784 )
2794 )
2785
2795
2786 return getfilectx
2796 return getfilectx
2787
2797
2788
2798
2789 def memfilefrompatch(patchstore):
2799 def memfilefrompatch(patchstore):
2790 """Given a patch (e.g. patchstore object) return a memfilectx
2800 """Given a patch (e.g. patchstore object) return a memfilectx
2791
2801
2792 This is a convenience method for building a memctx based on a patchstore.
2802 This is a convenience method for building a memctx based on a patchstore.
2793 """
2803 """
2794
2804
2795 def getfilectx(repo, memctx, path):
2805 def getfilectx(repo, memctx, path):
2796 data, mode, copysource = patchstore.getfile(path)
2806 data, mode, copysource = patchstore.getfile(path)
2797 if data is None:
2807 if data is None:
2798 return None
2808 return None
2799 islink, isexec = mode
2809 islink, isexec = mode
2800 return memfilectx(
2810 return memfilectx(
2801 repo,
2811 repo,
2802 memctx,
2812 memctx,
2803 path,
2813 path,
2804 data,
2814 data,
2805 islink=islink,
2815 islink=islink,
2806 isexec=isexec,
2816 isexec=isexec,
2807 copysource=copysource,
2817 copysource=copysource,
2808 )
2818 )
2809
2819
2810 return getfilectx
2820 return getfilectx
2811
2821
2812
2822
2813 class memctx(committablectx):
2823 class memctx(committablectx):
2814 """Use memctx to perform in-memory commits via localrepo.commitctx().
2824 """Use memctx to perform in-memory commits via localrepo.commitctx().
2815
2825
2816 Revision information is supplied at initialization time while
2826 Revision information is supplied at initialization time while
2817 related files data and is made available through a callback
2827 related files data and is made available through a callback
2818 mechanism. 'repo' is the current localrepo, 'parents' is a
2828 mechanism. 'repo' is the current localrepo, 'parents' is a
2819 sequence of two parent revisions identifiers (pass None for every
2829 sequence of two parent revisions identifiers (pass None for every
2820 missing parent), 'text' is the commit message and 'files' lists
2830 missing parent), 'text' is the commit message and 'files' lists
2821 names of files touched by the revision (normalized and relative to
2831 names of files touched by the revision (normalized and relative to
2822 repository root).
2832 repository root).
2823
2833
2824 filectxfn(repo, memctx, path) is a callable receiving the
2834 filectxfn(repo, memctx, path) is a callable receiving the
2825 repository, the current memctx object and the normalized path of
2835 repository, the current memctx object and the normalized path of
2826 requested file, relative to repository root. It is fired by the
2836 requested file, relative to repository root. It is fired by the
2827 commit function for every file in 'files', but calls order is
2837 commit function for every file in 'files', but calls order is
2828 undefined. If the file is available in the revision being
2838 undefined. If the file is available in the revision being
2829 committed (updated or added), filectxfn returns a memfilectx
2839 committed (updated or added), filectxfn returns a memfilectx
2830 object. If the file was removed, filectxfn return None for recent
2840 object. If the file was removed, filectxfn return None for recent
2831 Mercurial. Moved files are represented by marking the source file
2841 Mercurial. Moved files are represented by marking the source file
2832 removed and the new file added with copy information (see
2842 removed and the new file added with copy information (see
2833 memfilectx).
2843 memfilectx).
2834
2844
2835 user receives the committer name and defaults to current
2845 user receives the committer name and defaults to current
2836 repository username, date is the commit date in any format
2846 repository username, date is the commit date in any format
2837 supported by dateutil.parsedate() and defaults to current date, extra
2847 supported by dateutil.parsedate() and defaults to current date, extra
2838 is a dictionary of metadata or is left empty.
2848 is a dictionary of metadata or is left empty.
2839 """
2849 """
2840
2850
2841 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
2851 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
2842 # Extensions that need to retain compatibility across Mercurial 3.1 can use
2852 # Extensions that need to retain compatibility across Mercurial 3.1 can use
2843 # this field to determine what to do in filectxfn.
2853 # this field to determine what to do in filectxfn.
2844 _returnnoneformissingfiles = True
2854 _returnnoneformissingfiles = True
2845
2855
2846 def __init__(
2856 def __init__(
2847 self,
2857 self,
2848 repo,
2858 repo,
2849 parents,
2859 parents,
2850 text,
2860 text,
2851 files,
2861 files,
2852 filectxfn,
2862 filectxfn,
2853 user=None,
2863 user=None,
2854 date=None,
2864 date=None,
2855 extra=None,
2865 extra=None,
2856 branch=None,
2866 branch=None,
2857 editor=None,
2867 editor=None,
2858 ):
2868 ):
2859 super(memctx, self).__init__(
2869 super(memctx, self).__init__(
2860 repo, text, user, date, extra, branch=branch
2870 repo, text, user, date, extra, branch=branch
2861 )
2871 )
2862 self._rev = None
2872 self._rev = None
2863 self._node = None
2873 self._node = None
2864 parents = [(p or self._repo.nodeconstants.nullid) for p in parents]
2874 parents = [(p or self._repo.nodeconstants.nullid) for p in parents]
2865 p1, p2 = parents
2875 p1, p2 = parents
2866 self._parents = [self._repo[p] for p in (p1, p2)]
2876 self._parents = [self._repo[p] for p in (p1, p2)]
2867 files = sorted(set(files))
2877 files = sorted(set(files))
2868 self._files = files
2878 self._files = files
2869 self.substate = {}
2879 self.substate = {}
2870
2880
2871 if isinstance(filectxfn, patch.filestore):
2881 if isinstance(filectxfn, patch.filestore):
2872 filectxfn = memfilefrompatch(filectxfn)
2882 filectxfn = memfilefrompatch(filectxfn)
2873 elif not callable(filectxfn):
2883 elif not callable(filectxfn):
2874 # if store is not callable, wrap it in a function
2884 # if store is not callable, wrap it in a function
2875 filectxfn = memfilefromctx(filectxfn)
2885 filectxfn = memfilefromctx(filectxfn)
2876
2886
2877 # memoizing increases performance for e.g. vcs convert scenarios.
2887 # memoizing increases performance for e.g. vcs convert scenarios.
2878 self._filectxfn = makecachingfilectxfn(filectxfn)
2888 self._filectxfn = makecachingfilectxfn(filectxfn)
2879
2889
2880 if editor:
2890 if editor:
2881 self._text = editor(self._repo, self, [])
2891 self._text = editor(self._repo, self, [])
2882 self._repo.savecommitmessage(self._text)
2892 self._repo.savecommitmessage(self._text)
2883
2893
2884 def filectx(self, path, filelog=None):
2894 def filectx(self, path, filelog=None):
2885 """get a file context from the working directory
2895 """get a file context from the working directory
2886
2896
2887 Returns None if file doesn't exist and should be removed."""
2897 Returns None if file doesn't exist and should be removed."""
2888 return self._filectxfn(self._repo, self, path)
2898 return self._filectxfn(self._repo, self, path)
2889
2899
2890 def commit(self):
2900 def commit(self):
2891 """commit context to the repo"""
2901 """commit context to the repo"""
2892 return self._repo.commitctx(self)
2902 return self._repo.commitctx(self)
2893
2903
2894 @propertycache
2904 @propertycache
2895 def _manifest(self):
2905 def _manifest(self):
2896 """generate a manifest based on the return values of filectxfn"""
2906 """generate a manifest based on the return values of filectxfn"""
2897
2907
2898 # keep this simple for now; just worry about p1
2908 # keep this simple for now; just worry about p1
2899 pctx = self._parents[0]
2909 pctx = self._parents[0]
2900 man = pctx.manifest().copy()
2910 man = pctx.manifest().copy()
2901
2911
2902 for f in self._status.modified:
2912 for f in self._status.modified:
2903 man[f] = self._repo.nodeconstants.modifiednodeid
2913 man[f] = self._repo.nodeconstants.modifiednodeid
2904
2914
2905 for f in self._status.added:
2915 for f in self._status.added:
2906 man[f] = self._repo.nodeconstants.addednodeid
2916 man[f] = self._repo.nodeconstants.addednodeid
2907
2917
2908 for f in self._status.removed:
2918 for f in self._status.removed:
2909 if f in man:
2919 if f in man:
2910 del man[f]
2920 del man[f]
2911
2921
2912 return man
2922 return man
2913
2923
2914 @propertycache
2924 @propertycache
2915 def _status(self):
2925 def _status(self):
2916 """Calculate exact status from ``files`` specified at construction"""
2926 """Calculate exact status from ``files`` specified at construction"""
2917 man1 = self.p1().manifest()
2927 man1 = self.p1().manifest()
2918 p2 = self._parents[1]
2928 p2 = self._parents[1]
2919 # "1 < len(self._parents)" can't be used for checking
2929 # "1 < len(self._parents)" can't be used for checking
2920 # existence of the 2nd parent, because "memctx._parents" is
2930 # existence of the 2nd parent, because "memctx._parents" is
2921 # explicitly initialized by the list, of which length is 2.
2931 # explicitly initialized by the list, of which length is 2.
2922 if p2.rev() != nullrev:
2932 if p2.rev() != nullrev:
2923 man2 = p2.manifest()
2933 man2 = p2.manifest()
2924 managing = lambda f: f in man1 or f in man2
2934 managing = lambda f: f in man1 or f in man2
2925 else:
2935 else:
2926 managing = lambda f: f in man1
2936 managing = lambda f: f in man1
2927
2937
2928 modified, added, removed = [], [], []
2938 modified, added, removed = [], [], []
2929 for f in self._files:
2939 for f in self._files:
2930 if not managing(f):
2940 if not managing(f):
2931 added.append(f)
2941 added.append(f)
2932 elif self[f]:
2942 elif self[f]:
2933 modified.append(f)
2943 modified.append(f)
2934 else:
2944 else:
2935 removed.append(f)
2945 removed.append(f)
2936
2946
2937 return scmutil.status(modified, added, removed, [], [], [], [])
2947 return scmutil.status(modified, added, removed, [], [], [], [])
2938
2948
2939 def parents(self):
2949 def parents(self):
2940 if self._parents[1].rev() == nullrev:
2950 if self._parents[1].rev() == nullrev:
2941 return [self._parents[0]]
2951 return [self._parents[0]]
2942 return self._parents
2952 return self._parents
2943
2953
2944
2954
2945 class memfilectx(committablefilectx):
2955 class memfilectx(committablefilectx):
2946 """memfilectx represents an in-memory file to commit.
2956 """memfilectx represents an in-memory file to commit.
2947
2957
2948 See memctx and committablefilectx for more details.
2958 See memctx and committablefilectx for more details.
2949 """
2959 """
2950
2960
2951 def __init__(
2961 def __init__(
2952 self,
2962 self,
2953 repo,
2963 repo,
2954 changectx,
2964 changectx,
2955 path,
2965 path,
2956 data,
2966 data,
2957 islink=False,
2967 islink=False,
2958 isexec=False,
2968 isexec=False,
2959 copysource=None,
2969 copysource=None,
2960 ):
2970 ):
2961 """
2971 """
2962 path is the normalized file path relative to repository root.
2972 path is the normalized file path relative to repository root.
2963 data is the file content as a string.
2973 data is the file content as a string.
2964 islink is True if the file is a symbolic link.
2974 islink is True if the file is a symbolic link.
2965 isexec is True if the file is executable.
2975 isexec is True if the file is executable.
2966 copied is the source file path if current file was copied in the
2976 copied is the source file path if current file was copied in the
2967 revision being committed, or None."""
2977 revision being committed, or None."""
2968 super(memfilectx, self).__init__(repo, path, None, changectx)
2978 super(memfilectx, self).__init__(repo, path, None, changectx)
2969 self._data = data
2979 self._data = data
2970 if islink:
2980 if islink:
2971 self._flags = b'l'
2981 self._flags = b'l'
2972 elif isexec:
2982 elif isexec:
2973 self._flags = b'x'
2983 self._flags = b'x'
2974 else:
2984 else:
2975 self._flags = b''
2985 self._flags = b''
2976 self._copysource = copysource
2986 self._copysource = copysource
2977
2987
2978 def copysource(self):
2988 def copysource(self):
2979 return self._copysource
2989 return self._copysource
2980
2990
2981 def cmp(self, fctx):
2991 def cmp(self, fctx):
2982 return self.data() != fctx.data()
2992 return self.data() != fctx.data()
2983
2993
2984 def data(self):
2994 def data(self):
2985 return self._data
2995 return self._data
2986
2996
2987 def remove(self, ignoremissing=False):
2997 def remove(self, ignoremissing=False):
2988 """wraps unlink for a repo's working directory"""
2998 """wraps unlink for a repo's working directory"""
2989 # need to figure out what to do here
2999 # need to figure out what to do here
2990 del self._changectx[self._path]
3000 del self._changectx[self._path]
2991
3001
2992 def write(self, data, flags, **kwargs):
3002 def write(self, data, flags, **kwargs):
2993 """wraps repo.wwrite"""
3003 """wraps repo.wwrite"""
2994 self._data = data
3004 self._data = data
2995
3005
2996
3006
2997 class metadataonlyctx(committablectx):
3007 class metadataonlyctx(committablectx):
2998 """Like memctx but it's reusing the manifest of different commit.
3008 """Like memctx but it's reusing the manifest of different commit.
2999 Intended to be used by lightweight operations that are creating
3009 Intended to be used by lightweight operations that are creating
3000 metadata-only changes.
3010 metadata-only changes.
3001
3011
3002 Revision information is supplied at initialization time. 'repo' is the
3012 Revision information is supplied at initialization time. 'repo' is the
3003 current localrepo, 'ctx' is original revision which manifest we're reuisng
3013 current localrepo, 'ctx' is original revision which manifest we're reuisng
3004 'parents' is a sequence of two parent revisions identifiers (pass None for
3014 'parents' is a sequence of two parent revisions identifiers (pass None for
3005 every missing parent), 'text' is the commit.
3015 every missing parent), 'text' is the commit.
3006
3016
3007 user receives the committer name and defaults to current repository
3017 user receives the committer name and defaults to current repository
3008 username, date is the commit date in any format supported by
3018 username, date is the commit date in any format supported by
3009 dateutil.parsedate() and defaults to current date, extra is a dictionary of
3019 dateutil.parsedate() and defaults to current date, extra is a dictionary of
3010 metadata or is left empty.
3020 metadata or is left empty.
3011 """
3021 """
3012
3022
3013 def __init__(
3023 def __init__(
3014 self,
3024 self,
3015 repo,
3025 repo,
3016 originalctx,
3026 originalctx,
3017 parents=None,
3027 parents=None,
3018 text=None,
3028 text=None,
3019 user=None,
3029 user=None,
3020 date=None,
3030 date=None,
3021 extra=None,
3031 extra=None,
3022 editor=None,
3032 editor=None,
3023 ):
3033 ):
3024 if text is None:
3034 if text is None:
3025 text = originalctx.description()
3035 text = originalctx.description()
3026 super(metadataonlyctx, self).__init__(repo, text, user, date, extra)
3036 super(metadataonlyctx, self).__init__(repo, text, user, date, extra)
3027 self._rev = None
3037 self._rev = None
3028 self._node = None
3038 self._node = None
3029 self._originalctx = originalctx
3039 self._originalctx = originalctx
3030 self._manifestnode = originalctx.manifestnode()
3040 self._manifestnode = originalctx.manifestnode()
3031 if parents is None:
3041 if parents is None:
3032 parents = originalctx.parents()
3042 parents = originalctx.parents()
3033 else:
3043 else:
3034 parents = [repo[p] for p in parents if p is not None]
3044 parents = [repo[p] for p in parents if p is not None]
3035 parents = parents[:]
3045 parents = parents[:]
3036 while len(parents) < 2:
3046 while len(parents) < 2:
3037 parents.append(repo[nullrev])
3047 parents.append(repo[nullrev])
3038 p1, p2 = self._parents = parents
3048 p1, p2 = self._parents = parents
3039
3049
3040 # sanity check to ensure that the reused manifest parents are
3050 # sanity check to ensure that the reused manifest parents are
3041 # manifests of our commit parents
3051 # manifests of our commit parents
3042 mp1, mp2 = self.manifestctx().parents
3052 mp1, mp2 = self.manifestctx().parents
3043 if p1 != self._repo.nodeconstants.nullid and p1.manifestnode() != mp1:
3053 if p1 != self._repo.nodeconstants.nullid and p1.manifestnode() != mp1:
3044 raise RuntimeError(
3054 raise RuntimeError(
3045 r"can't reuse the manifest: its p1 "
3055 r"can't reuse the manifest: its p1 "
3046 r"doesn't match the new ctx p1"
3056 r"doesn't match the new ctx p1"
3047 )
3057 )
3048 if p2 != self._repo.nodeconstants.nullid and p2.manifestnode() != mp2:
3058 if p2 != self._repo.nodeconstants.nullid and p2.manifestnode() != mp2:
3049 raise RuntimeError(
3059 raise RuntimeError(
3050 r"can't reuse the manifest: "
3060 r"can't reuse the manifest: "
3051 r"its p2 doesn't match the new ctx p2"
3061 r"its p2 doesn't match the new ctx p2"
3052 )
3062 )
3053
3063
3054 self._files = originalctx.files()
3064 self._files = originalctx.files()
3055 self.substate = {}
3065 self.substate = {}
3056
3066
3057 if editor:
3067 if editor:
3058 self._text = editor(self._repo, self, [])
3068 self._text = editor(self._repo, self, [])
3059 self._repo.savecommitmessage(self._text)
3069 self._repo.savecommitmessage(self._text)
3060
3070
3061 def manifestnode(self):
3071 def manifestnode(self):
3062 return self._manifestnode
3072 return self._manifestnode
3063
3073
3064 @property
3074 @property
3065 def _manifestctx(self):
3075 def _manifestctx(self):
3066 return self._repo.manifestlog[self._manifestnode]
3076 return self._repo.manifestlog[self._manifestnode]
3067
3077
3068 def filectx(self, path, filelog=None):
3078 def filectx(self, path, filelog=None):
3069 return self._originalctx.filectx(path, filelog=filelog)
3079 return self._originalctx.filectx(path, filelog=filelog)
3070
3080
3071 def commit(self):
3081 def commit(self):
3072 """commit context to the repo"""
3082 """commit context to the repo"""
3073 return self._repo.commitctx(self)
3083 return self._repo.commitctx(self)
3074
3084
3075 @property
3085 @property
3076 def _manifest(self):
3086 def _manifest(self):
3077 return self._originalctx.manifest()
3087 return self._originalctx.manifest()
3078
3088
3079 @propertycache
3089 @propertycache
3080 def _status(self):
3090 def _status(self):
3081 """Calculate exact status from ``files`` specified in the ``origctx``
3091 """Calculate exact status from ``files`` specified in the ``origctx``
3082 and parents manifests.
3092 and parents manifests.
3083 """
3093 """
3084 man1 = self.p1().manifest()
3094 man1 = self.p1().manifest()
3085 p2 = self._parents[1]
3095 p2 = self._parents[1]
3086 # "1 < len(self._parents)" can't be used for checking
3096 # "1 < len(self._parents)" can't be used for checking
3087 # existence of the 2nd parent, because "metadataonlyctx._parents" is
3097 # existence of the 2nd parent, because "metadataonlyctx._parents" is
3088 # explicitly initialized by the list, of which length is 2.
3098 # explicitly initialized by the list, of which length is 2.
3089 if p2.rev() != nullrev:
3099 if p2.rev() != nullrev:
3090 man2 = p2.manifest()
3100 man2 = p2.manifest()
3091 managing = lambda f: f in man1 or f in man2
3101 managing = lambda f: f in man1 or f in man2
3092 else:
3102 else:
3093 managing = lambda f: f in man1
3103 managing = lambda f: f in man1
3094
3104
3095 modified, added, removed = [], [], []
3105 modified, added, removed = [], [], []
3096 for f in self._files:
3106 for f in self._files:
3097 if not managing(f):
3107 if not managing(f):
3098 added.append(f)
3108 added.append(f)
3099 elif f in self:
3109 elif f in self:
3100 modified.append(f)
3110 modified.append(f)
3101 else:
3111 else:
3102 removed.append(f)
3112 removed.append(f)
3103
3113
3104 return scmutil.status(modified, added, removed, [], [], [], [])
3114 return scmutil.status(modified, added, removed, [], [], [], [])
3105
3115
3106
3116
3107 class arbitraryfilectx:
3117 class arbitraryfilectx:
3108 """Allows you to use filectx-like functions on a file in an arbitrary
3118 """Allows you to use filectx-like functions on a file in an arbitrary
3109 location on disk, possibly not in the working directory.
3119 location on disk, possibly not in the working directory.
3110 """
3120 """
3111
3121
3112 def __init__(self, path, repo=None):
3122 def __init__(self, path, repo=None):
3113 # Repo is optional because contrib/simplemerge uses this class.
3123 # Repo is optional because contrib/simplemerge uses this class.
3114 self._repo = repo
3124 self._repo = repo
3115 self._path = path
3125 self._path = path
3116
3126
3117 def cmp(self, fctx):
3127 def cmp(self, fctx):
3118 # filecmp follows symlinks whereas `cmp` should not, so skip the fast
3128 # filecmp follows symlinks whereas `cmp` should not, so skip the fast
3119 # path if either side is a symlink.
3129 # path if either side is a symlink.
3120 symlinks = b'l' in self.flags() or b'l' in fctx.flags()
3130 symlinks = b'l' in self.flags() or b'l' in fctx.flags()
3121 if not symlinks and isinstance(fctx, workingfilectx) and self._repo:
3131 if not symlinks and isinstance(fctx, workingfilectx) and self._repo:
3122 # Add a fast-path for merge if both sides are disk-backed.
3132 # Add a fast-path for merge if both sides are disk-backed.
3123 # Note that filecmp uses the opposite return values (True if same)
3133 # Note that filecmp uses the opposite return values (True if same)
3124 # from our cmp functions (True if different).
3134 # from our cmp functions (True if different).
3125 return not filecmp.cmp(self.path(), self._repo.wjoin(fctx.path()))
3135 return not filecmp.cmp(self.path(), self._repo.wjoin(fctx.path()))
3126 return self.data() != fctx.data()
3136 return self.data() != fctx.data()
3127
3137
3128 def path(self):
3138 def path(self):
3129 return self._path
3139 return self._path
3130
3140
3131 def flags(self):
3141 def flags(self):
3132 return b''
3142 return b''
3133
3143
3134 def data(self):
3144 def data(self):
3135 return util.readfile(self._path)
3145 return util.readfile(self._path)
3136
3146
3137 def decodeddata(self):
3147 def decodeddata(self):
3138 return util.readfile(self._path)
3148 return util.readfile(self._path)
3139
3149
3140 def remove(self):
3150 def remove(self):
3141 util.unlink(self._path)
3151 util.unlink(self._path)
3142
3152
3143 def write(self, data, flags, **kwargs):
3153 def write(self, data, flags, **kwargs):
3144 assert not flags
3154 assert not flags
3145 util.writefile(self._path, data)
3155 util.writefile(self._path, data)
@@ -1,293 +1,295 b''
1 # filelog.py - file history class for mercurial
1 # filelog.py - file history class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005-2007 Olivia Mackall <olivia@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
8
9 from .i18n import _
9 from .i18n import _
10 from .node import nullrev
10 from .node import nullrev
11 from . import (
11 from . import (
12 error,
12 error,
13 revlog,
13 revlog,
14 )
14 )
15 from .interfaces import (
15 from .interfaces import (
16 repository,
16 repository,
17 util as interfaceutil,
17 util as interfaceutil,
18 )
18 )
19 from .utils import storageutil
19 from .utils import storageutil
20 from .revlogutils import (
20 from .revlogutils import (
21 constants as revlog_constants,
21 constants as revlog_constants,
22 rewrite,
22 rewrite,
23 )
23 )
24
24
25
25
26 @interfaceutil.implementer(repository.ifilestorage)
26 @interfaceutil.implementer(repository.ifilestorage)
27 class filelog:
27 class filelog:
28 def __init__(self, opener, path):
28 def __init__(self, opener, path):
29 self._revlog = revlog.revlog(
29 self._revlog = revlog.revlog(
30 opener,
30 opener,
31 # XXX should use the unencoded path
31 # XXX should use the unencoded path
32 target=(revlog_constants.KIND_FILELOG, path),
32 target=(revlog_constants.KIND_FILELOG, path),
33 radix=b'/'.join((b'data', path)),
33 radix=b'/'.join((b'data', path)),
34 censorable=True,
34 censorable=True,
35 canonical_parent_order=False, # see comment in revlog.py
35 )
36 )
36 # Full name of the user visible file, relative to the repository root.
37 # Full name of the user visible file, relative to the repository root.
37 # Used by LFS.
38 # Used by LFS.
38 self._revlog.filename = path
39 self._revlog.filename = path
39 self.nullid = self._revlog.nullid
40 self.nullid = self._revlog.nullid
40 opts = opener.options
41 opts = opener.options
41 self._fix_issue6528 = opts.get(b'issue6528.fix-incoming', True)
42 self._fix_issue6528 = opts.get(b'issue6528.fix-incoming', True)
42
43
43 def __len__(self):
44 def __len__(self):
44 return len(self._revlog)
45 return len(self._revlog)
45
46
46 def __iter__(self):
47 def __iter__(self):
47 return self._revlog.__iter__()
48 return self._revlog.__iter__()
48
49
49 def hasnode(self, node):
50 def hasnode(self, node):
50 if node in (self.nullid, nullrev):
51 if node in (self.nullid, nullrev):
51 return False
52 return False
52
53
53 try:
54 try:
54 self._revlog.rev(node)
55 self._revlog.rev(node)
55 return True
56 return True
56 except (TypeError, ValueError, IndexError, error.LookupError):
57 except (TypeError, ValueError, IndexError, error.LookupError):
57 return False
58 return False
58
59
59 def revs(self, start=0, stop=None):
60 def revs(self, start=0, stop=None):
60 return self._revlog.revs(start=start, stop=stop)
61 return self._revlog.revs(start=start, stop=stop)
61
62
62 def parents(self, node):
63 def parents(self, node):
63 return self._revlog.parents(node)
64 return self._revlog.parents(node)
64
65
65 def parentrevs(self, rev):
66 def parentrevs(self, rev):
66 return self._revlog.parentrevs(rev)
67 return self._revlog.parentrevs(rev)
67
68
68 def rev(self, node):
69 def rev(self, node):
69 return self._revlog.rev(node)
70 return self._revlog.rev(node)
70
71
71 def node(self, rev):
72 def node(self, rev):
72 return self._revlog.node(rev)
73 return self._revlog.node(rev)
73
74
74 def lookup(self, node):
75 def lookup(self, node):
75 return storageutil.fileidlookup(
76 return storageutil.fileidlookup(
76 self._revlog, node, self._revlog.display_id
77 self._revlog, node, self._revlog.display_id
77 )
78 )
78
79
79 def linkrev(self, rev):
80 def linkrev(self, rev):
80 return self._revlog.linkrev(rev)
81 return self._revlog.linkrev(rev)
81
82
82 def commonancestorsheads(self, node1, node2):
83 def commonancestorsheads(self, node1, node2):
83 return self._revlog.commonancestorsheads(node1, node2)
84 return self._revlog.commonancestorsheads(node1, node2)
84
85
85 # Used by dagop.blockdescendants().
86 # Used by dagop.blockdescendants().
86 def descendants(self, revs):
87 def descendants(self, revs):
87 return self._revlog.descendants(revs)
88 return self._revlog.descendants(revs)
88
89
89 def heads(self, start=None, stop=None):
90 def heads(self, start=None, stop=None):
90 return self._revlog.heads(start, stop)
91 return self._revlog.heads(start, stop)
91
92
92 # Used by hgweb, children extension.
93 # Used by hgweb, children extension.
93 def children(self, node):
94 def children(self, node):
94 return self._revlog.children(node)
95 return self._revlog.children(node)
95
96
96 def iscensored(self, rev):
97 def iscensored(self, rev):
97 return self._revlog.iscensored(rev)
98 return self._revlog.iscensored(rev)
98
99
99 def revision(self, node, _df=None):
100 def revision(self, node, _df=None):
100 return self._revlog.revision(node, _df=_df)
101 return self._revlog.revision(node, _df=_df)
101
102
102 def rawdata(self, node, _df=None):
103 def rawdata(self, node, _df=None):
103 return self._revlog.rawdata(node, _df=_df)
104 return self._revlog.rawdata(node, _df=_df)
104
105
105 def emitrevisions(
106 def emitrevisions(
106 self,
107 self,
107 nodes,
108 nodes,
108 nodesorder=None,
109 nodesorder=None,
109 revisiondata=False,
110 revisiondata=False,
110 assumehaveparentrevisions=False,
111 assumehaveparentrevisions=False,
111 deltamode=repository.CG_DELTAMODE_STD,
112 deltamode=repository.CG_DELTAMODE_STD,
112 sidedata_helpers=None,
113 sidedata_helpers=None,
113 ):
114 ):
114 return self._revlog.emitrevisions(
115 return self._revlog.emitrevisions(
115 nodes,
116 nodes,
116 nodesorder=nodesorder,
117 nodesorder=nodesorder,
117 revisiondata=revisiondata,
118 revisiondata=revisiondata,
118 assumehaveparentrevisions=assumehaveparentrevisions,
119 assumehaveparentrevisions=assumehaveparentrevisions,
119 deltamode=deltamode,
120 deltamode=deltamode,
120 sidedata_helpers=sidedata_helpers,
121 sidedata_helpers=sidedata_helpers,
121 )
122 )
122
123
123 def addrevision(
124 def addrevision(
124 self,
125 self,
125 revisiondata,
126 revisiondata,
126 transaction,
127 transaction,
127 linkrev,
128 linkrev,
128 p1,
129 p1,
129 p2,
130 p2,
130 node=None,
131 node=None,
131 flags=revlog.REVIDX_DEFAULT_FLAGS,
132 flags=revlog.REVIDX_DEFAULT_FLAGS,
132 cachedelta=None,
133 cachedelta=None,
133 ):
134 ):
134 return self._revlog.addrevision(
135 return self._revlog.addrevision(
135 revisiondata,
136 revisiondata,
136 transaction,
137 transaction,
137 linkrev,
138 linkrev,
138 p1,
139 p1,
139 p2,
140 p2,
140 node=node,
141 node=node,
141 flags=flags,
142 flags=flags,
142 cachedelta=cachedelta,
143 cachedelta=cachedelta,
143 )
144 )
144
145
145 def addgroup(
146 def addgroup(
146 self,
147 self,
147 deltas,
148 deltas,
148 linkmapper,
149 linkmapper,
149 transaction,
150 transaction,
150 addrevisioncb=None,
151 addrevisioncb=None,
151 duplicaterevisioncb=None,
152 duplicaterevisioncb=None,
152 maybemissingparents=False,
153 maybemissingparents=False,
153 ):
154 ):
154 if maybemissingparents:
155 if maybemissingparents:
155 raise error.Abort(
156 raise error.Abort(
156 _(
157 _(
157 b'revlog storage does not support missing '
158 b'revlog storage does not support missing '
158 b'parents write mode'
159 b'parents write mode'
159 )
160 )
160 )
161 )
161
162
162 with self._revlog._writing(transaction):
163 with self._revlog._writing(transaction):
163
164
164 if self._fix_issue6528:
165 if self._fix_issue6528:
165 deltas = rewrite.filter_delta_issue6528(self._revlog, deltas)
166 deltas = rewrite.filter_delta_issue6528(self._revlog, deltas)
166
167
167 return self._revlog.addgroup(
168 return self._revlog.addgroup(
168 deltas,
169 deltas,
169 linkmapper,
170 linkmapper,
170 transaction,
171 transaction,
171 addrevisioncb=addrevisioncb,
172 addrevisioncb=addrevisioncb,
172 duplicaterevisioncb=duplicaterevisioncb,
173 duplicaterevisioncb=duplicaterevisioncb,
173 )
174 )
174
175
175 def getstrippoint(self, minlink):
176 def getstrippoint(self, minlink):
176 return self._revlog.getstrippoint(minlink)
177 return self._revlog.getstrippoint(minlink)
177
178
178 def strip(self, minlink, transaction):
179 def strip(self, minlink, transaction):
179 return self._revlog.strip(minlink, transaction)
180 return self._revlog.strip(minlink, transaction)
180
181
181 def censorrevision(self, tr, node, tombstone=b''):
182 def censorrevision(self, tr, node, tombstone=b''):
182 return self._revlog.censorrevision(tr, node, tombstone=tombstone)
183 return self._revlog.censorrevision(tr, node, tombstone=tombstone)
183
184
184 def files(self):
185 def files(self):
185 return self._revlog.files()
186 return self._revlog.files()
186
187
187 def read(self, node):
188 def read(self, node):
188 return storageutil.filtermetadata(self.revision(node))
189 return storageutil.filtermetadata(self.revision(node))
189
190
190 def add(self, text, meta, transaction, link, p1=None, p2=None):
191 def add(self, text, meta, transaction, link, p1=None, p2=None):
191 if meta or text.startswith(b'\1\n'):
192 if meta or text.startswith(b'\1\n'):
192 text = storageutil.packmeta(meta, text)
193 text = storageutil.packmeta(meta, text)
193 rev = self.addrevision(text, transaction, link, p1, p2)
194 rev = self.addrevision(text, transaction, link, p1, p2)
194 return self.node(rev)
195 return self.node(rev)
195
196
196 def renamed(self, node):
197 def renamed(self, node):
197 return storageutil.filerevisioncopied(self, node)
198 return storageutil.filerevisioncopied(self, node)
198
199
199 def size(self, rev):
200 def size(self, rev):
200 """return the size of a given revision"""
201 """return the size of a given revision"""
201
202
202 # for revisions with renames, we have to go the slow way
203 # for revisions with renames, we have to go the slow way
203 node = self.node(rev)
204 node = self.node(rev)
204 if self.renamed(node):
205 if self.renamed(node):
205 return len(self.read(node))
206 return len(self.read(node))
206 if self.iscensored(rev):
207 if self.iscensored(rev):
207 return 0
208 return 0
208
209
209 # XXX if self.read(node).startswith("\1\n"), this returns (size+4)
210 # XXX if self.read(node).startswith("\1\n"), this returns (size+4)
211 # XXX See also basefilectx.cmp.
210 return self._revlog.size(rev)
212 return self._revlog.size(rev)
211
213
212 def cmp(self, node, text):
214 def cmp(self, node, text):
213 """compare text with a given file revision
215 """compare text with a given file revision
214
216
215 returns True if text is different than what is stored.
217 returns True if text is different than what is stored.
216 """
218 """
217 return not storageutil.filedataequivalent(self, node, text)
219 return not storageutil.filedataequivalent(self, node, text)
218
220
219 def verifyintegrity(self, state):
221 def verifyintegrity(self, state):
220 return self._revlog.verifyintegrity(state)
222 return self._revlog.verifyintegrity(state)
221
223
222 def storageinfo(
224 def storageinfo(
223 self,
225 self,
224 exclusivefiles=False,
226 exclusivefiles=False,
225 sharedfiles=False,
227 sharedfiles=False,
226 revisionscount=False,
228 revisionscount=False,
227 trackedsize=False,
229 trackedsize=False,
228 storedsize=False,
230 storedsize=False,
229 ):
231 ):
230 return self._revlog.storageinfo(
232 return self._revlog.storageinfo(
231 exclusivefiles=exclusivefiles,
233 exclusivefiles=exclusivefiles,
232 sharedfiles=sharedfiles,
234 sharedfiles=sharedfiles,
233 revisionscount=revisionscount,
235 revisionscount=revisionscount,
234 trackedsize=trackedsize,
236 trackedsize=trackedsize,
235 storedsize=storedsize,
237 storedsize=storedsize,
236 )
238 )
237
239
238 # Used by repo upgrade.
240 # Used by repo upgrade.
239 def clone(self, tr, destrevlog, **kwargs):
241 def clone(self, tr, destrevlog, **kwargs):
240 if not isinstance(destrevlog, filelog):
242 if not isinstance(destrevlog, filelog):
241 raise error.ProgrammingError(b'expected filelog to clone()')
243 raise error.ProgrammingError(b'expected filelog to clone()')
242
244
243 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
245 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
244
246
245
247
246 class narrowfilelog(filelog):
248 class narrowfilelog(filelog):
247 """Filelog variation to be used with narrow stores."""
249 """Filelog variation to be used with narrow stores."""
248
250
249 def __init__(self, opener, path, narrowmatch):
251 def __init__(self, opener, path, narrowmatch):
250 super(narrowfilelog, self).__init__(opener, path)
252 super(narrowfilelog, self).__init__(opener, path)
251 self._narrowmatch = narrowmatch
253 self._narrowmatch = narrowmatch
252
254
253 def renamed(self, node):
255 def renamed(self, node):
254 res = super(narrowfilelog, self).renamed(node)
256 res = super(narrowfilelog, self).renamed(node)
255
257
256 # Renames that come from outside the narrowspec are problematic
258 # Renames that come from outside the narrowspec are problematic
257 # because we may lack the base text for the rename. This can result
259 # because we may lack the base text for the rename. This can result
258 # in code attempting to walk the ancestry or compute a diff
260 # in code attempting to walk the ancestry or compute a diff
259 # encountering a missing revision. We address this by silently
261 # encountering a missing revision. We address this by silently
260 # removing rename metadata if the source file is outside the
262 # removing rename metadata if the source file is outside the
261 # narrow spec.
263 # narrow spec.
262 #
264 #
263 # A better solution would be to see if the base revision is available,
265 # A better solution would be to see if the base revision is available,
264 # rather than assuming it isn't.
266 # rather than assuming it isn't.
265 #
267 #
266 # An even better solution would be to teach all consumers of rename
268 # An even better solution would be to teach all consumers of rename
267 # metadata that the base revision may not be available.
269 # metadata that the base revision may not be available.
268 #
270 #
269 # TODO consider better ways of doing this.
271 # TODO consider better ways of doing this.
270 if res and not self._narrowmatch(res[0]):
272 if res and not self._narrowmatch(res[0]):
271 return None
273 return None
272
274
273 return res
275 return res
274
276
275 def size(self, rev):
277 def size(self, rev):
276 # Because we have a custom renamed() that may lie, we need to call
278 # Because we have a custom renamed() that may lie, we need to call
277 # the base renamed() to report accurate results.
279 # the base renamed() to report accurate results.
278 node = self.node(rev)
280 node = self.node(rev)
279 if super(narrowfilelog, self).renamed(node):
281 if super(narrowfilelog, self).renamed(node):
280 return len(self.read(node))
282 return len(self.read(node))
281 else:
283 else:
282 return super(narrowfilelog, self).size(rev)
284 return super(narrowfilelog, self).size(rev)
283
285
284 def cmp(self, node, text):
286 def cmp(self, node, text):
285 # We don't call `super` because narrow parents can be buggy in case of a
287 # We don't call `super` because narrow parents can be buggy in case of a
286 # ambiguous dirstate. Always take the slow path until there is a better
288 # ambiguous dirstate. Always take the slow path until there is a better
287 # fix, see issue6150.
289 # fix, see issue6150.
288
290
289 # Censored files compare against the empty file.
291 # Censored files compare against the empty file.
290 if self.iscensored(self.rev(node)):
292 if self.iscensored(self.rev(node)):
291 return text != b''
293 return text != b''
292
294
293 return self.read(node) != text
295 return self.read(node) != text
@@ -1,3307 +1,3322 b''
1 # revlog.py - storage back-end for mercurial
1 # revlog.py - storage back-end for mercurial
2 # coding: utf8
2 # coding: utf8
3 #
3 #
4 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 """Storage back-end for Mercurial.
9 """Storage back-end for Mercurial.
10
10
11 This provides efficient delta storage with O(1) retrieve and append
11 This provides efficient delta storage with O(1) retrieve and append
12 and O(changes) merge between branches.
12 and O(changes) merge between branches.
13 """
13 """
14
14
15
15
16 import binascii
16 import binascii
17 import collections
17 import collections
18 import contextlib
18 import contextlib
19 import errno
19 import errno
20 import io
20 import io
21 import os
21 import os
22 import struct
22 import struct
23 import zlib
23 import zlib
24
24
25 # import stuff from node for others to import from revlog
25 # import stuff from node for others to import from revlog
26 from .node import (
26 from .node import (
27 bin,
27 bin,
28 hex,
28 hex,
29 nullrev,
29 nullrev,
30 sha1nodeconstants,
30 sha1nodeconstants,
31 short,
31 short,
32 wdirrev,
32 wdirrev,
33 )
33 )
34 from .i18n import _
34 from .i18n import _
35 from .pycompat import getattr
35 from .pycompat import getattr
36 from .revlogutils.constants import (
36 from .revlogutils.constants import (
37 ALL_KINDS,
37 ALL_KINDS,
38 CHANGELOGV2,
38 CHANGELOGV2,
39 COMP_MODE_DEFAULT,
39 COMP_MODE_DEFAULT,
40 COMP_MODE_INLINE,
40 COMP_MODE_INLINE,
41 COMP_MODE_PLAIN,
41 COMP_MODE_PLAIN,
42 ENTRY_RANK,
42 ENTRY_RANK,
43 FEATURES_BY_VERSION,
43 FEATURES_BY_VERSION,
44 FLAG_GENERALDELTA,
44 FLAG_GENERALDELTA,
45 FLAG_INLINE_DATA,
45 FLAG_INLINE_DATA,
46 INDEX_HEADER,
46 INDEX_HEADER,
47 KIND_CHANGELOG,
47 KIND_CHANGELOG,
48 RANK_UNKNOWN,
48 RANK_UNKNOWN,
49 REVLOGV0,
49 REVLOGV0,
50 REVLOGV1,
50 REVLOGV1,
51 REVLOGV1_FLAGS,
51 REVLOGV1_FLAGS,
52 REVLOGV2,
52 REVLOGV2,
53 REVLOGV2_FLAGS,
53 REVLOGV2_FLAGS,
54 REVLOG_DEFAULT_FLAGS,
54 REVLOG_DEFAULT_FLAGS,
55 REVLOG_DEFAULT_FORMAT,
55 REVLOG_DEFAULT_FORMAT,
56 REVLOG_DEFAULT_VERSION,
56 REVLOG_DEFAULT_VERSION,
57 SUPPORTED_FLAGS,
57 SUPPORTED_FLAGS,
58 )
58 )
59 from .revlogutils.flagutil import (
59 from .revlogutils.flagutil import (
60 REVIDX_DEFAULT_FLAGS,
60 REVIDX_DEFAULT_FLAGS,
61 REVIDX_ELLIPSIS,
61 REVIDX_ELLIPSIS,
62 REVIDX_EXTSTORED,
62 REVIDX_EXTSTORED,
63 REVIDX_FLAGS_ORDER,
63 REVIDX_FLAGS_ORDER,
64 REVIDX_HASCOPIESINFO,
64 REVIDX_HASCOPIESINFO,
65 REVIDX_ISCENSORED,
65 REVIDX_ISCENSORED,
66 REVIDX_RAWTEXT_CHANGING_FLAGS,
66 REVIDX_RAWTEXT_CHANGING_FLAGS,
67 )
67 )
68 from .thirdparty import attr
68 from .thirdparty import attr
69 from . import (
69 from . import (
70 ancestor,
70 ancestor,
71 dagop,
71 dagop,
72 error,
72 error,
73 mdiff,
73 mdiff,
74 policy,
74 policy,
75 pycompat,
75 pycompat,
76 revlogutils,
76 revlogutils,
77 templatefilters,
77 templatefilters,
78 util,
78 util,
79 )
79 )
80 from .interfaces import (
80 from .interfaces import (
81 repository,
81 repository,
82 util as interfaceutil,
82 util as interfaceutil,
83 )
83 )
84 from .revlogutils import (
84 from .revlogutils import (
85 deltas as deltautil,
85 deltas as deltautil,
86 docket as docketutil,
86 docket as docketutil,
87 flagutil,
87 flagutil,
88 nodemap as nodemaputil,
88 nodemap as nodemaputil,
89 randomaccessfile,
89 randomaccessfile,
90 revlogv0,
90 revlogv0,
91 rewrite,
91 rewrite,
92 sidedata as sidedatautil,
92 sidedata as sidedatautil,
93 )
93 )
94 from .utils import (
94 from .utils import (
95 storageutil,
95 storageutil,
96 stringutil,
96 stringutil,
97 )
97 )
98
98
99 # blanked usage of all the name to prevent pyflakes constraints
99 # blanked usage of all the name to prevent pyflakes constraints
100 # We need these name available in the module for extensions.
100 # We need these name available in the module for extensions.
101
101
102 REVLOGV0
102 REVLOGV0
103 REVLOGV1
103 REVLOGV1
104 REVLOGV2
104 REVLOGV2
105 CHANGELOGV2
105 CHANGELOGV2
106 FLAG_INLINE_DATA
106 FLAG_INLINE_DATA
107 FLAG_GENERALDELTA
107 FLAG_GENERALDELTA
108 REVLOG_DEFAULT_FLAGS
108 REVLOG_DEFAULT_FLAGS
109 REVLOG_DEFAULT_FORMAT
109 REVLOG_DEFAULT_FORMAT
110 REVLOG_DEFAULT_VERSION
110 REVLOG_DEFAULT_VERSION
111 REVLOGV1_FLAGS
111 REVLOGV1_FLAGS
112 REVLOGV2_FLAGS
112 REVLOGV2_FLAGS
113 REVIDX_ISCENSORED
113 REVIDX_ISCENSORED
114 REVIDX_ELLIPSIS
114 REVIDX_ELLIPSIS
115 REVIDX_HASCOPIESINFO
115 REVIDX_HASCOPIESINFO
116 REVIDX_EXTSTORED
116 REVIDX_EXTSTORED
117 REVIDX_DEFAULT_FLAGS
117 REVIDX_DEFAULT_FLAGS
118 REVIDX_FLAGS_ORDER
118 REVIDX_FLAGS_ORDER
119 REVIDX_RAWTEXT_CHANGING_FLAGS
119 REVIDX_RAWTEXT_CHANGING_FLAGS
120
120
121 parsers = policy.importmod('parsers')
121 parsers = policy.importmod('parsers')
122 rustancestor = policy.importrust('ancestor')
122 rustancestor = policy.importrust('ancestor')
123 rustdagop = policy.importrust('dagop')
123 rustdagop = policy.importrust('dagop')
124 rustrevlog = policy.importrust('revlog')
124 rustrevlog = policy.importrust('revlog')
125
125
126 # Aliased for performance.
126 # Aliased for performance.
127 _zlibdecompress = zlib.decompress
127 _zlibdecompress = zlib.decompress
128
128
129 # max size of revlog with inline data
129 # max size of revlog with inline data
130 _maxinline = 131072
130 _maxinline = 131072
131
131
132 # Flag processors for REVIDX_ELLIPSIS.
132 # Flag processors for REVIDX_ELLIPSIS.
133 def ellipsisreadprocessor(rl, text):
133 def ellipsisreadprocessor(rl, text):
134 return text, False
134 return text, False
135
135
136
136
137 def ellipsiswriteprocessor(rl, text):
137 def ellipsiswriteprocessor(rl, text):
138 return text, False
138 return text, False
139
139
140
140
141 def ellipsisrawprocessor(rl, text):
141 def ellipsisrawprocessor(rl, text):
142 return False
142 return False
143
143
144
144
145 ellipsisprocessor = (
145 ellipsisprocessor = (
146 ellipsisreadprocessor,
146 ellipsisreadprocessor,
147 ellipsiswriteprocessor,
147 ellipsiswriteprocessor,
148 ellipsisrawprocessor,
148 ellipsisrawprocessor,
149 )
149 )
150
150
151
151
152 def _verify_revision(rl, skipflags, state, node):
152 def _verify_revision(rl, skipflags, state, node):
153 """Verify the integrity of the given revlog ``node`` while providing a hook
153 """Verify the integrity of the given revlog ``node`` while providing a hook
154 point for extensions to influence the operation."""
154 point for extensions to influence the operation."""
155 if skipflags:
155 if skipflags:
156 state[b'skipread'].add(node)
156 state[b'skipread'].add(node)
157 else:
157 else:
158 # Side-effect: read content and verify hash.
158 # Side-effect: read content and verify hash.
159 rl.revision(node)
159 rl.revision(node)
160
160
161
161
162 # True if a fast implementation for persistent-nodemap is available
162 # True if a fast implementation for persistent-nodemap is available
163 #
163 #
164 # We also consider we have a "fast" implementation in "pure" python because
164 # We also consider we have a "fast" implementation in "pure" python because
165 # people using pure don't really have performance consideration (and a
165 # people using pure don't really have performance consideration (and a
166 # wheelbarrow of other slowness source)
166 # wheelbarrow of other slowness source)
167 HAS_FAST_PERSISTENT_NODEMAP = rustrevlog is not None or util.safehasattr(
167 HAS_FAST_PERSISTENT_NODEMAP = rustrevlog is not None or util.safehasattr(
168 parsers, 'BaseIndexObject'
168 parsers, 'BaseIndexObject'
169 )
169 )
170
170
171
171
172 @interfaceutil.implementer(repository.irevisiondelta)
172 @interfaceutil.implementer(repository.irevisiondelta)
173 @attr.s(slots=True)
173 @attr.s(slots=True)
174 class revlogrevisiondelta:
174 class revlogrevisiondelta:
175 node = attr.ib()
175 node = attr.ib()
176 p1node = attr.ib()
176 p1node = attr.ib()
177 p2node = attr.ib()
177 p2node = attr.ib()
178 basenode = attr.ib()
178 basenode = attr.ib()
179 flags = attr.ib()
179 flags = attr.ib()
180 baserevisionsize = attr.ib()
180 baserevisionsize = attr.ib()
181 revision = attr.ib()
181 revision = attr.ib()
182 delta = attr.ib()
182 delta = attr.ib()
183 sidedata = attr.ib()
183 sidedata = attr.ib()
184 protocol_flags = attr.ib()
184 protocol_flags = attr.ib()
185 linknode = attr.ib(default=None)
185 linknode = attr.ib(default=None)
186
186
187
187
188 @interfaceutil.implementer(repository.iverifyproblem)
188 @interfaceutil.implementer(repository.iverifyproblem)
189 @attr.s(frozen=True)
189 @attr.s(frozen=True)
190 class revlogproblem:
190 class revlogproblem:
191 warning = attr.ib(default=None)
191 warning = attr.ib(default=None)
192 error = attr.ib(default=None)
192 error = attr.ib(default=None)
193 node = attr.ib(default=None)
193 node = attr.ib(default=None)
194
194
195
195
196 def parse_index_v1(data, inline):
196 def parse_index_v1(data, inline):
197 # call the C implementation to parse the index data
197 # call the C implementation to parse the index data
198 index, cache = parsers.parse_index2(data, inline)
198 index, cache = parsers.parse_index2(data, inline)
199 return index, cache
199 return index, cache
200
200
201
201
202 def parse_index_v2(data, inline):
202 def parse_index_v2(data, inline):
203 # call the C implementation to parse the index data
203 # call the C implementation to parse the index data
204 index, cache = parsers.parse_index2(data, inline, format=REVLOGV2)
204 index, cache = parsers.parse_index2(data, inline, format=REVLOGV2)
205 return index, cache
205 return index, cache
206
206
207
207
208 def parse_index_cl_v2(data, inline):
208 def parse_index_cl_v2(data, inline):
209 # call the C implementation to parse the index data
209 # call the C implementation to parse the index data
210 index, cache = parsers.parse_index2(data, inline, format=CHANGELOGV2)
210 index, cache = parsers.parse_index2(data, inline, format=CHANGELOGV2)
211 return index, cache
211 return index, cache
212
212
213
213
214 if util.safehasattr(parsers, 'parse_index_devel_nodemap'):
214 if util.safehasattr(parsers, 'parse_index_devel_nodemap'):
215
215
216 def parse_index_v1_nodemap(data, inline):
216 def parse_index_v1_nodemap(data, inline):
217 index, cache = parsers.parse_index_devel_nodemap(data, inline)
217 index, cache = parsers.parse_index_devel_nodemap(data, inline)
218 return index, cache
218 return index, cache
219
219
220
220
221 else:
221 else:
222 parse_index_v1_nodemap = None
222 parse_index_v1_nodemap = None
223
223
224
224
225 def parse_index_v1_mixed(data, inline):
225 def parse_index_v1_mixed(data, inline):
226 index, cache = parse_index_v1(data, inline)
226 index, cache = parse_index_v1(data, inline)
227 return rustrevlog.MixedIndex(index), cache
227 return rustrevlog.MixedIndex(index), cache
228
228
229
229
230 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
230 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
231 # signed integer)
231 # signed integer)
232 _maxentrysize = 0x7FFFFFFF
232 _maxentrysize = 0x7FFFFFFF
233
233
234 FILE_TOO_SHORT_MSG = _(
234 FILE_TOO_SHORT_MSG = _(
235 b'cannot read from revlog %s;'
235 b'cannot read from revlog %s;'
236 b' expected %d bytes from offset %d, data size is %d'
236 b' expected %d bytes from offset %d, data size is %d'
237 )
237 )
238
238
239
239
240 class revlog:
240 class revlog:
241 """
241 """
242 the underlying revision storage object
242 the underlying revision storage object
243
243
244 A revlog consists of two parts, an index and the revision data.
244 A revlog consists of two parts, an index and the revision data.
245
245
246 The index is a file with a fixed record size containing
246 The index is a file with a fixed record size containing
247 information on each revision, including its nodeid (hash), the
247 information on each revision, including its nodeid (hash), the
248 nodeids of its parents, the position and offset of its data within
248 nodeids of its parents, the position and offset of its data within
249 the data file, and the revision it's based on. Finally, each entry
249 the data file, and the revision it's based on. Finally, each entry
250 contains a linkrev entry that can serve as a pointer to external
250 contains a linkrev entry that can serve as a pointer to external
251 data.
251 data.
252
252
253 The revision data itself is a linear collection of data chunks.
253 The revision data itself is a linear collection of data chunks.
254 Each chunk represents a revision and is usually represented as a
254 Each chunk represents a revision and is usually represented as a
255 delta against the previous chunk. To bound lookup time, runs of
255 delta against the previous chunk. To bound lookup time, runs of
256 deltas are limited to about 2 times the length of the original
256 deltas are limited to about 2 times the length of the original
257 version data. This makes retrieval of a version proportional to
257 version data. This makes retrieval of a version proportional to
258 its size, or O(1) relative to the number of revisions.
258 its size, or O(1) relative to the number of revisions.
259
259
260 Both pieces of the revlog are written to in an append-only
260 Both pieces of the revlog are written to in an append-only
261 fashion, which means we never need to rewrite a file to insert or
261 fashion, which means we never need to rewrite a file to insert or
262 remove data, and can use some simple techniques to avoid the need
262 remove data, and can use some simple techniques to avoid the need
263 for locking while reading.
263 for locking while reading.
264
264
265 If checkambig, indexfile is opened with checkambig=True at
265 If checkambig, indexfile is opened with checkambig=True at
266 writing, to avoid file stat ambiguity.
266 writing, to avoid file stat ambiguity.
267
267
268 If mmaplargeindex is True, and an mmapindexthreshold is set, the
268 If mmaplargeindex is True, and an mmapindexthreshold is set, the
269 index will be mmapped rather than read if it is larger than the
269 index will be mmapped rather than read if it is larger than the
270 configured threshold.
270 configured threshold.
271
271
272 If censorable is True, the revlog can have censored revisions.
272 If censorable is True, the revlog can have censored revisions.
273
273
274 If `upperboundcomp` is not None, this is the expected maximal gain from
274 If `upperboundcomp` is not None, this is the expected maximal gain from
275 compression for the data content.
275 compression for the data content.
276
276
277 `concurrencychecker` is an optional function that receives 3 arguments: a
277 `concurrencychecker` is an optional function that receives 3 arguments: a
278 file handle, a filename, and an expected position. It should check whether
278 file handle, a filename, and an expected position. It should check whether
279 the current position in the file handle is valid, and log/warn/fail (by
279 the current position in the file handle is valid, and log/warn/fail (by
280 raising).
280 raising).
281
281
282 See mercurial/revlogutils/contants.py for details about the content of an
282 See mercurial/revlogutils/contants.py for details about the content of an
283 index entry.
283 index entry.
284 """
284 """
285
285
286 _flagserrorclass = error.RevlogError
286 _flagserrorclass = error.RevlogError
287
287
288 def __init__(
288 def __init__(
289 self,
289 self,
290 opener,
290 opener,
291 target,
291 target,
292 radix,
292 radix,
293 postfix=None, # only exist for `tmpcensored` now
293 postfix=None, # only exist for `tmpcensored` now
294 checkambig=False,
294 checkambig=False,
295 mmaplargeindex=False,
295 mmaplargeindex=False,
296 censorable=False,
296 censorable=False,
297 upperboundcomp=None,
297 upperboundcomp=None,
298 persistentnodemap=False,
298 persistentnodemap=False,
299 concurrencychecker=None,
299 concurrencychecker=None,
300 trypending=False,
300 trypending=False,
301 canonical_parent_order=True,
301 ):
302 ):
302 """
303 """
303 create a revlog object
304 create a revlog object
304
305
305 opener is a function that abstracts the file opening operation
306 opener is a function that abstracts the file opening operation
306 and can be used to implement COW semantics or the like.
307 and can be used to implement COW semantics or the like.
307
308
308 `target`: a (KIND, ID) tuple that identify the content stored in
309 `target`: a (KIND, ID) tuple that identify the content stored in
309 this revlog. It help the rest of the code to understand what the revlog
310 this revlog. It help the rest of the code to understand what the revlog
310 is about without having to resort to heuristic and index filename
311 is about without having to resort to heuristic and index filename
311 analysis. Note: that this must be reliably be set by normal code, but
312 analysis. Note: that this must be reliably be set by normal code, but
312 that test, debug, or performance measurement code might not set this to
313 that test, debug, or performance measurement code might not set this to
313 accurate value.
314 accurate value.
314 """
315 """
315 self.upperboundcomp = upperboundcomp
316 self.upperboundcomp = upperboundcomp
316
317
317 self.radix = radix
318 self.radix = radix
318
319
319 self._docket_file = None
320 self._docket_file = None
320 self._indexfile = None
321 self._indexfile = None
321 self._datafile = None
322 self._datafile = None
322 self._sidedatafile = None
323 self._sidedatafile = None
323 self._nodemap_file = None
324 self._nodemap_file = None
324 self.postfix = postfix
325 self.postfix = postfix
325 self._trypending = trypending
326 self._trypending = trypending
326 self.opener = opener
327 self.opener = opener
327 if persistentnodemap:
328 if persistentnodemap:
328 self._nodemap_file = nodemaputil.get_nodemap_file(self)
329 self._nodemap_file = nodemaputil.get_nodemap_file(self)
329
330
330 assert target[0] in ALL_KINDS
331 assert target[0] in ALL_KINDS
331 assert len(target) == 2
332 assert len(target) == 2
332 self.target = target
333 self.target = target
333 # When True, indexfile is opened with checkambig=True at writing, to
334 # When True, indexfile is opened with checkambig=True at writing, to
334 # avoid file stat ambiguity.
335 # avoid file stat ambiguity.
335 self._checkambig = checkambig
336 self._checkambig = checkambig
336 self._mmaplargeindex = mmaplargeindex
337 self._mmaplargeindex = mmaplargeindex
337 self._censorable = censorable
338 self._censorable = censorable
338 # 3-tuple of (node, rev, text) for a raw revision.
339 # 3-tuple of (node, rev, text) for a raw revision.
339 self._revisioncache = None
340 self._revisioncache = None
340 # Maps rev to chain base rev.
341 # Maps rev to chain base rev.
341 self._chainbasecache = util.lrucachedict(100)
342 self._chainbasecache = util.lrucachedict(100)
342 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
343 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
343 self._chunkcache = (0, b'')
344 self._chunkcache = (0, b'')
344 # How much data to read and cache into the raw revlog data cache.
345 # How much data to read and cache into the raw revlog data cache.
345 self._chunkcachesize = 65536
346 self._chunkcachesize = 65536
346 self._maxchainlen = None
347 self._maxchainlen = None
347 self._deltabothparents = True
348 self._deltabothparents = True
348 self.index = None
349 self.index = None
349 self._docket = None
350 self._docket = None
350 self._nodemap_docket = None
351 self._nodemap_docket = None
351 # Mapping of partial identifiers to full nodes.
352 # Mapping of partial identifiers to full nodes.
352 self._pcache = {}
353 self._pcache = {}
353 # Mapping of revision integer to full node.
354 # Mapping of revision integer to full node.
354 self._compengine = b'zlib'
355 self._compengine = b'zlib'
355 self._compengineopts = {}
356 self._compengineopts = {}
356 self._maxdeltachainspan = -1
357 self._maxdeltachainspan = -1
357 self._withsparseread = False
358 self._withsparseread = False
358 self._sparserevlog = False
359 self._sparserevlog = False
359 self.hassidedata = False
360 self.hassidedata = False
360 self._srdensitythreshold = 0.50
361 self._srdensitythreshold = 0.50
361 self._srmingapsize = 262144
362 self._srmingapsize = 262144
362
363
363 # Make copy of flag processors so each revlog instance can support
364 # Make copy of flag processors so each revlog instance can support
364 # custom flags.
365 # custom flags.
365 self._flagprocessors = dict(flagutil.flagprocessors)
366 self._flagprocessors = dict(flagutil.flagprocessors)
366
367
367 # 3-tuple of file handles being used for active writing.
368 # 3-tuple of file handles being used for active writing.
368 self._writinghandles = None
369 self._writinghandles = None
369 # prevent nesting of addgroup
370 # prevent nesting of addgroup
370 self._adding_group = None
371 self._adding_group = None
371
372
372 self._loadindex()
373 self._loadindex()
373
374
374 self._concurrencychecker = concurrencychecker
375 self._concurrencychecker = concurrencychecker
375
376
377 # parent order is supposed to be semantically irrelevant, so we
378 # normally resort parents to ensure that the first parent is non-null,
379 # if there is a non-null parent at all.
380 # filelog abuses the parent order as flag to mark some instances of
381 # meta-encoded files, so allow it to disable this behavior.
382 self.canonical_parent_order = canonical_parent_order
383
376 def _init_opts(self):
384 def _init_opts(self):
377 """process options (from above/config) to setup associated default revlog mode
385 """process options (from above/config) to setup associated default revlog mode
378
386
379 These values might be affected when actually reading on disk information.
387 These values might be affected when actually reading on disk information.
380
388
381 The relevant values are returned for use in _loadindex().
389 The relevant values are returned for use in _loadindex().
382
390
383 * newversionflags:
391 * newversionflags:
384 version header to use if we need to create a new revlog
392 version header to use if we need to create a new revlog
385
393
386 * mmapindexthreshold:
394 * mmapindexthreshold:
387 minimal index size for start to use mmap
395 minimal index size for start to use mmap
388
396
389 * force_nodemap:
397 * force_nodemap:
390 force the usage of a "development" version of the nodemap code
398 force the usage of a "development" version of the nodemap code
391 """
399 """
392 mmapindexthreshold = None
400 mmapindexthreshold = None
393 opts = self.opener.options
401 opts = self.opener.options
394
402
395 if b'changelogv2' in opts and self.revlog_kind == KIND_CHANGELOG:
403 if b'changelogv2' in opts and self.revlog_kind == KIND_CHANGELOG:
396 new_header = CHANGELOGV2
404 new_header = CHANGELOGV2
397 elif b'revlogv2' in opts:
405 elif b'revlogv2' in opts:
398 new_header = REVLOGV2
406 new_header = REVLOGV2
399 elif b'revlogv1' in opts:
407 elif b'revlogv1' in opts:
400 new_header = REVLOGV1 | FLAG_INLINE_DATA
408 new_header = REVLOGV1 | FLAG_INLINE_DATA
401 if b'generaldelta' in opts:
409 if b'generaldelta' in opts:
402 new_header |= FLAG_GENERALDELTA
410 new_header |= FLAG_GENERALDELTA
403 elif b'revlogv0' in self.opener.options:
411 elif b'revlogv0' in self.opener.options:
404 new_header = REVLOGV0
412 new_header = REVLOGV0
405 else:
413 else:
406 new_header = REVLOG_DEFAULT_VERSION
414 new_header = REVLOG_DEFAULT_VERSION
407
415
408 if b'chunkcachesize' in opts:
416 if b'chunkcachesize' in opts:
409 self._chunkcachesize = opts[b'chunkcachesize']
417 self._chunkcachesize = opts[b'chunkcachesize']
410 if b'maxchainlen' in opts:
418 if b'maxchainlen' in opts:
411 self._maxchainlen = opts[b'maxchainlen']
419 self._maxchainlen = opts[b'maxchainlen']
412 if b'deltabothparents' in opts:
420 if b'deltabothparents' in opts:
413 self._deltabothparents = opts[b'deltabothparents']
421 self._deltabothparents = opts[b'deltabothparents']
414 self._lazydelta = bool(opts.get(b'lazydelta', True))
422 self._lazydelta = bool(opts.get(b'lazydelta', True))
415 self._lazydeltabase = False
423 self._lazydeltabase = False
416 if self._lazydelta:
424 if self._lazydelta:
417 self._lazydeltabase = bool(opts.get(b'lazydeltabase', False))
425 self._lazydeltabase = bool(opts.get(b'lazydeltabase', False))
418 if b'compengine' in opts:
426 if b'compengine' in opts:
419 self._compengine = opts[b'compengine']
427 self._compengine = opts[b'compengine']
420 if b'zlib.level' in opts:
428 if b'zlib.level' in opts:
421 self._compengineopts[b'zlib.level'] = opts[b'zlib.level']
429 self._compengineopts[b'zlib.level'] = opts[b'zlib.level']
422 if b'zstd.level' in opts:
430 if b'zstd.level' in opts:
423 self._compengineopts[b'zstd.level'] = opts[b'zstd.level']
431 self._compengineopts[b'zstd.level'] = opts[b'zstd.level']
424 if b'maxdeltachainspan' in opts:
432 if b'maxdeltachainspan' in opts:
425 self._maxdeltachainspan = opts[b'maxdeltachainspan']
433 self._maxdeltachainspan = opts[b'maxdeltachainspan']
426 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
434 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
427 mmapindexthreshold = opts[b'mmapindexthreshold']
435 mmapindexthreshold = opts[b'mmapindexthreshold']
428 self._sparserevlog = bool(opts.get(b'sparse-revlog', False))
436 self._sparserevlog = bool(opts.get(b'sparse-revlog', False))
429 withsparseread = bool(opts.get(b'with-sparse-read', False))
437 withsparseread = bool(opts.get(b'with-sparse-read', False))
430 # sparse-revlog forces sparse-read
438 # sparse-revlog forces sparse-read
431 self._withsparseread = self._sparserevlog or withsparseread
439 self._withsparseread = self._sparserevlog or withsparseread
432 if b'sparse-read-density-threshold' in opts:
440 if b'sparse-read-density-threshold' in opts:
433 self._srdensitythreshold = opts[b'sparse-read-density-threshold']
441 self._srdensitythreshold = opts[b'sparse-read-density-threshold']
434 if b'sparse-read-min-gap-size' in opts:
442 if b'sparse-read-min-gap-size' in opts:
435 self._srmingapsize = opts[b'sparse-read-min-gap-size']
443 self._srmingapsize = opts[b'sparse-read-min-gap-size']
436 if opts.get(b'enableellipsis'):
444 if opts.get(b'enableellipsis'):
437 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
445 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
438
446
439 # revlog v0 doesn't have flag processors
447 # revlog v0 doesn't have flag processors
440 for flag, processor in opts.get(b'flagprocessors', {}).items():
448 for flag, processor in opts.get(b'flagprocessors', {}).items():
441 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
449 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
442
450
443 if self._chunkcachesize <= 0:
451 if self._chunkcachesize <= 0:
444 raise error.RevlogError(
452 raise error.RevlogError(
445 _(b'revlog chunk cache size %r is not greater than 0')
453 _(b'revlog chunk cache size %r is not greater than 0')
446 % self._chunkcachesize
454 % self._chunkcachesize
447 )
455 )
448 elif self._chunkcachesize & (self._chunkcachesize - 1):
456 elif self._chunkcachesize & (self._chunkcachesize - 1):
449 raise error.RevlogError(
457 raise error.RevlogError(
450 _(b'revlog chunk cache size %r is not a power of 2')
458 _(b'revlog chunk cache size %r is not a power of 2')
451 % self._chunkcachesize
459 % self._chunkcachesize
452 )
460 )
453 force_nodemap = opts.get(b'devel-force-nodemap', False)
461 force_nodemap = opts.get(b'devel-force-nodemap', False)
454 return new_header, mmapindexthreshold, force_nodemap
462 return new_header, mmapindexthreshold, force_nodemap
455
463
456 def _get_data(self, filepath, mmap_threshold, size=None):
464 def _get_data(self, filepath, mmap_threshold, size=None):
457 """return a file content with or without mmap
465 """return a file content with or without mmap
458
466
459 If the file is missing return the empty string"""
467 If the file is missing return the empty string"""
460 try:
468 try:
461 with self.opener(filepath) as fp:
469 with self.opener(filepath) as fp:
462 if mmap_threshold is not None:
470 if mmap_threshold is not None:
463 file_size = self.opener.fstat(fp).st_size
471 file_size = self.opener.fstat(fp).st_size
464 if file_size >= mmap_threshold:
472 if file_size >= mmap_threshold:
465 if size is not None:
473 if size is not None:
466 # avoid potentiel mmap crash
474 # avoid potentiel mmap crash
467 size = min(file_size, size)
475 size = min(file_size, size)
468 # TODO: should .close() to release resources without
476 # TODO: should .close() to release resources without
469 # relying on Python GC
477 # relying on Python GC
470 if size is None:
478 if size is None:
471 return util.buffer(util.mmapread(fp))
479 return util.buffer(util.mmapread(fp))
472 else:
480 else:
473 return util.buffer(util.mmapread(fp, size))
481 return util.buffer(util.mmapread(fp, size))
474 if size is None:
482 if size is None:
475 return fp.read()
483 return fp.read()
476 else:
484 else:
477 return fp.read(size)
485 return fp.read(size)
478 except IOError as inst:
486 except IOError as inst:
479 if inst.errno != errno.ENOENT:
487 if inst.errno != errno.ENOENT:
480 raise
488 raise
481 return b''
489 return b''
482
490
483 def _loadindex(self, docket=None):
491 def _loadindex(self, docket=None):
484
492
485 new_header, mmapindexthreshold, force_nodemap = self._init_opts()
493 new_header, mmapindexthreshold, force_nodemap = self._init_opts()
486
494
487 if self.postfix is not None:
495 if self.postfix is not None:
488 entry_point = b'%s.i.%s' % (self.radix, self.postfix)
496 entry_point = b'%s.i.%s' % (self.radix, self.postfix)
489 elif self._trypending and self.opener.exists(b'%s.i.a' % self.radix):
497 elif self._trypending and self.opener.exists(b'%s.i.a' % self.radix):
490 entry_point = b'%s.i.a' % self.radix
498 entry_point = b'%s.i.a' % self.radix
491 else:
499 else:
492 entry_point = b'%s.i' % self.radix
500 entry_point = b'%s.i' % self.radix
493
501
494 if docket is not None:
502 if docket is not None:
495 self._docket = docket
503 self._docket = docket
496 self._docket_file = entry_point
504 self._docket_file = entry_point
497 else:
505 else:
498 entry_data = b''
506 entry_data = b''
499 self._initempty = True
507 self._initempty = True
500 entry_data = self._get_data(entry_point, mmapindexthreshold)
508 entry_data = self._get_data(entry_point, mmapindexthreshold)
501 if len(entry_data) > 0:
509 if len(entry_data) > 0:
502 header = INDEX_HEADER.unpack(entry_data[:4])[0]
510 header = INDEX_HEADER.unpack(entry_data[:4])[0]
503 self._initempty = False
511 self._initempty = False
504 else:
512 else:
505 header = new_header
513 header = new_header
506
514
507 self._format_flags = header & ~0xFFFF
515 self._format_flags = header & ~0xFFFF
508 self._format_version = header & 0xFFFF
516 self._format_version = header & 0xFFFF
509
517
510 supported_flags = SUPPORTED_FLAGS.get(self._format_version)
518 supported_flags = SUPPORTED_FLAGS.get(self._format_version)
511 if supported_flags is None:
519 if supported_flags is None:
512 msg = _(b'unknown version (%d) in revlog %s')
520 msg = _(b'unknown version (%d) in revlog %s')
513 msg %= (self._format_version, self.display_id)
521 msg %= (self._format_version, self.display_id)
514 raise error.RevlogError(msg)
522 raise error.RevlogError(msg)
515 elif self._format_flags & ~supported_flags:
523 elif self._format_flags & ~supported_flags:
516 msg = _(b'unknown flags (%#04x) in version %d revlog %s')
524 msg = _(b'unknown flags (%#04x) in version %d revlog %s')
517 display_flag = self._format_flags >> 16
525 display_flag = self._format_flags >> 16
518 msg %= (display_flag, self._format_version, self.display_id)
526 msg %= (display_flag, self._format_version, self.display_id)
519 raise error.RevlogError(msg)
527 raise error.RevlogError(msg)
520
528
521 features = FEATURES_BY_VERSION[self._format_version]
529 features = FEATURES_BY_VERSION[self._format_version]
522 self._inline = features[b'inline'](self._format_flags)
530 self._inline = features[b'inline'](self._format_flags)
523 self._generaldelta = features[b'generaldelta'](self._format_flags)
531 self._generaldelta = features[b'generaldelta'](self._format_flags)
524 self.hassidedata = features[b'sidedata']
532 self.hassidedata = features[b'sidedata']
525
533
526 if not features[b'docket']:
534 if not features[b'docket']:
527 self._indexfile = entry_point
535 self._indexfile = entry_point
528 index_data = entry_data
536 index_data = entry_data
529 else:
537 else:
530 self._docket_file = entry_point
538 self._docket_file = entry_point
531 if self._initempty:
539 if self._initempty:
532 self._docket = docketutil.default_docket(self, header)
540 self._docket = docketutil.default_docket(self, header)
533 else:
541 else:
534 self._docket = docketutil.parse_docket(
542 self._docket = docketutil.parse_docket(
535 self, entry_data, use_pending=self._trypending
543 self, entry_data, use_pending=self._trypending
536 )
544 )
537
545
538 if self._docket is not None:
546 if self._docket is not None:
539 self._indexfile = self._docket.index_filepath()
547 self._indexfile = self._docket.index_filepath()
540 index_data = b''
548 index_data = b''
541 index_size = self._docket.index_end
549 index_size = self._docket.index_end
542 if index_size > 0:
550 if index_size > 0:
543 index_data = self._get_data(
551 index_data = self._get_data(
544 self._indexfile, mmapindexthreshold, size=index_size
552 self._indexfile, mmapindexthreshold, size=index_size
545 )
553 )
546 if len(index_data) < index_size:
554 if len(index_data) < index_size:
547 msg = _(b'too few index data for %s: got %d, expected %d')
555 msg = _(b'too few index data for %s: got %d, expected %d')
548 msg %= (self.display_id, len(index_data), index_size)
556 msg %= (self.display_id, len(index_data), index_size)
549 raise error.RevlogError(msg)
557 raise error.RevlogError(msg)
550
558
551 self._inline = False
559 self._inline = False
552 # generaldelta implied by version 2 revlogs.
560 # generaldelta implied by version 2 revlogs.
553 self._generaldelta = True
561 self._generaldelta = True
554 # the logic for persistent nodemap will be dealt with within the
562 # the logic for persistent nodemap will be dealt with within the
555 # main docket, so disable it for now.
563 # main docket, so disable it for now.
556 self._nodemap_file = None
564 self._nodemap_file = None
557
565
558 if self._docket is not None:
566 if self._docket is not None:
559 self._datafile = self._docket.data_filepath()
567 self._datafile = self._docket.data_filepath()
560 self._sidedatafile = self._docket.sidedata_filepath()
568 self._sidedatafile = self._docket.sidedata_filepath()
561 elif self.postfix is None:
569 elif self.postfix is None:
562 self._datafile = b'%s.d' % self.radix
570 self._datafile = b'%s.d' % self.radix
563 else:
571 else:
564 self._datafile = b'%s.d.%s' % (self.radix, self.postfix)
572 self._datafile = b'%s.d.%s' % (self.radix, self.postfix)
565
573
566 self.nodeconstants = sha1nodeconstants
574 self.nodeconstants = sha1nodeconstants
567 self.nullid = self.nodeconstants.nullid
575 self.nullid = self.nodeconstants.nullid
568
576
569 # sparse-revlog can't be on without general-delta (issue6056)
577 # sparse-revlog can't be on without general-delta (issue6056)
570 if not self._generaldelta:
578 if not self._generaldelta:
571 self._sparserevlog = False
579 self._sparserevlog = False
572
580
573 self._storedeltachains = True
581 self._storedeltachains = True
574
582
575 devel_nodemap = (
583 devel_nodemap = (
576 self._nodemap_file
584 self._nodemap_file
577 and force_nodemap
585 and force_nodemap
578 and parse_index_v1_nodemap is not None
586 and parse_index_v1_nodemap is not None
579 )
587 )
580
588
581 use_rust_index = False
589 use_rust_index = False
582 if rustrevlog is not None:
590 if rustrevlog is not None:
583 if self._nodemap_file is not None:
591 if self._nodemap_file is not None:
584 use_rust_index = True
592 use_rust_index = True
585 else:
593 else:
586 use_rust_index = self.opener.options.get(b'rust.index')
594 use_rust_index = self.opener.options.get(b'rust.index')
587
595
588 self._parse_index = parse_index_v1
596 self._parse_index = parse_index_v1
589 if self._format_version == REVLOGV0:
597 if self._format_version == REVLOGV0:
590 self._parse_index = revlogv0.parse_index_v0
598 self._parse_index = revlogv0.parse_index_v0
591 elif self._format_version == REVLOGV2:
599 elif self._format_version == REVLOGV2:
592 self._parse_index = parse_index_v2
600 self._parse_index = parse_index_v2
593 elif self._format_version == CHANGELOGV2:
601 elif self._format_version == CHANGELOGV2:
594 self._parse_index = parse_index_cl_v2
602 self._parse_index = parse_index_cl_v2
595 elif devel_nodemap:
603 elif devel_nodemap:
596 self._parse_index = parse_index_v1_nodemap
604 self._parse_index = parse_index_v1_nodemap
597 elif use_rust_index:
605 elif use_rust_index:
598 self._parse_index = parse_index_v1_mixed
606 self._parse_index = parse_index_v1_mixed
599 try:
607 try:
600 d = self._parse_index(index_data, self._inline)
608 d = self._parse_index(index_data, self._inline)
601 index, chunkcache = d
609 index, chunkcache = d
602 use_nodemap = (
610 use_nodemap = (
603 not self._inline
611 not self._inline
604 and self._nodemap_file is not None
612 and self._nodemap_file is not None
605 and util.safehasattr(index, 'update_nodemap_data')
613 and util.safehasattr(index, 'update_nodemap_data')
606 )
614 )
607 if use_nodemap:
615 if use_nodemap:
608 nodemap_data = nodemaputil.persisted_data(self)
616 nodemap_data = nodemaputil.persisted_data(self)
609 if nodemap_data is not None:
617 if nodemap_data is not None:
610 docket = nodemap_data[0]
618 docket = nodemap_data[0]
611 if (
619 if (
612 len(d[0]) > docket.tip_rev
620 len(d[0]) > docket.tip_rev
613 and d[0][docket.tip_rev][7] == docket.tip_node
621 and d[0][docket.tip_rev][7] == docket.tip_node
614 ):
622 ):
615 # no changelog tampering
623 # no changelog tampering
616 self._nodemap_docket = docket
624 self._nodemap_docket = docket
617 index.update_nodemap_data(*nodemap_data)
625 index.update_nodemap_data(*nodemap_data)
618 except (ValueError, IndexError):
626 except (ValueError, IndexError):
619 raise error.RevlogError(
627 raise error.RevlogError(
620 _(b"index %s is corrupted") % self.display_id
628 _(b"index %s is corrupted") % self.display_id
621 )
629 )
622 self.index = index
630 self.index = index
623 self._segmentfile = randomaccessfile.randomaccessfile(
631 self._segmentfile = randomaccessfile.randomaccessfile(
624 self.opener,
632 self.opener,
625 (self._indexfile if self._inline else self._datafile),
633 (self._indexfile if self._inline else self._datafile),
626 self._chunkcachesize,
634 self._chunkcachesize,
627 chunkcache,
635 chunkcache,
628 )
636 )
629 self._segmentfile_sidedata = randomaccessfile.randomaccessfile(
637 self._segmentfile_sidedata = randomaccessfile.randomaccessfile(
630 self.opener,
638 self.opener,
631 self._sidedatafile,
639 self._sidedatafile,
632 self._chunkcachesize,
640 self._chunkcachesize,
633 )
641 )
634 # revnum -> (chain-length, sum-delta-length)
642 # revnum -> (chain-length, sum-delta-length)
635 self._chaininfocache = util.lrucachedict(500)
643 self._chaininfocache = util.lrucachedict(500)
636 # revlog header -> revlog compressor
644 # revlog header -> revlog compressor
637 self._decompressors = {}
645 self._decompressors = {}
638
646
639 @util.propertycache
647 @util.propertycache
640 def revlog_kind(self):
648 def revlog_kind(self):
641 return self.target[0]
649 return self.target[0]
642
650
643 @util.propertycache
651 @util.propertycache
644 def display_id(self):
652 def display_id(self):
645 """The public facing "ID" of the revlog that we use in message"""
653 """The public facing "ID" of the revlog that we use in message"""
646 # Maybe we should build a user facing representation of
654 # Maybe we should build a user facing representation of
647 # revlog.target instead of using `self.radix`
655 # revlog.target instead of using `self.radix`
648 return self.radix
656 return self.radix
649
657
650 def _get_decompressor(self, t):
658 def _get_decompressor(self, t):
651 try:
659 try:
652 compressor = self._decompressors[t]
660 compressor = self._decompressors[t]
653 except KeyError:
661 except KeyError:
654 try:
662 try:
655 engine = util.compengines.forrevlogheader(t)
663 engine = util.compengines.forrevlogheader(t)
656 compressor = engine.revlogcompressor(self._compengineopts)
664 compressor = engine.revlogcompressor(self._compengineopts)
657 self._decompressors[t] = compressor
665 self._decompressors[t] = compressor
658 except KeyError:
666 except KeyError:
659 raise error.RevlogError(
667 raise error.RevlogError(
660 _(b'unknown compression type %s') % binascii.hexlify(t)
668 _(b'unknown compression type %s') % binascii.hexlify(t)
661 )
669 )
662 return compressor
670 return compressor
663
671
664 @util.propertycache
672 @util.propertycache
665 def _compressor(self):
673 def _compressor(self):
666 engine = util.compengines[self._compengine]
674 engine = util.compengines[self._compengine]
667 return engine.revlogcompressor(self._compengineopts)
675 return engine.revlogcompressor(self._compengineopts)
668
676
669 @util.propertycache
677 @util.propertycache
670 def _decompressor(self):
678 def _decompressor(self):
671 """the default decompressor"""
679 """the default decompressor"""
672 if self._docket is None:
680 if self._docket is None:
673 return None
681 return None
674 t = self._docket.default_compression_header
682 t = self._docket.default_compression_header
675 c = self._get_decompressor(t)
683 c = self._get_decompressor(t)
676 return c.decompress
684 return c.decompress
677
685
678 def _indexfp(self):
686 def _indexfp(self):
679 """file object for the revlog's index file"""
687 """file object for the revlog's index file"""
680 return self.opener(self._indexfile, mode=b"r")
688 return self.opener(self._indexfile, mode=b"r")
681
689
682 def __index_write_fp(self):
690 def __index_write_fp(self):
683 # You should not use this directly and use `_writing` instead
691 # You should not use this directly and use `_writing` instead
684 try:
692 try:
685 f = self.opener(
693 f = self.opener(
686 self._indexfile, mode=b"r+", checkambig=self._checkambig
694 self._indexfile, mode=b"r+", checkambig=self._checkambig
687 )
695 )
688 if self._docket is None:
696 if self._docket is None:
689 f.seek(0, os.SEEK_END)
697 f.seek(0, os.SEEK_END)
690 else:
698 else:
691 f.seek(self._docket.index_end, os.SEEK_SET)
699 f.seek(self._docket.index_end, os.SEEK_SET)
692 return f
700 return f
693 except IOError as inst:
701 except IOError as inst:
694 if inst.errno != errno.ENOENT:
702 if inst.errno != errno.ENOENT:
695 raise
703 raise
696 return self.opener(
704 return self.opener(
697 self._indexfile, mode=b"w+", checkambig=self._checkambig
705 self._indexfile, mode=b"w+", checkambig=self._checkambig
698 )
706 )
699
707
700 def __index_new_fp(self):
708 def __index_new_fp(self):
701 # You should not use this unless you are upgrading from inline revlog
709 # You should not use this unless you are upgrading from inline revlog
702 return self.opener(
710 return self.opener(
703 self._indexfile,
711 self._indexfile,
704 mode=b"w",
712 mode=b"w",
705 checkambig=self._checkambig,
713 checkambig=self._checkambig,
706 atomictemp=True,
714 atomictemp=True,
707 )
715 )
708
716
709 def _datafp(self, mode=b'r'):
717 def _datafp(self, mode=b'r'):
710 """file object for the revlog's data file"""
718 """file object for the revlog's data file"""
711 return self.opener(self._datafile, mode=mode)
719 return self.opener(self._datafile, mode=mode)
712
720
713 @contextlib.contextmanager
721 @contextlib.contextmanager
714 def _sidedatareadfp(self):
722 def _sidedatareadfp(self):
715 """file object suitable to read sidedata"""
723 """file object suitable to read sidedata"""
716 if self._writinghandles:
724 if self._writinghandles:
717 yield self._writinghandles[2]
725 yield self._writinghandles[2]
718 else:
726 else:
719 with self.opener(self._sidedatafile) as fp:
727 with self.opener(self._sidedatafile) as fp:
720 yield fp
728 yield fp
721
729
722 def tiprev(self):
730 def tiprev(self):
723 return len(self.index) - 1
731 return len(self.index) - 1
724
732
725 def tip(self):
733 def tip(self):
726 return self.node(self.tiprev())
734 return self.node(self.tiprev())
727
735
728 def __contains__(self, rev):
736 def __contains__(self, rev):
729 return 0 <= rev < len(self)
737 return 0 <= rev < len(self)
730
738
731 def __len__(self):
739 def __len__(self):
732 return len(self.index)
740 return len(self.index)
733
741
734 def __iter__(self):
742 def __iter__(self):
735 return iter(pycompat.xrange(len(self)))
743 return iter(pycompat.xrange(len(self)))
736
744
737 def revs(self, start=0, stop=None):
745 def revs(self, start=0, stop=None):
738 """iterate over all rev in this revlog (from start to stop)"""
746 """iterate over all rev in this revlog (from start to stop)"""
739 return storageutil.iterrevs(len(self), start=start, stop=stop)
747 return storageutil.iterrevs(len(self), start=start, stop=stop)
740
748
741 def hasnode(self, node):
749 def hasnode(self, node):
742 try:
750 try:
743 self.rev(node)
751 self.rev(node)
744 return True
752 return True
745 except KeyError:
753 except KeyError:
746 return False
754 return False
747
755
748 def candelta(self, baserev, rev):
756 def candelta(self, baserev, rev):
749 """whether two revisions (baserev, rev) can be delta-ed or not"""
757 """whether two revisions (baserev, rev) can be delta-ed or not"""
750 # Disable delta if either rev requires a content-changing flag
758 # Disable delta if either rev requires a content-changing flag
751 # processor (ex. LFS). This is because such flag processor can alter
759 # processor (ex. LFS). This is because such flag processor can alter
752 # the rawtext content that the delta will be based on, and two clients
760 # the rawtext content that the delta will be based on, and two clients
753 # could have a same revlog node with different flags (i.e. different
761 # could have a same revlog node with different flags (i.e. different
754 # rawtext contents) and the delta could be incompatible.
762 # rawtext contents) and the delta could be incompatible.
755 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
763 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
756 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
764 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
757 ):
765 ):
758 return False
766 return False
759 return True
767 return True
760
768
761 def update_caches(self, transaction):
769 def update_caches(self, transaction):
762 if self._nodemap_file is not None:
770 if self._nodemap_file is not None:
763 if transaction is None:
771 if transaction is None:
764 nodemaputil.update_persistent_nodemap(self)
772 nodemaputil.update_persistent_nodemap(self)
765 else:
773 else:
766 nodemaputil.setup_persistent_nodemap(transaction, self)
774 nodemaputil.setup_persistent_nodemap(transaction, self)
767
775
768 def clearcaches(self):
776 def clearcaches(self):
769 self._revisioncache = None
777 self._revisioncache = None
770 self._chainbasecache.clear()
778 self._chainbasecache.clear()
771 self._segmentfile.clear_cache()
779 self._segmentfile.clear_cache()
772 self._segmentfile_sidedata.clear_cache()
780 self._segmentfile_sidedata.clear_cache()
773 self._pcache = {}
781 self._pcache = {}
774 self._nodemap_docket = None
782 self._nodemap_docket = None
775 self.index.clearcaches()
783 self.index.clearcaches()
776 # The python code is the one responsible for validating the docket, we
784 # The python code is the one responsible for validating the docket, we
777 # end up having to refresh it here.
785 # end up having to refresh it here.
778 use_nodemap = (
786 use_nodemap = (
779 not self._inline
787 not self._inline
780 and self._nodemap_file is not None
788 and self._nodemap_file is not None
781 and util.safehasattr(self.index, 'update_nodemap_data')
789 and util.safehasattr(self.index, 'update_nodemap_data')
782 )
790 )
783 if use_nodemap:
791 if use_nodemap:
784 nodemap_data = nodemaputil.persisted_data(self)
792 nodemap_data = nodemaputil.persisted_data(self)
785 if nodemap_data is not None:
793 if nodemap_data is not None:
786 self._nodemap_docket = nodemap_data[0]
794 self._nodemap_docket = nodemap_data[0]
787 self.index.update_nodemap_data(*nodemap_data)
795 self.index.update_nodemap_data(*nodemap_data)
788
796
789 def rev(self, node):
797 def rev(self, node):
790 try:
798 try:
791 return self.index.rev(node)
799 return self.index.rev(node)
792 except TypeError:
800 except TypeError:
793 raise
801 raise
794 except error.RevlogError:
802 except error.RevlogError:
795 # parsers.c radix tree lookup failed
803 # parsers.c radix tree lookup failed
796 if (
804 if (
797 node == self.nodeconstants.wdirid
805 node == self.nodeconstants.wdirid
798 or node in self.nodeconstants.wdirfilenodeids
806 or node in self.nodeconstants.wdirfilenodeids
799 ):
807 ):
800 raise error.WdirUnsupported
808 raise error.WdirUnsupported
801 raise error.LookupError(node, self.display_id, _(b'no node'))
809 raise error.LookupError(node, self.display_id, _(b'no node'))
802
810
803 # Accessors for index entries.
811 # Accessors for index entries.
804
812
805 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
813 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
806 # are flags.
814 # are flags.
807 def start(self, rev):
815 def start(self, rev):
808 return int(self.index[rev][0] >> 16)
816 return int(self.index[rev][0] >> 16)
809
817
810 def sidedata_cut_off(self, rev):
818 def sidedata_cut_off(self, rev):
811 sd_cut_off = self.index[rev][8]
819 sd_cut_off = self.index[rev][8]
812 if sd_cut_off != 0:
820 if sd_cut_off != 0:
813 return sd_cut_off
821 return sd_cut_off
814 # This is some annoying dance, because entries without sidedata
822 # This is some annoying dance, because entries without sidedata
815 # currently use 0 as their ofsset. (instead of previous-offset +
823 # currently use 0 as their ofsset. (instead of previous-offset +
816 # previous-size)
824 # previous-size)
817 #
825 #
818 # We should reconsider this sidedata β†’ 0 sidata_offset policy.
826 # We should reconsider this sidedata β†’ 0 sidata_offset policy.
819 # In the meantime, we need this.
827 # In the meantime, we need this.
820 while 0 <= rev:
828 while 0 <= rev:
821 e = self.index[rev]
829 e = self.index[rev]
822 if e[9] != 0:
830 if e[9] != 0:
823 return e[8] + e[9]
831 return e[8] + e[9]
824 rev -= 1
832 rev -= 1
825 return 0
833 return 0
826
834
827 def flags(self, rev):
835 def flags(self, rev):
828 return self.index[rev][0] & 0xFFFF
836 return self.index[rev][0] & 0xFFFF
829
837
830 def length(self, rev):
838 def length(self, rev):
831 return self.index[rev][1]
839 return self.index[rev][1]
832
840
833 def sidedata_length(self, rev):
841 def sidedata_length(self, rev):
834 if not self.hassidedata:
842 if not self.hassidedata:
835 return 0
843 return 0
836 return self.index[rev][9]
844 return self.index[rev][9]
837
845
838 def rawsize(self, rev):
846 def rawsize(self, rev):
839 """return the length of the uncompressed text for a given revision"""
847 """return the length of the uncompressed text for a given revision"""
840 l = self.index[rev][2]
848 l = self.index[rev][2]
841 if l >= 0:
849 if l >= 0:
842 return l
850 return l
843
851
844 t = self.rawdata(rev)
852 t = self.rawdata(rev)
845 return len(t)
853 return len(t)
846
854
847 def size(self, rev):
855 def size(self, rev):
848 """length of non-raw text (processed by a "read" flag processor)"""
856 """length of non-raw text (processed by a "read" flag processor)"""
849 # fast path: if no "read" flag processor could change the content,
857 # fast path: if no "read" flag processor could change the content,
850 # size is rawsize. note: ELLIPSIS is known to not change the content.
858 # size is rawsize. note: ELLIPSIS is known to not change the content.
851 flags = self.flags(rev)
859 flags = self.flags(rev)
852 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
860 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
853 return self.rawsize(rev)
861 return self.rawsize(rev)
854
862
855 return len(self.revision(rev))
863 return len(self.revision(rev))
856
864
857 def fast_rank(self, rev):
865 def fast_rank(self, rev):
858 """Return the rank of a revision if already known, or None otherwise.
866 """Return the rank of a revision if already known, or None otherwise.
859
867
860 The rank of a revision is the size of the sub-graph it defines as a
868 The rank of a revision is the size of the sub-graph it defines as a
861 head. Equivalently, the rank of a revision `r` is the size of the set
869 head. Equivalently, the rank of a revision `r` is the size of the set
862 `ancestors(r)`, `r` included.
870 `ancestors(r)`, `r` included.
863
871
864 This method returns the rank retrieved from the revlog in constant
872 This method returns the rank retrieved from the revlog in constant
865 time. It makes no attempt at computing unknown values for versions of
873 time. It makes no attempt at computing unknown values for versions of
866 the revlog which do not persist the rank.
874 the revlog which do not persist the rank.
867 """
875 """
868 rank = self.index[rev][ENTRY_RANK]
876 rank = self.index[rev][ENTRY_RANK]
869 if self._format_version != CHANGELOGV2 or rank == RANK_UNKNOWN:
877 if self._format_version != CHANGELOGV2 or rank == RANK_UNKNOWN:
870 return None
878 return None
871 if rev == nullrev:
879 if rev == nullrev:
872 return 0 # convention
880 return 0 # convention
873 return rank
881 return rank
874
882
875 def chainbase(self, rev):
883 def chainbase(self, rev):
876 base = self._chainbasecache.get(rev)
884 base = self._chainbasecache.get(rev)
877 if base is not None:
885 if base is not None:
878 return base
886 return base
879
887
880 index = self.index
888 index = self.index
881 iterrev = rev
889 iterrev = rev
882 base = index[iterrev][3]
890 base = index[iterrev][3]
883 while base != iterrev:
891 while base != iterrev:
884 iterrev = base
892 iterrev = base
885 base = index[iterrev][3]
893 base = index[iterrev][3]
886
894
887 self._chainbasecache[rev] = base
895 self._chainbasecache[rev] = base
888 return base
896 return base
889
897
890 def linkrev(self, rev):
898 def linkrev(self, rev):
891 return self.index[rev][4]
899 return self.index[rev][4]
892
900
893 def parentrevs(self, rev):
901 def parentrevs(self, rev):
894 try:
902 try:
895 entry = self.index[rev]
903 entry = self.index[rev]
896 except IndexError:
904 except IndexError:
897 if rev == wdirrev:
905 if rev == wdirrev:
898 raise error.WdirUnsupported
906 raise error.WdirUnsupported
899 raise
907 raise
900
908
901 return entry[5], entry[6]
909 if self.canonical_parent_order and entry[5] == nullrev:
910 return entry[6], entry[5]
911 else:
912 return entry[5], entry[6]
902
913
903 # fast parentrevs(rev) where rev isn't filtered
914 # fast parentrevs(rev) where rev isn't filtered
904 _uncheckedparentrevs = parentrevs
915 _uncheckedparentrevs = parentrevs
905
916
906 def node(self, rev):
917 def node(self, rev):
907 try:
918 try:
908 return self.index[rev][7]
919 return self.index[rev][7]
909 except IndexError:
920 except IndexError:
910 if rev == wdirrev:
921 if rev == wdirrev:
911 raise error.WdirUnsupported
922 raise error.WdirUnsupported
912 raise
923 raise
913
924
914 # Derived from index values.
925 # Derived from index values.
915
926
916 def end(self, rev):
927 def end(self, rev):
917 return self.start(rev) + self.length(rev)
928 return self.start(rev) + self.length(rev)
918
929
919 def parents(self, node):
930 def parents(self, node):
920 i = self.index
931 i = self.index
921 d = i[self.rev(node)]
932 d = i[self.rev(node)]
922 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
933 # inline node() to avoid function call overhead
934 if self.canonical_parent_order and d[5] == self.nullid:
935 return i[d[6]][7], i[d[5]][7]
936 else:
937 return i[d[5]][7], i[d[6]][7]
923
938
924 def chainlen(self, rev):
939 def chainlen(self, rev):
925 return self._chaininfo(rev)[0]
940 return self._chaininfo(rev)[0]
926
941
927 def _chaininfo(self, rev):
942 def _chaininfo(self, rev):
928 chaininfocache = self._chaininfocache
943 chaininfocache = self._chaininfocache
929 if rev in chaininfocache:
944 if rev in chaininfocache:
930 return chaininfocache[rev]
945 return chaininfocache[rev]
931 index = self.index
946 index = self.index
932 generaldelta = self._generaldelta
947 generaldelta = self._generaldelta
933 iterrev = rev
948 iterrev = rev
934 e = index[iterrev]
949 e = index[iterrev]
935 clen = 0
950 clen = 0
936 compresseddeltalen = 0
951 compresseddeltalen = 0
937 while iterrev != e[3]:
952 while iterrev != e[3]:
938 clen += 1
953 clen += 1
939 compresseddeltalen += e[1]
954 compresseddeltalen += e[1]
940 if generaldelta:
955 if generaldelta:
941 iterrev = e[3]
956 iterrev = e[3]
942 else:
957 else:
943 iterrev -= 1
958 iterrev -= 1
944 if iterrev in chaininfocache:
959 if iterrev in chaininfocache:
945 t = chaininfocache[iterrev]
960 t = chaininfocache[iterrev]
946 clen += t[0]
961 clen += t[0]
947 compresseddeltalen += t[1]
962 compresseddeltalen += t[1]
948 break
963 break
949 e = index[iterrev]
964 e = index[iterrev]
950 else:
965 else:
951 # Add text length of base since decompressing that also takes
966 # Add text length of base since decompressing that also takes
952 # work. For cache hits the length is already included.
967 # work. For cache hits the length is already included.
953 compresseddeltalen += e[1]
968 compresseddeltalen += e[1]
954 r = (clen, compresseddeltalen)
969 r = (clen, compresseddeltalen)
955 chaininfocache[rev] = r
970 chaininfocache[rev] = r
956 return r
971 return r
957
972
958 def _deltachain(self, rev, stoprev=None):
973 def _deltachain(self, rev, stoprev=None):
959 """Obtain the delta chain for a revision.
974 """Obtain the delta chain for a revision.
960
975
961 ``stoprev`` specifies a revision to stop at. If not specified, we
976 ``stoprev`` specifies a revision to stop at. If not specified, we
962 stop at the base of the chain.
977 stop at the base of the chain.
963
978
964 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
979 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
965 revs in ascending order and ``stopped`` is a bool indicating whether
980 revs in ascending order and ``stopped`` is a bool indicating whether
966 ``stoprev`` was hit.
981 ``stoprev`` was hit.
967 """
982 """
968 # Try C implementation.
983 # Try C implementation.
969 try:
984 try:
970 return self.index.deltachain(rev, stoprev, self._generaldelta)
985 return self.index.deltachain(rev, stoprev, self._generaldelta)
971 except AttributeError:
986 except AttributeError:
972 pass
987 pass
973
988
974 chain = []
989 chain = []
975
990
976 # Alias to prevent attribute lookup in tight loop.
991 # Alias to prevent attribute lookup in tight loop.
977 index = self.index
992 index = self.index
978 generaldelta = self._generaldelta
993 generaldelta = self._generaldelta
979
994
980 iterrev = rev
995 iterrev = rev
981 e = index[iterrev]
996 e = index[iterrev]
982 while iterrev != e[3] and iterrev != stoprev:
997 while iterrev != e[3] and iterrev != stoprev:
983 chain.append(iterrev)
998 chain.append(iterrev)
984 if generaldelta:
999 if generaldelta:
985 iterrev = e[3]
1000 iterrev = e[3]
986 else:
1001 else:
987 iterrev -= 1
1002 iterrev -= 1
988 e = index[iterrev]
1003 e = index[iterrev]
989
1004
990 if iterrev == stoprev:
1005 if iterrev == stoprev:
991 stopped = True
1006 stopped = True
992 else:
1007 else:
993 chain.append(iterrev)
1008 chain.append(iterrev)
994 stopped = False
1009 stopped = False
995
1010
996 chain.reverse()
1011 chain.reverse()
997 return chain, stopped
1012 return chain, stopped
998
1013
999 def ancestors(self, revs, stoprev=0, inclusive=False):
1014 def ancestors(self, revs, stoprev=0, inclusive=False):
1000 """Generate the ancestors of 'revs' in reverse revision order.
1015 """Generate the ancestors of 'revs' in reverse revision order.
1001 Does not generate revs lower than stoprev.
1016 Does not generate revs lower than stoprev.
1002
1017
1003 See the documentation for ancestor.lazyancestors for more details."""
1018 See the documentation for ancestor.lazyancestors for more details."""
1004
1019
1005 # first, make sure start revisions aren't filtered
1020 # first, make sure start revisions aren't filtered
1006 revs = list(revs)
1021 revs = list(revs)
1007 checkrev = self.node
1022 checkrev = self.node
1008 for r in revs:
1023 for r in revs:
1009 checkrev(r)
1024 checkrev(r)
1010 # and we're sure ancestors aren't filtered as well
1025 # and we're sure ancestors aren't filtered as well
1011
1026
1012 if rustancestor is not None and self.index.rust_ext_compat:
1027 if rustancestor is not None and self.index.rust_ext_compat:
1013 lazyancestors = rustancestor.LazyAncestors
1028 lazyancestors = rustancestor.LazyAncestors
1014 arg = self.index
1029 arg = self.index
1015 else:
1030 else:
1016 lazyancestors = ancestor.lazyancestors
1031 lazyancestors = ancestor.lazyancestors
1017 arg = self._uncheckedparentrevs
1032 arg = self._uncheckedparentrevs
1018 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
1033 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
1019
1034
1020 def descendants(self, revs):
1035 def descendants(self, revs):
1021 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
1036 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
1022
1037
1023 def findcommonmissing(self, common=None, heads=None):
1038 def findcommonmissing(self, common=None, heads=None):
1024 """Return a tuple of the ancestors of common and the ancestors of heads
1039 """Return a tuple of the ancestors of common and the ancestors of heads
1025 that are not ancestors of common. In revset terminology, we return the
1040 that are not ancestors of common. In revset terminology, we return the
1026 tuple:
1041 tuple:
1027
1042
1028 ::common, (::heads) - (::common)
1043 ::common, (::heads) - (::common)
1029
1044
1030 The list is sorted by revision number, meaning it is
1045 The list is sorted by revision number, meaning it is
1031 topologically sorted.
1046 topologically sorted.
1032
1047
1033 'heads' and 'common' are both lists of node IDs. If heads is
1048 'heads' and 'common' are both lists of node IDs. If heads is
1034 not supplied, uses all of the revlog's heads. If common is not
1049 not supplied, uses all of the revlog's heads. If common is not
1035 supplied, uses nullid."""
1050 supplied, uses nullid."""
1036 if common is None:
1051 if common is None:
1037 common = [self.nullid]
1052 common = [self.nullid]
1038 if heads is None:
1053 if heads is None:
1039 heads = self.heads()
1054 heads = self.heads()
1040
1055
1041 common = [self.rev(n) for n in common]
1056 common = [self.rev(n) for n in common]
1042 heads = [self.rev(n) for n in heads]
1057 heads = [self.rev(n) for n in heads]
1043
1058
1044 # we want the ancestors, but inclusive
1059 # we want the ancestors, but inclusive
1045 class lazyset:
1060 class lazyset:
1046 def __init__(self, lazyvalues):
1061 def __init__(self, lazyvalues):
1047 self.addedvalues = set()
1062 self.addedvalues = set()
1048 self.lazyvalues = lazyvalues
1063 self.lazyvalues = lazyvalues
1049
1064
1050 def __contains__(self, value):
1065 def __contains__(self, value):
1051 return value in self.addedvalues or value in self.lazyvalues
1066 return value in self.addedvalues or value in self.lazyvalues
1052
1067
1053 def __iter__(self):
1068 def __iter__(self):
1054 added = self.addedvalues
1069 added = self.addedvalues
1055 for r in added:
1070 for r in added:
1056 yield r
1071 yield r
1057 for r in self.lazyvalues:
1072 for r in self.lazyvalues:
1058 if not r in added:
1073 if not r in added:
1059 yield r
1074 yield r
1060
1075
1061 def add(self, value):
1076 def add(self, value):
1062 self.addedvalues.add(value)
1077 self.addedvalues.add(value)
1063
1078
1064 def update(self, values):
1079 def update(self, values):
1065 self.addedvalues.update(values)
1080 self.addedvalues.update(values)
1066
1081
1067 has = lazyset(self.ancestors(common))
1082 has = lazyset(self.ancestors(common))
1068 has.add(nullrev)
1083 has.add(nullrev)
1069 has.update(common)
1084 has.update(common)
1070
1085
1071 # take all ancestors from heads that aren't in has
1086 # take all ancestors from heads that aren't in has
1072 missing = set()
1087 missing = set()
1073 visit = collections.deque(r for r in heads if r not in has)
1088 visit = collections.deque(r for r in heads if r not in has)
1074 while visit:
1089 while visit:
1075 r = visit.popleft()
1090 r = visit.popleft()
1076 if r in missing:
1091 if r in missing:
1077 continue
1092 continue
1078 else:
1093 else:
1079 missing.add(r)
1094 missing.add(r)
1080 for p in self.parentrevs(r):
1095 for p in self.parentrevs(r):
1081 if p not in has:
1096 if p not in has:
1082 visit.append(p)
1097 visit.append(p)
1083 missing = list(missing)
1098 missing = list(missing)
1084 missing.sort()
1099 missing.sort()
1085 return has, [self.node(miss) for miss in missing]
1100 return has, [self.node(miss) for miss in missing]
1086
1101
1087 def incrementalmissingrevs(self, common=None):
1102 def incrementalmissingrevs(self, common=None):
1088 """Return an object that can be used to incrementally compute the
1103 """Return an object that can be used to incrementally compute the
1089 revision numbers of the ancestors of arbitrary sets that are not
1104 revision numbers of the ancestors of arbitrary sets that are not
1090 ancestors of common. This is an ancestor.incrementalmissingancestors
1105 ancestors of common. This is an ancestor.incrementalmissingancestors
1091 object.
1106 object.
1092
1107
1093 'common' is a list of revision numbers. If common is not supplied, uses
1108 'common' is a list of revision numbers. If common is not supplied, uses
1094 nullrev.
1109 nullrev.
1095 """
1110 """
1096 if common is None:
1111 if common is None:
1097 common = [nullrev]
1112 common = [nullrev]
1098
1113
1099 if rustancestor is not None and self.index.rust_ext_compat:
1114 if rustancestor is not None and self.index.rust_ext_compat:
1100 return rustancestor.MissingAncestors(self.index, common)
1115 return rustancestor.MissingAncestors(self.index, common)
1101 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1116 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1102
1117
1103 def findmissingrevs(self, common=None, heads=None):
1118 def findmissingrevs(self, common=None, heads=None):
1104 """Return the revision numbers of the ancestors of heads that
1119 """Return the revision numbers of the ancestors of heads that
1105 are not ancestors of common.
1120 are not ancestors of common.
1106
1121
1107 More specifically, return a list of revision numbers corresponding to
1122 More specifically, return a list of revision numbers corresponding to
1108 nodes N such that every N satisfies the following constraints:
1123 nodes N such that every N satisfies the following constraints:
1109
1124
1110 1. N is an ancestor of some node in 'heads'
1125 1. N is an ancestor of some node in 'heads'
1111 2. N is not an ancestor of any node in 'common'
1126 2. N is not an ancestor of any node in 'common'
1112
1127
1113 The list is sorted by revision number, meaning it is
1128 The list is sorted by revision number, meaning it is
1114 topologically sorted.
1129 topologically sorted.
1115
1130
1116 'heads' and 'common' are both lists of revision numbers. If heads is
1131 'heads' and 'common' are both lists of revision numbers. If heads is
1117 not supplied, uses all of the revlog's heads. If common is not
1132 not supplied, uses all of the revlog's heads. If common is not
1118 supplied, uses nullid."""
1133 supplied, uses nullid."""
1119 if common is None:
1134 if common is None:
1120 common = [nullrev]
1135 common = [nullrev]
1121 if heads is None:
1136 if heads is None:
1122 heads = self.headrevs()
1137 heads = self.headrevs()
1123
1138
1124 inc = self.incrementalmissingrevs(common=common)
1139 inc = self.incrementalmissingrevs(common=common)
1125 return inc.missingancestors(heads)
1140 return inc.missingancestors(heads)
1126
1141
1127 def findmissing(self, common=None, heads=None):
1142 def findmissing(self, common=None, heads=None):
1128 """Return the ancestors of heads that are not ancestors of common.
1143 """Return the ancestors of heads that are not ancestors of common.
1129
1144
1130 More specifically, return a list of nodes N such that every N
1145 More specifically, return a list of nodes N such that every N
1131 satisfies the following constraints:
1146 satisfies the following constraints:
1132
1147
1133 1. N is an ancestor of some node in 'heads'
1148 1. N is an ancestor of some node in 'heads'
1134 2. N is not an ancestor of any node in 'common'
1149 2. N is not an ancestor of any node in 'common'
1135
1150
1136 The list is sorted by revision number, meaning it is
1151 The list is sorted by revision number, meaning it is
1137 topologically sorted.
1152 topologically sorted.
1138
1153
1139 'heads' and 'common' are both lists of node IDs. If heads is
1154 'heads' and 'common' are both lists of node IDs. If heads is
1140 not supplied, uses all of the revlog's heads. If common is not
1155 not supplied, uses all of the revlog's heads. If common is not
1141 supplied, uses nullid."""
1156 supplied, uses nullid."""
1142 if common is None:
1157 if common is None:
1143 common = [self.nullid]
1158 common = [self.nullid]
1144 if heads is None:
1159 if heads is None:
1145 heads = self.heads()
1160 heads = self.heads()
1146
1161
1147 common = [self.rev(n) for n in common]
1162 common = [self.rev(n) for n in common]
1148 heads = [self.rev(n) for n in heads]
1163 heads = [self.rev(n) for n in heads]
1149
1164
1150 inc = self.incrementalmissingrevs(common=common)
1165 inc = self.incrementalmissingrevs(common=common)
1151 return [self.node(r) for r in inc.missingancestors(heads)]
1166 return [self.node(r) for r in inc.missingancestors(heads)]
1152
1167
1153 def nodesbetween(self, roots=None, heads=None):
1168 def nodesbetween(self, roots=None, heads=None):
1154 """Return a topological path from 'roots' to 'heads'.
1169 """Return a topological path from 'roots' to 'heads'.
1155
1170
1156 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1171 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1157 topologically sorted list of all nodes N that satisfy both of
1172 topologically sorted list of all nodes N that satisfy both of
1158 these constraints:
1173 these constraints:
1159
1174
1160 1. N is a descendant of some node in 'roots'
1175 1. N is a descendant of some node in 'roots'
1161 2. N is an ancestor of some node in 'heads'
1176 2. N is an ancestor of some node in 'heads'
1162
1177
1163 Every node is considered to be both a descendant and an ancestor
1178 Every node is considered to be both a descendant and an ancestor
1164 of itself, so every reachable node in 'roots' and 'heads' will be
1179 of itself, so every reachable node in 'roots' and 'heads' will be
1165 included in 'nodes'.
1180 included in 'nodes'.
1166
1181
1167 'outroots' is the list of reachable nodes in 'roots', i.e., the
1182 'outroots' is the list of reachable nodes in 'roots', i.e., the
1168 subset of 'roots' that is returned in 'nodes'. Likewise,
1183 subset of 'roots' that is returned in 'nodes'. Likewise,
1169 'outheads' is the subset of 'heads' that is also in 'nodes'.
1184 'outheads' is the subset of 'heads' that is also in 'nodes'.
1170
1185
1171 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1186 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1172 unspecified, uses nullid as the only root. If 'heads' is
1187 unspecified, uses nullid as the only root. If 'heads' is
1173 unspecified, uses list of all of the revlog's heads."""
1188 unspecified, uses list of all of the revlog's heads."""
1174 nonodes = ([], [], [])
1189 nonodes = ([], [], [])
1175 if roots is not None:
1190 if roots is not None:
1176 roots = list(roots)
1191 roots = list(roots)
1177 if not roots:
1192 if not roots:
1178 return nonodes
1193 return nonodes
1179 lowestrev = min([self.rev(n) for n in roots])
1194 lowestrev = min([self.rev(n) for n in roots])
1180 else:
1195 else:
1181 roots = [self.nullid] # Everybody's a descendant of nullid
1196 roots = [self.nullid] # Everybody's a descendant of nullid
1182 lowestrev = nullrev
1197 lowestrev = nullrev
1183 if (lowestrev == nullrev) and (heads is None):
1198 if (lowestrev == nullrev) and (heads is None):
1184 # We want _all_ the nodes!
1199 # We want _all_ the nodes!
1185 return (
1200 return (
1186 [self.node(r) for r in self],
1201 [self.node(r) for r in self],
1187 [self.nullid],
1202 [self.nullid],
1188 list(self.heads()),
1203 list(self.heads()),
1189 )
1204 )
1190 if heads is None:
1205 if heads is None:
1191 # All nodes are ancestors, so the latest ancestor is the last
1206 # All nodes are ancestors, so the latest ancestor is the last
1192 # node.
1207 # node.
1193 highestrev = len(self) - 1
1208 highestrev = len(self) - 1
1194 # Set ancestors to None to signal that every node is an ancestor.
1209 # Set ancestors to None to signal that every node is an ancestor.
1195 ancestors = None
1210 ancestors = None
1196 # Set heads to an empty dictionary for later discovery of heads
1211 # Set heads to an empty dictionary for later discovery of heads
1197 heads = {}
1212 heads = {}
1198 else:
1213 else:
1199 heads = list(heads)
1214 heads = list(heads)
1200 if not heads:
1215 if not heads:
1201 return nonodes
1216 return nonodes
1202 ancestors = set()
1217 ancestors = set()
1203 # Turn heads into a dictionary so we can remove 'fake' heads.
1218 # Turn heads into a dictionary so we can remove 'fake' heads.
1204 # Also, later we will be using it to filter out the heads we can't
1219 # Also, later we will be using it to filter out the heads we can't
1205 # find from roots.
1220 # find from roots.
1206 heads = dict.fromkeys(heads, False)
1221 heads = dict.fromkeys(heads, False)
1207 # Start at the top and keep marking parents until we're done.
1222 # Start at the top and keep marking parents until we're done.
1208 nodestotag = set(heads)
1223 nodestotag = set(heads)
1209 # Remember where the top was so we can use it as a limit later.
1224 # Remember where the top was so we can use it as a limit later.
1210 highestrev = max([self.rev(n) for n in nodestotag])
1225 highestrev = max([self.rev(n) for n in nodestotag])
1211 while nodestotag:
1226 while nodestotag:
1212 # grab a node to tag
1227 # grab a node to tag
1213 n = nodestotag.pop()
1228 n = nodestotag.pop()
1214 # Never tag nullid
1229 # Never tag nullid
1215 if n == self.nullid:
1230 if n == self.nullid:
1216 continue
1231 continue
1217 # A node's revision number represents its place in a
1232 # A node's revision number represents its place in a
1218 # topologically sorted list of nodes.
1233 # topologically sorted list of nodes.
1219 r = self.rev(n)
1234 r = self.rev(n)
1220 if r >= lowestrev:
1235 if r >= lowestrev:
1221 if n not in ancestors:
1236 if n not in ancestors:
1222 # If we are possibly a descendant of one of the roots
1237 # If we are possibly a descendant of one of the roots
1223 # and we haven't already been marked as an ancestor
1238 # and we haven't already been marked as an ancestor
1224 ancestors.add(n) # Mark as ancestor
1239 ancestors.add(n) # Mark as ancestor
1225 # Add non-nullid parents to list of nodes to tag.
1240 # Add non-nullid parents to list of nodes to tag.
1226 nodestotag.update(
1241 nodestotag.update(
1227 [p for p in self.parents(n) if p != self.nullid]
1242 [p for p in self.parents(n) if p != self.nullid]
1228 )
1243 )
1229 elif n in heads: # We've seen it before, is it a fake head?
1244 elif n in heads: # We've seen it before, is it a fake head?
1230 # So it is, real heads should not be the ancestors of
1245 # So it is, real heads should not be the ancestors of
1231 # any other heads.
1246 # any other heads.
1232 heads.pop(n)
1247 heads.pop(n)
1233 if not ancestors:
1248 if not ancestors:
1234 return nonodes
1249 return nonodes
1235 # Now that we have our set of ancestors, we want to remove any
1250 # Now that we have our set of ancestors, we want to remove any
1236 # roots that are not ancestors.
1251 # roots that are not ancestors.
1237
1252
1238 # If one of the roots was nullid, everything is included anyway.
1253 # If one of the roots was nullid, everything is included anyway.
1239 if lowestrev > nullrev:
1254 if lowestrev > nullrev:
1240 # But, since we weren't, let's recompute the lowest rev to not
1255 # But, since we weren't, let's recompute the lowest rev to not
1241 # include roots that aren't ancestors.
1256 # include roots that aren't ancestors.
1242
1257
1243 # Filter out roots that aren't ancestors of heads
1258 # Filter out roots that aren't ancestors of heads
1244 roots = [root for root in roots if root in ancestors]
1259 roots = [root for root in roots if root in ancestors]
1245 # Recompute the lowest revision
1260 # Recompute the lowest revision
1246 if roots:
1261 if roots:
1247 lowestrev = min([self.rev(root) for root in roots])
1262 lowestrev = min([self.rev(root) for root in roots])
1248 else:
1263 else:
1249 # No more roots? Return empty list
1264 # No more roots? Return empty list
1250 return nonodes
1265 return nonodes
1251 else:
1266 else:
1252 # We are descending from nullid, and don't need to care about
1267 # We are descending from nullid, and don't need to care about
1253 # any other roots.
1268 # any other roots.
1254 lowestrev = nullrev
1269 lowestrev = nullrev
1255 roots = [self.nullid]
1270 roots = [self.nullid]
1256 # Transform our roots list into a set.
1271 # Transform our roots list into a set.
1257 descendants = set(roots)
1272 descendants = set(roots)
1258 # Also, keep the original roots so we can filter out roots that aren't
1273 # Also, keep the original roots so we can filter out roots that aren't
1259 # 'real' roots (i.e. are descended from other roots).
1274 # 'real' roots (i.e. are descended from other roots).
1260 roots = descendants.copy()
1275 roots = descendants.copy()
1261 # Our topologically sorted list of output nodes.
1276 # Our topologically sorted list of output nodes.
1262 orderedout = []
1277 orderedout = []
1263 # Don't start at nullid since we don't want nullid in our output list,
1278 # Don't start at nullid since we don't want nullid in our output list,
1264 # and if nullid shows up in descendants, empty parents will look like
1279 # and if nullid shows up in descendants, empty parents will look like
1265 # they're descendants.
1280 # they're descendants.
1266 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1281 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1267 n = self.node(r)
1282 n = self.node(r)
1268 isdescendant = False
1283 isdescendant = False
1269 if lowestrev == nullrev: # Everybody is a descendant of nullid
1284 if lowestrev == nullrev: # Everybody is a descendant of nullid
1270 isdescendant = True
1285 isdescendant = True
1271 elif n in descendants:
1286 elif n in descendants:
1272 # n is already a descendant
1287 # n is already a descendant
1273 isdescendant = True
1288 isdescendant = True
1274 # This check only needs to be done here because all the roots
1289 # This check only needs to be done here because all the roots
1275 # will start being marked is descendants before the loop.
1290 # will start being marked is descendants before the loop.
1276 if n in roots:
1291 if n in roots:
1277 # If n was a root, check if it's a 'real' root.
1292 # If n was a root, check if it's a 'real' root.
1278 p = tuple(self.parents(n))
1293 p = tuple(self.parents(n))
1279 # If any of its parents are descendants, it's not a root.
1294 # If any of its parents are descendants, it's not a root.
1280 if (p[0] in descendants) or (p[1] in descendants):
1295 if (p[0] in descendants) or (p[1] in descendants):
1281 roots.remove(n)
1296 roots.remove(n)
1282 else:
1297 else:
1283 p = tuple(self.parents(n))
1298 p = tuple(self.parents(n))
1284 # A node is a descendant if either of its parents are
1299 # A node is a descendant if either of its parents are
1285 # descendants. (We seeded the dependents list with the roots
1300 # descendants. (We seeded the dependents list with the roots
1286 # up there, remember?)
1301 # up there, remember?)
1287 if (p[0] in descendants) or (p[1] in descendants):
1302 if (p[0] in descendants) or (p[1] in descendants):
1288 descendants.add(n)
1303 descendants.add(n)
1289 isdescendant = True
1304 isdescendant = True
1290 if isdescendant and ((ancestors is None) or (n in ancestors)):
1305 if isdescendant and ((ancestors is None) or (n in ancestors)):
1291 # Only include nodes that are both descendants and ancestors.
1306 # Only include nodes that are both descendants and ancestors.
1292 orderedout.append(n)
1307 orderedout.append(n)
1293 if (ancestors is not None) and (n in heads):
1308 if (ancestors is not None) and (n in heads):
1294 # We're trying to figure out which heads are reachable
1309 # We're trying to figure out which heads are reachable
1295 # from roots.
1310 # from roots.
1296 # Mark this head as having been reached
1311 # Mark this head as having been reached
1297 heads[n] = True
1312 heads[n] = True
1298 elif ancestors is None:
1313 elif ancestors is None:
1299 # Otherwise, we're trying to discover the heads.
1314 # Otherwise, we're trying to discover the heads.
1300 # Assume this is a head because if it isn't, the next step
1315 # Assume this is a head because if it isn't, the next step
1301 # will eventually remove it.
1316 # will eventually remove it.
1302 heads[n] = True
1317 heads[n] = True
1303 # But, obviously its parents aren't.
1318 # But, obviously its parents aren't.
1304 for p in self.parents(n):
1319 for p in self.parents(n):
1305 heads.pop(p, None)
1320 heads.pop(p, None)
1306 heads = [head for head, flag in heads.items() if flag]
1321 heads = [head for head, flag in heads.items() if flag]
1307 roots = list(roots)
1322 roots = list(roots)
1308 assert orderedout
1323 assert orderedout
1309 assert roots
1324 assert roots
1310 assert heads
1325 assert heads
1311 return (orderedout, roots, heads)
1326 return (orderedout, roots, heads)
1312
1327
1313 def headrevs(self, revs=None):
1328 def headrevs(self, revs=None):
1314 if revs is None:
1329 if revs is None:
1315 try:
1330 try:
1316 return self.index.headrevs()
1331 return self.index.headrevs()
1317 except AttributeError:
1332 except AttributeError:
1318 return self._headrevs()
1333 return self._headrevs()
1319 if rustdagop is not None and self.index.rust_ext_compat:
1334 if rustdagop is not None and self.index.rust_ext_compat:
1320 return rustdagop.headrevs(self.index, revs)
1335 return rustdagop.headrevs(self.index, revs)
1321 return dagop.headrevs(revs, self._uncheckedparentrevs)
1336 return dagop.headrevs(revs, self._uncheckedparentrevs)
1322
1337
1323 def computephases(self, roots):
1338 def computephases(self, roots):
1324 return self.index.computephasesmapsets(roots)
1339 return self.index.computephasesmapsets(roots)
1325
1340
1326 def _headrevs(self):
1341 def _headrevs(self):
1327 count = len(self)
1342 count = len(self)
1328 if not count:
1343 if not count:
1329 return [nullrev]
1344 return [nullrev]
1330 # we won't iter over filtered rev so nobody is a head at start
1345 # we won't iter over filtered rev so nobody is a head at start
1331 ishead = [0] * (count + 1)
1346 ishead = [0] * (count + 1)
1332 index = self.index
1347 index = self.index
1333 for r in self:
1348 for r in self:
1334 ishead[r] = 1 # I may be an head
1349 ishead[r] = 1 # I may be an head
1335 e = index[r]
1350 e = index[r]
1336 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1351 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1337 return [r for r, val in enumerate(ishead) if val]
1352 return [r for r, val in enumerate(ishead) if val]
1338
1353
1339 def heads(self, start=None, stop=None):
1354 def heads(self, start=None, stop=None):
1340 """return the list of all nodes that have no children
1355 """return the list of all nodes that have no children
1341
1356
1342 if start is specified, only heads that are descendants of
1357 if start is specified, only heads that are descendants of
1343 start will be returned
1358 start will be returned
1344 if stop is specified, it will consider all the revs from stop
1359 if stop is specified, it will consider all the revs from stop
1345 as if they had no children
1360 as if they had no children
1346 """
1361 """
1347 if start is None and stop is None:
1362 if start is None and stop is None:
1348 if not len(self):
1363 if not len(self):
1349 return [self.nullid]
1364 return [self.nullid]
1350 return [self.node(r) for r in self.headrevs()]
1365 return [self.node(r) for r in self.headrevs()]
1351
1366
1352 if start is None:
1367 if start is None:
1353 start = nullrev
1368 start = nullrev
1354 else:
1369 else:
1355 start = self.rev(start)
1370 start = self.rev(start)
1356
1371
1357 stoprevs = {self.rev(n) for n in stop or []}
1372 stoprevs = {self.rev(n) for n in stop or []}
1358
1373
1359 revs = dagop.headrevssubset(
1374 revs = dagop.headrevssubset(
1360 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1375 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1361 )
1376 )
1362
1377
1363 return [self.node(rev) for rev in revs]
1378 return [self.node(rev) for rev in revs]
1364
1379
1365 def children(self, node):
1380 def children(self, node):
1366 """find the children of a given node"""
1381 """find the children of a given node"""
1367 c = []
1382 c = []
1368 p = self.rev(node)
1383 p = self.rev(node)
1369 for r in self.revs(start=p + 1):
1384 for r in self.revs(start=p + 1):
1370 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1385 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1371 if prevs:
1386 if prevs:
1372 for pr in prevs:
1387 for pr in prevs:
1373 if pr == p:
1388 if pr == p:
1374 c.append(self.node(r))
1389 c.append(self.node(r))
1375 elif p == nullrev:
1390 elif p == nullrev:
1376 c.append(self.node(r))
1391 c.append(self.node(r))
1377 return c
1392 return c
1378
1393
1379 def commonancestorsheads(self, a, b):
1394 def commonancestorsheads(self, a, b):
1380 """calculate all the heads of the common ancestors of nodes a and b"""
1395 """calculate all the heads of the common ancestors of nodes a and b"""
1381 a, b = self.rev(a), self.rev(b)
1396 a, b = self.rev(a), self.rev(b)
1382 ancs = self._commonancestorsheads(a, b)
1397 ancs = self._commonancestorsheads(a, b)
1383 return pycompat.maplist(self.node, ancs)
1398 return pycompat.maplist(self.node, ancs)
1384
1399
1385 def _commonancestorsheads(self, *revs):
1400 def _commonancestorsheads(self, *revs):
1386 """calculate all the heads of the common ancestors of revs"""
1401 """calculate all the heads of the common ancestors of revs"""
1387 try:
1402 try:
1388 ancs = self.index.commonancestorsheads(*revs)
1403 ancs = self.index.commonancestorsheads(*revs)
1389 except (AttributeError, OverflowError): # C implementation failed
1404 except (AttributeError, OverflowError): # C implementation failed
1390 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1405 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1391 return ancs
1406 return ancs
1392
1407
1393 def isancestor(self, a, b):
1408 def isancestor(self, a, b):
1394 """return True if node a is an ancestor of node b
1409 """return True if node a is an ancestor of node b
1395
1410
1396 A revision is considered an ancestor of itself."""
1411 A revision is considered an ancestor of itself."""
1397 a, b = self.rev(a), self.rev(b)
1412 a, b = self.rev(a), self.rev(b)
1398 return self.isancestorrev(a, b)
1413 return self.isancestorrev(a, b)
1399
1414
1400 def isancestorrev(self, a, b):
1415 def isancestorrev(self, a, b):
1401 """return True if revision a is an ancestor of revision b
1416 """return True if revision a is an ancestor of revision b
1402
1417
1403 A revision is considered an ancestor of itself.
1418 A revision is considered an ancestor of itself.
1404
1419
1405 The implementation of this is trivial but the use of
1420 The implementation of this is trivial but the use of
1406 reachableroots is not."""
1421 reachableroots is not."""
1407 if a == nullrev:
1422 if a == nullrev:
1408 return True
1423 return True
1409 elif a == b:
1424 elif a == b:
1410 return True
1425 return True
1411 elif a > b:
1426 elif a > b:
1412 return False
1427 return False
1413 return bool(self.reachableroots(a, [b], [a], includepath=False))
1428 return bool(self.reachableroots(a, [b], [a], includepath=False))
1414
1429
1415 def reachableroots(self, minroot, heads, roots, includepath=False):
1430 def reachableroots(self, minroot, heads, roots, includepath=False):
1416 """return (heads(::(<roots> and <roots>::<heads>)))
1431 """return (heads(::(<roots> and <roots>::<heads>)))
1417
1432
1418 If includepath is True, return (<roots>::<heads>)."""
1433 If includepath is True, return (<roots>::<heads>)."""
1419 try:
1434 try:
1420 return self.index.reachableroots2(
1435 return self.index.reachableroots2(
1421 minroot, heads, roots, includepath
1436 minroot, heads, roots, includepath
1422 )
1437 )
1423 except AttributeError:
1438 except AttributeError:
1424 return dagop._reachablerootspure(
1439 return dagop._reachablerootspure(
1425 self.parentrevs, minroot, roots, heads, includepath
1440 self.parentrevs, minroot, roots, heads, includepath
1426 )
1441 )
1427
1442
1428 def ancestor(self, a, b):
1443 def ancestor(self, a, b):
1429 """calculate the "best" common ancestor of nodes a and b"""
1444 """calculate the "best" common ancestor of nodes a and b"""
1430
1445
1431 a, b = self.rev(a), self.rev(b)
1446 a, b = self.rev(a), self.rev(b)
1432 try:
1447 try:
1433 ancs = self.index.ancestors(a, b)
1448 ancs = self.index.ancestors(a, b)
1434 except (AttributeError, OverflowError):
1449 except (AttributeError, OverflowError):
1435 ancs = ancestor.ancestors(self.parentrevs, a, b)
1450 ancs = ancestor.ancestors(self.parentrevs, a, b)
1436 if ancs:
1451 if ancs:
1437 # choose a consistent winner when there's a tie
1452 # choose a consistent winner when there's a tie
1438 return min(map(self.node, ancs))
1453 return min(map(self.node, ancs))
1439 return self.nullid
1454 return self.nullid
1440
1455
1441 def _match(self, id):
1456 def _match(self, id):
1442 if isinstance(id, int):
1457 if isinstance(id, int):
1443 # rev
1458 # rev
1444 return self.node(id)
1459 return self.node(id)
1445 if len(id) == self.nodeconstants.nodelen:
1460 if len(id) == self.nodeconstants.nodelen:
1446 # possibly a binary node
1461 # possibly a binary node
1447 # odds of a binary node being all hex in ASCII are 1 in 10**25
1462 # odds of a binary node being all hex in ASCII are 1 in 10**25
1448 try:
1463 try:
1449 node = id
1464 node = id
1450 self.rev(node) # quick search the index
1465 self.rev(node) # quick search the index
1451 return node
1466 return node
1452 except error.LookupError:
1467 except error.LookupError:
1453 pass # may be partial hex id
1468 pass # may be partial hex id
1454 try:
1469 try:
1455 # str(rev)
1470 # str(rev)
1456 rev = int(id)
1471 rev = int(id)
1457 if b"%d" % rev != id:
1472 if b"%d" % rev != id:
1458 raise ValueError
1473 raise ValueError
1459 if rev < 0:
1474 if rev < 0:
1460 rev = len(self) + rev
1475 rev = len(self) + rev
1461 if rev < 0 or rev >= len(self):
1476 if rev < 0 or rev >= len(self):
1462 raise ValueError
1477 raise ValueError
1463 return self.node(rev)
1478 return self.node(rev)
1464 except (ValueError, OverflowError):
1479 except (ValueError, OverflowError):
1465 pass
1480 pass
1466 if len(id) == 2 * self.nodeconstants.nodelen:
1481 if len(id) == 2 * self.nodeconstants.nodelen:
1467 try:
1482 try:
1468 # a full hex nodeid?
1483 # a full hex nodeid?
1469 node = bin(id)
1484 node = bin(id)
1470 self.rev(node)
1485 self.rev(node)
1471 return node
1486 return node
1472 except (TypeError, error.LookupError):
1487 except (TypeError, error.LookupError):
1473 pass
1488 pass
1474
1489
1475 def _partialmatch(self, id):
1490 def _partialmatch(self, id):
1476 # we don't care wdirfilenodeids as they should be always full hash
1491 # we don't care wdirfilenodeids as they should be always full hash
1477 maybewdir = self.nodeconstants.wdirhex.startswith(id)
1492 maybewdir = self.nodeconstants.wdirhex.startswith(id)
1478 ambiguous = False
1493 ambiguous = False
1479 try:
1494 try:
1480 partial = self.index.partialmatch(id)
1495 partial = self.index.partialmatch(id)
1481 if partial and self.hasnode(partial):
1496 if partial and self.hasnode(partial):
1482 if maybewdir:
1497 if maybewdir:
1483 # single 'ff...' match in radix tree, ambiguous with wdir
1498 # single 'ff...' match in radix tree, ambiguous with wdir
1484 ambiguous = True
1499 ambiguous = True
1485 else:
1500 else:
1486 return partial
1501 return partial
1487 elif maybewdir:
1502 elif maybewdir:
1488 # no 'ff...' match in radix tree, wdir identified
1503 # no 'ff...' match in radix tree, wdir identified
1489 raise error.WdirUnsupported
1504 raise error.WdirUnsupported
1490 else:
1505 else:
1491 return None
1506 return None
1492 except error.RevlogError:
1507 except error.RevlogError:
1493 # parsers.c radix tree lookup gave multiple matches
1508 # parsers.c radix tree lookup gave multiple matches
1494 # fast path: for unfiltered changelog, radix tree is accurate
1509 # fast path: for unfiltered changelog, radix tree is accurate
1495 if not getattr(self, 'filteredrevs', None):
1510 if not getattr(self, 'filteredrevs', None):
1496 ambiguous = True
1511 ambiguous = True
1497 # fall through to slow path that filters hidden revisions
1512 # fall through to slow path that filters hidden revisions
1498 except (AttributeError, ValueError):
1513 except (AttributeError, ValueError):
1499 # we are pure python, or key was too short to search radix tree
1514 # we are pure python, or key was too short to search radix tree
1500 pass
1515 pass
1501 if ambiguous:
1516 if ambiguous:
1502 raise error.AmbiguousPrefixLookupError(
1517 raise error.AmbiguousPrefixLookupError(
1503 id, self.display_id, _(b'ambiguous identifier')
1518 id, self.display_id, _(b'ambiguous identifier')
1504 )
1519 )
1505
1520
1506 if id in self._pcache:
1521 if id in self._pcache:
1507 return self._pcache[id]
1522 return self._pcache[id]
1508
1523
1509 if len(id) <= 40:
1524 if len(id) <= 40:
1510 try:
1525 try:
1511 # hex(node)[:...]
1526 # hex(node)[:...]
1512 l = len(id) // 2 # grab an even number of digits
1527 l = len(id) // 2 # grab an even number of digits
1513 prefix = bin(id[: l * 2])
1528 prefix = bin(id[: l * 2])
1514 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1529 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1515 nl = [
1530 nl = [
1516 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1531 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1517 ]
1532 ]
1518 if self.nodeconstants.nullhex.startswith(id):
1533 if self.nodeconstants.nullhex.startswith(id):
1519 nl.append(self.nullid)
1534 nl.append(self.nullid)
1520 if len(nl) > 0:
1535 if len(nl) > 0:
1521 if len(nl) == 1 and not maybewdir:
1536 if len(nl) == 1 and not maybewdir:
1522 self._pcache[id] = nl[0]
1537 self._pcache[id] = nl[0]
1523 return nl[0]
1538 return nl[0]
1524 raise error.AmbiguousPrefixLookupError(
1539 raise error.AmbiguousPrefixLookupError(
1525 id, self.display_id, _(b'ambiguous identifier')
1540 id, self.display_id, _(b'ambiguous identifier')
1526 )
1541 )
1527 if maybewdir:
1542 if maybewdir:
1528 raise error.WdirUnsupported
1543 raise error.WdirUnsupported
1529 return None
1544 return None
1530 except TypeError:
1545 except TypeError:
1531 pass
1546 pass
1532
1547
1533 def lookup(self, id):
1548 def lookup(self, id):
1534 """locate a node based on:
1549 """locate a node based on:
1535 - revision number or str(revision number)
1550 - revision number or str(revision number)
1536 - nodeid or subset of hex nodeid
1551 - nodeid or subset of hex nodeid
1537 """
1552 """
1538 n = self._match(id)
1553 n = self._match(id)
1539 if n is not None:
1554 if n is not None:
1540 return n
1555 return n
1541 n = self._partialmatch(id)
1556 n = self._partialmatch(id)
1542 if n:
1557 if n:
1543 return n
1558 return n
1544
1559
1545 raise error.LookupError(id, self.display_id, _(b'no match found'))
1560 raise error.LookupError(id, self.display_id, _(b'no match found'))
1546
1561
1547 def shortest(self, node, minlength=1):
1562 def shortest(self, node, minlength=1):
1548 """Find the shortest unambiguous prefix that matches node."""
1563 """Find the shortest unambiguous prefix that matches node."""
1549
1564
1550 def isvalid(prefix):
1565 def isvalid(prefix):
1551 try:
1566 try:
1552 matchednode = self._partialmatch(prefix)
1567 matchednode = self._partialmatch(prefix)
1553 except error.AmbiguousPrefixLookupError:
1568 except error.AmbiguousPrefixLookupError:
1554 return False
1569 return False
1555 except error.WdirUnsupported:
1570 except error.WdirUnsupported:
1556 # single 'ff...' match
1571 # single 'ff...' match
1557 return True
1572 return True
1558 if matchednode is None:
1573 if matchednode is None:
1559 raise error.LookupError(node, self.display_id, _(b'no node'))
1574 raise error.LookupError(node, self.display_id, _(b'no node'))
1560 return True
1575 return True
1561
1576
1562 def maybewdir(prefix):
1577 def maybewdir(prefix):
1563 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1578 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1564
1579
1565 hexnode = hex(node)
1580 hexnode = hex(node)
1566
1581
1567 def disambiguate(hexnode, minlength):
1582 def disambiguate(hexnode, minlength):
1568 """Disambiguate against wdirid."""
1583 """Disambiguate against wdirid."""
1569 for length in range(minlength, len(hexnode) + 1):
1584 for length in range(minlength, len(hexnode) + 1):
1570 prefix = hexnode[:length]
1585 prefix = hexnode[:length]
1571 if not maybewdir(prefix):
1586 if not maybewdir(prefix):
1572 return prefix
1587 return prefix
1573
1588
1574 if not getattr(self, 'filteredrevs', None):
1589 if not getattr(self, 'filteredrevs', None):
1575 try:
1590 try:
1576 length = max(self.index.shortest(node), minlength)
1591 length = max(self.index.shortest(node), minlength)
1577 return disambiguate(hexnode, length)
1592 return disambiguate(hexnode, length)
1578 except error.RevlogError:
1593 except error.RevlogError:
1579 if node != self.nodeconstants.wdirid:
1594 if node != self.nodeconstants.wdirid:
1580 raise error.LookupError(
1595 raise error.LookupError(
1581 node, self.display_id, _(b'no node')
1596 node, self.display_id, _(b'no node')
1582 )
1597 )
1583 except AttributeError:
1598 except AttributeError:
1584 # Fall through to pure code
1599 # Fall through to pure code
1585 pass
1600 pass
1586
1601
1587 if node == self.nodeconstants.wdirid:
1602 if node == self.nodeconstants.wdirid:
1588 for length in range(minlength, len(hexnode) + 1):
1603 for length in range(minlength, len(hexnode) + 1):
1589 prefix = hexnode[:length]
1604 prefix = hexnode[:length]
1590 if isvalid(prefix):
1605 if isvalid(prefix):
1591 return prefix
1606 return prefix
1592
1607
1593 for length in range(minlength, len(hexnode) + 1):
1608 for length in range(minlength, len(hexnode) + 1):
1594 prefix = hexnode[:length]
1609 prefix = hexnode[:length]
1595 if isvalid(prefix):
1610 if isvalid(prefix):
1596 return disambiguate(hexnode, length)
1611 return disambiguate(hexnode, length)
1597
1612
1598 def cmp(self, node, text):
1613 def cmp(self, node, text):
1599 """compare text with a given file revision
1614 """compare text with a given file revision
1600
1615
1601 returns True if text is different than what is stored.
1616 returns True if text is different than what is stored.
1602 """
1617 """
1603 p1, p2 = self.parents(node)
1618 p1, p2 = self.parents(node)
1604 return storageutil.hashrevisionsha1(text, p1, p2) != node
1619 return storageutil.hashrevisionsha1(text, p1, p2) != node
1605
1620
1606 def _getsegmentforrevs(self, startrev, endrev, df=None):
1621 def _getsegmentforrevs(self, startrev, endrev, df=None):
1607 """Obtain a segment of raw data corresponding to a range of revisions.
1622 """Obtain a segment of raw data corresponding to a range of revisions.
1608
1623
1609 Accepts the start and end revisions and an optional already-open
1624 Accepts the start and end revisions and an optional already-open
1610 file handle to be used for reading. If the file handle is read, its
1625 file handle to be used for reading. If the file handle is read, its
1611 seek position will not be preserved.
1626 seek position will not be preserved.
1612
1627
1613 Requests for data may be satisfied by a cache.
1628 Requests for data may be satisfied by a cache.
1614
1629
1615 Returns a 2-tuple of (offset, data) for the requested range of
1630 Returns a 2-tuple of (offset, data) for the requested range of
1616 revisions. Offset is the integer offset from the beginning of the
1631 revisions. Offset is the integer offset from the beginning of the
1617 revlog and data is a str or buffer of the raw byte data.
1632 revlog and data is a str or buffer of the raw byte data.
1618
1633
1619 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1634 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1620 to determine where each revision's data begins and ends.
1635 to determine where each revision's data begins and ends.
1621 """
1636 """
1622 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1637 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1623 # (functions are expensive).
1638 # (functions are expensive).
1624 index = self.index
1639 index = self.index
1625 istart = index[startrev]
1640 istart = index[startrev]
1626 start = int(istart[0] >> 16)
1641 start = int(istart[0] >> 16)
1627 if startrev == endrev:
1642 if startrev == endrev:
1628 end = start + istart[1]
1643 end = start + istart[1]
1629 else:
1644 else:
1630 iend = index[endrev]
1645 iend = index[endrev]
1631 end = int(iend[0] >> 16) + iend[1]
1646 end = int(iend[0] >> 16) + iend[1]
1632
1647
1633 if self._inline:
1648 if self._inline:
1634 start += (startrev + 1) * self.index.entry_size
1649 start += (startrev + 1) * self.index.entry_size
1635 end += (endrev + 1) * self.index.entry_size
1650 end += (endrev + 1) * self.index.entry_size
1636 length = end - start
1651 length = end - start
1637
1652
1638 return start, self._segmentfile.read_chunk(start, length, df)
1653 return start, self._segmentfile.read_chunk(start, length, df)
1639
1654
1640 def _chunk(self, rev, df=None):
1655 def _chunk(self, rev, df=None):
1641 """Obtain a single decompressed chunk for a revision.
1656 """Obtain a single decompressed chunk for a revision.
1642
1657
1643 Accepts an integer revision and an optional already-open file handle
1658 Accepts an integer revision and an optional already-open file handle
1644 to be used for reading. If used, the seek position of the file will not
1659 to be used for reading. If used, the seek position of the file will not
1645 be preserved.
1660 be preserved.
1646
1661
1647 Returns a str holding uncompressed data for the requested revision.
1662 Returns a str holding uncompressed data for the requested revision.
1648 """
1663 """
1649 compression_mode = self.index[rev][10]
1664 compression_mode = self.index[rev][10]
1650 data = self._getsegmentforrevs(rev, rev, df=df)[1]
1665 data = self._getsegmentforrevs(rev, rev, df=df)[1]
1651 if compression_mode == COMP_MODE_PLAIN:
1666 if compression_mode == COMP_MODE_PLAIN:
1652 return data
1667 return data
1653 elif compression_mode == COMP_MODE_DEFAULT:
1668 elif compression_mode == COMP_MODE_DEFAULT:
1654 return self._decompressor(data)
1669 return self._decompressor(data)
1655 elif compression_mode == COMP_MODE_INLINE:
1670 elif compression_mode == COMP_MODE_INLINE:
1656 return self.decompress(data)
1671 return self.decompress(data)
1657 else:
1672 else:
1658 msg = b'unknown compression mode %d'
1673 msg = b'unknown compression mode %d'
1659 msg %= compression_mode
1674 msg %= compression_mode
1660 raise error.RevlogError(msg)
1675 raise error.RevlogError(msg)
1661
1676
1662 def _chunks(self, revs, df=None, targetsize=None):
1677 def _chunks(self, revs, df=None, targetsize=None):
1663 """Obtain decompressed chunks for the specified revisions.
1678 """Obtain decompressed chunks for the specified revisions.
1664
1679
1665 Accepts an iterable of numeric revisions that are assumed to be in
1680 Accepts an iterable of numeric revisions that are assumed to be in
1666 ascending order. Also accepts an optional already-open file handle
1681 ascending order. Also accepts an optional already-open file handle
1667 to be used for reading. If used, the seek position of the file will
1682 to be used for reading. If used, the seek position of the file will
1668 not be preserved.
1683 not be preserved.
1669
1684
1670 This function is similar to calling ``self._chunk()`` multiple times,
1685 This function is similar to calling ``self._chunk()`` multiple times,
1671 but is faster.
1686 but is faster.
1672
1687
1673 Returns a list with decompressed data for each requested revision.
1688 Returns a list with decompressed data for each requested revision.
1674 """
1689 """
1675 if not revs:
1690 if not revs:
1676 return []
1691 return []
1677 start = self.start
1692 start = self.start
1678 length = self.length
1693 length = self.length
1679 inline = self._inline
1694 inline = self._inline
1680 iosize = self.index.entry_size
1695 iosize = self.index.entry_size
1681 buffer = util.buffer
1696 buffer = util.buffer
1682
1697
1683 l = []
1698 l = []
1684 ladd = l.append
1699 ladd = l.append
1685
1700
1686 if not self._withsparseread:
1701 if not self._withsparseread:
1687 slicedchunks = (revs,)
1702 slicedchunks = (revs,)
1688 else:
1703 else:
1689 slicedchunks = deltautil.slicechunk(
1704 slicedchunks = deltautil.slicechunk(
1690 self, revs, targetsize=targetsize
1705 self, revs, targetsize=targetsize
1691 )
1706 )
1692
1707
1693 for revschunk in slicedchunks:
1708 for revschunk in slicedchunks:
1694 firstrev = revschunk[0]
1709 firstrev = revschunk[0]
1695 # Skip trailing revisions with empty diff
1710 # Skip trailing revisions with empty diff
1696 for lastrev in revschunk[::-1]:
1711 for lastrev in revschunk[::-1]:
1697 if length(lastrev) != 0:
1712 if length(lastrev) != 0:
1698 break
1713 break
1699
1714
1700 try:
1715 try:
1701 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1716 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1702 except OverflowError:
1717 except OverflowError:
1703 # issue4215 - we can't cache a run of chunks greater than
1718 # issue4215 - we can't cache a run of chunks greater than
1704 # 2G on Windows
1719 # 2G on Windows
1705 return [self._chunk(rev, df=df) for rev in revschunk]
1720 return [self._chunk(rev, df=df) for rev in revschunk]
1706
1721
1707 decomp = self.decompress
1722 decomp = self.decompress
1708 # self._decompressor might be None, but will not be used in that case
1723 # self._decompressor might be None, but will not be used in that case
1709 def_decomp = self._decompressor
1724 def_decomp = self._decompressor
1710 for rev in revschunk:
1725 for rev in revschunk:
1711 chunkstart = start(rev)
1726 chunkstart = start(rev)
1712 if inline:
1727 if inline:
1713 chunkstart += (rev + 1) * iosize
1728 chunkstart += (rev + 1) * iosize
1714 chunklength = length(rev)
1729 chunklength = length(rev)
1715 comp_mode = self.index[rev][10]
1730 comp_mode = self.index[rev][10]
1716 c = buffer(data, chunkstart - offset, chunklength)
1731 c = buffer(data, chunkstart - offset, chunklength)
1717 if comp_mode == COMP_MODE_PLAIN:
1732 if comp_mode == COMP_MODE_PLAIN:
1718 ladd(c)
1733 ladd(c)
1719 elif comp_mode == COMP_MODE_INLINE:
1734 elif comp_mode == COMP_MODE_INLINE:
1720 ladd(decomp(c))
1735 ladd(decomp(c))
1721 elif comp_mode == COMP_MODE_DEFAULT:
1736 elif comp_mode == COMP_MODE_DEFAULT:
1722 ladd(def_decomp(c))
1737 ladd(def_decomp(c))
1723 else:
1738 else:
1724 msg = b'unknown compression mode %d'
1739 msg = b'unknown compression mode %d'
1725 msg %= comp_mode
1740 msg %= comp_mode
1726 raise error.RevlogError(msg)
1741 raise error.RevlogError(msg)
1727
1742
1728 return l
1743 return l
1729
1744
1730 def deltaparent(self, rev):
1745 def deltaparent(self, rev):
1731 """return deltaparent of the given revision"""
1746 """return deltaparent of the given revision"""
1732 base = self.index[rev][3]
1747 base = self.index[rev][3]
1733 if base == rev:
1748 if base == rev:
1734 return nullrev
1749 return nullrev
1735 elif self._generaldelta:
1750 elif self._generaldelta:
1736 return base
1751 return base
1737 else:
1752 else:
1738 return rev - 1
1753 return rev - 1
1739
1754
1740 def issnapshot(self, rev):
1755 def issnapshot(self, rev):
1741 """tells whether rev is a snapshot"""
1756 """tells whether rev is a snapshot"""
1742 if not self._sparserevlog:
1757 if not self._sparserevlog:
1743 return self.deltaparent(rev) == nullrev
1758 return self.deltaparent(rev) == nullrev
1744 elif util.safehasattr(self.index, b'issnapshot'):
1759 elif util.safehasattr(self.index, b'issnapshot'):
1745 # directly assign the method to cache the testing and access
1760 # directly assign the method to cache the testing and access
1746 self.issnapshot = self.index.issnapshot
1761 self.issnapshot = self.index.issnapshot
1747 return self.issnapshot(rev)
1762 return self.issnapshot(rev)
1748 if rev == nullrev:
1763 if rev == nullrev:
1749 return True
1764 return True
1750 entry = self.index[rev]
1765 entry = self.index[rev]
1751 base = entry[3]
1766 base = entry[3]
1752 if base == rev:
1767 if base == rev:
1753 return True
1768 return True
1754 if base == nullrev:
1769 if base == nullrev:
1755 return True
1770 return True
1756 p1 = entry[5]
1771 p1 = entry[5]
1757 p2 = entry[6]
1772 p2 = entry[6]
1758 if base == p1 or base == p2:
1773 if base == p1 or base == p2:
1759 return False
1774 return False
1760 return self.issnapshot(base)
1775 return self.issnapshot(base)
1761
1776
1762 def snapshotdepth(self, rev):
1777 def snapshotdepth(self, rev):
1763 """number of snapshot in the chain before this one"""
1778 """number of snapshot in the chain before this one"""
1764 if not self.issnapshot(rev):
1779 if not self.issnapshot(rev):
1765 raise error.ProgrammingError(b'revision %d not a snapshot')
1780 raise error.ProgrammingError(b'revision %d not a snapshot')
1766 return len(self._deltachain(rev)[0]) - 1
1781 return len(self._deltachain(rev)[0]) - 1
1767
1782
1768 def revdiff(self, rev1, rev2):
1783 def revdiff(self, rev1, rev2):
1769 """return or calculate a delta between two revisions
1784 """return or calculate a delta between two revisions
1770
1785
1771 The delta calculated is in binary form and is intended to be written to
1786 The delta calculated is in binary form and is intended to be written to
1772 revlog data directly. So this function needs raw revision data.
1787 revlog data directly. So this function needs raw revision data.
1773 """
1788 """
1774 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1789 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1775 return bytes(self._chunk(rev2))
1790 return bytes(self._chunk(rev2))
1776
1791
1777 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
1792 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
1778
1793
1779 def revision(self, nodeorrev, _df=None):
1794 def revision(self, nodeorrev, _df=None):
1780 """return an uncompressed revision of a given node or revision
1795 """return an uncompressed revision of a given node or revision
1781 number.
1796 number.
1782
1797
1783 _df - an existing file handle to read from. (internal-only)
1798 _df - an existing file handle to read from. (internal-only)
1784 """
1799 """
1785 return self._revisiondata(nodeorrev, _df)
1800 return self._revisiondata(nodeorrev, _df)
1786
1801
1787 def sidedata(self, nodeorrev, _df=None):
1802 def sidedata(self, nodeorrev, _df=None):
1788 """a map of extra data related to the changeset but not part of the hash
1803 """a map of extra data related to the changeset but not part of the hash
1789
1804
1790 This function currently return a dictionary. However, more advanced
1805 This function currently return a dictionary. However, more advanced
1791 mapping object will likely be used in the future for a more
1806 mapping object will likely be used in the future for a more
1792 efficient/lazy code.
1807 efficient/lazy code.
1793 """
1808 """
1794 # deal with <nodeorrev> argument type
1809 # deal with <nodeorrev> argument type
1795 if isinstance(nodeorrev, int):
1810 if isinstance(nodeorrev, int):
1796 rev = nodeorrev
1811 rev = nodeorrev
1797 else:
1812 else:
1798 rev = self.rev(nodeorrev)
1813 rev = self.rev(nodeorrev)
1799 return self._sidedata(rev)
1814 return self._sidedata(rev)
1800
1815
1801 def _revisiondata(self, nodeorrev, _df=None, raw=False):
1816 def _revisiondata(self, nodeorrev, _df=None, raw=False):
1802 # deal with <nodeorrev> argument type
1817 # deal with <nodeorrev> argument type
1803 if isinstance(nodeorrev, int):
1818 if isinstance(nodeorrev, int):
1804 rev = nodeorrev
1819 rev = nodeorrev
1805 node = self.node(rev)
1820 node = self.node(rev)
1806 else:
1821 else:
1807 node = nodeorrev
1822 node = nodeorrev
1808 rev = None
1823 rev = None
1809
1824
1810 # fast path the special `nullid` rev
1825 # fast path the special `nullid` rev
1811 if node == self.nullid:
1826 if node == self.nullid:
1812 return b""
1827 return b""
1813
1828
1814 # ``rawtext`` is the text as stored inside the revlog. Might be the
1829 # ``rawtext`` is the text as stored inside the revlog. Might be the
1815 # revision or might need to be processed to retrieve the revision.
1830 # revision or might need to be processed to retrieve the revision.
1816 rev, rawtext, validated = self._rawtext(node, rev, _df=_df)
1831 rev, rawtext, validated = self._rawtext(node, rev, _df=_df)
1817
1832
1818 if raw and validated:
1833 if raw and validated:
1819 # if we don't want to process the raw text and that raw
1834 # if we don't want to process the raw text and that raw
1820 # text is cached, we can exit early.
1835 # text is cached, we can exit early.
1821 return rawtext
1836 return rawtext
1822 if rev is None:
1837 if rev is None:
1823 rev = self.rev(node)
1838 rev = self.rev(node)
1824 # the revlog's flag for this revision
1839 # the revlog's flag for this revision
1825 # (usually alter its state or content)
1840 # (usually alter its state or content)
1826 flags = self.flags(rev)
1841 flags = self.flags(rev)
1827
1842
1828 if validated and flags == REVIDX_DEFAULT_FLAGS:
1843 if validated and flags == REVIDX_DEFAULT_FLAGS:
1829 # no extra flags set, no flag processor runs, text = rawtext
1844 # no extra flags set, no flag processor runs, text = rawtext
1830 return rawtext
1845 return rawtext
1831
1846
1832 if raw:
1847 if raw:
1833 validatehash = flagutil.processflagsraw(self, rawtext, flags)
1848 validatehash = flagutil.processflagsraw(self, rawtext, flags)
1834 text = rawtext
1849 text = rawtext
1835 else:
1850 else:
1836 r = flagutil.processflagsread(self, rawtext, flags)
1851 r = flagutil.processflagsread(self, rawtext, flags)
1837 text, validatehash = r
1852 text, validatehash = r
1838 if validatehash:
1853 if validatehash:
1839 self.checkhash(text, node, rev=rev)
1854 self.checkhash(text, node, rev=rev)
1840 if not validated:
1855 if not validated:
1841 self._revisioncache = (node, rev, rawtext)
1856 self._revisioncache = (node, rev, rawtext)
1842
1857
1843 return text
1858 return text
1844
1859
1845 def _rawtext(self, node, rev, _df=None):
1860 def _rawtext(self, node, rev, _df=None):
1846 """return the possibly unvalidated rawtext for a revision
1861 """return the possibly unvalidated rawtext for a revision
1847
1862
1848 returns (rev, rawtext, validated)
1863 returns (rev, rawtext, validated)
1849 """
1864 """
1850
1865
1851 # revision in the cache (could be useful to apply delta)
1866 # revision in the cache (could be useful to apply delta)
1852 cachedrev = None
1867 cachedrev = None
1853 # An intermediate text to apply deltas to
1868 # An intermediate text to apply deltas to
1854 basetext = None
1869 basetext = None
1855
1870
1856 # Check if we have the entry in cache
1871 # Check if we have the entry in cache
1857 # The cache entry looks like (node, rev, rawtext)
1872 # The cache entry looks like (node, rev, rawtext)
1858 if self._revisioncache:
1873 if self._revisioncache:
1859 if self._revisioncache[0] == node:
1874 if self._revisioncache[0] == node:
1860 return (rev, self._revisioncache[2], True)
1875 return (rev, self._revisioncache[2], True)
1861 cachedrev = self._revisioncache[1]
1876 cachedrev = self._revisioncache[1]
1862
1877
1863 if rev is None:
1878 if rev is None:
1864 rev = self.rev(node)
1879 rev = self.rev(node)
1865
1880
1866 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1881 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1867 if stopped:
1882 if stopped:
1868 basetext = self._revisioncache[2]
1883 basetext = self._revisioncache[2]
1869
1884
1870 # drop cache to save memory, the caller is expected to
1885 # drop cache to save memory, the caller is expected to
1871 # update self._revisioncache after validating the text
1886 # update self._revisioncache after validating the text
1872 self._revisioncache = None
1887 self._revisioncache = None
1873
1888
1874 targetsize = None
1889 targetsize = None
1875 rawsize = self.index[rev][2]
1890 rawsize = self.index[rev][2]
1876 if 0 <= rawsize:
1891 if 0 <= rawsize:
1877 targetsize = 4 * rawsize
1892 targetsize = 4 * rawsize
1878
1893
1879 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1894 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1880 if basetext is None:
1895 if basetext is None:
1881 basetext = bytes(bins[0])
1896 basetext = bytes(bins[0])
1882 bins = bins[1:]
1897 bins = bins[1:]
1883
1898
1884 rawtext = mdiff.patches(basetext, bins)
1899 rawtext = mdiff.patches(basetext, bins)
1885 del basetext # let us have a chance to free memory early
1900 del basetext # let us have a chance to free memory early
1886 return (rev, rawtext, False)
1901 return (rev, rawtext, False)
1887
1902
1888 def _sidedata(self, rev):
1903 def _sidedata(self, rev):
1889 """Return the sidedata for a given revision number."""
1904 """Return the sidedata for a given revision number."""
1890 index_entry = self.index[rev]
1905 index_entry = self.index[rev]
1891 sidedata_offset = index_entry[8]
1906 sidedata_offset = index_entry[8]
1892 sidedata_size = index_entry[9]
1907 sidedata_size = index_entry[9]
1893
1908
1894 if self._inline:
1909 if self._inline:
1895 sidedata_offset += self.index.entry_size * (1 + rev)
1910 sidedata_offset += self.index.entry_size * (1 + rev)
1896 if sidedata_size == 0:
1911 if sidedata_size == 0:
1897 return {}
1912 return {}
1898
1913
1899 if self._docket.sidedata_end < sidedata_offset + sidedata_size:
1914 if self._docket.sidedata_end < sidedata_offset + sidedata_size:
1900 filename = self._sidedatafile
1915 filename = self._sidedatafile
1901 end = self._docket.sidedata_end
1916 end = self._docket.sidedata_end
1902 offset = sidedata_offset
1917 offset = sidedata_offset
1903 length = sidedata_size
1918 length = sidedata_size
1904 m = FILE_TOO_SHORT_MSG % (filename, length, offset, end)
1919 m = FILE_TOO_SHORT_MSG % (filename, length, offset, end)
1905 raise error.RevlogError(m)
1920 raise error.RevlogError(m)
1906
1921
1907 comp_segment = self._segmentfile_sidedata.read_chunk(
1922 comp_segment = self._segmentfile_sidedata.read_chunk(
1908 sidedata_offset, sidedata_size
1923 sidedata_offset, sidedata_size
1909 )
1924 )
1910
1925
1911 comp = self.index[rev][11]
1926 comp = self.index[rev][11]
1912 if comp == COMP_MODE_PLAIN:
1927 if comp == COMP_MODE_PLAIN:
1913 segment = comp_segment
1928 segment = comp_segment
1914 elif comp == COMP_MODE_DEFAULT:
1929 elif comp == COMP_MODE_DEFAULT:
1915 segment = self._decompressor(comp_segment)
1930 segment = self._decompressor(comp_segment)
1916 elif comp == COMP_MODE_INLINE:
1931 elif comp == COMP_MODE_INLINE:
1917 segment = self.decompress(comp_segment)
1932 segment = self.decompress(comp_segment)
1918 else:
1933 else:
1919 msg = b'unknown compression mode %d'
1934 msg = b'unknown compression mode %d'
1920 msg %= comp
1935 msg %= comp
1921 raise error.RevlogError(msg)
1936 raise error.RevlogError(msg)
1922
1937
1923 sidedata = sidedatautil.deserialize_sidedata(segment)
1938 sidedata = sidedatautil.deserialize_sidedata(segment)
1924 return sidedata
1939 return sidedata
1925
1940
1926 def rawdata(self, nodeorrev, _df=None):
1941 def rawdata(self, nodeorrev, _df=None):
1927 """return an uncompressed raw data of a given node or revision number.
1942 """return an uncompressed raw data of a given node or revision number.
1928
1943
1929 _df - an existing file handle to read from. (internal-only)
1944 _df - an existing file handle to read from. (internal-only)
1930 """
1945 """
1931 return self._revisiondata(nodeorrev, _df, raw=True)
1946 return self._revisiondata(nodeorrev, _df, raw=True)
1932
1947
1933 def hash(self, text, p1, p2):
1948 def hash(self, text, p1, p2):
1934 """Compute a node hash.
1949 """Compute a node hash.
1935
1950
1936 Available as a function so that subclasses can replace the hash
1951 Available as a function so that subclasses can replace the hash
1937 as needed.
1952 as needed.
1938 """
1953 """
1939 return storageutil.hashrevisionsha1(text, p1, p2)
1954 return storageutil.hashrevisionsha1(text, p1, p2)
1940
1955
1941 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1956 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1942 """Check node hash integrity.
1957 """Check node hash integrity.
1943
1958
1944 Available as a function so that subclasses can extend hash mismatch
1959 Available as a function so that subclasses can extend hash mismatch
1945 behaviors as needed.
1960 behaviors as needed.
1946 """
1961 """
1947 try:
1962 try:
1948 if p1 is None and p2 is None:
1963 if p1 is None and p2 is None:
1949 p1, p2 = self.parents(node)
1964 p1, p2 = self.parents(node)
1950 if node != self.hash(text, p1, p2):
1965 if node != self.hash(text, p1, p2):
1951 # Clear the revision cache on hash failure. The revision cache
1966 # Clear the revision cache on hash failure. The revision cache
1952 # only stores the raw revision and clearing the cache does have
1967 # only stores the raw revision and clearing the cache does have
1953 # the side-effect that we won't have a cache hit when the raw
1968 # the side-effect that we won't have a cache hit when the raw
1954 # revision data is accessed. But this case should be rare and
1969 # revision data is accessed. But this case should be rare and
1955 # it is extra work to teach the cache about the hash
1970 # it is extra work to teach the cache about the hash
1956 # verification state.
1971 # verification state.
1957 if self._revisioncache and self._revisioncache[0] == node:
1972 if self._revisioncache and self._revisioncache[0] == node:
1958 self._revisioncache = None
1973 self._revisioncache = None
1959
1974
1960 revornode = rev
1975 revornode = rev
1961 if revornode is None:
1976 if revornode is None:
1962 revornode = templatefilters.short(hex(node))
1977 revornode = templatefilters.short(hex(node))
1963 raise error.RevlogError(
1978 raise error.RevlogError(
1964 _(b"integrity check failed on %s:%s")
1979 _(b"integrity check failed on %s:%s")
1965 % (self.display_id, pycompat.bytestr(revornode))
1980 % (self.display_id, pycompat.bytestr(revornode))
1966 )
1981 )
1967 except error.RevlogError:
1982 except error.RevlogError:
1968 if self._censorable and storageutil.iscensoredtext(text):
1983 if self._censorable and storageutil.iscensoredtext(text):
1969 raise error.CensoredNodeError(self.display_id, node, text)
1984 raise error.CensoredNodeError(self.display_id, node, text)
1970 raise
1985 raise
1971
1986
1972 def _enforceinlinesize(self, tr):
1987 def _enforceinlinesize(self, tr):
1973 """Check if the revlog is too big for inline and convert if so.
1988 """Check if the revlog is too big for inline and convert if so.
1974
1989
1975 This should be called after revisions are added to the revlog. If the
1990 This should be called after revisions are added to the revlog. If the
1976 revlog has grown too large to be an inline revlog, it will convert it
1991 revlog has grown too large to be an inline revlog, it will convert it
1977 to use multiple index and data files.
1992 to use multiple index and data files.
1978 """
1993 """
1979 tiprev = len(self) - 1
1994 tiprev = len(self) - 1
1980 total_size = self.start(tiprev) + self.length(tiprev)
1995 total_size = self.start(tiprev) + self.length(tiprev)
1981 if not self._inline or total_size < _maxinline:
1996 if not self._inline or total_size < _maxinline:
1982 return
1997 return
1983
1998
1984 troffset = tr.findoffset(self._indexfile)
1999 troffset = tr.findoffset(self._indexfile)
1985 if troffset is None:
2000 if troffset is None:
1986 raise error.RevlogError(
2001 raise error.RevlogError(
1987 _(b"%s not found in the transaction") % self._indexfile
2002 _(b"%s not found in the transaction") % self._indexfile
1988 )
2003 )
1989 trindex = None
2004 trindex = None
1990 tr.add(self._datafile, 0)
2005 tr.add(self._datafile, 0)
1991
2006
1992 existing_handles = False
2007 existing_handles = False
1993 if self._writinghandles is not None:
2008 if self._writinghandles is not None:
1994 existing_handles = True
2009 existing_handles = True
1995 fp = self._writinghandles[0]
2010 fp = self._writinghandles[0]
1996 fp.flush()
2011 fp.flush()
1997 fp.close()
2012 fp.close()
1998 # We can't use the cached file handle after close(). So prevent
2013 # We can't use the cached file handle after close(). So prevent
1999 # its usage.
2014 # its usage.
2000 self._writinghandles = None
2015 self._writinghandles = None
2001 self._segmentfile.writing_handle = None
2016 self._segmentfile.writing_handle = None
2002 # No need to deal with sidedata writing handle as it is only
2017 # No need to deal with sidedata writing handle as it is only
2003 # relevant with revlog-v2 which is never inline, not reaching
2018 # relevant with revlog-v2 which is never inline, not reaching
2004 # this code
2019 # this code
2005
2020
2006 new_dfh = self._datafp(b'w+')
2021 new_dfh = self._datafp(b'w+')
2007 new_dfh.truncate(0) # drop any potentially existing data
2022 new_dfh.truncate(0) # drop any potentially existing data
2008 try:
2023 try:
2009 with self._indexfp() as read_ifh:
2024 with self._indexfp() as read_ifh:
2010 for r in self:
2025 for r in self:
2011 new_dfh.write(self._getsegmentforrevs(r, r, df=read_ifh)[1])
2026 new_dfh.write(self._getsegmentforrevs(r, r, df=read_ifh)[1])
2012 if (
2027 if (
2013 trindex is None
2028 trindex is None
2014 and troffset
2029 and troffset
2015 <= self.start(r) + r * self.index.entry_size
2030 <= self.start(r) + r * self.index.entry_size
2016 ):
2031 ):
2017 trindex = r
2032 trindex = r
2018 new_dfh.flush()
2033 new_dfh.flush()
2019
2034
2020 if trindex is None:
2035 if trindex is None:
2021 trindex = 0
2036 trindex = 0
2022
2037
2023 with self.__index_new_fp() as fp:
2038 with self.__index_new_fp() as fp:
2024 self._format_flags &= ~FLAG_INLINE_DATA
2039 self._format_flags &= ~FLAG_INLINE_DATA
2025 self._inline = False
2040 self._inline = False
2026 for i in self:
2041 for i in self:
2027 e = self.index.entry_binary(i)
2042 e = self.index.entry_binary(i)
2028 if i == 0 and self._docket is None:
2043 if i == 0 and self._docket is None:
2029 header = self._format_flags | self._format_version
2044 header = self._format_flags | self._format_version
2030 header = self.index.pack_header(header)
2045 header = self.index.pack_header(header)
2031 e = header + e
2046 e = header + e
2032 fp.write(e)
2047 fp.write(e)
2033 if self._docket is not None:
2048 if self._docket is not None:
2034 self._docket.index_end = fp.tell()
2049 self._docket.index_end = fp.tell()
2035
2050
2036 # There is a small transactional race here. If the rename of
2051 # There is a small transactional race here. If the rename of
2037 # the index fails, we should remove the datafile. It is more
2052 # the index fails, we should remove the datafile. It is more
2038 # important to ensure that the data file is not truncated
2053 # important to ensure that the data file is not truncated
2039 # when the index is replaced as otherwise data is lost.
2054 # when the index is replaced as otherwise data is lost.
2040 tr.replace(self._datafile, self.start(trindex))
2055 tr.replace(self._datafile, self.start(trindex))
2041
2056
2042 # the temp file replace the real index when we exit the context
2057 # the temp file replace the real index when we exit the context
2043 # manager
2058 # manager
2044
2059
2045 tr.replace(self._indexfile, trindex * self.index.entry_size)
2060 tr.replace(self._indexfile, trindex * self.index.entry_size)
2046 nodemaputil.setup_persistent_nodemap(tr, self)
2061 nodemaputil.setup_persistent_nodemap(tr, self)
2047 self._segmentfile = randomaccessfile.randomaccessfile(
2062 self._segmentfile = randomaccessfile.randomaccessfile(
2048 self.opener,
2063 self.opener,
2049 self._datafile,
2064 self._datafile,
2050 self._chunkcachesize,
2065 self._chunkcachesize,
2051 )
2066 )
2052
2067
2053 if existing_handles:
2068 if existing_handles:
2054 # switched from inline to conventional reopen the index
2069 # switched from inline to conventional reopen the index
2055 ifh = self.__index_write_fp()
2070 ifh = self.__index_write_fp()
2056 self._writinghandles = (ifh, new_dfh, None)
2071 self._writinghandles = (ifh, new_dfh, None)
2057 self._segmentfile.writing_handle = new_dfh
2072 self._segmentfile.writing_handle = new_dfh
2058 new_dfh = None
2073 new_dfh = None
2059 # No need to deal with sidedata writing handle as it is only
2074 # No need to deal with sidedata writing handle as it is only
2060 # relevant with revlog-v2 which is never inline, not reaching
2075 # relevant with revlog-v2 which is never inline, not reaching
2061 # this code
2076 # this code
2062 finally:
2077 finally:
2063 if new_dfh is not None:
2078 if new_dfh is not None:
2064 new_dfh.close()
2079 new_dfh.close()
2065
2080
2066 def _nodeduplicatecallback(self, transaction, node):
2081 def _nodeduplicatecallback(self, transaction, node):
2067 """called when trying to add a node already stored."""
2082 """called when trying to add a node already stored."""
2068
2083
2069 @contextlib.contextmanager
2084 @contextlib.contextmanager
2070 def reading(self):
2085 def reading(self):
2071 """Context manager that keeps data and sidedata files open for reading"""
2086 """Context manager that keeps data and sidedata files open for reading"""
2072 with self._segmentfile.reading():
2087 with self._segmentfile.reading():
2073 with self._segmentfile_sidedata.reading():
2088 with self._segmentfile_sidedata.reading():
2074 yield
2089 yield
2075
2090
2076 @contextlib.contextmanager
2091 @contextlib.contextmanager
2077 def _writing(self, transaction):
2092 def _writing(self, transaction):
2078 if self._trypending:
2093 if self._trypending:
2079 msg = b'try to write in a `trypending` revlog: %s'
2094 msg = b'try to write in a `trypending` revlog: %s'
2080 msg %= self.display_id
2095 msg %= self.display_id
2081 raise error.ProgrammingError(msg)
2096 raise error.ProgrammingError(msg)
2082 if self._writinghandles is not None:
2097 if self._writinghandles is not None:
2083 yield
2098 yield
2084 else:
2099 else:
2085 ifh = dfh = sdfh = None
2100 ifh = dfh = sdfh = None
2086 try:
2101 try:
2087 r = len(self)
2102 r = len(self)
2088 # opening the data file.
2103 # opening the data file.
2089 dsize = 0
2104 dsize = 0
2090 if r:
2105 if r:
2091 dsize = self.end(r - 1)
2106 dsize = self.end(r - 1)
2092 dfh = None
2107 dfh = None
2093 if not self._inline:
2108 if not self._inline:
2094 try:
2109 try:
2095 dfh = self._datafp(b"r+")
2110 dfh = self._datafp(b"r+")
2096 if self._docket is None:
2111 if self._docket is None:
2097 dfh.seek(0, os.SEEK_END)
2112 dfh.seek(0, os.SEEK_END)
2098 else:
2113 else:
2099 dfh.seek(self._docket.data_end, os.SEEK_SET)
2114 dfh.seek(self._docket.data_end, os.SEEK_SET)
2100 except IOError as inst:
2115 except IOError as inst:
2101 if inst.errno != errno.ENOENT:
2116 if inst.errno != errno.ENOENT:
2102 raise
2117 raise
2103 dfh = self._datafp(b"w+")
2118 dfh = self._datafp(b"w+")
2104 transaction.add(self._datafile, dsize)
2119 transaction.add(self._datafile, dsize)
2105 if self._sidedatafile is not None:
2120 if self._sidedatafile is not None:
2106 # revlog-v2 does not inline, help Pytype
2121 # revlog-v2 does not inline, help Pytype
2107 assert dfh is not None
2122 assert dfh is not None
2108 try:
2123 try:
2109 sdfh = self.opener(self._sidedatafile, mode=b"r+")
2124 sdfh = self.opener(self._sidedatafile, mode=b"r+")
2110 dfh.seek(self._docket.sidedata_end, os.SEEK_SET)
2125 dfh.seek(self._docket.sidedata_end, os.SEEK_SET)
2111 except IOError as inst:
2126 except IOError as inst:
2112 if inst.errno != errno.ENOENT:
2127 if inst.errno != errno.ENOENT:
2113 raise
2128 raise
2114 sdfh = self.opener(self._sidedatafile, mode=b"w+")
2129 sdfh = self.opener(self._sidedatafile, mode=b"w+")
2115 transaction.add(
2130 transaction.add(
2116 self._sidedatafile, self._docket.sidedata_end
2131 self._sidedatafile, self._docket.sidedata_end
2117 )
2132 )
2118
2133
2119 # opening the index file.
2134 # opening the index file.
2120 isize = r * self.index.entry_size
2135 isize = r * self.index.entry_size
2121 ifh = self.__index_write_fp()
2136 ifh = self.__index_write_fp()
2122 if self._inline:
2137 if self._inline:
2123 transaction.add(self._indexfile, dsize + isize)
2138 transaction.add(self._indexfile, dsize + isize)
2124 else:
2139 else:
2125 transaction.add(self._indexfile, isize)
2140 transaction.add(self._indexfile, isize)
2126 # exposing all file handle for writing.
2141 # exposing all file handle for writing.
2127 self._writinghandles = (ifh, dfh, sdfh)
2142 self._writinghandles = (ifh, dfh, sdfh)
2128 self._segmentfile.writing_handle = ifh if self._inline else dfh
2143 self._segmentfile.writing_handle = ifh if self._inline else dfh
2129 self._segmentfile_sidedata.writing_handle = sdfh
2144 self._segmentfile_sidedata.writing_handle = sdfh
2130 yield
2145 yield
2131 if self._docket is not None:
2146 if self._docket is not None:
2132 self._write_docket(transaction)
2147 self._write_docket(transaction)
2133 finally:
2148 finally:
2134 self._writinghandles = None
2149 self._writinghandles = None
2135 self._segmentfile.writing_handle = None
2150 self._segmentfile.writing_handle = None
2136 self._segmentfile_sidedata.writing_handle = None
2151 self._segmentfile_sidedata.writing_handle = None
2137 if dfh is not None:
2152 if dfh is not None:
2138 dfh.close()
2153 dfh.close()
2139 if sdfh is not None:
2154 if sdfh is not None:
2140 sdfh.close()
2155 sdfh.close()
2141 # closing the index file last to avoid exposing referent to
2156 # closing the index file last to avoid exposing referent to
2142 # potential unflushed data content.
2157 # potential unflushed data content.
2143 if ifh is not None:
2158 if ifh is not None:
2144 ifh.close()
2159 ifh.close()
2145
2160
2146 def _write_docket(self, transaction):
2161 def _write_docket(self, transaction):
2147 """write the current docket on disk
2162 """write the current docket on disk
2148
2163
2149 Exist as a method to help changelog to implement transaction logic
2164 Exist as a method to help changelog to implement transaction logic
2150
2165
2151 We could also imagine using the same transaction logic for all revlog
2166 We could also imagine using the same transaction logic for all revlog
2152 since docket are cheap."""
2167 since docket are cheap."""
2153 self._docket.write(transaction)
2168 self._docket.write(transaction)
2154
2169
2155 def addrevision(
2170 def addrevision(
2156 self,
2171 self,
2157 text,
2172 text,
2158 transaction,
2173 transaction,
2159 link,
2174 link,
2160 p1,
2175 p1,
2161 p2,
2176 p2,
2162 cachedelta=None,
2177 cachedelta=None,
2163 node=None,
2178 node=None,
2164 flags=REVIDX_DEFAULT_FLAGS,
2179 flags=REVIDX_DEFAULT_FLAGS,
2165 deltacomputer=None,
2180 deltacomputer=None,
2166 sidedata=None,
2181 sidedata=None,
2167 ):
2182 ):
2168 """add a revision to the log
2183 """add a revision to the log
2169
2184
2170 text - the revision data to add
2185 text - the revision data to add
2171 transaction - the transaction object used for rollback
2186 transaction - the transaction object used for rollback
2172 link - the linkrev data to add
2187 link - the linkrev data to add
2173 p1, p2 - the parent nodeids of the revision
2188 p1, p2 - the parent nodeids of the revision
2174 cachedelta - an optional precomputed delta
2189 cachedelta - an optional precomputed delta
2175 node - nodeid of revision; typically node is not specified, and it is
2190 node - nodeid of revision; typically node is not specified, and it is
2176 computed by default as hash(text, p1, p2), however subclasses might
2191 computed by default as hash(text, p1, p2), however subclasses might
2177 use different hashing method (and override checkhash() in such case)
2192 use different hashing method (and override checkhash() in such case)
2178 flags - the known flags to set on the revision
2193 flags - the known flags to set on the revision
2179 deltacomputer - an optional deltacomputer instance shared between
2194 deltacomputer - an optional deltacomputer instance shared between
2180 multiple calls
2195 multiple calls
2181 """
2196 """
2182 if link == nullrev:
2197 if link == nullrev:
2183 raise error.RevlogError(
2198 raise error.RevlogError(
2184 _(b"attempted to add linkrev -1 to %s") % self.display_id
2199 _(b"attempted to add linkrev -1 to %s") % self.display_id
2185 )
2200 )
2186
2201
2187 if sidedata is None:
2202 if sidedata is None:
2188 sidedata = {}
2203 sidedata = {}
2189 elif sidedata and not self.hassidedata:
2204 elif sidedata and not self.hassidedata:
2190 raise error.ProgrammingError(
2205 raise error.ProgrammingError(
2191 _(b"trying to add sidedata to a revlog who don't support them")
2206 _(b"trying to add sidedata to a revlog who don't support them")
2192 )
2207 )
2193
2208
2194 if flags:
2209 if flags:
2195 node = node or self.hash(text, p1, p2)
2210 node = node or self.hash(text, p1, p2)
2196
2211
2197 rawtext, validatehash = flagutil.processflagswrite(self, text, flags)
2212 rawtext, validatehash = flagutil.processflagswrite(self, text, flags)
2198
2213
2199 # If the flag processor modifies the revision data, ignore any provided
2214 # If the flag processor modifies the revision data, ignore any provided
2200 # cachedelta.
2215 # cachedelta.
2201 if rawtext != text:
2216 if rawtext != text:
2202 cachedelta = None
2217 cachedelta = None
2203
2218
2204 if len(rawtext) > _maxentrysize:
2219 if len(rawtext) > _maxentrysize:
2205 raise error.RevlogError(
2220 raise error.RevlogError(
2206 _(
2221 _(
2207 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2222 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2208 )
2223 )
2209 % (self.display_id, len(rawtext))
2224 % (self.display_id, len(rawtext))
2210 )
2225 )
2211
2226
2212 node = node or self.hash(rawtext, p1, p2)
2227 node = node or self.hash(rawtext, p1, p2)
2213 rev = self.index.get_rev(node)
2228 rev = self.index.get_rev(node)
2214 if rev is not None:
2229 if rev is not None:
2215 return rev
2230 return rev
2216
2231
2217 if validatehash:
2232 if validatehash:
2218 self.checkhash(rawtext, node, p1=p1, p2=p2)
2233 self.checkhash(rawtext, node, p1=p1, p2=p2)
2219
2234
2220 return self.addrawrevision(
2235 return self.addrawrevision(
2221 rawtext,
2236 rawtext,
2222 transaction,
2237 transaction,
2223 link,
2238 link,
2224 p1,
2239 p1,
2225 p2,
2240 p2,
2226 node,
2241 node,
2227 flags,
2242 flags,
2228 cachedelta=cachedelta,
2243 cachedelta=cachedelta,
2229 deltacomputer=deltacomputer,
2244 deltacomputer=deltacomputer,
2230 sidedata=sidedata,
2245 sidedata=sidedata,
2231 )
2246 )
2232
2247
2233 def addrawrevision(
2248 def addrawrevision(
2234 self,
2249 self,
2235 rawtext,
2250 rawtext,
2236 transaction,
2251 transaction,
2237 link,
2252 link,
2238 p1,
2253 p1,
2239 p2,
2254 p2,
2240 node,
2255 node,
2241 flags,
2256 flags,
2242 cachedelta=None,
2257 cachedelta=None,
2243 deltacomputer=None,
2258 deltacomputer=None,
2244 sidedata=None,
2259 sidedata=None,
2245 ):
2260 ):
2246 """add a raw revision with known flags, node and parents
2261 """add a raw revision with known flags, node and parents
2247 useful when reusing a revision not stored in this revlog (ex: received
2262 useful when reusing a revision not stored in this revlog (ex: received
2248 over wire, or read from an external bundle).
2263 over wire, or read from an external bundle).
2249 """
2264 """
2250 with self._writing(transaction):
2265 with self._writing(transaction):
2251 return self._addrevision(
2266 return self._addrevision(
2252 node,
2267 node,
2253 rawtext,
2268 rawtext,
2254 transaction,
2269 transaction,
2255 link,
2270 link,
2256 p1,
2271 p1,
2257 p2,
2272 p2,
2258 flags,
2273 flags,
2259 cachedelta,
2274 cachedelta,
2260 deltacomputer=deltacomputer,
2275 deltacomputer=deltacomputer,
2261 sidedata=sidedata,
2276 sidedata=sidedata,
2262 )
2277 )
2263
2278
2264 def compress(self, data):
2279 def compress(self, data):
2265 """Generate a possibly-compressed representation of data."""
2280 """Generate a possibly-compressed representation of data."""
2266 if not data:
2281 if not data:
2267 return b'', data
2282 return b'', data
2268
2283
2269 compressed = self._compressor.compress(data)
2284 compressed = self._compressor.compress(data)
2270
2285
2271 if compressed:
2286 if compressed:
2272 # The revlog compressor added the header in the returned data.
2287 # The revlog compressor added the header in the returned data.
2273 return b'', compressed
2288 return b'', compressed
2274
2289
2275 if data[0:1] == b'\0':
2290 if data[0:1] == b'\0':
2276 return b'', data
2291 return b'', data
2277 return b'u', data
2292 return b'u', data
2278
2293
2279 def decompress(self, data):
2294 def decompress(self, data):
2280 """Decompress a revlog chunk.
2295 """Decompress a revlog chunk.
2281
2296
2282 The chunk is expected to begin with a header identifying the
2297 The chunk is expected to begin with a header identifying the
2283 format type so it can be routed to an appropriate decompressor.
2298 format type so it can be routed to an appropriate decompressor.
2284 """
2299 """
2285 if not data:
2300 if not data:
2286 return data
2301 return data
2287
2302
2288 # Revlogs are read much more frequently than they are written and many
2303 # Revlogs are read much more frequently than they are written and many
2289 # chunks only take microseconds to decompress, so performance is
2304 # chunks only take microseconds to decompress, so performance is
2290 # important here.
2305 # important here.
2291 #
2306 #
2292 # We can make a few assumptions about revlogs:
2307 # We can make a few assumptions about revlogs:
2293 #
2308 #
2294 # 1) the majority of chunks will be compressed (as opposed to inline
2309 # 1) the majority of chunks will be compressed (as opposed to inline
2295 # raw data).
2310 # raw data).
2296 # 2) decompressing *any* data will likely by at least 10x slower than
2311 # 2) decompressing *any* data will likely by at least 10x slower than
2297 # returning raw inline data.
2312 # returning raw inline data.
2298 # 3) we want to prioritize common and officially supported compression
2313 # 3) we want to prioritize common and officially supported compression
2299 # engines
2314 # engines
2300 #
2315 #
2301 # It follows that we want to optimize for "decompress compressed data
2316 # It follows that we want to optimize for "decompress compressed data
2302 # when encoded with common and officially supported compression engines"
2317 # when encoded with common and officially supported compression engines"
2303 # case over "raw data" and "data encoded by less common or non-official
2318 # case over "raw data" and "data encoded by less common or non-official
2304 # compression engines." That is why we have the inline lookup first
2319 # compression engines." That is why we have the inline lookup first
2305 # followed by the compengines lookup.
2320 # followed by the compengines lookup.
2306 #
2321 #
2307 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2322 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2308 # compressed chunks. And this matters for changelog and manifest reads.
2323 # compressed chunks. And this matters for changelog and manifest reads.
2309 t = data[0:1]
2324 t = data[0:1]
2310
2325
2311 if t == b'x':
2326 if t == b'x':
2312 try:
2327 try:
2313 return _zlibdecompress(data)
2328 return _zlibdecompress(data)
2314 except zlib.error as e:
2329 except zlib.error as e:
2315 raise error.RevlogError(
2330 raise error.RevlogError(
2316 _(b'revlog decompress error: %s')
2331 _(b'revlog decompress error: %s')
2317 % stringutil.forcebytestr(e)
2332 % stringutil.forcebytestr(e)
2318 )
2333 )
2319 # '\0' is more common than 'u' so it goes first.
2334 # '\0' is more common than 'u' so it goes first.
2320 elif t == b'\0':
2335 elif t == b'\0':
2321 return data
2336 return data
2322 elif t == b'u':
2337 elif t == b'u':
2323 return util.buffer(data, 1)
2338 return util.buffer(data, 1)
2324
2339
2325 compressor = self._get_decompressor(t)
2340 compressor = self._get_decompressor(t)
2326
2341
2327 return compressor.decompress(data)
2342 return compressor.decompress(data)
2328
2343
2329 def _addrevision(
2344 def _addrevision(
2330 self,
2345 self,
2331 node,
2346 node,
2332 rawtext,
2347 rawtext,
2333 transaction,
2348 transaction,
2334 link,
2349 link,
2335 p1,
2350 p1,
2336 p2,
2351 p2,
2337 flags,
2352 flags,
2338 cachedelta,
2353 cachedelta,
2339 alwayscache=False,
2354 alwayscache=False,
2340 deltacomputer=None,
2355 deltacomputer=None,
2341 sidedata=None,
2356 sidedata=None,
2342 ):
2357 ):
2343 """internal function to add revisions to the log
2358 """internal function to add revisions to the log
2344
2359
2345 see addrevision for argument descriptions.
2360 see addrevision for argument descriptions.
2346
2361
2347 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2362 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2348
2363
2349 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2364 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2350 be used.
2365 be used.
2351
2366
2352 invariants:
2367 invariants:
2353 - rawtext is optional (can be None); if not set, cachedelta must be set.
2368 - rawtext is optional (can be None); if not set, cachedelta must be set.
2354 if both are set, they must correspond to each other.
2369 if both are set, they must correspond to each other.
2355 """
2370 """
2356 if node == self.nullid:
2371 if node == self.nullid:
2357 raise error.RevlogError(
2372 raise error.RevlogError(
2358 _(b"%s: attempt to add null revision") % self.display_id
2373 _(b"%s: attempt to add null revision") % self.display_id
2359 )
2374 )
2360 if (
2375 if (
2361 node == self.nodeconstants.wdirid
2376 node == self.nodeconstants.wdirid
2362 or node in self.nodeconstants.wdirfilenodeids
2377 or node in self.nodeconstants.wdirfilenodeids
2363 ):
2378 ):
2364 raise error.RevlogError(
2379 raise error.RevlogError(
2365 _(b"%s: attempt to add wdir revision") % self.display_id
2380 _(b"%s: attempt to add wdir revision") % self.display_id
2366 )
2381 )
2367 if self._writinghandles is None:
2382 if self._writinghandles is None:
2368 msg = b'adding revision outside `revlog._writing` context'
2383 msg = b'adding revision outside `revlog._writing` context'
2369 raise error.ProgrammingError(msg)
2384 raise error.ProgrammingError(msg)
2370
2385
2371 if self._inline:
2386 if self._inline:
2372 fh = self._writinghandles[0]
2387 fh = self._writinghandles[0]
2373 else:
2388 else:
2374 fh = self._writinghandles[1]
2389 fh = self._writinghandles[1]
2375
2390
2376 btext = [rawtext]
2391 btext = [rawtext]
2377
2392
2378 curr = len(self)
2393 curr = len(self)
2379 prev = curr - 1
2394 prev = curr - 1
2380
2395
2381 offset = self._get_data_offset(prev)
2396 offset = self._get_data_offset(prev)
2382
2397
2383 if self._concurrencychecker:
2398 if self._concurrencychecker:
2384 ifh, dfh, sdfh = self._writinghandles
2399 ifh, dfh, sdfh = self._writinghandles
2385 # XXX no checking for the sidedata file
2400 # XXX no checking for the sidedata file
2386 if self._inline:
2401 if self._inline:
2387 # offset is "as if" it were in the .d file, so we need to add on
2402 # offset is "as if" it were in the .d file, so we need to add on
2388 # the size of the entry metadata.
2403 # the size of the entry metadata.
2389 self._concurrencychecker(
2404 self._concurrencychecker(
2390 ifh, self._indexfile, offset + curr * self.index.entry_size
2405 ifh, self._indexfile, offset + curr * self.index.entry_size
2391 )
2406 )
2392 else:
2407 else:
2393 # Entries in the .i are a consistent size.
2408 # Entries in the .i are a consistent size.
2394 self._concurrencychecker(
2409 self._concurrencychecker(
2395 ifh, self._indexfile, curr * self.index.entry_size
2410 ifh, self._indexfile, curr * self.index.entry_size
2396 )
2411 )
2397 self._concurrencychecker(dfh, self._datafile, offset)
2412 self._concurrencychecker(dfh, self._datafile, offset)
2398
2413
2399 p1r, p2r = self.rev(p1), self.rev(p2)
2414 p1r, p2r = self.rev(p1), self.rev(p2)
2400
2415
2401 # full versions are inserted when the needed deltas
2416 # full versions are inserted when the needed deltas
2402 # become comparable to the uncompressed text
2417 # become comparable to the uncompressed text
2403 if rawtext is None:
2418 if rawtext is None:
2404 # need rawtext size, before changed by flag processors, which is
2419 # need rawtext size, before changed by flag processors, which is
2405 # the non-raw size. use revlog explicitly to avoid filelog's extra
2420 # the non-raw size. use revlog explicitly to avoid filelog's extra
2406 # logic that might remove metadata size.
2421 # logic that might remove metadata size.
2407 textlen = mdiff.patchedsize(
2422 textlen = mdiff.patchedsize(
2408 revlog.size(self, cachedelta[0]), cachedelta[1]
2423 revlog.size(self, cachedelta[0]), cachedelta[1]
2409 )
2424 )
2410 else:
2425 else:
2411 textlen = len(rawtext)
2426 textlen = len(rawtext)
2412
2427
2413 if deltacomputer is None:
2428 if deltacomputer is None:
2414 deltacomputer = deltautil.deltacomputer(self)
2429 deltacomputer = deltautil.deltacomputer(self)
2415
2430
2416 revinfo = revlogutils.revisioninfo(
2431 revinfo = revlogutils.revisioninfo(
2417 node,
2432 node,
2418 p1,
2433 p1,
2419 p2,
2434 p2,
2420 btext,
2435 btext,
2421 textlen,
2436 textlen,
2422 cachedelta,
2437 cachedelta,
2423 flags,
2438 flags,
2424 )
2439 )
2425
2440
2426 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2441 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2427
2442
2428 compression_mode = COMP_MODE_INLINE
2443 compression_mode = COMP_MODE_INLINE
2429 if self._docket is not None:
2444 if self._docket is not None:
2430 default_comp = self._docket.default_compression_header
2445 default_comp = self._docket.default_compression_header
2431 r = deltautil.delta_compression(default_comp, deltainfo)
2446 r = deltautil.delta_compression(default_comp, deltainfo)
2432 compression_mode, deltainfo = r
2447 compression_mode, deltainfo = r
2433
2448
2434 sidedata_compression_mode = COMP_MODE_INLINE
2449 sidedata_compression_mode = COMP_MODE_INLINE
2435 if sidedata and self.hassidedata:
2450 if sidedata and self.hassidedata:
2436 sidedata_compression_mode = COMP_MODE_PLAIN
2451 sidedata_compression_mode = COMP_MODE_PLAIN
2437 serialized_sidedata = sidedatautil.serialize_sidedata(sidedata)
2452 serialized_sidedata = sidedatautil.serialize_sidedata(sidedata)
2438 sidedata_offset = self._docket.sidedata_end
2453 sidedata_offset = self._docket.sidedata_end
2439 h, comp_sidedata = self.compress(serialized_sidedata)
2454 h, comp_sidedata = self.compress(serialized_sidedata)
2440 if (
2455 if (
2441 h != b'u'
2456 h != b'u'
2442 and comp_sidedata[0:1] != b'\0'
2457 and comp_sidedata[0:1] != b'\0'
2443 and len(comp_sidedata) < len(serialized_sidedata)
2458 and len(comp_sidedata) < len(serialized_sidedata)
2444 ):
2459 ):
2445 assert not h
2460 assert not h
2446 if (
2461 if (
2447 comp_sidedata[0:1]
2462 comp_sidedata[0:1]
2448 == self._docket.default_compression_header
2463 == self._docket.default_compression_header
2449 ):
2464 ):
2450 sidedata_compression_mode = COMP_MODE_DEFAULT
2465 sidedata_compression_mode = COMP_MODE_DEFAULT
2451 serialized_sidedata = comp_sidedata
2466 serialized_sidedata = comp_sidedata
2452 else:
2467 else:
2453 sidedata_compression_mode = COMP_MODE_INLINE
2468 sidedata_compression_mode = COMP_MODE_INLINE
2454 serialized_sidedata = comp_sidedata
2469 serialized_sidedata = comp_sidedata
2455 else:
2470 else:
2456 serialized_sidedata = b""
2471 serialized_sidedata = b""
2457 # Don't store the offset if the sidedata is empty, that way
2472 # Don't store the offset if the sidedata is empty, that way
2458 # we can easily detect empty sidedata and they will be no different
2473 # we can easily detect empty sidedata and they will be no different
2459 # than ones we manually add.
2474 # than ones we manually add.
2460 sidedata_offset = 0
2475 sidedata_offset = 0
2461
2476
2462 rank = RANK_UNKNOWN
2477 rank = RANK_UNKNOWN
2463 if self._format_version == CHANGELOGV2:
2478 if self._format_version == CHANGELOGV2:
2464 if (p1r, p2r) == (nullrev, nullrev):
2479 if (p1r, p2r) == (nullrev, nullrev):
2465 rank = 1
2480 rank = 1
2466 elif p1r != nullrev and p2r == nullrev:
2481 elif p1r != nullrev and p2r == nullrev:
2467 rank = 1 + self.fast_rank(p1r)
2482 rank = 1 + self.fast_rank(p1r)
2468 elif p1r == nullrev and p2r != nullrev:
2483 elif p1r == nullrev and p2r != nullrev:
2469 rank = 1 + self.fast_rank(p2r)
2484 rank = 1 + self.fast_rank(p2r)
2470 else: # merge node
2485 else: # merge node
2471 if rustdagop is not None and self.index.rust_ext_compat:
2486 if rustdagop is not None and self.index.rust_ext_compat:
2472 rank = rustdagop.rank(self.index, p1r, p2r)
2487 rank = rustdagop.rank(self.index, p1r, p2r)
2473 else:
2488 else:
2474 pmin, pmax = sorted((p1r, p2r))
2489 pmin, pmax = sorted((p1r, p2r))
2475 rank = 1 + self.fast_rank(pmax)
2490 rank = 1 + self.fast_rank(pmax)
2476 rank += sum(1 for _ in self.findmissingrevs([pmax], [pmin]))
2491 rank += sum(1 for _ in self.findmissingrevs([pmax], [pmin]))
2477
2492
2478 e = revlogutils.entry(
2493 e = revlogutils.entry(
2479 flags=flags,
2494 flags=flags,
2480 data_offset=offset,
2495 data_offset=offset,
2481 data_compressed_length=deltainfo.deltalen,
2496 data_compressed_length=deltainfo.deltalen,
2482 data_uncompressed_length=textlen,
2497 data_uncompressed_length=textlen,
2483 data_compression_mode=compression_mode,
2498 data_compression_mode=compression_mode,
2484 data_delta_base=deltainfo.base,
2499 data_delta_base=deltainfo.base,
2485 link_rev=link,
2500 link_rev=link,
2486 parent_rev_1=p1r,
2501 parent_rev_1=p1r,
2487 parent_rev_2=p2r,
2502 parent_rev_2=p2r,
2488 node_id=node,
2503 node_id=node,
2489 sidedata_offset=sidedata_offset,
2504 sidedata_offset=sidedata_offset,
2490 sidedata_compressed_length=len(serialized_sidedata),
2505 sidedata_compressed_length=len(serialized_sidedata),
2491 sidedata_compression_mode=sidedata_compression_mode,
2506 sidedata_compression_mode=sidedata_compression_mode,
2492 rank=rank,
2507 rank=rank,
2493 )
2508 )
2494
2509
2495 self.index.append(e)
2510 self.index.append(e)
2496 entry = self.index.entry_binary(curr)
2511 entry = self.index.entry_binary(curr)
2497 if curr == 0 and self._docket is None:
2512 if curr == 0 and self._docket is None:
2498 header = self._format_flags | self._format_version
2513 header = self._format_flags | self._format_version
2499 header = self.index.pack_header(header)
2514 header = self.index.pack_header(header)
2500 entry = header + entry
2515 entry = header + entry
2501 self._writeentry(
2516 self._writeentry(
2502 transaction,
2517 transaction,
2503 entry,
2518 entry,
2504 deltainfo.data,
2519 deltainfo.data,
2505 link,
2520 link,
2506 offset,
2521 offset,
2507 serialized_sidedata,
2522 serialized_sidedata,
2508 sidedata_offset,
2523 sidedata_offset,
2509 )
2524 )
2510
2525
2511 rawtext = btext[0]
2526 rawtext = btext[0]
2512
2527
2513 if alwayscache and rawtext is None:
2528 if alwayscache and rawtext is None:
2514 rawtext = deltacomputer.buildtext(revinfo, fh)
2529 rawtext = deltacomputer.buildtext(revinfo, fh)
2515
2530
2516 if type(rawtext) == bytes: # only accept immutable objects
2531 if type(rawtext) == bytes: # only accept immutable objects
2517 self._revisioncache = (node, curr, rawtext)
2532 self._revisioncache = (node, curr, rawtext)
2518 self._chainbasecache[curr] = deltainfo.chainbase
2533 self._chainbasecache[curr] = deltainfo.chainbase
2519 return curr
2534 return curr
2520
2535
2521 def _get_data_offset(self, prev):
2536 def _get_data_offset(self, prev):
2522 """Returns the current offset in the (in-transaction) data file.
2537 """Returns the current offset in the (in-transaction) data file.
2523 Versions < 2 of the revlog can get this 0(1), revlog v2 needs a docket
2538 Versions < 2 of the revlog can get this 0(1), revlog v2 needs a docket
2524 file to store that information: since sidedata can be rewritten to the
2539 file to store that information: since sidedata can be rewritten to the
2525 end of the data file within a transaction, you can have cases where, for
2540 end of the data file within a transaction, you can have cases where, for
2526 example, rev `n` does not have sidedata while rev `n - 1` does, leading
2541 example, rev `n` does not have sidedata while rev `n - 1` does, leading
2527 to `n - 1`'s sidedata being written after `n`'s data.
2542 to `n - 1`'s sidedata being written after `n`'s data.
2528
2543
2529 TODO cache this in a docket file before getting out of experimental."""
2544 TODO cache this in a docket file before getting out of experimental."""
2530 if self._docket is None:
2545 if self._docket is None:
2531 return self.end(prev)
2546 return self.end(prev)
2532 else:
2547 else:
2533 return self._docket.data_end
2548 return self._docket.data_end
2534
2549
2535 def _writeentry(
2550 def _writeentry(
2536 self, transaction, entry, data, link, offset, sidedata, sidedata_offset
2551 self, transaction, entry, data, link, offset, sidedata, sidedata_offset
2537 ):
2552 ):
2538 # Files opened in a+ mode have inconsistent behavior on various
2553 # Files opened in a+ mode have inconsistent behavior on various
2539 # platforms. Windows requires that a file positioning call be made
2554 # platforms. Windows requires that a file positioning call be made
2540 # when the file handle transitions between reads and writes. See
2555 # when the file handle transitions between reads and writes. See
2541 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2556 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2542 # platforms, Python or the platform itself can be buggy. Some versions
2557 # platforms, Python or the platform itself can be buggy. Some versions
2543 # of Solaris have been observed to not append at the end of the file
2558 # of Solaris have been observed to not append at the end of the file
2544 # if the file was seeked to before the end. See issue4943 for more.
2559 # if the file was seeked to before the end. See issue4943 for more.
2545 #
2560 #
2546 # We work around this issue by inserting a seek() before writing.
2561 # We work around this issue by inserting a seek() before writing.
2547 # Note: This is likely not necessary on Python 3. However, because
2562 # Note: This is likely not necessary on Python 3. However, because
2548 # the file handle is reused for reads and may be seeked there, we need
2563 # the file handle is reused for reads and may be seeked there, we need
2549 # to be careful before changing this.
2564 # to be careful before changing this.
2550 if self._writinghandles is None:
2565 if self._writinghandles is None:
2551 msg = b'adding revision outside `revlog._writing` context'
2566 msg = b'adding revision outside `revlog._writing` context'
2552 raise error.ProgrammingError(msg)
2567 raise error.ProgrammingError(msg)
2553 ifh, dfh, sdfh = self._writinghandles
2568 ifh, dfh, sdfh = self._writinghandles
2554 if self._docket is None:
2569 if self._docket is None:
2555 ifh.seek(0, os.SEEK_END)
2570 ifh.seek(0, os.SEEK_END)
2556 else:
2571 else:
2557 ifh.seek(self._docket.index_end, os.SEEK_SET)
2572 ifh.seek(self._docket.index_end, os.SEEK_SET)
2558 if dfh:
2573 if dfh:
2559 if self._docket is None:
2574 if self._docket is None:
2560 dfh.seek(0, os.SEEK_END)
2575 dfh.seek(0, os.SEEK_END)
2561 else:
2576 else:
2562 dfh.seek(self._docket.data_end, os.SEEK_SET)
2577 dfh.seek(self._docket.data_end, os.SEEK_SET)
2563 if sdfh:
2578 if sdfh:
2564 sdfh.seek(self._docket.sidedata_end, os.SEEK_SET)
2579 sdfh.seek(self._docket.sidedata_end, os.SEEK_SET)
2565
2580
2566 curr = len(self) - 1
2581 curr = len(self) - 1
2567 if not self._inline:
2582 if not self._inline:
2568 transaction.add(self._datafile, offset)
2583 transaction.add(self._datafile, offset)
2569 if self._sidedatafile:
2584 if self._sidedatafile:
2570 transaction.add(self._sidedatafile, sidedata_offset)
2585 transaction.add(self._sidedatafile, sidedata_offset)
2571 transaction.add(self._indexfile, curr * len(entry))
2586 transaction.add(self._indexfile, curr * len(entry))
2572 if data[0]:
2587 if data[0]:
2573 dfh.write(data[0])
2588 dfh.write(data[0])
2574 dfh.write(data[1])
2589 dfh.write(data[1])
2575 if sidedata:
2590 if sidedata:
2576 sdfh.write(sidedata)
2591 sdfh.write(sidedata)
2577 ifh.write(entry)
2592 ifh.write(entry)
2578 else:
2593 else:
2579 offset += curr * self.index.entry_size
2594 offset += curr * self.index.entry_size
2580 transaction.add(self._indexfile, offset)
2595 transaction.add(self._indexfile, offset)
2581 ifh.write(entry)
2596 ifh.write(entry)
2582 ifh.write(data[0])
2597 ifh.write(data[0])
2583 ifh.write(data[1])
2598 ifh.write(data[1])
2584 assert not sidedata
2599 assert not sidedata
2585 self._enforceinlinesize(transaction)
2600 self._enforceinlinesize(transaction)
2586 if self._docket is not None:
2601 if self._docket is not None:
2587 # revlog-v2 always has 3 writing handles, help Pytype
2602 # revlog-v2 always has 3 writing handles, help Pytype
2588 wh1 = self._writinghandles[0]
2603 wh1 = self._writinghandles[0]
2589 wh2 = self._writinghandles[1]
2604 wh2 = self._writinghandles[1]
2590 wh3 = self._writinghandles[2]
2605 wh3 = self._writinghandles[2]
2591 assert wh1 is not None
2606 assert wh1 is not None
2592 assert wh2 is not None
2607 assert wh2 is not None
2593 assert wh3 is not None
2608 assert wh3 is not None
2594 self._docket.index_end = wh1.tell()
2609 self._docket.index_end = wh1.tell()
2595 self._docket.data_end = wh2.tell()
2610 self._docket.data_end = wh2.tell()
2596 self._docket.sidedata_end = wh3.tell()
2611 self._docket.sidedata_end = wh3.tell()
2597
2612
2598 nodemaputil.setup_persistent_nodemap(transaction, self)
2613 nodemaputil.setup_persistent_nodemap(transaction, self)
2599
2614
2600 def addgroup(
2615 def addgroup(
2601 self,
2616 self,
2602 deltas,
2617 deltas,
2603 linkmapper,
2618 linkmapper,
2604 transaction,
2619 transaction,
2605 alwayscache=False,
2620 alwayscache=False,
2606 addrevisioncb=None,
2621 addrevisioncb=None,
2607 duplicaterevisioncb=None,
2622 duplicaterevisioncb=None,
2608 ):
2623 ):
2609 """
2624 """
2610 add a delta group
2625 add a delta group
2611
2626
2612 given a set of deltas, add them to the revision log. the
2627 given a set of deltas, add them to the revision log. the
2613 first delta is against its parent, which should be in our
2628 first delta is against its parent, which should be in our
2614 log, the rest are against the previous delta.
2629 log, the rest are against the previous delta.
2615
2630
2616 If ``addrevisioncb`` is defined, it will be called with arguments of
2631 If ``addrevisioncb`` is defined, it will be called with arguments of
2617 this revlog and the node that was added.
2632 this revlog and the node that was added.
2618 """
2633 """
2619
2634
2620 if self._adding_group:
2635 if self._adding_group:
2621 raise error.ProgrammingError(b'cannot nest addgroup() calls')
2636 raise error.ProgrammingError(b'cannot nest addgroup() calls')
2622
2637
2623 self._adding_group = True
2638 self._adding_group = True
2624 empty = True
2639 empty = True
2625 try:
2640 try:
2626 with self._writing(transaction):
2641 with self._writing(transaction):
2627 deltacomputer = deltautil.deltacomputer(self)
2642 deltacomputer = deltautil.deltacomputer(self)
2628 # loop through our set of deltas
2643 # loop through our set of deltas
2629 for data in deltas:
2644 for data in deltas:
2630 (
2645 (
2631 node,
2646 node,
2632 p1,
2647 p1,
2633 p2,
2648 p2,
2634 linknode,
2649 linknode,
2635 deltabase,
2650 deltabase,
2636 delta,
2651 delta,
2637 flags,
2652 flags,
2638 sidedata,
2653 sidedata,
2639 ) = data
2654 ) = data
2640 link = linkmapper(linknode)
2655 link = linkmapper(linknode)
2641 flags = flags or REVIDX_DEFAULT_FLAGS
2656 flags = flags or REVIDX_DEFAULT_FLAGS
2642
2657
2643 rev = self.index.get_rev(node)
2658 rev = self.index.get_rev(node)
2644 if rev is not None:
2659 if rev is not None:
2645 # this can happen if two branches make the same change
2660 # this can happen if two branches make the same change
2646 self._nodeduplicatecallback(transaction, rev)
2661 self._nodeduplicatecallback(transaction, rev)
2647 if duplicaterevisioncb:
2662 if duplicaterevisioncb:
2648 duplicaterevisioncb(self, rev)
2663 duplicaterevisioncb(self, rev)
2649 empty = False
2664 empty = False
2650 continue
2665 continue
2651
2666
2652 for p in (p1, p2):
2667 for p in (p1, p2):
2653 if not self.index.has_node(p):
2668 if not self.index.has_node(p):
2654 raise error.LookupError(
2669 raise error.LookupError(
2655 p, self.radix, _(b'unknown parent')
2670 p, self.radix, _(b'unknown parent')
2656 )
2671 )
2657
2672
2658 if not self.index.has_node(deltabase):
2673 if not self.index.has_node(deltabase):
2659 raise error.LookupError(
2674 raise error.LookupError(
2660 deltabase, self.display_id, _(b'unknown delta base')
2675 deltabase, self.display_id, _(b'unknown delta base')
2661 )
2676 )
2662
2677
2663 baserev = self.rev(deltabase)
2678 baserev = self.rev(deltabase)
2664
2679
2665 if baserev != nullrev and self.iscensored(baserev):
2680 if baserev != nullrev and self.iscensored(baserev):
2666 # if base is censored, delta must be full replacement in a
2681 # if base is censored, delta must be full replacement in a
2667 # single patch operation
2682 # single patch operation
2668 hlen = struct.calcsize(b">lll")
2683 hlen = struct.calcsize(b">lll")
2669 oldlen = self.rawsize(baserev)
2684 oldlen = self.rawsize(baserev)
2670 newlen = len(delta) - hlen
2685 newlen = len(delta) - hlen
2671 if delta[:hlen] != mdiff.replacediffheader(
2686 if delta[:hlen] != mdiff.replacediffheader(
2672 oldlen, newlen
2687 oldlen, newlen
2673 ):
2688 ):
2674 raise error.CensoredBaseError(
2689 raise error.CensoredBaseError(
2675 self.display_id, self.node(baserev)
2690 self.display_id, self.node(baserev)
2676 )
2691 )
2677
2692
2678 if not flags and self._peek_iscensored(baserev, delta):
2693 if not flags and self._peek_iscensored(baserev, delta):
2679 flags |= REVIDX_ISCENSORED
2694 flags |= REVIDX_ISCENSORED
2680
2695
2681 # We assume consumers of addrevisioncb will want to retrieve
2696 # We assume consumers of addrevisioncb will want to retrieve
2682 # the added revision, which will require a call to
2697 # the added revision, which will require a call to
2683 # revision(). revision() will fast path if there is a cache
2698 # revision(). revision() will fast path if there is a cache
2684 # hit. So, we tell _addrevision() to always cache in this case.
2699 # hit. So, we tell _addrevision() to always cache in this case.
2685 # We're only using addgroup() in the context of changegroup
2700 # We're only using addgroup() in the context of changegroup
2686 # generation so the revision data can always be handled as raw
2701 # generation so the revision data can always be handled as raw
2687 # by the flagprocessor.
2702 # by the flagprocessor.
2688 rev = self._addrevision(
2703 rev = self._addrevision(
2689 node,
2704 node,
2690 None,
2705 None,
2691 transaction,
2706 transaction,
2692 link,
2707 link,
2693 p1,
2708 p1,
2694 p2,
2709 p2,
2695 flags,
2710 flags,
2696 (baserev, delta),
2711 (baserev, delta),
2697 alwayscache=alwayscache,
2712 alwayscache=alwayscache,
2698 deltacomputer=deltacomputer,
2713 deltacomputer=deltacomputer,
2699 sidedata=sidedata,
2714 sidedata=sidedata,
2700 )
2715 )
2701
2716
2702 if addrevisioncb:
2717 if addrevisioncb:
2703 addrevisioncb(self, rev)
2718 addrevisioncb(self, rev)
2704 empty = False
2719 empty = False
2705 finally:
2720 finally:
2706 self._adding_group = False
2721 self._adding_group = False
2707 return not empty
2722 return not empty
2708
2723
2709 def iscensored(self, rev):
2724 def iscensored(self, rev):
2710 """Check if a file revision is censored."""
2725 """Check if a file revision is censored."""
2711 if not self._censorable:
2726 if not self._censorable:
2712 return False
2727 return False
2713
2728
2714 return self.flags(rev) & REVIDX_ISCENSORED
2729 return self.flags(rev) & REVIDX_ISCENSORED
2715
2730
2716 def _peek_iscensored(self, baserev, delta):
2731 def _peek_iscensored(self, baserev, delta):
2717 """Quickly check if a delta produces a censored revision."""
2732 """Quickly check if a delta produces a censored revision."""
2718 if not self._censorable:
2733 if not self._censorable:
2719 return False
2734 return False
2720
2735
2721 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2736 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2722
2737
2723 def getstrippoint(self, minlink):
2738 def getstrippoint(self, minlink):
2724 """find the minimum rev that must be stripped to strip the linkrev
2739 """find the minimum rev that must be stripped to strip the linkrev
2725
2740
2726 Returns a tuple containing the minimum rev and a set of all revs that
2741 Returns a tuple containing the minimum rev and a set of all revs that
2727 have linkrevs that will be broken by this strip.
2742 have linkrevs that will be broken by this strip.
2728 """
2743 """
2729 return storageutil.resolvestripinfo(
2744 return storageutil.resolvestripinfo(
2730 minlink,
2745 minlink,
2731 len(self) - 1,
2746 len(self) - 1,
2732 self.headrevs(),
2747 self.headrevs(),
2733 self.linkrev,
2748 self.linkrev,
2734 self.parentrevs,
2749 self.parentrevs,
2735 )
2750 )
2736
2751
2737 def strip(self, minlink, transaction):
2752 def strip(self, minlink, transaction):
2738 """truncate the revlog on the first revision with a linkrev >= minlink
2753 """truncate the revlog on the first revision with a linkrev >= minlink
2739
2754
2740 This function is called when we're stripping revision minlink and
2755 This function is called when we're stripping revision minlink and
2741 its descendants from the repository.
2756 its descendants from the repository.
2742
2757
2743 We have to remove all revisions with linkrev >= minlink, because
2758 We have to remove all revisions with linkrev >= minlink, because
2744 the equivalent changelog revisions will be renumbered after the
2759 the equivalent changelog revisions will be renumbered after the
2745 strip.
2760 strip.
2746
2761
2747 So we truncate the revlog on the first of these revisions, and
2762 So we truncate the revlog on the first of these revisions, and
2748 trust that the caller has saved the revisions that shouldn't be
2763 trust that the caller has saved the revisions that shouldn't be
2749 removed and that it'll re-add them after this truncation.
2764 removed and that it'll re-add them after this truncation.
2750 """
2765 """
2751 if len(self) == 0:
2766 if len(self) == 0:
2752 return
2767 return
2753
2768
2754 rev, _ = self.getstrippoint(minlink)
2769 rev, _ = self.getstrippoint(minlink)
2755 if rev == len(self):
2770 if rev == len(self):
2756 return
2771 return
2757
2772
2758 # first truncate the files on disk
2773 # first truncate the files on disk
2759 data_end = self.start(rev)
2774 data_end = self.start(rev)
2760 if not self._inline:
2775 if not self._inline:
2761 transaction.add(self._datafile, data_end)
2776 transaction.add(self._datafile, data_end)
2762 end = rev * self.index.entry_size
2777 end = rev * self.index.entry_size
2763 else:
2778 else:
2764 end = data_end + (rev * self.index.entry_size)
2779 end = data_end + (rev * self.index.entry_size)
2765
2780
2766 if self._sidedatafile:
2781 if self._sidedatafile:
2767 sidedata_end = self.sidedata_cut_off(rev)
2782 sidedata_end = self.sidedata_cut_off(rev)
2768 transaction.add(self._sidedatafile, sidedata_end)
2783 transaction.add(self._sidedatafile, sidedata_end)
2769
2784
2770 transaction.add(self._indexfile, end)
2785 transaction.add(self._indexfile, end)
2771 if self._docket is not None:
2786 if self._docket is not None:
2772 # XXX we could, leverage the docket while stripping. However it is
2787 # XXX we could, leverage the docket while stripping. However it is
2773 # not powerfull enough at the time of this comment
2788 # not powerfull enough at the time of this comment
2774 self._docket.index_end = end
2789 self._docket.index_end = end
2775 self._docket.data_end = data_end
2790 self._docket.data_end = data_end
2776 self._docket.sidedata_end = sidedata_end
2791 self._docket.sidedata_end = sidedata_end
2777 self._docket.write(transaction, stripping=True)
2792 self._docket.write(transaction, stripping=True)
2778
2793
2779 # then reset internal state in memory to forget those revisions
2794 # then reset internal state in memory to forget those revisions
2780 self._revisioncache = None
2795 self._revisioncache = None
2781 self._chaininfocache = util.lrucachedict(500)
2796 self._chaininfocache = util.lrucachedict(500)
2782 self._segmentfile.clear_cache()
2797 self._segmentfile.clear_cache()
2783 self._segmentfile_sidedata.clear_cache()
2798 self._segmentfile_sidedata.clear_cache()
2784
2799
2785 del self.index[rev:-1]
2800 del self.index[rev:-1]
2786
2801
2787 def checksize(self):
2802 def checksize(self):
2788 """Check size of index and data files
2803 """Check size of index and data files
2789
2804
2790 return a (dd, di) tuple.
2805 return a (dd, di) tuple.
2791 - dd: extra bytes for the "data" file
2806 - dd: extra bytes for the "data" file
2792 - di: extra bytes for the "index" file
2807 - di: extra bytes for the "index" file
2793
2808
2794 A healthy revlog will return (0, 0).
2809 A healthy revlog will return (0, 0).
2795 """
2810 """
2796 expected = 0
2811 expected = 0
2797 if len(self):
2812 if len(self):
2798 expected = max(0, self.end(len(self) - 1))
2813 expected = max(0, self.end(len(self) - 1))
2799
2814
2800 try:
2815 try:
2801 with self._datafp() as f:
2816 with self._datafp() as f:
2802 f.seek(0, io.SEEK_END)
2817 f.seek(0, io.SEEK_END)
2803 actual = f.tell()
2818 actual = f.tell()
2804 dd = actual - expected
2819 dd = actual - expected
2805 except IOError as inst:
2820 except IOError as inst:
2806 if inst.errno != errno.ENOENT:
2821 if inst.errno != errno.ENOENT:
2807 raise
2822 raise
2808 dd = 0
2823 dd = 0
2809
2824
2810 try:
2825 try:
2811 f = self.opener(self._indexfile)
2826 f = self.opener(self._indexfile)
2812 f.seek(0, io.SEEK_END)
2827 f.seek(0, io.SEEK_END)
2813 actual = f.tell()
2828 actual = f.tell()
2814 f.close()
2829 f.close()
2815 s = self.index.entry_size
2830 s = self.index.entry_size
2816 i = max(0, actual // s)
2831 i = max(0, actual // s)
2817 di = actual - (i * s)
2832 di = actual - (i * s)
2818 if self._inline:
2833 if self._inline:
2819 databytes = 0
2834 databytes = 0
2820 for r in self:
2835 for r in self:
2821 databytes += max(0, self.length(r))
2836 databytes += max(0, self.length(r))
2822 dd = 0
2837 dd = 0
2823 di = actual - len(self) * s - databytes
2838 di = actual - len(self) * s - databytes
2824 except IOError as inst:
2839 except IOError as inst:
2825 if inst.errno != errno.ENOENT:
2840 if inst.errno != errno.ENOENT:
2826 raise
2841 raise
2827 di = 0
2842 di = 0
2828
2843
2829 return (dd, di)
2844 return (dd, di)
2830
2845
2831 def files(self):
2846 def files(self):
2832 res = [self._indexfile]
2847 res = [self._indexfile]
2833 if self._docket_file is None:
2848 if self._docket_file is None:
2834 if not self._inline:
2849 if not self._inline:
2835 res.append(self._datafile)
2850 res.append(self._datafile)
2836 else:
2851 else:
2837 res.append(self._docket_file)
2852 res.append(self._docket_file)
2838 res.extend(self._docket.old_index_filepaths(include_empty=False))
2853 res.extend(self._docket.old_index_filepaths(include_empty=False))
2839 if self._docket.data_end:
2854 if self._docket.data_end:
2840 res.append(self._datafile)
2855 res.append(self._datafile)
2841 res.extend(self._docket.old_data_filepaths(include_empty=False))
2856 res.extend(self._docket.old_data_filepaths(include_empty=False))
2842 if self._docket.sidedata_end:
2857 if self._docket.sidedata_end:
2843 res.append(self._sidedatafile)
2858 res.append(self._sidedatafile)
2844 res.extend(self._docket.old_sidedata_filepaths(include_empty=False))
2859 res.extend(self._docket.old_sidedata_filepaths(include_empty=False))
2845 return res
2860 return res
2846
2861
2847 def emitrevisions(
2862 def emitrevisions(
2848 self,
2863 self,
2849 nodes,
2864 nodes,
2850 nodesorder=None,
2865 nodesorder=None,
2851 revisiondata=False,
2866 revisiondata=False,
2852 assumehaveparentrevisions=False,
2867 assumehaveparentrevisions=False,
2853 deltamode=repository.CG_DELTAMODE_STD,
2868 deltamode=repository.CG_DELTAMODE_STD,
2854 sidedata_helpers=None,
2869 sidedata_helpers=None,
2855 ):
2870 ):
2856 if nodesorder not in (b'nodes', b'storage', b'linear', None):
2871 if nodesorder not in (b'nodes', b'storage', b'linear', None):
2857 raise error.ProgrammingError(
2872 raise error.ProgrammingError(
2858 b'unhandled value for nodesorder: %s' % nodesorder
2873 b'unhandled value for nodesorder: %s' % nodesorder
2859 )
2874 )
2860
2875
2861 if nodesorder is None and not self._generaldelta:
2876 if nodesorder is None and not self._generaldelta:
2862 nodesorder = b'storage'
2877 nodesorder = b'storage'
2863
2878
2864 if (
2879 if (
2865 not self._storedeltachains
2880 not self._storedeltachains
2866 and deltamode != repository.CG_DELTAMODE_PREV
2881 and deltamode != repository.CG_DELTAMODE_PREV
2867 ):
2882 ):
2868 deltamode = repository.CG_DELTAMODE_FULL
2883 deltamode = repository.CG_DELTAMODE_FULL
2869
2884
2870 return storageutil.emitrevisions(
2885 return storageutil.emitrevisions(
2871 self,
2886 self,
2872 nodes,
2887 nodes,
2873 nodesorder,
2888 nodesorder,
2874 revlogrevisiondelta,
2889 revlogrevisiondelta,
2875 deltaparentfn=self.deltaparent,
2890 deltaparentfn=self.deltaparent,
2876 candeltafn=self.candelta,
2891 candeltafn=self.candelta,
2877 rawsizefn=self.rawsize,
2892 rawsizefn=self.rawsize,
2878 revdifffn=self.revdiff,
2893 revdifffn=self.revdiff,
2879 flagsfn=self.flags,
2894 flagsfn=self.flags,
2880 deltamode=deltamode,
2895 deltamode=deltamode,
2881 revisiondata=revisiondata,
2896 revisiondata=revisiondata,
2882 assumehaveparentrevisions=assumehaveparentrevisions,
2897 assumehaveparentrevisions=assumehaveparentrevisions,
2883 sidedata_helpers=sidedata_helpers,
2898 sidedata_helpers=sidedata_helpers,
2884 )
2899 )
2885
2900
2886 DELTAREUSEALWAYS = b'always'
2901 DELTAREUSEALWAYS = b'always'
2887 DELTAREUSESAMEREVS = b'samerevs'
2902 DELTAREUSESAMEREVS = b'samerevs'
2888 DELTAREUSENEVER = b'never'
2903 DELTAREUSENEVER = b'never'
2889
2904
2890 DELTAREUSEFULLADD = b'fulladd'
2905 DELTAREUSEFULLADD = b'fulladd'
2891
2906
2892 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
2907 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
2893
2908
2894 def clone(
2909 def clone(
2895 self,
2910 self,
2896 tr,
2911 tr,
2897 destrevlog,
2912 destrevlog,
2898 addrevisioncb=None,
2913 addrevisioncb=None,
2899 deltareuse=DELTAREUSESAMEREVS,
2914 deltareuse=DELTAREUSESAMEREVS,
2900 forcedeltabothparents=None,
2915 forcedeltabothparents=None,
2901 sidedata_helpers=None,
2916 sidedata_helpers=None,
2902 ):
2917 ):
2903 """Copy this revlog to another, possibly with format changes.
2918 """Copy this revlog to another, possibly with format changes.
2904
2919
2905 The destination revlog will contain the same revisions and nodes.
2920 The destination revlog will contain the same revisions and nodes.
2906 However, it may not be bit-for-bit identical due to e.g. delta encoding
2921 However, it may not be bit-for-bit identical due to e.g. delta encoding
2907 differences.
2922 differences.
2908
2923
2909 The ``deltareuse`` argument control how deltas from the existing revlog
2924 The ``deltareuse`` argument control how deltas from the existing revlog
2910 are preserved in the destination revlog. The argument can have the
2925 are preserved in the destination revlog. The argument can have the
2911 following values:
2926 following values:
2912
2927
2913 DELTAREUSEALWAYS
2928 DELTAREUSEALWAYS
2914 Deltas will always be reused (if possible), even if the destination
2929 Deltas will always be reused (if possible), even if the destination
2915 revlog would not select the same revisions for the delta. This is the
2930 revlog would not select the same revisions for the delta. This is the
2916 fastest mode of operation.
2931 fastest mode of operation.
2917 DELTAREUSESAMEREVS
2932 DELTAREUSESAMEREVS
2918 Deltas will be reused if the destination revlog would pick the same
2933 Deltas will be reused if the destination revlog would pick the same
2919 revisions for the delta. This mode strikes a balance between speed
2934 revisions for the delta. This mode strikes a balance between speed
2920 and optimization.
2935 and optimization.
2921 DELTAREUSENEVER
2936 DELTAREUSENEVER
2922 Deltas will never be reused. This is the slowest mode of execution.
2937 Deltas will never be reused. This is the slowest mode of execution.
2923 This mode can be used to recompute deltas (e.g. if the diff/delta
2938 This mode can be used to recompute deltas (e.g. if the diff/delta
2924 algorithm changes).
2939 algorithm changes).
2925 DELTAREUSEFULLADD
2940 DELTAREUSEFULLADD
2926 Revision will be re-added as if their were new content. This is
2941 Revision will be re-added as if their were new content. This is
2927 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
2942 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
2928 eg: large file detection and handling.
2943 eg: large file detection and handling.
2929
2944
2930 Delta computation can be slow, so the choice of delta reuse policy can
2945 Delta computation can be slow, so the choice of delta reuse policy can
2931 significantly affect run time.
2946 significantly affect run time.
2932
2947
2933 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2948 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2934 two extremes. Deltas will be reused if they are appropriate. But if the
2949 two extremes. Deltas will be reused if they are appropriate. But if the
2935 delta could choose a better revision, it will do so. This means if you
2950 delta could choose a better revision, it will do so. This means if you
2936 are converting a non-generaldelta revlog to a generaldelta revlog,
2951 are converting a non-generaldelta revlog to a generaldelta revlog,
2937 deltas will be recomputed if the delta's parent isn't a parent of the
2952 deltas will be recomputed if the delta's parent isn't a parent of the
2938 revision.
2953 revision.
2939
2954
2940 In addition to the delta policy, the ``forcedeltabothparents``
2955 In addition to the delta policy, the ``forcedeltabothparents``
2941 argument controls whether to force compute deltas against both parents
2956 argument controls whether to force compute deltas against both parents
2942 for merges. By default, the current default is used.
2957 for merges. By default, the current default is used.
2943
2958
2944 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
2959 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
2945 `sidedata_helpers`.
2960 `sidedata_helpers`.
2946 """
2961 """
2947 if deltareuse not in self.DELTAREUSEALL:
2962 if deltareuse not in self.DELTAREUSEALL:
2948 raise ValueError(
2963 raise ValueError(
2949 _(b'value for deltareuse invalid: %s') % deltareuse
2964 _(b'value for deltareuse invalid: %s') % deltareuse
2950 )
2965 )
2951
2966
2952 if len(destrevlog):
2967 if len(destrevlog):
2953 raise ValueError(_(b'destination revlog is not empty'))
2968 raise ValueError(_(b'destination revlog is not empty'))
2954
2969
2955 if getattr(self, 'filteredrevs', None):
2970 if getattr(self, 'filteredrevs', None):
2956 raise ValueError(_(b'source revlog has filtered revisions'))
2971 raise ValueError(_(b'source revlog has filtered revisions'))
2957 if getattr(destrevlog, 'filteredrevs', None):
2972 if getattr(destrevlog, 'filteredrevs', None):
2958 raise ValueError(_(b'destination revlog has filtered revisions'))
2973 raise ValueError(_(b'destination revlog has filtered revisions'))
2959
2974
2960 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
2975 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
2961 # if possible.
2976 # if possible.
2962 oldlazydelta = destrevlog._lazydelta
2977 oldlazydelta = destrevlog._lazydelta
2963 oldlazydeltabase = destrevlog._lazydeltabase
2978 oldlazydeltabase = destrevlog._lazydeltabase
2964 oldamd = destrevlog._deltabothparents
2979 oldamd = destrevlog._deltabothparents
2965
2980
2966 try:
2981 try:
2967 if deltareuse == self.DELTAREUSEALWAYS:
2982 if deltareuse == self.DELTAREUSEALWAYS:
2968 destrevlog._lazydeltabase = True
2983 destrevlog._lazydeltabase = True
2969 destrevlog._lazydelta = True
2984 destrevlog._lazydelta = True
2970 elif deltareuse == self.DELTAREUSESAMEREVS:
2985 elif deltareuse == self.DELTAREUSESAMEREVS:
2971 destrevlog._lazydeltabase = False
2986 destrevlog._lazydeltabase = False
2972 destrevlog._lazydelta = True
2987 destrevlog._lazydelta = True
2973 elif deltareuse == self.DELTAREUSENEVER:
2988 elif deltareuse == self.DELTAREUSENEVER:
2974 destrevlog._lazydeltabase = False
2989 destrevlog._lazydeltabase = False
2975 destrevlog._lazydelta = False
2990 destrevlog._lazydelta = False
2976
2991
2977 destrevlog._deltabothparents = forcedeltabothparents or oldamd
2992 destrevlog._deltabothparents = forcedeltabothparents or oldamd
2978
2993
2979 self._clone(
2994 self._clone(
2980 tr,
2995 tr,
2981 destrevlog,
2996 destrevlog,
2982 addrevisioncb,
2997 addrevisioncb,
2983 deltareuse,
2998 deltareuse,
2984 forcedeltabothparents,
2999 forcedeltabothparents,
2985 sidedata_helpers,
3000 sidedata_helpers,
2986 )
3001 )
2987
3002
2988 finally:
3003 finally:
2989 destrevlog._lazydelta = oldlazydelta
3004 destrevlog._lazydelta = oldlazydelta
2990 destrevlog._lazydeltabase = oldlazydeltabase
3005 destrevlog._lazydeltabase = oldlazydeltabase
2991 destrevlog._deltabothparents = oldamd
3006 destrevlog._deltabothparents = oldamd
2992
3007
2993 def _clone(
3008 def _clone(
2994 self,
3009 self,
2995 tr,
3010 tr,
2996 destrevlog,
3011 destrevlog,
2997 addrevisioncb,
3012 addrevisioncb,
2998 deltareuse,
3013 deltareuse,
2999 forcedeltabothparents,
3014 forcedeltabothparents,
3000 sidedata_helpers,
3015 sidedata_helpers,
3001 ):
3016 ):
3002 """perform the core duty of `revlog.clone` after parameter processing"""
3017 """perform the core duty of `revlog.clone` after parameter processing"""
3003 deltacomputer = deltautil.deltacomputer(destrevlog)
3018 deltacomputer = deltautil.deltacomputer(destrevlog)
3004 index = self.index
3019 index = self.index
3005 for rev in self:
3020 for rev in self:
3006 entry = index[rev]
3021 entry = index[rev]
3007
3022
3008 # Some classes override linkrev to take filtered revs into
3023 # Some classes override linkrev to take filtered revs into
3009 # account. Use raw entry from index.
3024 # account. Use raw entry from index.
3010 flags = entry[0] & 0xFFFF
3025 flags = entry[0] & 0xFFFF
3011 linkrev = entry[4]
3026 linkrev = entry[4]
3012 p1 = index[entry[5]][7]
3027 p1 = index[entry[5]][7]
3013 p2 = index[entry[6]][7]
3028 p2 = index[entry[6]][7]
3014 node = entry[7]
3029 node = entry[7]
3015
3030
3016 # (Possibly) reuse the delta from the revlog if allowed and
3031 # (Possibly) reuse the delta from the revlog if allowed and
3017 # the revlog chunk is a delta.
3032 # the revlog chunk is a delta.
3018 cachedelta = None
3033 cachedelta = None
3019 rawtext = None
3034 rawtext = None
3020 if deltareuse == self.DELTAREUSEFULLADD:
3035 if deltareuse == self.DELTAREUSEFULLADD:
3021 text = self._revisiondata(rev)
3036 text = self._revisiondata(rev)
3022 sidedata = self.sidedata(rev)
3037 sidedata = self.sidedata(rev)
3023
3038
3024 if sidedata_helpers is not None:
3039 if sidedata_helpers is not None:
3025 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
3040 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
3026 self, sidedata_helpers, sidedata, rev
3041 self, sidedata_helpers, sidedata, rev
3027 )
3042 )
3028 flags = flags | new_flags[0] & ~new_flags[1]
3043 flags = flags | new_flags[0] & ~new_flags[1]
3029
3044
3030 destrevlog.addrevision(
3045 destrevlog.addrevision(
3031 text,
3046 text,
3032 tr,
3047 tr,
3033 linkrev,
3048 linkrev,
3034 p1,
3049 p1,
3035 p2,
3050 p2,
3036 cachedelta=cachedelta,
3051 cachedelta=cachedelta,
3037 node=node,
3052 node=node,
3038 flags=flags,
3053 flags=flags,
3039 deltacomputer=deltacomputer,
3054 deltacomputer=deltacomputer,
3040 sidedata=sidedata,
3055 sidedata=sidedata,
3041 )
3056 )
3042 else:
3057 else:
3043 if destrevlog._lazydelta:
3058 if destrevlog._lazydelta:
3044 dp = self.deltaparent(rev)
3059 dp = self.deltaparent(rev)
3045 if dp != nullrev:
3060 if dp != nullrev:
3046 cachedelta = (dp, bytes(self._chunk(rev)))
3061 cachedelta = (dp, bytes(self._chunk(rev)))
3047
3062
3048 sidedata = None
3063 sidedata = None
3049 if not cachedelta:
3064 if not cachedelta:
3050 rawtext = self._revisiondata(rev)
3065 rawtext = self._revisiondata(rev)
3051 sidedata = self.sidedata(rev)
3066 sidedata = self.sidedata(rev)
3052 if sidedata is None:
3067 if sidedata is None:
3053 sidedata = self.sidedata(rev)
3068 sidedata = self.sidedata(rev)
3054
3069
3055 if sidedata_helpers is not None:
3070 if sidedata_helpers is not None:
3056 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
3071 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
3057 self, sidedata_helpers, sidedata, rev
3072 self, sidedata_helpers, sidedata, rev
3058 )
3073 )
3059 flags = flags | new_flags[0] & ~new_flags[1]
3074 flags = flags | new_flags[0] & ~new_flags[1]
3060
3075
3061 with destrevlog._writing(tr):
3076 with destrevlog._writing(tr):
3062 destrevlog._addrevision(
3077 destrevlog._addrevision(
3063 node,
3078 node,
3064 rawtext,
3079 rawtext,
3065 tr,
3080 tr,
3066 linkrev,
3081 linkrev,
3067 p1,
3082 p1,
3068 p2,
3083 p2,
3069 flags,
3084 flags,
3070 cachedelta,
3085 cachedelta,
3071 deltacomputer=deltacomputer,
3086 deltacomputer=deltacomputer,
3072 sidedata=sidedata,
3087 sidedata=sidedata,
3073 )
3088 )
3074
3089
3075 if addrevisioncb:
3090 if addrevisioncb:
3076 addrevisioncb(self, rev, node)
3091 addrevisioncb(self, rev, node)
3077
3092
3078 def censorrevision(self, tr, censornode, tombstone=b''):
3093 def censorrevision(self, tr, censornode, tombstone=b''):
3079 if self._format_version == REVLOGV0:
3094 if self._format_version == REVLOGV0:
3080 raise error.RevlogError(
3095 raise error.RevlogError(
3081 _(b'cannot censor with version %d revlogs')
3096 _(b'cannot censor with version %d revlogs')
3082 % self._format_version
3097 % self._format_version
3083 )
3098 )
3084 elif self._format_version == REVLOGV1:
3099 elif self._format_version == REVLOGV1:
3085 rewrite.v1_censor(self, tr, censornode, tombstone)
3100 rewrite.v1_censor(self, tr, censornode, tombstone)
3086 else:
3101 else:
3087 rewrite.v2_censor(self, tr, censornode, tombstone)
3102 rewrite.v2_censor(self, tr, censornode, tombstone)
3088
3103
3089 def verifyintegrity(self, state):
3104 def verifyintegrity(self, state):
3090 """Verifies the integrity of the revlog.
3105 """Verifies the integrity of the revlog.
3091
3106
3092 Yields ``revlogproblem`` instances describing problems that are
3107 Yields ``revlogproblem`` instances describing problems that are
3093 found.
3108 found.
3094 """
3109 """
3095 dd, di = self.checksize()
3110 dd, di = self.checksize()
3096 if dd:
3111 if dd:
3097 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
3112 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
3098 if di:
3113 if di:
3099 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
3114 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
3100
3115
3101 version = self._format_version
3116 version = self._format_version
3102
3117
3103 # The verifier tells us what version revlog we should be.
3118 # The verifier tells us what version revlog we should be.
3104 if version != state[b'expectedversion']:
3119 if version != state[b'expectedversion']:
3105 yield revlogproblem(
3120 yield revlogproblem(
3106 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
3121 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
3107 % (self.display_id, version, state[b'expectedversion'])
3122 % (self.display_id, version, state[b'expectedversion'])
3108 )
3123 )
3109
3124
3110 state[b'skipread'] = set()
3125 state[b'skipread'] = set()
3111 state[b'safe_renamed'] = set()
3126 state[b'safe_renamed'] = set()
3112
3127
3113 for rev in self:
3128 for rev in self:
3114 node = self.node(rev)
3129 node = self.node(rev)
3115
3130
3116 # Verify contents. 4 cases to care about:
3131 # Verify contents. 4 cases to care about:
3117 #
3132 #
3118 # common: the most common case
3133 # common: the most common case
3119 # rename: with a rename
3134 # rename: with a rename
3120 # meta: file content starts with b'\1\n', the metadata
3135 # meta: file content starts with b'\1\n', the metadata
3121 # header defined in filelog.py, but without a rename
3136 # header defined in filelog.py, but without a rename
3122 # ext: content stored externally
3137 # ext: content stored externally
3123 #
3138 #
3124 # More formally, their differences are shown below:
3139 # More formally, their differences are shown below:
3125 #
3140 #
3126 # | common | rename | meta | ext
3141 # | common | rename | meta | ext
3127 # -------------------------------------------------------
3142 # -------------------------------------------------------
3128 # flags() | 0 | 0 | 0 | not 0
3143 # flags() | 0 | 0 | 0 | not 0
3129 # renamed() | False | True | False | ?
3144 # renamed() | False | True | False | ?
3130 # rawtext[0:2]=='\1\n'| False | True | True | ?
3145 # rawtext[0:2]=='\1\n'| False | True | True | ?
3131 #
3146 #
3132 # "rawtext" means the raw text stored in revlog data, which
3147 # "rawtext" means the raw text stored in revlog data, which
3133 # could be retrieved by "rawdata(rev)". "text"
3148 # could be retrieved by "rawdata(rev)". "text"
3134 # mentioned below is "revision(rev)".
3149 # mentioned below is "revision(rev)".
3135 #
3150 #
3136 # There are 3 different lengths stored physically:
3151 # There are 3 different lengths stored physically:
3137 # 1. L1: rawsize, stored in revlog index
3152 # 1. L1: rawsize, stored in revlog index
3138 # 2. L2: len(rawtext), stored in revlog data
3153 # 2. L2: len(rawtext), stored in revlog data
3139 # 3. L3: len(text), stored in revlog data if flags==0, or
3154 # 3. L3: len(text), stored in revlog data if flags==0, or
3140 # possibly somewhere else if flags!=0
3155 # possibly somewhere else if flags!=0
3141 #
3156 #
3142 # L1 should be equal to L2. L3 could be different from them.
3157 # L1 should be equal to L2. L3 could be different from them.
3143 # "text" may or may not affect commit hash depending on flag
3158 # "text" may or may not affect commit hash depending on flag
3144 # processors (see flagutil.addflagprocessor).
3159 # processors (see flagutil.addflagprocessor).
3145 #
3160 #
3146 # | common | rename | meta | ext
3161 # | common | rename | meta | ext
3147 # -------------------------------------------------
3162 # -------------------------------------------------
3148 # rawsize() | L1 | L1 | L1 | L1
3163 # rawsize() | L1 | L1 | L1 | L1
3149 # size() | L1 | L2-LM | L1(*) | L1 (?)
3164 # size() | L1 | L2-LM | L1(*) | L1 (?)
3150 # len(rawtext) | L2 | L2 | L2 | L2
3165 # len(rawtext) | L2 | L2 | L2 | L2
3151 # len(text) | L2 | L2 | L2 | L3
3166 # len(text) | L2 | L2 | L2 | L3
3152 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
3167 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
3153 #
3168 #
3154 # LM: length of metadata, depending on rawtext
3169 # LM: length of metadata, depending on rawtext
3155 # (*): not ideal, see comment in filelog.size
3170 # (*): not ideal, see comment in filelog.size
3156 # (?): could be "- len(meta)" if the resolved content has
3171 # (?): could be "- len(meta)" if the resolved content has
3157 # rename metadata
3172 # rename metadata
3158 #
3173 #
3159 # Checks needed to be done:
3174 # Checks needed to be done:
3160 # 1. length check: L1 == L2, in all cases.
3175 # 1. length check: L1 == L2, in all cases.
3161 # 2. hash check: depending on flag processor, we may need to
3176 # 2. hash check: depending on flag processor, we may need to
3162 # use either "text" (external), or "rawtext" (in revlog).
3177 # use either "text" (external), or "rawtext" (in revlog).
3163
3178
3164 try:
3179 try:
3165 skipflags = state.get(b'skipflags', 0)
3180 skipflags = state.get(b'skipflags', 0)
3166 if skipflags:
3181 if skipflags:
3167 skipflags &= self.flags(rev)
3182 skipflags &= self.flags(rev)
3168
3183
3169 _verify_revision(self, skipflags, state, node)
3184 _verify_revision(self, skipflags, state, node)
3170
3185
3171 l1 = self.rawsize(rev)
3186 l1 = self.rawsize(rev)
3172 l2 = len(self.rawdata(node))
3187 l2 = len(self.rawdata(node))
3173
3188
3174 if l1 != l2:
3189 if l1 != l2:
3175 yield revlogproblem(
3190 yield revlogproblem(
3176 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3191 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3177 node=node,
3192 node=node,
3178 )
3193 )
3179
3194
3180 except error.CensoredNodeError:
3195 except error.CensoredNodeError:
3181 if state[b'erroroncensored']:
3196 if state[b'erroroncensored']:
3182 yield revlogproblem(
3197 yield revlogproblem(
3183 error=_(b'censored file data'), node=node
3198 error=_(b'censored file data'), node=node
3184 )
3199 )
3185 state[b'skipread'].add(node)
3200 state[b'skipread'].add(node)
3186 except Exception as e:
3201 except Exception as e:
3187 yield revlogproblem(
3202 yield revlogproblem(
3188 error=_(b'unpacking %s: %s')
3203 error=_(b'unpacking %s: %s')
3189 % (short(node), stringutil.forcebytestr(e)),
3204 % (short(node), stringutil.forcebytestr(e)),
3190 node=node,
3205 node=node,
3191 )
3206 )
3192 state[b'skipread'].add(node)
3207 state[b'skipread'].add(node)
3193
3208
3194 def storageinfo(
3209 def storageinfo(
3195 self,
3210 self,
3196 exclusivefiles=False,
3211 exclusivefiles=False,
3197 sharedfiles=False,
3212 sharedfiles=False,
3198 revisionscount=False,
3213 revisionscount=False,
3199 trackedsize=False,
3214 trackedsize=False,
3200 storedsize=False,
3215 storedsize=False,
3201 ):
3216 ):
3202 d = {}
3217 d = {}
3203
3218
3204 if exclusivefiles:
3219 if exclusivefiles:
3205 d[b'exclusivefiles'] = [(self.opener, self._indexfile)]
3220 d[b'exclusivefiles'] = [(self.opener, self._indexfile)]
3206 if not self._inline:
3221 if not self._inline:
3207 d[b'exclusivefiles'].append((self.opener, self._datafile))
3222 d[b'exclusivefiles'].append((self.opener, self._datafile))
3208
3223
3209 if sharedfiles:
3224 if sharedfiles:
3210 d[b'sharedfiles'] = []
3225 d[b'sharedfiles'] = []
3211
3226
3212 if revisionscount:
3227 if revisionscount:
3213 d[b'revisionscount'] = len(self)
3228 d[b'revisionscount'] = len(self)
3214
3229
3215 if trackedsize:
3230 if trackedsize:
3216 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3231 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3217
3232
3218 if storedsize:
3233 if storedsize:
3219 d[b'storedsize'] = sum(
3234 d[b'storedsize'] = sum(
3220 self.opener.stat(path).st_size for path in self.files()
3235 self.opener.stat(path).st_size for path in self.files()
3221 )
3236 )
3222
3237
3223 return d
3238 return d
3224
3239
3225 def rewrite_sidedata(self, transaction, helpers, startrev, endrev):
3240 def rewrite_sidedata(self, transaction, helpers, startrev, endrev):
3226 if not self.hassidedata:
3241 if not self.hassidedata:
3227 return
3242 return
3228 # revlog formats with sidedata support does not support inline
3243 # revlog formats with sidedata support does not support inline
3229 assert not self._inline
3244 assert not self._inline
3230 if not helpers[1] and not helpers[2]:
3245 if not helpers[1] and not helpers[2]:
3231 # Nothing to generate or remove
3246 # Nothing to generate or remove
3232 return
3247 return
3233
3248
3234 new_entries = []
3249 new_entries = []
3235 # append the new sidedata
3250 # append the new sidedata
3236 with self._writing(transaction):
3251 with self._writing(transaction):
3237 ifh, dfh, sdfh = self._writinghandles
3252 ifh, dfh, sdfh = self._writinghandles
3238 dfh.seek(self._docket.sidedata_end, os.SEEK_SET)
3253 dfh.seek(self._docket.sidedata_end, os.SEEK_SET)
3239
3254
3240 current_offset = sdfh.tell()
3255 current_offset = sdfh.tell()
3241 for rev in range(startrev, endrev + 1):
3256 for rev in range(startrev, endrev + 1):
3242 entry = self.index[rev]
3257 entry = self.index[rev]
3243 new_sidedata, flags = sidedatautil.run_sidedata_helpers(
3258 new_sidedata, flags = sidedatautil.run_sidedata_helpers(
3244 store=self,
3259 store=self,
3245 sidedata_helpers=helpers,
3260 sidedata_helpers=helpers,
3246 sidedata={},
3261 sidedata={},
3247 rev=rev,
3262 rev=rev,
3248 )
3263 )
3249
3264
3250 serialized_sidedata = sidedatautil.serialize_sidedata(
3265 serialized_sidedata = sidedatautil.serialize_sidedata(
3251 new_sidedata
3266 new_sidedata
3252 )
3267 )
3253
3268
3254 sidedata_compression_mode = COMP_MODE_INLINE
3269 sidedata_compression_mode = COMP_MODE_INLINE
3255 if serialized_sidedata and self.hassidedata:
3270 if serialized_sidedata and self.hassidedata:
3256 sidedata_compression_mode = COMP_MODE_PLAIN
3271 sidedata_compression_mode = COMP_MODE_PLAIN
3257 h, comp_sidedata = self.compress(serialized_sidedata)
3272 h, comp_sidedata = self.compress(serialized_sidedata)
3258 if (
3273 if (
3259 h != b'u'
3274 h != b'u'
3260 and comp_sidedata[0] != b'\0'
3275 and comp_sidedata[0] != b'\0'
3261 and len(comp_sidedata) < len(serialized_sidedata)
3276 and len(comp_sidedata) < len(serialized_sidedata)
3262 ):
3277 ):
3263 assert not h
3278 assert not h
3264 if (
3279 if (
3265 comp_sidedata[0]
3280 comp_sidedata[0]
3266 == self._docket.default_compression_header
3281 == self._docket.default_compression_header
3267 ):
3282 ):
3268 sidedata_compression_mode = COMP_MODE_DEFAULT
3283 sidedata_compression_mode = COMP_MODE_DEFAULT
3269 serialized_sidedata = comp_sidedata
3284 serialized_sidedata = comp_sidedata
3270 else:
3285 else:
3271 sidedata_compression_mode = COMP_MODE_INLINE
3286 sidedata_compression_mode = COMP_MODE_INLINE
3272 serialized_sidedata = comp_sidedata
3287 serialized_sidedata = comp_sidedata
3273 if entry[8] != 0 or entry[9] != 0:
3288 if entry[8] != 0 or entry[9] != 0:
3274 # rewriting entries that already have sidedata is not
3289 # rewriting entries that already have sidedata is not
3275 # supported yet, because it introduces garbage data in the
3290 # supported yet, because it introduces garbage data in the
3276 # revlog.
3291 # revlog.
3277 msg = b"rewriting existing sidedata is not supported yet"
3292 msg = b"rewriting existing sidedata is not supported yet"
3278 raise error.Abort(msg)
3293 raise error.Abort(msg)
3279
3294
3280 # Apply (potential) flags to add and to remove after running
3295 # Apply (potential) flags to add and to remove after running
3281 # the sidedata helpers
3296 # the sidedata helpers
3282 new_offset_flags = entry[0] | flags[0] & ~flags[1]
3297 new_offset_flags = entry[0] | flags[0] & ~flags[1]
3283 entry_update = (
3298 entry_update = (
3284 current_offset,
3299 current_offset,
3285 len(serialized_sidedata),
3300 len(serialized_sidedata),
3286 new_offset_flags,
3301 new_offset_flags,
3287 sidedata_compression_mode,
3302 sidedata_compression_mode,
3288 )
3303 )
3289
3304
3290 # the sidedata computation might have move the file cursors around
3305 # the sidedata computation might have move the file cursors around
3291 sdfh.seek(current_offset, os.SEEK_SET)
3306 sdfh.seek(current_offset, os.SEEK_SET)
3292 sdfh.write(serialized_sidedata)
3307 sdfh.write(serialized_sidedata)
3293 new_entries.append(entry_update)
3308 new_entries.append(entry_update)
3294 current_offset += len(serialized_sidedata)
3309 current_offset += len(serialized_sidedata)
3295 self._docket.sidedata_end = sdfh.tell()
3310 self._docket.sidedata_end = sdfh.tell()
3296
3311
3297 # rewrite the new index entries
3312 # rewrite the new index entries
3298 ifh.seek(startrev * self.index.entry_size)
3313 ifh.seek(startrev * self.index.entry_size)
3299 for i, e in enumerate(new_entries):
3314 for i, e in enumerate(new_entries):
3300 rev = startrev + i
3315 rev = startrev + i
3301 self.index.replace_sidedata_info(rev, *e)
3316 self.index.replace_sidedata_info(rev, *e)
3302 packed = self.index.entry_binary(rev)
3317 packed = self.index.entry_binary(rev)
3303 if rev == 0 and self._docket is None:
3318 if rev == 0 and self._docket is None:
3304 header = self._format_flags | self._format_version
3319 header = self._format_flags | self._format_version
3305 header = self.index.pack_header(header)
3320 header = self.index.pack_header(header)
3306 packed = header + packed
3321 packed = header + packed
3307 ifh.write(packed)
3322 ifh.write(packed)
@@ -1,347 +1,347 b''
1 #require no-reposimplestore
1 #require no-reposimplestore
2
2
3 $ . "$TESTDIR/narrow-library.sh"
3 $ . "$TESTDIR/narrow-library.sh"
4
4
5 create full repo
5 create full repo
6
6
7 $ hg init master
7 $ hg init master
8 $ cd master
8 $ cd master
9 $ cat >> .hg/hgrc <<EOF
9 $ cat >> .hg/hgrc <<EOF
10 > [narrow]
10 > [narrow]
11 > serveellipses=True
11 > serveellipses=True
12 > EOF
12 > EOF
13
13
14 $ mkdir inside
14 $ mkdir inside
15 $ echo 1 > inside/f
15 $ echo 1 > inside/f
16 $ hg commit -Aqm 'initial inside'
16 $ hg commit -Aqm 'initial inside'
17
17
18 $ mkdir outside
18 $ mkdir outside
19 $ echo 1 > outside/f
19 $ echo 1 > outside/f
20 $ hg commit -Aqm 'initial outside'
20 $ hg commit -Aqm 'initial outside'
21
21
22 $ echo 2a > outside/f
22 $ echo 2a > outside/f
23 $ hg commit -Aqm 'outside 2a'
23 $ hg commit -Aqm 'outside 2a'
24 $ echo 3 > inside/f
24 $ echo 3 > inside/f
25 $ hg commit -Aqm 'inside 3'
25 $ hg commit -Aqm 'inside 3'
26 $ echo 4a > outside/f
26 $ echo 4a > outside/f
27 $ hg commit -Aqm 'outside 4a'
27 $ hg commit -Aqm 'outside 4a'
28 $ hg update '.~3'
28 $ hg update '.~3'
29 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
29 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
30
30
31 $ echo 2b > outside/f
31 $ echo 2b > outside/f
32 $ hg commit -Aqm 'outside 2b'
32 $ hg commit -Aqm 'outside 2b'
33 $ echo 3 > inside/f
33 $ echo 3 > inside/f
34 $ hg commit -Aqm 'inside 3'
34 $ hg commit -Aqm 'inside 3'
35 $ echo 4b > outside/f
35 $ echo 4b > outside/f
36 $ hg commit -Aqm 'outside 4b'
36 $ hg commit -Aqm 'outside 4b'
37 $ hg update '.~3'
37 $ hg update '.~3'
38 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
38 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
39
39
40 $ echo 2c > outside/f
40 $ echo 2c > outside/f
41 $ hg commit -Aqm 'outside 2c'
41 $ hg commit -Aqm 'outside 2c'
42 $ echo 3 > inside/f
42 $ echo 3 > inside/f
43 $ hg commit -Aqm 'inside 3'
43 $ hg commit -Aqm 'inside 3'
44 $ echo 4c > outside/f
44 $ echo 4c > outside/f
45 $ hg commit -Aqm 'outside 4c'
45 $ hg commit -Aqm 'outside 4c'
46 $ hg update '.~3'
46 $ hg update '.~3'
47 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
47 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
48
48
49 $ echo 2d > outside/f
49 $ echo 2d > outside/f
50 $ hg commit -Aqm 'outside 2d'
50 $ hg commit -Aqm 'outside 2d'
51 $ echo 3 > inside/f
51 $ echo 3 > inside/f
52 $ hg commit -Aqm 'inside 3'
52 $ hg commit -Aqm 'inside 3'
53 $ echo 4d > outside/f
53 $ echo 4d > outside/f
54 $ hg commit -Aqm 'outside 4d'
54 $ hg commit -Aqm 'outside 4d'
55
55
56 $ hg update -r 'desc("outside 4a")'
56 $ hg update -r 'desc("outside 4a")'
57 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
57 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
58 $ hg merge -r 'desc("outside 4b")' 2>&1 | egrep -v '(warning:|incomplete!)'
58 $ hg merge -r 'desc("outside 4b")' 2>&1 | egrep -v '(warning:|incomplete!)'
59 merging outside/f
59 merging outside/f
60 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
60 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
61 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
61 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
62 $ echo 5 > outside/f
62 $ echo 5 > outside/f
63 $ rm outside/f.orig
63 $ rm outside/f.orig
64 $ hg resolve --mark outside/f
64 $ hg resolve --mark outside/f
65 (no more unresolved files)
65 (no more unresolved files)
66 $ hg commit -m 'merge a/b 5'
66 $ hg commit -m 'merge a/b 5'
67 $ echo 6 > outside/f
67 $ echo 6 > outside/f
68 $ hg commit -Aqm 'outside 6'
68 $ hg commit -Aqm 'outside 6'
69
69
70 $ hg merge -r 'desc("outside 4c")' 2>&1 | egrep -v '(warning:|incomplete!)'
70 $ hg merge -r 'desc("outside 4c")' 2>&1 | egrep -v '(warning:|incomplete!)'
71 merging outside/f
71 merging outside/f
72 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
72 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
73 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
73 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
74 $ echo 7 > outside/f
74 $ echo 7 > outside/f
75 $ rm outside/f.orig
75 $ rm outside/f.orig
76 $ hg resolve --mark outside/f
76 $ hg resolve --mark outside/f
77 (no more unresolved files)
77 (no more unresolved files)
78 $ hg commit -Aqm 'merge a/b/c 7'
78 $ hg commit -Aqm 'merge a/b/c 7'
79 $ echo 8 > outside/f
79 $ echo 8 > outside/f
80 $ hg commit -Aqm 'outside 8'
80 $ hg commit -Aqm 'outside 8'
81
81
82 $ hg merge -r 'desc("outside 4d")' 2>&1 | egrep -v '(warning:|incomplete!)'
82 $ hg merge -r 'desc("outside 4d")' 2>&1 | egrep -v '(warning:|incomplete!)'
83 merging outside/f
83 merging outside/f
84 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
84 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
85 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
85 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
86 $ echo 9 > outside/f
86 $ echo 9 > outside/f
87 $ rm outside/f.orig
87 $ rm outside/f.orig
88 $ hg resolve --mark outside/f
88 $ hg resolve --mark outside/f
89 (no more unresolved files)
89 (no more unresolved files)
90 $ hg commit -Aqm 'merge a/b/c/d 9'
90 $ hg commit -Aqm 'merge a/b/c/d 9'
91 $ echo 10 > outside/f
91 $ echo 10 > outside/f
92 $ hg commit -Aqm 'outside 10'
92 $ hg commit -Aqm 'outside 10'
93
93
94 $ echo 11 > inside/f
94 $ echo 11 > inside/f
95 $ hg commit -Aqm 'inside 11'
95 $ hg commit -Aqm 'inside 11'
96 $ echo 12 > outside/f
96 $ echo 12 > outside/f
97 $ hg commit -Aqm 'outside 12'
97 $ hg commit -Aqm 'outside 12'
98
98
99 $ hg log -G -T '{rev} {node|short} {desc}\n'
99 $ hg log -G -T '{rev} {node|short} {desc}\n'
100 @ 21 8d874d57adea outside 12
100 @ 21 8d874d57adea outside 12
101 |
101 |
102 o 20 7ef88b4dd4fa inside 11
102 o 20 7ef88b4dd4fa inside 11
103 |
103 |
104 o 19 2a20009de83e outside 10
104 o 19 2a20009de83e outside 10
105 |
105 |
106 o 18 3ac1f5779de3 merge a/b/c/d 9
106 o 18 3ac1f5779de3 merge a/b/c/d 9
107 |\
107 |\
108 | o 17 38a9c2f7e546 outside 8
108 | o 17 38a9c2f7e546 outside 8
109 | |
109 | |
110 | o 16 094aa62fc898 merge a/b/c 7
110 | o 16 094aa62fc898 merge a/b/c 7
111 | |\
111 | |\
112 | | o 15 f29d083d32e4 outside 6
112 | | o 15 f29d083d32e4 outside 6
113 | | |
113 | | |
114 | | o 14 2dc11382541d merge a/b 5
114 | | o 14 2dc11382541d merge a/b 5
115 | | |\
115 | | |\
116 o | | | 13 27d07ef97221 outside 4d
116 o | | | 13 27d07ef97221 outside 4d
117 | | | |
117 | | | |
118 o | | | 12 465567bdfb2d inside 3
118 o | | | 12 465567bdfb2d inside 3
119 | | | |
119 | | | |
120 o | | | 11 d1c61993ec83 outside 2d
120 o | | | 11 d1c61993ec83 outside 2d
121 | | | |
121 | | | |
122 | o | | 10 56859a8e33b9 outside 4c
122 | o | | 10 56859a8e33b9 outside 4c
123 | | | |
123 | | | |
124 | o | | 9 bb96a08b062a inside 3
124 | o | | 9 bb96a08b062a inside 3
125 | | | |
125 | | | |
126 | o | | 8 b844052e7b3b outside 2c
126 | o | | 8 b844052e7b3b outside 2c
127 |/ / /
127 |/ / /
128 | | o 7 9db2d8fcc2a6 outside 4b
128 | | o 7 9db2d8fcc2a6 outside 4b
129 | | |
129 | | |
130 | | o 6 6418167787a6 inside 3
130 | | o 6 6418167787a6 inside 3
131 | | |
131 | | |
132 +---o 5 77344f344d83 outside 2b
132 +---o 5 77344f344d83 outside 2b
133 | |
133 | |
134 | o 4 9cadde08dc9f outside 4a
134 | o 4 9cadde08dc9f outside 4a
135 | |
135 | |
136 | o 3 019ef06f125b inside 3
136 | o 3 019ef06f125b inside 3
137 | |
137 | |
138 | o 2 75e40c075a19 outside 2a
138 | o 2 75e40c075a19 outside 2a
139 |/
139 |/
140 o 1 906d6c682641 initial outside
140 o 1 906d6c682641 initial outside
141 |
141 |
142 o 0 9f8e82b51004 initial inside
142 o 0 9f8e82b51004 initial inside
143
143
144
144
145 Now narrow and shallow clone this and get a hopefully correct graph
145 Now narrow and shallow clone this and get a hopefully correct graph
146
146
147 $ cd ..
147 $ cd ..
148 $ hg clone --narrow ssh://user@dummy/master narrow --include inside --depth 7
148 $ hg clone --narrow ssh://user@dummy/master narrow --include inside --depth 7
149 requesting all changes
149 requesting all changes
150 adding changesets
150 adding changesets
151 adding manifests
151 adding manifests
152 adding file changes
152 adding file changes
153 added 8 changesets with 3 changes to 1 files
153 added 8 changesets with 3 changes to 1 files
154 new changesets *:* (glob)
154 new changesets *:* (glob)
155 updating to branch default
155 updating to branch default
156 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
156 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
157 $ cd narrow
157 $ cd narrow
158
158
159 To make updating the tests easier, we print the emitted nodes
159 To make updating the tests easier, we print the emitted nodes
160 sorted. This makes it easier to identify when the same node structure
160 sorted. This makes it easier to identify when the same node structure
161 has been emitted, just in a different order.
161 has been emitted, just in a different order.
162
162
163 $ hg log -G -T '{rev} {node|short}{if(ellipsis,"...")} {desc}\n'
163 $ hg log -G -T '{rev} {node|short}{if(ellipsis,"...")} {desc}\n'
164 @ 7 8d874d57adea... outside 12
164 @ 7 8d874d57adea... outside 12
165 |
165 |
166 o 6 7ef88b4dd4fa inside 11
166 o 6 7ef88b4dd4fa inside 11
167 |
167 |
168 o 5 2a20009de83e... outside 10
168 o 5 2a20009de83e... outside 10
169 |
169 |
170 o 4 3ac1f5779de3... merge a/b/c/d 9
170 o 4 3ac1f5779de3... merge a/b/c/d 9
171 |\
171 |\
172 | o 3 465567bdfb2d inside 3
172 | o 3 465567bdfb2d inside 3
173 | |
173 | |
174 | o 2 d1c61993ec83... outside 2d
174 | o 2 d1c61993ec83... outside 2d
175 |
175 |
176 o 1 bb96a08b062a inside 3
176 o 1 bb96a08b062a inside 3
177 |
177 |
178 o 0 b844052e7b3b... outside 2c
178 o 0 b844052e7b3b... outside 2c
179
179
180
180
181 $ hg log -T '{if(ellipsis,"...")}{node|short} {p1node|short} {p2node|short} {desc}\n' | sort
181 $ hg log -T '{if(ellipsis,"...")}{node|short} {p1node|short} {p2node|short} {desc}\n' | sort
182 ...2a20009de83e 000000000000 3ac1f5779de3 outside 10
182 ...2a20009de83e 3ac1f5779de3 000000000000 outside 10
183 ...3ac1f5779de3 bb96a08b062a 465567bdfb2d merge a/b/c/d 9
183 ...3ac1f5779de3 bb96a08b062a 465567bdfb2d merge a/b/c/d 9
184 ...8d874d57adea 7ef88b4dd4fa 000000000000 outside 12
184 ...8d874d57adea 7ef88b4dd4fa 000000000000 outside 12
185 ...b844052e7b3b 000000000000 000000000000 outside 2c
185 ...b844052e7b3b 000000000000 000000000000 outside 2c
186 ...d1c61993ec83 000000000000 000000000000 outside 2d
186 ...d1c61993ec83 000000000000 000000000000 outside 2d
187 465567bdfb2d d1c61993ec83 000000000000 inside 3
187 465567bdfb2d d1c61993ec83 000000000000 inside 3
188 7ef88b4dd4fa 2a20009de83e 000000000000 inside 11
188 7ef88b4dd4fa 2a20009de83e 000000000000 inside 11
189 bb96a08b062a b844052e7b3b 000000000000 inside 3
189 bb96a08b062a b844052e7b3b 000000000000 inside 3
190
190
191 $ cd ..
191 $ cd ..
192
192
193 Incremental test case: show a pull can pull in a conflicted merge even if elided
193 Incremental test case: show a pull can pull in a conflicted merge even if elided
194
194
195 $ hg init pullmaster
195 $ hg init pullmaster
196 $ cd pullmaster
196 $ cd pullmaster
197 $ cat >> .hg/hgrc <<EOF
197 $ cat >> .hg/hgrc <<EOF
198 > [narrow]
198 > [narrow]
199 > serveellipses=True
199 > serveellipses=True
200 > EOF
200 > EOF
201 $ mkdir inside outside
201 $ mkdir inside outside
202 $ echo v1 > inside/f
202 $ echo v1 > inside/f
203 $ echo v1 > outside/f
203 $ echo v1 > outside/f
204 $ hg add inside/f outside/f
204 $ hg add inside/f outside/f
205 $ hg commit -m init
205 $ hg commit -m init
206
206
207 $ for line in a b c d
207 $ for line in a b c d
208 > do
208 > do
209 > hg update -r 0
209 > hg update -r 0
210 > echo v2$line > outside/f
210 > echo v2$line > outside/f
211 > hg commit -m "outside 2$line"
211 > hg commit -m "outside 2$line"
212 > echo v2$line > inside/f
212 > echo v2$line > inside/f
213 > hg commit -m "inside 2$line"
213 > hg commit -m "inside 2$line"
214 > echo v3$line > outside/f
214 > echo v3$line > outside/f
215 > hg commit -m "outside 3$line"
215 > hg commit -m "outside 3$line"
216 > echo v4$line > outside/f
216 > echo v4$line > outside/f
217 > hg commit -m "outside 4$line"
217 > hg commit -m "outside 4$line"
218 > done
218 > done
219 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
219 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
220 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
220 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
221 created new head
221 created new head
222 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
222 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
223 created new head
223 created new head
224 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
224 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
225 created new head
225 created new head
226
226
227 $ cd ..
227 $ cd ..
228 $ hg clone --narrow ssh://user@dummy/pullmaster pullshallow \
228 $ hg clone --narrow ssh://user@dummy/pullmaster pullshallow \
229 > --include inside --depth 3
229 > --include inside --depth 3
230 requesting all changes
230 requesting all changes
231 adding changesets
231 adding changesets
232 adding manifests
232 adding manifests
233 adding file changes
233 adding file changes
234 added 12 changesets with 5 changes to 1 files (+3 heads)
234 added 12 changesets with 5 changes to 1 files (+3 heads)
235 new changesets *:* (glob)
235 new changesets *:* (glob)
236 updating to branch default
236 updating to branch default
237 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
237 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
238 $ cd pullshallow
238 $ cd pullshallow
239
239
240 $ hg log -G -T '{rev} {node|short}{if(ellipsis,"...")} {desc}\n'
240 $ hg log -G -T '{rev} {node|short}{if(ellipsis,"...")} {desc}\n'
241 @ 11 0ebbd712a0c8... outside 4d
241 @ 11 0ebbd712a0c8... outside 4d
242 |
242 |
243 o 10 0d4c867aeb23 inside 2d
243 o 10 0d4c867aeb23 inside 2d
244 |
244 |
245 o 9 e932969c3961... outside 2d
245 o 9 e932969c3961... outside 2d
246
246
247 o 8 33d530345455... outside 4c
247 o 8 33d530345455... outside 4c
248 |
248 |
249 o 7 0ce6481bfe07 inside 2c
249 o 7 0ce6481bfe07 inside 2c
250 |
250 |
251 o 6 caa65c940632... outside 2c
251 o 6 caa65c940632... outside 2c
252
252
253 o 5 3df233defecc... outside 4b
253 o 5 3df233defecc... outside 4b
254 |
254 |
255 o 4 7162cc6d11a4 inside 2b
255 o 4 7162cc6d11a4 inside 2b
256 |
256 |
257 o 3 f2a632f0082d... outside 2b
257 o 3 f2a632f0082d... outside 2b
258
258
259 o 2 b8a3da16ba49... outside 4a
259 o 2 b8a3da16ba49... outside 4a
260 |
260 |
261 o 1 53f543eb8e45 inside 2a
261 o 1 53f543eb8e45 inside 2a
262 |
262 |
263 o 0 1be3e5221c6a... outside 2a
263 o 0 1be3e5221c6a... outside 2a
264
264
265 $ hg log -T '{if(ellipsis,"...")}{node|short} {p1node|short} {p2node|short} {desc}\n' | sort
265 $ hg log -T '{if(ellipsis,"...")}{node|short} {p1node|short} {p2node|short} {desc}\n' | sort
266 ...0ebbd712a0c8 0d4c867aeb23 000000000000 outside 4d
266 ...0ebbd712a0c8 0d4c867aeb23 000000000000 outside 4d
267 ...1be3e5221c6a 000000000000 000000000000 outside 2a
267 ...1be3e5221c6a 000000000000 000000000000 outside 2a
268 ...33d530345455 0ce6481bfe07 000000000000 outside 4c
268 ...33d530345455 0ce6481bfe07 000000000000 outside 4c
269 ...3df233defecc 7162cc6d11a4 000000000000 outside 4b
269 ...3df233defecc 7162cc6d11a4 000000000000 outside 4b
270 ...b8a3da16ba49 53f543eb8e45 000000000000 outside 4a
270 ...b8a3da16ba49 53f543eb8e45 000000000000 outside 4a
271 ...caa65c940632 000000000000 000000000000 outside 2c
271 ...caa65c940632 000000000000 000000000000 outside 2c
272 ...e932969c3961 000000000000 000000000000 outside 2d
272 ...e932969c3961 000000000000 000000000000 outside 2d
273 ...f2a632f0082d 000000000000 000000000000 outside 2b
273 ...f2a632f0082d 000000000000 000000000000 outside 2b
274 0ce6481bfe07 caa65c940632 000000000000 inside 2c
274 0ce6481bfe07 caa65c940632 000000000000 inside 2c
275 0d4c867aeb23 e932969c3961 000000000000 inside 2d
275 0d4c867aeb23 e932969c3961 000000000000 inside 2d
276 53f543eb8e45 1be3e5221c6a 000000000000 inside 2a
276 53f543eb8e45 1be3e5221c6a 000000000000 inside 2a
277 7162cc6d11a4 f2a632f0082d 000000000000 inside 2b
277 7162cc6d11a4 f2a632f0082d 000000000000 inside 2b
278
278
279 $ cd ../pullmaster
279 $ cd ../pullmaster
280 $ hg update -r 'desc("outside 4a")'
280 $ hg update -r 'desc("outside 4a")'
281 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
281 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
282 $ hg merge -r 'desc("outside 4b")' 2>&1 | egrep -v '(warning:|incomplete!)'
282 $ hg merge -r 'desc("outside 4b")' 2>&1 | egrep -v '(warning:|incomplete!)'
283 merging inside/f
283 merging inside/f
284 merging outside/f
284 merging outside/f
285 0 files updated, 0 files merged, 0 files removed, 2 files unresolved
285 0 files updated, 0 files merged, 0 files removed, 2 files unresolved
286 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
286 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
287 $ echo 3 > inside/f
287 $ echo 3 > inside/f
288 $ echo 5 > outside/f
288 $ echo 5 > outside/f
289 $ rm -f {in,out}side/f.orig
289 $ rm -f {in,out}side/f.orig
290 $ hg resolve --mark inside/f outside/f
290 $ hg resolve --mark inside/f outside/f
291 (no more unresolved files)
291 (no more unresolved files)
292 $ hg commit -m 'merge a/b 5'
292 $ hg commit -m 'merge a/b 5'
293
293
294 $ hg update -r 'desc("outside 4c")'
294 $ hg update -r 'desc("outside 4c")'
295 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
295 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
296 $ hg merge -r 'desc("outside 4d")' 2>&1 | egrep -v '(warning:|incomplete!)'
296 $ hg merge -r 'desc("outside 4d")' 2>&1 | egrep -v '(warning:|incomplete!)'
297 merging inside/f
297 merging inside/f
298 merging outside/f
298 merging outside/f
299 0 files updated, 0 files merged, 0 files removed, 2 files unresolved
299 0 files updated, 0 files merged, 0 files removed, 2 files unresolved
300 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
300 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
301 $ echo 3 > inside/f
301 $ echo 3 > inside/f
302 $ echo 5 > outside/f
302 $ echo 5 > outside/f
303 $ rm -f {in,out}side/f.orig
303 $ rm -f {in,out}side/f.orig
304 $ hg resolve --mark inside/f outside/f
304 $ hg resolve --mark inside/f outside/f
305 (no more unresolved files)
305 (no more unresolved files)
306 $ hg commit -m 'merge c/d 5'
306 $ hg commit -m 'merge c/d 5'
307
307
308 $ hg update -r 'desc("merge a/b 5")'
308 $ hg update -r 'desc("merge a/b 5")'
309 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
309 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
310 $ hg merge -r 'desc("merge c/d 5")'
310 $ hg merge -r 'desc("merge c/d 5")'
311 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
311 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
312 (branch merge, don't forget to commit)
312 (branch merge, don't forget to commit)
313 $ echo 6 > outside/f
313 $ echo 6 > outside/f
314 $ hg commit -m 'outside 6'
314 $ hg commit -m 'outside 6'
315 $ echo 7 > outside/f
315 $ echo 7 > outside/f
316 $ hg commit -m 'outside 7'
316 $ hg commit -m 'outside 7'
317 $ echo 8 > outside/f
317 $ echo 8 > outside/f
318 $ hg commit -m 'outside 8'
318 $ hg commit -m 'outside 8'
319
319
320 $ cd ../pullshallow
320 $ cd ../pullshallow
321 $ hg pull --depth 3
321 $ hg pull --depth 3
322 pulling from ssh://user@dummy/pullmaster
322 pulling from ssh://user@dummy/pullmaster
323 searching for changes
323 searching for changes
324 adding changesets
324 adding changesets
325 adding manifests
325 adding manifests
326 adding file changes
326 adding file changes
327 added 4 changesets with 3 changes to 1 files (-3 heads)
327 added 4 changesets with 3 changes to 1 files (-3 heads)
328 new changesets *:* (glob)
328 new changesets *:* (glob)
329 (run 'hg update' to get a working copy)
329 (run 'hg update' to get a working copy)
330
330
331 $ hg log -T '{if(ellipsis,"...")}{node|short} {p1node|short} {p2node|short} {desc}\n' | sort
331 $ hg log -T '{if(ellipsis,"...")}{node|short} {p1node|short} {p2node|short} {desc}\n' | sort
332 ...0ebbd712a0c8 0d4c867aeb23 000000000000 outside 4d
332 ...0ebbd712a0c8 0d4c867aeb23 000000000000 outside 4d
333 ...1be3e5221c6a 000000000000 000000000000 outside 2a
333 ...1be3e5221c6a 000000000000 000000000000 outside 2a
334 ...33d530345455 0ce6481bfe07 000000000000 outside 4c
334 ...33d530345455 0ce6481bfe07 000000000000 outside 4c
335 ...3df233defecc 7162cc6d11a4 000000000000 outside 4b
335 ...3df233defecc 7162cc6d11a4 000000000000 outside 4b
336 ...b8a3da16ba49 53f543eb8e45 000000000000 outside 4a
336 ...b8a3da16ba49 53f543eb8e45 000000000000 outside 4a
337 ...bf545653453e 968003d40c60 000000000000 outside 8
337 ...bf545653453e 968003d40c60 000000000000 outside 8
338 ...caa65c940632 000000000000 000000000000 outside 2c
338 ...caa65c940632 000000000000 000000000000 outside 2c
339 ...e932969c3961 000000000000 000000000000 outside 2d
339 ...e932969c3961 000000000000 000000000000 outside 2d
340 ...f2a632f0082d 000000000000 000000000000 outside 2b
340 ...f2a632f0082d 000000000000 000000000000 outside 2b
341 0ce6481bfe07 caa65c940632 000000000000 inside 2c
341 0ce6481bfe07 caa65c940632 000000000000 inside 2c
342 0d4c867aeb23 e932969c3961 000000000000 inside 2d
342 0d4c867aeb23 e932969c3961 000000000000 inside 2d
343 53f543eb8e45 1be3e5221c6a 000000000000 inside 2a
343 53f543eb8e45 1be3e5221c6a 000000000000 inside 2a
344 67d49c0bdbda b8a3da16ba49 3df233defecc merge a/b 5
344 67d49c0bdbda b8a3da16ba49 3df233defecc merge a/b 5
345 7162cc6d11a4 f2a632f0082d 000000000000 inside 2b
345 7162cc6d11a4 f2a632f0082d 000000000000 inside 2b
346 968003d40c60 67d49c0bdbda e867021d52c2 outside 6
346 968003d40c60 67d49c0bdbda e867021d52c2 outside 6
347 e867021d52c2 33d530345455 0ebbd712a0c8 merge c/d 5
347 e867021d52c2 33d530345455 0ebbd712a0c8 merge c/d 5
General Comments 0
You need to be logged in to leave comments. Login now